Fix widget layout clipping and add comprehensive widget previews

- Fix LargeVotingView mood icons getting clipped at edges by using
  flexible HStack spacing with maxWidth: .infinity
- Fix VotingView medium layout with smaller icons and even distribution
- Add comprehensive #Preview macros for all widget states:
  - Vote widget: small/medium, voted/not voted, all mood states
  - Timeline widget: small/medium/large with various data states
- Reduce icon sizes and padding to fit within widget bounds
- Update accessibility labels and hints across views

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-24 09:53:40 -06:00
parent 5f7d909d62
commit be84825aba
33 changed files with 10467 additions and 9725 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -227,31 +227,31 @@ struct VotingView: View {
// MARK: - Medium Widget: Single row // MARK: - Medium Widget: Single row
private var mediumLayout: some View { private var mediumLayout: some View {
VStack { VStack(spacing: 12) {
Text(hasSubscription ? promptText : "Subscribe to track your mood") Text(hasSubscription ? promptText : "Subscribe to track your mood")
.font(.headline) .font(.headline)
.foregroundStyle(.primary) .foregroundStyle(.primary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.lineLimit(2) .lineLimit(2)
.minimumScaleFactor(0.8) .minimumScaleFactor(0.8)
.padding(.bottom, 20)
HStack(spacing: 16) { HStack(spacing: 0) {
ForEach([Mood.great, .good, .average, .bad, .horrible], id: \.rawValue) { mood in ForEach([Mood.great, .good, .average, .bad, .horrible], id: \.rawValue) { mood in
moodButton(for: mood, size: 44) moodButtonMedium(for: mood)
.frame(maxWidth: .infinity)
} }
} }
} }
.padding() .padding(.horizontal, 12)
.padding(.vertical, 16)
} }
@ViewBuilder @ViewBuilder
private func moodButton(for mood: Mood, size: CGFloat) -> some View { private func moodButton(for mood: Mood, size: CGFloat) -> some View {
// Ensure minimum 44x44 touch target for accessibility // Used for small widget
let touchSize = max(size, 44) let touchSize = max(size, 44)
if hasSubscription { if hasSubscription {
// Active subscription: vote normally
Button(intent: VoteMoodIntent(mood: mood)) { Button(intent: VoteMoodIntent(mood: mood)) {
moodIcon(for: mood, size: size) moodIcon(for: mood, size: size)
.frame(minWidth: touchSize, minHeight: touchSize) .frame(minWidth: touchSize, minHeight: touchSize)
@@ -260,7 +260,6 @@ struct VotingView: View {
.accessibilityLabel(mood.strValue) .accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Log this mood")) .accessibilityHint(String(localized: "Log this mood"))
} else { } else {
// Trial expired: open app to subscribe
Link(destination: URL(string: "feels://subscribe")!) { Link(destination: URL(string: "feels://subscribe")!) {
moodIcon(for: mood, size: size) moodIcon(for: mood, size: size)
.frame(minWidth: touchSize, minHeight: touchSize) .frame(minWidth: touchSize, minHeight: touchSize)
@@ -270,6 +269,39 @@ struct VotingView: View {
} }
} }
@ViewBuilder
private func moodButtonMedium(for mood: Mood) -> some View {
// Medium widget uses smaller icons with labels, flexible width
let content = VStack(spacing: 4) {
moodImages.icon(forMood: mood)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 32, height: 32)
.foregroundColor(moodTint.color(forMood: mood))
Text(mood.widgetDisplayName)
.font(.caption2)
.foregroundColor(moodTint.color(forMood: mood))
.lineLimit(1)
.minimumScaleFactor(0.8)
}
if hasSubscription {
Button(intent: VoteMoodIntent(mood: mood)) {
content
}
.buttonStyle(.plain)
.accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Log this mood"))
} else {
Link(destination: URL(string: "feels://subscribe")!) {
content
}
.accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Open app to subscribe"))
}
}
private func moodIcon(for mood: Mood, size: CGFloat) -> some View { private func moodIcon(for mood: Mood, size: CGFloat) -> some View {
moodImages.icon(forMood: mood) moodImages.icon(forMood: mood)
.resizable() .resizable()
@@ -315,11 +347,13 @@ struct VotedStatsView: View {
// Checkmark badge // Checkmark badge
Image(systemName: "checkmark.circle.fill") Image(systemName: "checkmark.circle.fill")
.font(.system(size: 18)) .font(.headline)
.foregroundColor(.green) .foregroundColor(.green)
.background(Circle().fill(.white).frame(width: 14, height: 14)) .background(Circle().fill(.white).frame(width: 14, height: 14))
.offset(x: 4, y: 4) .offset(x: 4, y: 4)
} }
.accessibilityElement(children: .combine)
.accessibilityLabel(String(localized: "Mood logged: \(mood.strValue)"))
Text("Logged!") Text("Logged!")
.font(.caption.weight(.semibold)) .font(.caption.weight(.semibold))
@@ -331,8 +365,6 @@ struct VotedStatsView: View {
.foregroundStyle(.tertiary) .foregroundStyle(.tertiary)
} }
} }
.accessibilityElement(children: .combine)
.accessibilityLabel(String(localized: "Mood logged: \(entry.todaysMood?.strValue ?? "")"))
} }
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(12) .padding(12)
@@ -340,7 +372,7 @@ struct VotedStatsView: View {
// MARK: - Medium: Mood + stats bar // MARK: - Medium: Mood + stats bar
private var mediumLayout: some View { private var mediumLayout: some View {
HStack(spacing: 20) { HStack(alignment: .top, spacing: 20) {
if let mood = entry.todaysMood { if let mood = entry.todaysMood {
// Left: Mood display // Left: Mood display
VStack(spacing: 6) { VStack(spacing: 6) {
@@ -349,6 +381,7 @@ struct VotedStatsView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 48, height: 48) .frame(width: 48, height: 48)
.foregroundColor(moodTint.color(forMood: mood)) .foregroundColor(moodTint.color(forMood: mood))
.accessibilityLabel(mood.strValue)
Text(mood.widgetDisplayName) Text(mood.widgetDisplayName)
.font(.subheadline.weight(.semibold)) .font(.subheadline.weight(.semibold))
@@ -359,11 +392,11 @@ struct VotedStatsView: View {
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
// Right: Stats // Right: Stats with progress bar aligned under title
if let stats = entry.stats { if let stats = entry.stats {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 10) {
Text("\(stats.totalEntries) entries") Text("\(stats.totalEntries) entries")
.font(.caption.weight(.medium)) .font(.headline.weight(.semibold))
.foregroundStyle(.primary) .foregroundStyle(.primary)
// Mini mood breakdown // Mini mood breakdown
@@ -383,21 +416,21 @@ struct VotedStatsView: View {
} }
} }
// Progress bar // Progress bar - aligned with title
GeometryReader { geo in GeometryReader { geo in
HStack(spacing: 1) { HStack(spacing: 1) {
ForEach([Mood.great, .good, .average, .bad, .horrible], id: \.rawValue) { mood in ForEach([Mood.great, .good, .average, .bad, .horrible], id: \.rawValue) { m in
let percentage = stats.percentage(for: mood) let percentage = stats.percentage(for: m)
if percentage > 0 { if percentage > 0 {
RoundedRectangle(cornerRadius: 2) RoundedRectangle(cornerRadius: 2)
.fill(moodTint.color(forMood: mood)) .fill(moodTint.color(forMood: m))
.frame(width: max(4, geo.size.width * CGFloat(percentage) / 100)) .frame(width: max(4, geo.size.width * CGFloat(percentage) / 100))
} }
} }
} }
} }
.frame(height: 8) .frame(height: 10)
.clipShape(RoundedRectangle(cornerRadius: 4)) .clipShape(RoundedRectangle(cornerRadius: 5))
} }
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
} }
@@ -449,12 +482,202 @@ struct FeelsVoteWidget: Widget {
} }
} }
// MARK: - Preview // MARK: - Preview Helpers
#Preview(as: .systemSmall) { private enum VoteWidgetPreviewHelpers {
static let sampleStats = MoodStats(
totalEntries: 30,
moodCounts: [.great: 10, .good: 12, .average: 5, .bad: 2, .horrible: 1]
)
static let largeStats = MoodStats(
totalEntries: 100,
moodCounts: [.great: 35, .good: 40, .average: 15, .bad: 7, .horrible: 3]
)
}
// MARK: - Small Widget Previews
#Preview("Vote Small - Not Voted", as: .systemSmall) {
FeelsVoteWidget() FeelsVoteWidget()
} timeline: { } timeline: {
VoteWidgetEntry(date: Date(), hasSubscription: true, hasVotedToday: false, todaysMood: nil, stats: nil, promptText: "How are you feeling today?") VoteWidgetEntry(
VoteWidgetEntry(date: Date(), hasSubscription: true, hasVotedToday: true, todaysMood: .great, stats: MoodStats(totalEntries: 30, moodCounts: [.great: 10, .good: 12, .average: 5, .bad: 2, .horrible: 1]), promptText: "") date: Date(),
VoteWidgetEntry(date: Date(), hasSubscription: false, hasVotedToday: false, todaysMood: nil, stats: nil, promptText: "") hasSubscription: true,
hasVotedToday: false,
todaysMood: nil,
stats: nil,
promptText: "How are you feeling today?"
)
}
#Preview("Vote Small - Voted Great", as: .systemSmall) {
FeelsVoteWidget()
} timeline: {
VoteWidgetEntry(
date: Date(),
hasSubscription: true,
hasVotedToday: true,
todaysMood: .great,
stats: VoteWidgetPreviewHelpers.sampleStats,
promptText: ""
)
}
#Preview("Vote Small - Voted Good", as: .systemSmall) {
FeelsVoteWidget()
} timeline: {
VoteWidgetEntry(
date: Date(),
hasSubscription: true,
hasVotedToday: true,
todaysMood: .good,
stats: VoteWidgetPreviewHelpers.sampleStats,
promptText: ""
)
}
#Preview("Vote Small - Voted Average", as: .systemSmall) {
FeelsVoteWidget()
} timeline: {
VoteWidgetEntry(
date: Date(),
hasSubscription: true,
hasVotedToday: true,
todaysMood: .average,
stats: VoteWidgetPreviewHelpers.sampleStats,
promptText: ""
)
}
#Preview("Vote Small - Voted Bad", as: .systemSmall) {
FeelsVoteWidget()
} timeline: {
VoteWidgetEntry(
date: Date(),
hasSubscription: true,
hasVotedToday: true,
todaysMood: .bad,
stats: VoteWidgetPreviewHelpers.sampleStats,
promptText: ""
)
}
#Preview("Vote Small - Voted Horrible", as: .systemSmall) {
FeelsVoteWidget()
} timeline: {
VoteWidgetEntry(
date: Date(),
hasSubscription: true,
hasVotedToday: true,
todaysMood: .horrible,
stats: VoteWidgetPreviewHelpers.sampleStats,
promptText: ""
)
}
#Preview("Vote Small - Non-Subscriber", as: .systemSmall) {
FeelsVoteWidget()
} timeline: {
VoteWidgetEntry(
date: Date(),
hasSubscription: false,
hasVotedToday: false,
todaysMood: nil,
stats: nil,
promptText: ""
)
}
// MARK: - Medium Widget Previews
#Preview("Vote Medium - Not Voted", as: .systemMedium) {
FeelsVoteWidget()
} timeline: {
VoteWidgetEntry(
date: Date(),
hasSubscription: true,
hasVotedToday: false,
todaysMood: nil,
stats: nil,
promptText: "How are you feeling today?"
)
}
#Preview("Vote Medium - Voted Great", as: .systemMedium) {
FeelsVoteWidget()
} timeline: {
VoteWidgetEntry(
date: Date(),
hasSubscription: true,
hasVotedToday: true,
todaysMood: .great,
stats: VoteWidgetPreviewHelpers.largeStats,
promptText: ""
)
}
#Preview("Vote Medium - Voted Good", as: .systemMedium) {
FeelsVoteWidget()
} timeline: {
VoteWidgetEntry(
date: Date(),
hasSubscription: true,
hasVotedToday: true,
todaysMood: .good,
stats: VoteWidgetPreviewHelpers.largeStats,
promptText: ""
)
}
#Preview("Vote Medium - Voted Average", as: .systemMedium) {
FeelsVoteWidget()
} timeline: {
VoteWidgetEntry(
date: Date(),
hasSubscription: true,
hasVotedToday: true,
todaysMood: .average,
stats: VoteWidgetPreviewHelpers.sampleStats,
promptText: ""
)
}
#Preview("Vote Medium - Voted Bad", as: .systemMedium) {
FeelsVoteWidget()
} timeline: {
VoteWidgetEntry(
date: Date(),
hasSubscription: true,
hasVotedToday: true,
todaysMood: .bad,
stats: VoteWidgetPreviewHelpers.sampleStats,
promptText: ""
)
}
#Preview("Vote Medium - Voted Horrible", as: .systemMedium) {
FeelsVoteWidget()
} timeline: {
VoteWidgetEntry(
date: Date(),
hasSubscription: true,
hasVotedToday: true,
todaysMood: .horrible,
stats: VoteWidgetPreviewHelpers.sampleStats,
promptText: ""
)
}
#Preview("Vote Medium - Non-Subscriber", as: .systemMedium) {
FeelsVoteWidget()
} timeline: {
VoteWidgetEntry(
date: Date(),
hasSubscription: false,
hasVotedToday: false,
todaysMood: nil,
stats: nil,
promptText: ""
)
} }

View File

@@ -138,13 +138,15 @@ class WatchTimelineView: Identifiable {
let date: Date let date: Date
let color: Color let color: Color
let secondaryColor: Color let secondaryColor: Color
let mood: Mood
init(image: Image, graphic: Image, date: Date, color: Color, secondaryColor: Color) {
init(image: Image, graphic: Image, date: Date, color: Color, secondaryColor: Color, mood: Mood) {
self.image = image self.image = image
self.date = date self.date = date
self.color = color self.color = color
self.graphic = graphic self.graphic = graphic
self.secondaryColor = secondaryColor self.secondaryColor = secondaryColor
self.mood = mood
} }
} }
@@ -171,13 +173,15 @@ struct TimeLineCreator {
graphic: moodImages.icon(forMood: todayEntry.mood), graphic: moodImages.icon(forMood: todayEntry.mood),
date: dayStart, date: dayStart,
color: moodTint.color(forMood: todayEntry.mood), color: moodTint.color(forMood: todayEntry.mood),
secondaryColor: moodTint.secondary(forMood: todayEntry.mood))) secondaryColor: moodTint.secondary(forMood: todayEntry.mood),
mood: todayEntry.mood))
} else { } else {
timeLineView.append(WatchTimelineView(image: moodImages.icon(forMood: .missing), timeLineView.append(WatchTimelineView(image: moodImages.icon(forMood: .missing),
graphic: moodImages.icon(forMood: .missing), graphic: moodImages.icon(forMood: .missing),
date: dayStart, date: dayStart,
color: moodTint.color(forMood: .missing), color: moodTint.color(forMood: .missing),
secondaryColor: moodTint.secondary(forMood: .missing))) secondaryColor: moodTint.secondary(forMood: .missing),
mood: .missing))
} }
} }
@@ -202,7 +206,8 @@ struct TimeLineCreator {
graphic: moodImages.icon(forMood: mood), graphic: moodImages.icon(forMood: mood),
date: dayStart, date: dayStart,
color: moodTint.color(forMood: mood), color: moodTint.color(forMood: mood),
secondaryColor: moodTint.secondary(forMood: mood) secondaryColor: moodTint.secondary(forMood: mood),
mood: mood
)) ))
} }
@@ -377,6 +382,7 @@ struct SmallWidgetView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 70, height: 70) .frame(width: 70, height: 70)
.foregroundColor(today.color) .foregroundColor(today.color)
.accessibilityLabel(today.mood.strValue)
Spacer() Spacer()
.frame(height: 12) .frame(height: 12)
@@ -470,7 +476,8 @@ struct MediumWidgetView: View {
image: item.image, image: item.image,
color: item.color, color: item.color,
isToday: index == 0, isToday: index == 0,
height: cellHeight height: cellHeight,
mood: item.mood
) )
} }
} }
@@ -491,6 +498,7 @@ struct MediumDayCell: View {
let color: Color let color: Color
let isToday: Bool let isToday: Bool
let height: CGFloat let height: CGFloat
let mood: Mood
var body: some View { var body: some View {
ZStack { ZStack {
@@ -500,7 +508,7 @@ struct MediumDayCell: View {
VStack(spacing: 4) { VStack(spacing: 4) {
Text(dayLabel) Text(dayLabel)
.font(.system(size: 10, weight: isToday ? .bold : .medium)) .font(.caption2.weight(isToday ? .bold : .medium))
.foregroundStyle(isToday ? .primary : .secondary) .foregroundStyle(isToday ? .primary : .secondary)
.textCase(.uppercase) .textCase(.uppercase)
@@ -509,9 +517,10 @@ struct MediumDayCell: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 36, height: 36) .frame(width: 36, height: 36)
.foregroundColor(color) .foregroundColor(color)
.accessibilityLabel(mood.strValue)
Text(dateLabel) Text(dateLabel)
.font(.system(size: 13, weight: isToday ? .bold : .semibold)) .font(.caption.weight(isToday ? .bold : .semibold))
.foregroundStyle(isToday ? color : .secondary) .foregroundStyle(isToday ? color : .secondary)
} }
} }
@@ -584,7 +593,8 @@ struct LargeWidgetView: View {
image: item.image, image: item.image,
color: item.color, color: item.color,
isToday: index == 0, isToday: index == 0,
height: cellHeight height: cellHeight,
mood: item.mood
) )
} }
} }
@@ -598,7 +608,8 @@ struct LargeWidgetView: View {
image: item.image, image: item.image,
color: item.color, color: item.color,
isToday: false, isToday: false,
height: cellHeight height: cellHeight,
mood: item.mood
) )
} }
} }
@@ -634,11 +645,12 @@ struct DayCell: View {
let color: Color let color: Color
let isToday: Bool let isToday: Bool
let height: CGFloat let height: CGFloat
let mood: Mood
var body: some View { var body: some View {
VStack(spacing: 2) { VStack(spacing: 2) {
Text(dayLabel) Text(dayLabel)
.font(.system(size: 10, weight: isToday ? .bold : .medium)) .font(.caption2.weight(isToday ? .bold : .medium))
.foregroundStyle(isToday ? .primary : .secondary) .foregroundStyle(isToday ? .primary : .secondary)
.textCase(.uppercase) .textCase(.uppercase)
@@ -653,9 +665,10 @@ struct DayCell: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 38, height: 38) .frame(width: 38, height: 38)
.foregroundColor(color) .foregroundColor(color)
.accessibilityLabel(mood.strValue)
Text(dateLabel) Text(dateLabel)
.font(.system(size: 13, weight: isToday ? .bold : .semibold)) .font(.caption.weight(isToday ? .bold : .semibold))
.foregroundStyle(isToday ? color : .secondary) .foregroundStyle(isToday ? color : .secondary)
} }
} }
@@ -679,26 +692,29 @@ struct LargeVotingView: View {
} }
var body: some View { var body: some View {
VStack(spacing: 24) { VStack(spacing: 16) {
Spacer() Spacer()
Text(hasSubscription ? promptText : "Subscribe to track your mood") Text(hasSubscription ? promptText : "Subscribe to track your mood")
.font(.title2.weight(.semibold)) .font(.title3.weight(.semibold))
.foregroundStyle(.primary) .foregroundStyle(.primary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.lineLimit(2) .lineLimit(2)
.minimumScaleFactor(0.8) .minimumScaleFactor(0.8)
.padding(.horizontal, 8)
// Large mood buttons in a row // Large mood buttons in a row - flexible spacing
HStack(spacing: 20) { HStack(spacing: 0) {
ForEach([Mood.great, .good, .average, .bad, .horrible], id: \.rawValue) { mood in ForEach([Mood.great, .good, .average, .bad, .horrible], id: \.rawValue) { mood in
moodButton(for: mood) moodButton(for: mood)
.frame(maxWidth: .infinity)
} }
} }
Spacer() Spacer()
} }
.padding() .padding(.horizontal, 12)
.padding(.vertical, 16)
} }
@ViewBuilder @ViewBuilder
@@ -708,29 +724,35 @@ struct LargeVotingView: View {
moodButtonContent(for: mood) moodButtonContent(for: mood)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Log this mood"))
} else { } else {
Link(destination: URL(string: "feels://subscribe")!) { Link(destination: URL(string: "feels://subscribe")!) {
moodButtonContent(for: mood) moodButtonContent(for: mood)
} }
.accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Open app to subscribe"))
} }
} }
private func moodButtonContent(for mood: Mood) -> some View { private func moodButtonContent(for mood: Mood) -> some View {
VStack(spacing: 8) { VStack(spacing: 4) {
moodImages.icon(forMood: mood) moodImages.icon(forMood: mood)
.resizable() .resizable()
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 56, height: 56) .frame(width: 40, height: 40)
.foregroundColor(moodTint.color(forMood: mood)) .foregroundColor(moodTint.color(forMood: mood))
Text(mood.strValue) Text(mood.widgetDisplayName)
.font(.caption.weight(.medium)) .font(.caption2.weight(.medium))
.foregroundColor(moodTint.color(forMood: mood)) .foregroundColor(moodTint.color(forMood: mood))
.lineLimit(1)
.minimumScaleFactor(0.8)
} }
.padding(.vertical, 12) .padding(.vertical, 8)
.padding(.horizontal, 8) .padding(.horizontal, 4)
.background( .background(
RoundedRectangle(cornerRadius: 16) RoundedRectangle(cornerRadius: 12)
.fill(moodTint.color(forMood: mood).opacity(0.15)) .fill(moodTint.color(forMood: mood).opacity(0.15))
) )
} }
@@ -899,10 +921,14 @@ struct InlineVotingView: View {
moodIcon(for: mood) moodIcon(for: mood)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Log this mood"))
} else { } else {
Link(destination: URL(string: "feels://subscribe")!) { Link(destination: URL(string: "feels://subscribe")!) {
moodIcon(for: mood) moodIcon(for: mood)
} }
.accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Open app to subscribe"))
} }
} }
@@ -917,13 +943,14 @@ struct InlineVotingView: View {
struct EntryCard: View { struct EntryCard: View {
var timeLineView: WatchTimelineView var timeLineView: WatchTimelineView
var body: some View { var body: some View {
timeLineView.image timeLineView.image
.resizable() .resizable()
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50, alignment: .center) .frame(width: 50, height: 50, alignment: .center)
.foregroundColor(timeLineView.color) .foregroundColor(timeLineView.color)
.accessibilityLabel(timeLineView.mood.strValue)
} }
} }
@@ -1011,115 +1038,364 @@ struct FeelsGraphicWidget: Widget {
// MARK: - Preview Helpers // MARK: - Preview Helpers
private extension FeelsWidget_Previews { private enum WidgetPreviewHelpers {
static func sampleTimelineViews(count: Int) -> [WatchTimelineView] { static func sampleTimelineViews(count: Int, startMood: Mood = .great) -> [WatchTimelineView] {
let moods: [Mood] = [.great, .good, .average, .bad, .horrible] let moods: [Mood] = [.great, .good, .average, .bad, .horrible]
let startIndex = moods.firstIndex(of: startMood) ?? 0
return (0..<count).map { index in return (0..<count).map { index in
let mood = moods[index % moods.count] let mood = moods[(startIndex + index) % moods.count]
return WatchTimelineView( return WatchTimelineView(
image: EmojiMoodImages.icon(forMood: mood), image: EmojiMoodImages.icon(forMood: mood),
graphic: EmojiMoodImages.icon(forMood: mood), graphic: EmojiMoodImages.icon(forMood: mood),
date: Calendar.current.date(byAdding: .day, value: -index, to: Date())!, date: Calendar.current.date(byAdding: .day, value: -index, to: Date())!,
color: MoodTints.Default.color(forMood: mood), color: MoodTints.Default.color(forMood: mood),
secondaryColor: MoodTints.Default.secondary(forMood: mood) secondaryColor: MoodTints.Default.secondary(forMood: mood),
mood: mood
) )
} }
} }
static func sampleEntry(timelineCount: Int = 5) -> SimpleEntry { static func sampleEntry(timelineCount: Int = 5, hasVotedToday: Bool = true, hasSubscription: Bool = true, startMood: Mood = .great) -> SimpleEntry {
SimpleEntry( SimpleEntry(
date: Date(), date: Date(),
configuration: ConfigurationIntent(), configuration: ConfigurationIntent(),
timeLineViews: sampleTimelineViews(count: timelineCount), timeLineViews: sampleTimelineViews(count: timelineCount, startMood: startMood),
hasSubscription: true, hasSubscription: hasSubscription,
hasVotedToday: true hasVotedToday: hasVotedToday,
promptText: "How are you feeling today?"
) )
} }
} }
struct FeelsWidget_Previews: PreviewProvider { // MARK: - FeelsWidget Previews (Timeline Widget)
static var previews: some View {
Group {
// MARK: - FeelsWidget (Timeline)
FeelsWidgetEntryView(entry: sampleEntry(timelineCount: 1))
.previewContext(WidgetPreviewContext(family: .systemSmall))
.previewDisplayName("Timeline - Small")
FeelsWidgetEntryView(entry: sampleEntry(timelineCount: 5)) // Small - Logged States
.previewContext(WidgetPreviewContext(family: .systemMedium)) #Preview("Timeline Small - Great", as: .systemSmall) {
.previewDisplayName("Timeline - Medium") FeelsWidget()
} timeline: {
FeelsWidgetEntryView(entry: sampleEntry(timelineCount: 10)) WidgetPreviewHelpers.sampleEntry(timelineCount: 1, startMood: .great)
.previewContext(WidgetPreviewContext(family: .systemLarge)) }
.previewDisplayName("Timeline - Large")
#Preview("Timeline Small - Good", as: .systemSmall) {
// MARK: - FeelsGraphicWidget (Mood Graphic) FeelsWidget()
FeelsGraphicWidgetEntryView(entry: sampleEntry(timelineCount: 2)) } timeline: {
.previewContext(WidgetPreviewContext(family: .systemSmall)) WidgetPreviewHelpers.sampleEntry(timelineCount: 1, startMood: .good)
.previewDisplayName("Mood Graphic - Small") }
// MARK: - FeelsIconWidget (Custom Icon) #Preview("Timeline Small - Average", as: .systemSmall) {
FeelsIconWidgetEntryView(entry: sampleEntry()) FeelsWidget()
.previewContext(WidgetPreviewContext(family: .systemSmall)) } timeline: {
.previewDisplayName("Custom Icon - Small") WidgetPreviewHelpers.sampleEntry(timelineCount: 1, startMood: .average)
}
// MARK: - FeelsVoteWidget (Vote - Not Voted)
FeelsVoteWidgetEntryView(entry: VoteWidgetEntry( #Preview("Timeline Small - Bad", as: .systemSmall) {
date: Date(), FeelsWidget()
hasSubscription: true, } timeline: {
hasVotedToday: false, WidgetPreviewHelpers.sampleEntry(timelineCount: 1, startMood: .bad)
todaysMood: nil, }
stats: nil,
promptText: "How are you feeling?" #Preview("Timeline Small - Horrible", as: .systemSmall) {
)) FeelsWidget()
.previewContext(WidgetPreviewContext(family: .systemSmall)) } timeline: {
.previewDisplayName("Vote - Small (Not Voted)") WidgetPreviewHelpers.sampleEntry(timelineCount: 1, startMood: .horrible)
}
FeelsVoteWidgetEntryView(entry: VoteWidgetEntry(
date: Date(), // Small - Voting States
hasSubscription: true, #Preview("Timeline Small - Voting", as: .systemSmall) {
hasVotedToday: false, FeelsWidget()
todaysMood: nil, } timeline: {
stats: nil, WidgetPreviewHelpers.sampleEntry(timelineCount: 1, hasVotedToday: false)
promptText: "How are you feeling?" }
))
.previewContext(WidgetPreviewContext(family: .systemMedium)) #Preview("Timeline Small - Non-Subscriber", as: .systemSmall) {
.previewDisplayName("Vote - Medium (Not Voted)") FeelsWidget()
} timeline: {
// MARK: - FeelsVoteWidget (Vote - Already Voted) WidgetPreviewHelpers.sampleEntry(timelineCount: 1, hasVotedToday: false, hasSubscription: false)
FeelsVoteWidgetEntryView(entry: VoteWidgetEntry( }
date: Date(),
hasSubscription: true, // Medium - Logged States
hasVotedToday: true, #Preview("Timeline Medium - Logged", as: .systemMedium) {
todaysMood: .great, FeelsWidget()
stats: MoodStats(totalEntries: 30, moodCounts: [.great: 10, .good: 12, .average: 5, .bad: 2, .horrible: 1]), } timeline: {
promptText: "" WidgetPreviewHelpers.sampleEntry(timelineCount: 5)
)) }
.previewContext(WidgetPreviewContext(family: .systemSmall))
.previewDisplayName("Vote - Small (Voted)") // Medium - Voting States
#Preview("Timeline Medium - Voting", as: .systemMedium) {
FeelsVoteWidgetEntryView(entry: VoteWidgetEntry( FeelsWidget()
date: Date(), } timeline: {
hasSubscription: true, WidgetPreviewHelpers.sampleEntry(timelineCount: 5, hasVotedToday: false)
hasVotedToday: true, }
todaysMood: .good,
stats: MoodStats(totalEntries: 45, moodCounts: [.great: 15, .good: 18, .average: 8, .bad: 3, .horrible: 1]), #Preview("Timeline Medium - Non-Subscriber", as: .systemMedium) {
promptText: "" FeelsWidget()
)) } timeline: {
.previewContext(WidgetPreviewContext(family: .systemMedium)) WidgetPreviewHelpers.sampleEntry(timelineCount: 5, hasVotedToday: false, hasSubscription: false)
.previewDisplayName("Vote - Medium (Voted)") }
// MARK: - FeelsVoteWidget (Non-Subscriber) // Large - Logged States
FeelsVoteWidgetEntryView(entry: VoteWidgetEntry( #Preview("Timeline Large - Logged", as: .systemLarge) {
date: Date(), FeelsWidget()
hasSubscription: false, } timeline: {
hasVotedToday: false, WidgetPreviewHelpers.sampleEntry(timelineCount: 10)
todaysMood: nil, }
stats: nil,
promptText: "" // Large - Voting States
)) #Preview("Timeline Large - Voting", as: .systemLarge) {
.previewContext(WidgetPreviewContext(family: .systemSmall)) FeelsWidget()
.previewDisplayName("Vote - Small (Non-Subscriber)") } timeline: {
} WidgetPreviewHelpers.sampleEntry(timelineCount: 10, hasVotedToday: false)
} }
#Preview("Timeline Large - Non-Subscriber", as: .systemLarge) {
FeelsWidget()
} timeline: {
WidgetPreviewHelpers.sampleEntry(timelineCount: 10, hasVotedToday: false, hasSubscription: false)
}
// MARK: - FeelsGraphicWidget Previews (Mood Graphic)
#Preview("Graphic - Great", as: .systemSmall) {
FeelsGraphicWidget()
} timeline: {
WidgetPreviewHelpers.sampleEntry(timelineCount: 2, startMood: .great)
}
#Preview("Graphic - Good", as: .systemSmall) {
FeelsGraphicWidget()
} timeline: {
WidgetPreviewHelpers.sampleEntry(timelineCount: 2, startMood: .good)
}
#Preview("Graphic - Average", as: .systemSmall) {
FeelsGraphicWidget()
} timeline: {
WidgetPreviewHelpers.sampleEntry(timelineCount: 2, startMood: .average)
}
#Preview("Graphic - Bad", as: .systemSmall) {
FeelsGraphicWidget()
} timeline: {
WidgetPreviewHelpers.sampleEntry(timelineCount: 2, startMood: .bad)
}
#Preview("Graphic - Horrible", as: .systemSmall) {
FeelsGraphicWidget()
} timeline: {
WidgetPreviewHelpers.sampleEntry(timelineCount: 2, startMood: .horrible)
}
// MARK: - FeelsIconWidget Previews (Custom Icon)
#Preview("Custom Icon", as: .systemSmall) {
FeelsIconWidget()
} timeline: {
WidgetPreviewHelpers.sampleEntry()
}
// MARK: - Live Activity Previews (Lock Screen View)
#Preview("Live Activity - Not Logged") {
HStack(spacing: 16) {
VStack(spacing: 4) {
Image(systemName: "flame.fill")
.font(.title)
.foregroundColor(.orange)
Text("7")
.font(.title.bold())
Text("day streak")
.font(.caption)
.foregroundColor(.secondary)
}
Divider()
.frame(height: 50)
VStack(alignment: .leading) {
Text("Don't break your streak!")
.font(.headline)
Text("Tap to log your mood")
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
}
.padding()
.background(Color(.systemBackground).opacity(0.8))
}
#Preview("Live Activity - Great") {
HStack(spacing: 16) {
VStack(spacing: 4) {
Image(systemName: "flame.fill")
.font(.title)
.foregroundColor(.orange)
Text("15")
.font(.title.bold())
Text("day streak")
.font(.caption)
.foregroundColor(.secondary)
}
Divider()
.frame(height: 50)
HStack(spacing: 8) {
Circle()
.fill(MoodTints.Default.color(forMood: .great))
.frame(width: 24, height: 24)
VStack(alignment: .leading) {
Text("Today's mood")
.font(.caption)
.foregroundColor(.secondary)
Text("Great")
.font(.headline)
}
}
Spacer()
}
.padding()
.background(Color(.systemBackground).opacity(0.8))
}
#Preview("Live Activity - Good") {
HStack(spacing: 16) {
VStack(spacing: 4) {
Image(systemName: "flame.fill")
.font(.title)
.foregroundColor(.orange)
Text("30")
.font(.title.bold())
Text("day streak")
.font(.caption)
.foregroundColor(.secondary)
}
Divider()
.frame(height: 50)
HStack(spacing: 8) {
Circle()
.fill(MoodTints.Default.color(forMood: .good))
.frame(width: 24, height: 24)
VStack(alignment: .leading) {
Text("Today's mood")
.font(.caption)
.foregroundColor(.secondary)
Text("Good")
.font(.headline)
}
}
Spacer()
}
.padding()
.background(Color(.systemBackground).opacity(0.8))
}
#Preview("Live Activity - Average") {
HStack(spacing: 16) {
VStack(spacing: 4) {
Image(systemName: "flame.fill")
.font(.title)
.foregroundColor(.orange)
Text("10")
.font(.title.bold())
Text("day streak")
.font(.caption)
.foregroundColor(.secondary)
}
Divider()
.frame(height: 50)
HStack(spacing: 8) {
Circle()
.fill(MoodTints.Default.color(forMood: .average))
.frame(width: 24, height: 24)
VStack(alignment: .leading) {
Text("Today's mood")
.font(.caption)
.foregroundColor(.secondary)
Text("Average")
.font(.headline)
}
}
Spacer()
}
.padding()
.background(Color(.systemBackground).opacity(0.8))
}
#Preview("Live Activity - Bad") {
HStack(spacing: 16) {
VStack(spacing: 4) {
Image(systemName: "flame.fill")
.font(.title)
.foregroundColor(.orange)
Text("5")
.font(.title.bold())
Text("day streak")
.font(.caption)
.foregroundColor(.secondary)
}
Divider()
.frame(height: 50)
HStack(spacing: 8) {
Circle()
.fill(MoodTints.Default.color(forMood: .bad))
.frame(width: 24, height: 24)
VStack(alignment: .leading) {
Text("Today's mood")
.font(.caption)
.foregroundColor(.secondary)
Text("Bad")
.font(.headline)
}
}
Spacer()
}
.padding()
.background(Color(.systemBackground).opacity(0.8))
}
#Preview("Live Activity - Horrible") {
HStack(spacing: 16) {
VStack(spacing: 4) {
Image(systemName: "flame.fill")
.font(.title)
.foregroundColor(.orange)
Text("3")
.font(.title.bold())
Text("day streak")
.font(.caption)
.foregroundColor(.secondary)
}
Divider()
.frame(height: 50)
HStack(spacing: 8) {
Circle()
.fill(MoodTints.Default.color(forMood: .horrible))
.frame(width: 24, height: 24)
VStack(alignment: .leading) {
Text("Today's mood")
.font(.caption)
.foregroundColor(.secondary)
Text("Horrible")
.font(.headline)
}
}
Spacer()
}
.padding()
.background(Color(.systemBackground).opacity(0.8))
} }

View File

@@ -75,7 +75,6 @@ enum Mood: Int {
var graphic: Image { var graphic: Image {
switch self { switch self {
case .horrible: case .horrible:
return Image("HorribleGraphic", bundle: .main) return Image("HorribleGraphic", bundle: .main)
case .bad: case .bad:

View File

@@ -25,6 +25,7 @@ struct OnboardingCustomizeOne: View {
.foregroundColor(Color(UIColor.darkText)) .foregroundColor(Color(UIColor.darkText))
.opacity(0.04) .opacity(0.04)
.scaleEffect(1.2, anchor: .trailing) .scaleEffect(1.2, anchor: .trailing)
.accessibilityHidden(true)
Spacer() Spacer()
} }

View File

@@ -25,6 +25,7 @@ struct OnboardingCustomizeTwo: View {
.foregroundColor(Color(UIColor.darkText)) .foregroundColor(Color(UIColor.darkText))
.opacity(0.04) .opacity(0.04)
.scaleEffect(1.2, anchor: .trailing) .scaleEffect(1.2, anchor: .trailing)
.accessibilityHidden(true)
Spacer() Spacer()
} }

View File

@@ -44,21 +44,21 @@ struct OnboardingDay: View {
.frame(width: 120, height: 120) .frame(width: 120, height: 120)
Image(systemName: "calendar") Image(systemName: "calendar")
.font(.system(size: 44)) .font(.largeTitle)
.foregroundColor(.white) .foregroundColor(.white)
} }
.padding(.bottom, 32) .padding(.bottom, 32)
// Title // Title
Text("Which day should\nyou rate?") Text("Which day should\nyou rate?")
.font(.system(size: 28, weight: .bold, design: .rounded)) .font(.title.weight(.bold))
.foregroundColor(.white) .foregroundColor(.white)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.padding(.bottom, 12) .padding(.bottom, 12)
// Subtitle // Subtitle
Text("When you get your reminder, do you want to rate today or yesterday?") Text("When you get your reminder, do you want to rate today or yesterday?")
.font(.system(size: 16, weight: .medium)) .font(.body.weight(.medium))
.foregroundColor(.white.opacity(0.85)) .foregroundColor(.white.opacity(0.85))
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.padding(.horizontal, 40) .padding(.horizontal, 40)
@@ -92,11 +92,11 @@ struct OnboardingDay: View {
// Tip // Tip
HStack(spacing: 12) { HStack(spacing: 12) {
Image(systemName: "lightbulb.fill") Image(systemName: "lightbulb.fill")
.font(.system(size: 18)) .font(.headline)
.foregroundColor(.yellow) .foregroundColor(.yellow)
Text("Tip: \"Yesterday\" works great for evening reminders") Text("Tip: \"Yesterday\" works great for evening reminders")
.font(.system(size: 14, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundColor(.white.opacity(0.9)) .foregroundColor(.white.opacity(0.9))
} }
.padding(.horizontal, 30) .padding(.horizontal, 30)
@@ -124,7 +124,7 @@ struct DayOptionCard: View {
.frame(width: 46, height: 46) .frame(width: 46, height: 46)
Image(systemName: icon) Image(systemName: icon)
.font(.system(size: 20)) .font(.title3)
.foregroundColor(isSelected ? Color(hex: "4facfe") : .white) .foregroundColor(isSelected ? Color(hex: "4facfe") : .white)
} }
.accessibilityHidden(true) .accessibilityHidden(true)
@@ -132,15 +132,15 @@ struct DayOptionCard: View {
// Text // Text
VStack(alignment: .leading, spacing: 3) { VStack(alignment: .leading, spacing: 3) {
Text(title) Text(title)
.font(.system(size: 17, weight: .semibold)) .font(.body.weight(.semibold))
.foregroundColor(isSelected ? Color(hex: "4facfe") : .white) .foregroundColor(isSelected ? Color(hex: "4facfe") : .white)
Text(subtitle) Text(subtitle)
.font(.system(size: 13)) .font(.caption)
.foregroundColor(isSelected ? Color(hex: "4facfe").opacity(0.8) : .white.opacity(0.8)) .foregroundColor(isSelected ? Color(hex: "4facfe").opacity(0.8) : .white.opacity(0.8))
Text(example) Text(example)
.font(.system(size: 11, weight: .medium)) .font(.caption2.weight(.medium))
.foregroundColor(isSelected ? Color(hex: "4facfe").opacity(0.6) : .white.opacity(0.6)) .foregroundColor(isSelected ? Color(hex: "4facfe").opacity(0.6) : .white.opacity(0.6))
.lineLimit(1) .lineLimit(1)
.minimumScaleFactor(0.8) .minimumScaleFactor(0.8)
@@ -151,7 +151,7 @@ struct DayOptionCard: View {
// Checkmark // Checkmark
if isSelected { if isSelected {
Image(systemName: "checkmark.circle.fill") Image(systemName: "checkmark.circle.fill")
.font(.system(size: 22)) .font(.title3)
.foregroundColor(Color(hex: "4facfe")) .foregroundColor(Color(hex: "4facfe"))
.accessibilityHidden(true) .accessibilityHidden(true)
} }

View File

@@ -31,7 +31,7 @@ struct OnboardingStyle: View {
.frame(width: 100, height: 100) .frame(width: 100, height: 100)
Image(systemName: "paintpalette.fill") Image(systemName: "paintpalette.fill")
.font(.system(size: 40)) .font(.largeTitle)
.foregroundColor(.white) .foregroundColor(.white)
} }
.padding(.top, 40) .padding(.top, 40)
@@ -39,13 +39,13 @@ struct OnboardingStyle: View {
// Title // Title
Text("Make it yours") Text("Make it yours")
.font(.system(size: 28, weight: .bold, design: .rounded)) .font(.title.weight(.bold))
.foregroundColor(.white) .foregroundColor(.white)
.padding(.bottom, 8) .padding(.bottom, 8)
// Subtitle // Subtitle
Text("Choose your favorite style") Text("Choose your favorite style")
.font(.system(size: 16, weight: .medium)) .font(.body.weight(.medium))
.foregroundColor(.white.opacity(0.85)) .foregroundColor(.white.opacity(0.85))
.padding(.bottom, 20) .padding(.bottom, 20)
@@ -57,7 +57,7 @@ struct OnboardingStyle: View {
// Icon Style Section // Icon Style Section
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
Text("Icon Style") Text("Icon Style")
.font(.system(size: 14, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(.white.opacity(0.9)) .foregroundColor(.white.opacity(0.9))
.padding(.horizontal, 24) .padding(.horizontal, 24)
@@ -82,7 +82,7 @@ struct OnboardingStyle: View {
// Color Theme Section // Color Theme Section
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
Text("Mood Colors") Text("Mood Colors")
.font(.system(size: 14, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(.white.opacity(0.9)) .foregroundColor(.white.opacity(0.9))
.padding(.horizontal, 24) .padding(.horizontal, 24)
@@ -105,9 +105,9 @@ struct OnboardingStyle: View {
// Hint // Hint
HStack(spacing: 8) { HStack(spacing: 8) {
Image(systemName: "arrow.left.arrow.right") Image(systemName: "arrow.left.arrow.right")
.font(.system(size: 14)) .font(.subheadline)
Text("You can change these anytime in Customize") Text("You can change these anytime in Customize")
.font(.system(size: 13, weight: .medium)) .font(.caption.weight(.medium))
} }
.foregroundColor(.white.opacity(0.7)) .foregroundColor(.white.opacity(0.7))
.padding(.top, 20) .padding(.top, 20)
@@ -130,14 +130,15 @@ struct OnboardingStylePreview: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 44, height: 44) .frame(width: 44, height: 44)
.foregroundColor(moodTint.color(forMood: .good)) .foregroundColor(moodTint.color(forMood: .good))
.accessibilityLabel(Mood.good.strValue)
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text("Wednesday - 10th") Text("Wednesday - 10th")
.font(.system(size: 17, weight: .semibold)) .font(.body.weight(.semibold))
.foregroundColor(.white) .foregroundColor(.white)
Text(Mood.good.strValue) Text(Mood.good.strValue)
.font(.system(size: 14)) .font(.subheadline)
.foregroundColor(.white.opacity(0.8)) .foregroundColor(.white.opacity(0.8))
} }
@@ -167,6 +168,7 @@ struct OnboardingIconPackOption: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 24, height: 24) .frame(width: 24, height: 24)
.foregroundColor(moodTint.color(forMood: mood)) .foregroundColor(moodTint.color(forMood: mood))
.accessibilityLabel(mood.strValue)
} }
} }
.padding(.horizontal, 16) .padding(.horizontal, 16)

View File

@@ -34,14 +34,14 @@ struct OnboardingSubscription: View {
.frame(width: 120, height: 120) .frame(width: 120, height: 120)
Image(systemName: "crown.fill") Image(systemName: "crown.fill")
.font(.system(size: 48)) .font(.largeTitle)
.foregroundColor(.yellow) .foregroundColor(.yellow)
} }
.padding(.bottom, 24) .padding(.bottom, 24)
// Title // Title
Text("Unlock the Full\nExperience") Text("Unlock the Full\nExperience")
.font(.system(size: 28, weight: .bold, design: .rounded)) .font(.title.weight(.bold))
.foregroundColor(.white) .foregroundColor(.white)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.padding(.bottom, 8) .padding(.bottom, 8)
@@ -101,10 +101,10 @@ struct OnboardingSubscription: View {
}) { }) {
HStack { HStack {
Image(systemName: "sparkles") Image(systemName: "sparkles")
.font(.system(size: 18, weight: .semibold)) .font(.headline.weight(.semibold))
Text("Get Personal Insights") Text("Get Personal Insights")
.font(.system(size: 18, weight: .bold)) .font(.headline.weight(.bold))
} }
.foregroundColor(Color(hex: "11998e")) .foregroundColor(Color(hex: "11998e"))
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@@ -125,7 +125,7 @@ struct OnboardingSubscription: View {
completionClosure(onboardingData) completionClosure(onboardingData)
}) { }) {
Text("Maybe Later") Text("Maybe Later")
.font(.system(size: 16, weight: .medium)) .font(.body.weight(.medium))
.foregroundColor(.white.opacity(0.8)) .foregroundColor(.white.opacity(0.8))
} }
.accessibilityLabel(String(localized: "Maybe Later")) .accessibilityLabel(String(localized: "Maybe Later"))
@@ -154,18 +154,18 @@ struct BenefitRow: View {
var body: some View { var body: some View {
HStack(spacing: 16) { HStack(spacing: 16) {
Image(systemName: icon) Image(systemName: icon)
.font(.system(size: 22)) .font(.title3)
.foregroundColor(.white) .foregroundColor(.white)
.frame(width: 40) .frame(width: 40)
.accessibilityHidden(true) .accessibilityHidden(true)
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text(title) Text(title)
.font(.system(size: 16, weight: .semibold)) .font(.body.weight(.semibold))
.foregroundColor(.white) .foregroundColor(.white)
Text(description) Text(description)
.font(.system(size: 13)) .font(.caption)
.foregroundColor(.white.opacity(0.8)) .foregroundColor(.white.opacity(0.8))
} }

View File

@@ -36,21 +36,21 @@ struct OnboardingTime: View {
.frame(width: 120, height: 120) .frame(width: 120, height: 120)
Image(systemName: "bell.fill") Image(systemName: "bell.fill")
.font(.system(size: 44)) .font(.largeTitle)
.foregroundColor(.white) .foregroundColor(.white)
} }
.padding(.bottom, 32) .padding(.bottom, 32)
// Title // Title
Text("When should we\nremind you?") Text("When should we\nremind you?")
.font(.system(size: 28, weight: .bold, design: .rounded)) .font(.title.weight(.bold))
.foregroundColor(.white) .foregroundColor(.white)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.padding(.bottom, 12) .padding(.bottom, 12)
// Subtitle // Subtitle
Text("Pick a time that works for your daily check-in") Text("Pick a time that works for your daily check-in")
.font(.system(size: 16, weight: .medium)) .font(.body.weight(.medium))
.foregroundColor(.white.opacity(0.85)) .foregroundColor(.white.opacity(0.85))
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.padding(.horizontal, 40) .padding(.horizontal, 40)
@@ -80,12 +80,12 @@ struct OnboardingTime: View {
// Info text // Info text
HStack(spacing: 12) { HStack(spacing: 12) {
Image(systemName: "info.circle.fill") Image(systemName: "info.circle.fill")
.font(.system(size: 20)) .font(.title3)
.foregroundColor(.white.opacity(0.8)) .foregroundColor(.white.opacity(0.8))
.accessibilityHidden(true) .accessibilityHidden(true)
Text("You'll get a gentle reminder at \(formatter.string(from: onboardingData.date)) every day") Text("You'll get a gentle reminder at \(formatter.string(from: onboardingData.date)) every day")
.font(.system(size: 14, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundColor(.white.opacity(0.9)) .foregroundColor(.white.opacity(0.9))
} }
.padding(.horizontal, 30) .padding(.horizontal, 30)

View File

@@ -23,6 +23,7 @@ struct OnboardingTitle: View {
.opacity(0.04) .opacity(0.04)
.scaleEffect(1.2) .scaleEffect(1.2)
.padding(.bottom, 55) .padding(.bottom, 55)
.accessibilityHidden(true)
ScrollView { ScrollView {
VStack{ VStack{
@@ -37,8 +38,7 @@ struct OnboardingTitle: View {
// onboardingData.title = option // onboardingData.title = option
}, label: { }, label: {
Text(option) Text(option)
.font(.system(size: 15)) .font(.subheadline.weight(.bold))
.fontWeight(.bold)
.foregroundColor(.white) .foregroundColor(.white)
.padding(10) .padding(10)
.background(RoundedRectangle(cornerRadius: 10).stroke().foregroundColor(Color.white)) .background(RoundedRectangle(cornerRadius: 10).stroke().foregroundColor(Color.white))

View File

@@ -32,20 +32,20 @@ struct OnboardingWelcome: View {
.frame(width: 120, height: 120) .frame(width: 120, height: 120)
Image(systemName: "heart.fill") Image(systemName: "heart.fill")
.font(.system(size: 50)) .font(.largeTitle)
.foregroundColor(.white) .foregroundColor(.white)
} }
.padding(.bottom, 40) .padding(.bottom, 40)
// Title // Title
Text("Welcome to Feels") Text("Welcome to Feels")
.font(.system(size: 34, weight: .bold, design: .rounded)) .font(.largeTitle.weight(.bold))
.foregroundColor(.white) .foregroundColor(.white)
.padding(.bottom, 12) .padding(.bottom, 12)
// Subtitle // Subtitle
Text("Track your mood, discover patterns,\nand understand yourself better.") Text("Track your mood, discover patterns,\nand understand yourself better.")
.font(.system(size: 18, weight: .medium)) .font(.headline.weight(.medium))
.foregroundColor(.white.opacity(0.9)) .foregroundColor(.white.opacity(0.9))
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.padding(.horizontal, 40) .padding(.horizontal, 40)
@@ -91,18 +91,18 @@ struct FeatureRow: View {
.frame(width: 50, height: 50) .frame(width: 50, height: 50)
Image(systemName: icon) Image(systemName: icon)
.font(.system(size: 22)) .font(.title3)
.foregroundColor(.white) .foregroundColor(.white)
} }
.accessibilityHidden(true) .accessibilityHidden(true)
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text(title) Text(title)
.font(.system(size: 16, weight: .semibold)) .font(.body.weight(.semibold))
.foregroundColor(.white) .foregroundColor(.white)
Text(description) Text(description)
.font(.system(size: 14)) .font(.subheadline)
.foregroundColor(.white.opacity(0.8)) .foregroundColor(.white.opacity(0.8))
} }

View File

@@ -27,6 +27,7 @@ struct OnboardingWrapup: View {
.foregroundColor(Color(UIColor.darkText)) .foregroundColor(Color(UIColor.darkText))
.opacity(0.04) .opacity(0.04)
.scaleEffect(1.2, anchor: .trailing) .scaleEffect(1.2, anchor: .trailing)
.accessibilityHidden(true)
Spacer() Spacer()
} }

View File

@@ -90,11 +90,6 @@ extension Font {
static func scalable(_ style: Font.TextStyle, weight: Font.Weight = .regular) -> Font { static func scalable(_ style: Font.TextStyle, weight: Font.Weight = .regular) -> Font {
Font.system(style, design: .rounded).weight(weight) Font.system(style, design: .rounded).weight(weight)
} }
/// Returns a custom-sized font that scales with Dynamic Type
static func scaledSystem(size: CGFloat, weight: Font.Weight = .regular, design: Font.Design = .default, relativeTo style: Font.TextStyle = .body) -> Font {
Font.system(size: size, weight: weight, design: design)
}
} }
/// Property wrapper for scaled metrics that respect Dynamic Type /// Property wrapper for scaled metrics that respect Dynamic Type

View File

@@ -333,7 +333,7 @@ struct AuraVotingView: View {
// Label with elegant typography // Label with elegant typography
Text(mood.strValue) Text(mood.strValue)
.font(.system(size: 12, weight: .semibold, design: .rounded)) .font(.caption.weight(.semibold))
.foregroundColor(color) .foregroundColor(color)
.tracking(0.5) .tracking(0.5)
} }

View File

@@ -32,6 +32,7 @@ struct BGViewItem: View {
.foregroundColor(DefaultMoodTint.color(forMood: mood)) .foregroundColor(DefaultMoodTint.color(forMood: mood))
// .blur(radius: 3) // .blur(radius: 3)
.opacity(0.1) .opacity(0.1)
.accessibilityHidden(true)
} }
} }

View File

@@ -194,7 +194,7 @@ struct CustomizeView: View {
private var headerView: some View { private var headerView: some View {
HStack { HStack {
Text("Customize") Text("Customize")
.font(.system(size: 28, weight: .bold, design: .rounded)) .font(.title.weight(.bold))
.foregroundColor(textColor) .foregroundColor(textColor)
Spacer() Spacer()
@@ -214,7 +214,7 @@ struct SettingsSection<Content: View>: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
Text(title.uppercased()) Text(title.uppercased())
.font(.system(size: 13, weight: .semibold)) .font(.caption.weight(.semibold))
.foregroundColor(textColor.opacity(0.4)) .foregroundColor(textColor.opacity(0.4))
.tracking(0.5) .tracking(0.5)
@@ -240,7 +240,7 @@ struct SettingsRow<Content: View>: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
Text(title) Text(title)
.font(.system(size: 15, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundColor(textColor.opacity(0.7)) .foregroundColor(textColor.opacity(0.7))
content content
@@ -272,7 +272,7 @@ struct ThemePickerCompact: View {
if theme == aTheme { if theme == aTheme {
Image(systemName: "checkmark.circle.fill") Image(systemName: "checkmark.circle.fill")
.font(.system(size: 18)) .font(.headline)
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
.background(Circle().fill(.white).padding(2)) .background(Circle().fill(.white).padding(2))
.offset(x: 14, y: 14) .offset(x: 14, y: 14)
@@ -280,7 +280,7 @@ struct ThemePickerCompact: View {
} }
Text(aTheme.title) Text(aTheme.title)
.font(.system(size: 12, weight: .medium)) .font(.caption.weight(.medium))
.foregroundColor(theme == aTheme ? .accentColor : textColor.opacity(0.6)) .foregroundColor(theme == aTheme ? .accentColor : textColor.opacity(0.6))
} }
} }
@@ -310,7 +310,7 @@ struct TextColorPickerCompact: View {
.labelsHidden() .labelsHidden()
Text("Sample Text") Text("Sample Text")
.font(.system(size: 16, weight: .medium)) .font(.body.weight(.medium))
.foregroundColor(textColor) .foregroundColor(textColor)
Spacer() Spacer()
@@ -344,6 +344,7 @@ struct ImagePackPickerCompact: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 28, height: 28) .frame(width: 28, height: 28)
.foregroundColor(moodTint.color(forMood: mood)) .foregroundColor(moodTint.color(forMood: mood))
.accessibilityLabel(mood.strValue)
} }
} }
@@ -351,7 +352,7 @@ struct ImagePackPickerCompact: View {
if imagePack == images { if imagePack == images {
Image(systemName: "checkmark.circle.fill") Image(systemName: "checkmark.circle.fill")
.font(.system(size: 22)) .font(.title2)
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
} }
} }
@@ -398,7 +399,7 @@ struct TintPickerCompact: View {
if moodTint == tint { if moodTint == tint {
Image(systemName: "checkmark.circle.fill") Image(systemName: "checkmark.circle.fill")
.font(.system(size: 22)) .font(.title2)
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
} }
} }
@@ -432,11 +433,11 @@ struct TintPickerCompact: View {
if moodTint == .Custom { if moodTint == .Custom {
Image(systemName: "checkmark.circle.fill") Image(systemName: "checkmark.circle.fill")
.font(.system(size: 22)) .font(.title2)
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
} else { } else {
Text("Custom") Text("Custom")
.font(.system(size: 13)) .font(.caption)
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
} }
@@ -485,8 +486,12 @@ struct VotingLayoutPickerCompact: View {
HStack(spacing: 10) { HStack(spacing: 10) {
ForEach(VotingLayoutStyle.allCases, id: \.rawValue) { layout in ForEach(VotingLayoutStyle.allCases, id: \.rawValue) { layout in
Button(action: { Button(action: {
withAnimation(.easeInOut(duration: 0.2)) { if UIAccessibility.isReduceMotionEnabled {
votingLayoutStyle = layout.rawValue votingLayoutStyle = layout.rawValue
} else {
withAnimation(.easeInOut(duration: 0.2)) {
votingLayoutStyle = layout.rawValue
}
} }
EventLogger.log(event: "change_voting_layout", withData: ["layout": layout.displayName]) EventLogger.log(event: "change_voting_layout", withData: ["layout": layout.displayName])
}) { }) {
@@ -496,7 +501,7 @@ struct VotingLayoutPickerCompact: View {
.foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.4)) .foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.4))
Text(layout.displayName) Text(layout.displayName)
.font(.system(size: 11, weight: .medium)) .font(.caption2.weight(.medium))
.foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.5)) .foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.5))
} }
.frame(width: 70) .frame(width: 70)
@@ -608,7 +613,7 @@ struct CustomWidgetSection: View {
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
Image(systemName: "plus") Image(systemName: "plus")
.font(.system(size: 24, weight: .medium)) .font(.title2.weight(.medium))
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
} }
@@ -618,9 +623,9 @@ struct CustomWidgetSection: View {
Link(destination: URL(string: "https://support.apple.com/guide/iphone/add-widgets-iphb8f1bf206/ios")!) { Link(destination: URL(string: "https://support.apple.com/guide/iphone/add-widgets-iphb8f1bf206/ios")!) {
HStack(spacing: 6) { HStack(spacing: 6) {
Image(systemName: "questionmark.circle") Image(systemName: "questionmark.circle")
.font(.system(size: 14)) .font(.subheadline)
Text("How to add widgets") Text("How to add widgets")
.font(.system(size: 14)) .font(.subheadline)
} }
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
} }
@@ -659,12 +664,12 @@ struct PersonalityPackPickerCompact: View {
HStack { HStack {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text(String(aPack.title())) Text(String(aPack.title()))
.font(.system(size: 15, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(textColor) .foregroundColor(textColor)
let strings = aPack.randomPushNotificationStrings() let strings = aPack.randomPushNotificationStrings()
Text(strings.body) Text(strings.body)
.font(.system(size: 13)) .font(.caption)
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
.lineLimit(2) .lineLimit(2)
} }
@@ -673,7 +678,7 @@ struct PersonalityPackPickerCompact: View {
if personalityPack == aPack { if personalityPack == aPack {
Image(systemName: "checkmark.circle.fill") Image(systemName: "checkmark.circle.fill")
.font(.system(size: 22)) .font(.title2)
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
} }
} }
@@ -736,7 +741,7 @@ struct DayFilterPickerCompact: View {
impactMed.impactOccurred() impactMed.impactOccurred()
}) { }) {
Text(day.prefix(2).uppercased()) Text(day.prefix(2).uppercased())
.font(.system(size: 13, weight: .semibold)) .font(.caption.weight(.semibold))
.foregroundColor(isActive ? .white : textColor.opacity(0.5)) .foregroundColor(isActive ? .white : textColor.opacity(0.5))
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(height: 40) .frame(height: 40)
@@ -750,7 +755,7 @@ struct DayFilterPickerCompact: View {
} }
Text(String(localized: "day_picker_view_text")) Text(String(localized: "day_picker_view_text"))
.font(.system(size: 13)) .font(.caption)
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
} }
@@ -774,15 +779,15 @@ struct SubscriptionBannerView: View {
private var subscribedView: some View { private var subscribedView: some View {
HStack(spacing: 12) { HStack(spacing: 12) {
Image(systemName: "checkmark.seal.fill") Image(systemName: "checkmark.seal.fill")
.font(.system(size: 28)) .font(.title)
.foregroundColor(.green) .foregroundColor(.green)
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text("Premium Active") Text("Premium Active")
.font(.system(size: 16, weight: .semibold)) .font(.body.weight(.semibold))
Text("You have full access") Text("You have full access")
.font(.system(size: 13)) .font(.caption)
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
@@ -793,7 +798,7 @@ struct SubscriptionBannerView: View {
await openSubscriptionManagement() await openSubscriptionManagement()
} }
} }
.font(.system(size: 14, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(.green) .foregroundColor(.green)
.padding(.horizontal, 16) .padding(.horizontal, 16)
.padding(.vertical, 8) .padding(.vertical, 8)
@@ -813,23 +818,23 @@ struct SubscriptionBannerView: View {
}) { }) {
HStack(spacing: 12) { HStack(spacing: 12) {
Image(systemName: "crown.fill") Image(systemName: "crown.fill")
.font(.system(size: 28)) .font(.title)
.foregroundColor(.orange) .foregroundColor(.orange)
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text("Unlock Premium") Text("Unlock Premium")
.font(.system(size: 16, weight: .semibold)) .font(.body.weight(.semibold))
.foregroundColor(colorScheme == .dark ? .white : .black) .foregroundColor(colorScheme == .dark ? .white : .black)
Text("Month & Year views, Insights & more") Text("Month & Year views, Insights & more")
.font(.system(size: 13)) .font(.caption)
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
Spacer() Spacer()
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
.font(.system(size: 14, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
.padding(16) .padding(16)
@@ -870,8 +875,12 @@ struct DayViewStylePickerCompact: View {
HStack(spacing: 10) { HStack(spacing: 10) {
ForEach(DayViewStyle.allCases, id: \.rawValue) { style in ForEach(DayViewStyle.allCases, id: \.rawValue) { style in
Button(action: { Button(action: {
withAnimation(.easeInOut(duration: 0.2)) { if UIAccessibility.isReduceMotionEnabled {
dayViewStyle = style dayViewStyle = style
} else {
withAnimation(.easeInOut(duration: 0.2)) {
dayViewStyle = style
}
} }
let impactMed = UIImpactFeedbackGenerator(style: .medium) let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred() impactMed.impactOccurred()
@@ -883,7 +892,7 @@ struct DayViewStylePickerCompact: View {
.foregroundColor(dayViewStyle == style ? .accentColor : textColor.opacity(0.4)) .foregroundColor(dayViewStyle == style ? .accentColor : textColor.opacity(0.4))
Text(style.displayName) Text(style.displayName)
.font(.system(size: 11, weight: .medium)) .font(.caption2.weight(.medium))
.foregroundColor(dayViewStyle == style ? .accentColor : textColor.opacity(0.5)) .foregroundColor(dayViewStyle == style ? .accentColor : textColor.opacity(0.5))
} }
.frame(width: 70) .frame(width: 70)
@@ -962,7 +971,7 @@ struct DayViewStylePickerCompact: View {
// Giant number with glowing orb // Giant number with glowing orb
HStack(spacing: 4) { HStack(spacing: 4) {
Text("17") Text("17")
.font(.system(size: 20, weight: .black, design: .rounded)) .font(.title3.weight(.black))
.foregroundStyle( .foregroundStyle(
LinearGradient(colors: [.green, .green.opacity(0.5)], startPoint: .top, endPoint: .bottom) LinearGradient(colors: [.green, .green.opacity(0.5)], startPoint: .top, endPoint: .bottom)
) )
@@ -983,7 +992,7 @@ struct DayViewStylePickerCompact: View {
Rectangle().frame(width: 34, height: 2) Rectangle().frame(width: 34, height: 2)
HStack(spacing: 4) { HStack(spacing: 4) {
Text("12") Text("12")
.font(.system(size: 18, weight: .regular, design: .serif)) .font(.headline.weight(.regular))
Rectangle().frame(width: 1, height: 20) Rectangle().frame(width: 1, height: 20)
VStack(alignment: .leading, spacing: 1) { VStack(alignment: .leading, spacing: 1) {
RoundedRectangle(cornerRadius: 1).frame(width: 12, height: 3) RoundedRectangle(cornerRadius: 1).frame(width: 12, height: 3)
@@ -1176,7 +1185,7 @@ struct DayViewStylePickerCompact: View {
.offset(x: -6, y: 4) .offset(x: -6, y: 4)
.blur(radius: 2) .blur(radius: 2)
Image(systemName: "gyroscope") Image(systemName: "gyroscope")
.font(.system(size: 12, weight: .medium)) .font(.caption.weight(.medium))
.foregroundColor(.white) .foregroundColor(.white)
} }
case .micro: case .micro:

View File

@@ -64,6 +64,8 @@ struct IconPickerView: View {
.frame(width: 50, height:50) .frame(width: 50, height:50)
.cornerRadius(10) .cornerRadius(10)
}) })
.accessibilityLabel(String(localized: "Default app icon"))
.accessibilityHint(String(localized: "Double tap to select"))
ForEach(iconSets, id: \.self.0){ iconSet in ForEach(iconSets, id: \.self.0){ iconSet in
@@ -78,6 +80,8 @@ struct IconPickerView: View {
.frame(width: 50, height:50) .frame(width: 50, height:50)
.cornerRadius(10) .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() .padding()

View File

@@ -31,6 +31,7 @@ struct ImagePackPickerView: View {
.foregroundColor( .foregroundColor(
moodTint.color(forMood: mood) moodTint.color(forMood: mood)
) )
.accessibilityLabel(mood.strValue)
} }
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 0, maxWidth: .infinity)
} }

View File

@@ -31,8 +31,12 @@ struct VotingLayoutPickerView: View {
HStack(spacing: 8) { HStack(spacing: 8) {
ForEach(VotingLayoutStyle.allCases, id: \.rawValue) { layout in ForEach(VotingLayoutStyle.allCases, id: \.rawValue) { layout in
Button(action: { Button(action: {
withAnimation(.easeInOut(duration: 0.2)) { if UIAccessibility.isReduceMotionEnabled {
votingLayoutStyle = layout.rawValue votingLayoutStyle = layout.rawValue
} else {
withAnimation(.easeInOut(duration: 0.2)) {
votingLayoutStyle = layout.rawValue
}
} }
EventLogger.log(event: "change_voting_layout", withData: ["layout": layout.displayName]) EventLogger.log(event: "change_voting_layout", withData: ["layout": layout.displayName])
}) { }) {

View File

@@ -172,12 +172,12 @@ extension DayView {
HStack(spacing: 10) { HStack(spacing: 10) {
// Calendar icon // Calendar icon
Image(systemName: "calendar") Image(systemName: "calendar")
.font(.system(size: 16, weight: .semibold)) .font(.body.weight(.semibold))
.foregroundColor(textColor.opacity(0.6)) .foregroundColor(textColor.opacity(0.6))
.accessibilityHidden(true) .accessibilityHidden(true)
Text("\(Random.monthName(fromMonthInt: month)) \(String(year))") Text("\(Random.monthName(fromMonthInt: month)) \(String(year))")
.font(.system(size: 20, weight: .bold, design: .rounded)) .font(.title3.weight(.bold))
.foregroundColor(textColor) .foregroundColor(textColor)
Spacer() Spacer()
@@ -194,7 +194,7 @@ extension DayView {
HStack(spacing: 0) { HStack(spacing: 0) {
// Large month number as hero element // Large month number as hero element
Text(String(format: "%02d", month)) Text(String(format: "%02d", month))
.font(.system(size: 48, weight: .black, design: .rounded)) .font(.largeTitle.weight(.black))
.foregroundStyle( .foregroundStyle(
LinearGradient( LinearGradient(
colors: [textColor, textColor.opacity(0.4)], colors: [textColor, textColor.opacity(0.4)],
@@ -206,12 +206,12 @@ extension DayView {
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text(Random.monthName(fromMonthInt: month).uppercased()) Text(Random.monthName(fromMonthInt: month).uppercased())
.font(.system(size: 14, weight: .bold, design: .rounded)) .font(.subheadline.weight(.bold))
.tracking(3) .tracking(3)
.foregroundColor(textColor) .foregroundColor(textColor)
Text(String(year)) Text(String(year))
.font(.system(size: 12, weight: .medium, design: .rounded)) .font(.caption.weight(.medium))
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
} }
@@ -253,12 +253,12 @@ extension DayView {
HStack(alignment: .firstTextBaseline, spacing: 12) { HStack(alignment: .firstTextBaseline, spacing: 12) {
// Large serif month name // Large serif month name
Text(Random.monthName(fromMonthInt: month).uppercased()) Text(Random.monthName(fromMonthInt: month).uppercased())
.font(.system(size: 28, weight: .regular, design: .serif)) .font(.title.weight(.regular))
.foregroundColor(textColor) .foregroundColor(textColor)
// Year in lighter weight // Year in lighter weight
Text(String(year)) Text(String(year))
.font(.system(size: 16, weight: .light, design: .serif)) .font(.body.weight(.light))
.italic() .italic()
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
@@ -266,7 +266,7 @@ extension DayView {
// Decorative flourish // Decorative flourish
Text("§") Text("§")
.font(.system(size: 20, weight: .regular, design: .serif)) .font(.title3.weight(.regular))
.foregroundColor(textColor.opacity(0.3)) .foregroundColor(textColor.opacity(0.3))
} }
.padding(.horizontal, 16) .padding(.horizontal, 16)
@@ -284,12 +284,12 @@ extension DayView {
HStack(spacing: 12) { HStack(spacing: 12) {
// Glowing terminal prompt // Glowing terminal prompt
Text(">") Text(">")
.font(.system(size: 18, weight: .bold, design: .monospaced)) .font(.headline.weight(.bold).monospaced())
.foregroundColor(Color(red: 0.4, green: 1.0, blue: 0.4)) .foregroundColor(Color(red: 0.4, green: 1.0, blue: 0.4))
.shadow(color: Color(red: 0.4, green: 1.0, blue: 0.4).opacity(0.8), radius: 4, x: 0, y: 0) .shadow(color: Color(red: 0.4, green: 1.0, blue: 0.4).opacity(0.8), radius: 4, x: 0, y: 0)
Text("\(Random.monthName(fromMonthInt: month).uppercased())_\(String(year))") Text("\(Random.monthName(fromMonthInt: month).uppercased())_\(String(year))")
.font(.system(size: 16, weight: .bold, design: .monospaced)) .font(.body.weight(.bold).monospaced())
.foregroundColor(.white) .foregroundColor(.white)
.shadow(color: .white.opacity(0.3), radius: 2, x: 0, y: 0) .shadow(color: .white.opacity(0.3), radius: 2, x: 0, y: 0)
@@ -329,12 +329,12 @@ extension DayView {
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text(Random.monthName(fromMonthInt: month)) Text(Random.monthName(fromMonthInt: month))
.font(.system(size: 18, weight: .thin)) .font(.headline.weight(.thin))
.tracking(4) .tracking(4)
.foregroundColor(textColor) .foregroundColor(textColor)
Text(String(year)) Text(String(year))
.font(.system(size: 11, weight: .ultraLight)) .font(.caption2.weight(.ultraLight))
.foregroundColor(textColor.opacity(0.4)) .foregroundColor(textColor.opacity(0.4))
} }
@@ -371,7 +371,7 @@ extension DayView {
// Glass content // Glass content
HStack(spacing: 12) { HStack(spacing: 12) {
Text(Random.monthName(fromMonthInt: month)) Text(Random.monthName(fromMonthInt: month))
.font(.system(size: 20, weight: .semibold)) .font(.title3.weight(.semibold))
.foregroundColor(textColor) .foregroundColor(textColor)
Capsule() Capsule()
@@ -379,7 +379,7 @@ extension DayView {
.frame(width: 4, height: 4) .frame(width: 4, height: 4)
Text(String(year)) Text(String(year))
.font(.system(size: 16, weight: .medium)) .font(.body.weight(.medium))
.foregroundColor(textColor.opacity(0.6)) .foregroundColor(textColor.opacity(0.6))
Spacer() Spacer()
@@ -405,11 +405,11 @@ extension DayView {
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text("SIDE A") Text("SIDE A")
.font(.system(size: 10, weight: .bold, design: .monospaced)) .font(.caption2.weight(.bold).monospaced())
.foregroundColor(textColor.opacity(0.4)) .foregroundColor(textColor.opacity(0.4))
Text("\(Random.monthName(fromMonthInt: month).uppercased()) '\(String(year).suffix(2))") Text("\(Random.monthName(fromMonthInt: month).uppercased()) '\(String(year).suffix(2))")
.font(.system(size: 16, weight: .black, design: .rounded)) .font(.body.weight(.black))
.foregroundColor(textColor) .foregroundColor(textColor)
.tracking(1) .tracking(1)
} }
@@ -418,7 +418,7 @@ extension DayView {
// Track counter // Track counter
Text(String(format: "%02d", month)) Text(String(format: "%02d", month))
.font(.system(size: 20, weight: .bold, design: .monospaced)) .font(.title3.weight(.bold).monospaced())
.foregroundColor(textColor.opacity(0.3)) .foregroundColor(textColor.opacity(0.3))
} }
.padding(.horizontal, 16) .padding(.horizontal, 16)
@@ -443,11 +443,11 @@ extension DayView {
HStack(spacing: 16) { HStack(spacing: 16) {
Text(Random.monthName(fromMonthInt: month)) Text(Random.monthName(fromMonthInt: month))
.font(.system(size: 22, weight: .light)) .font(.title2.weight(.light))
.foregroundColor(textColor) .foregroundColor(textColor)
Text(String(year)) Text(String(year))
.font(.system(size: 14, weight: .regular)) .font(.subheadline.weight(.regular))
.foregroundColor(textColor.opacity(0.4)) .foregroundColor(textColor.opacity(0.4))
Spacer() Spacer()
@@ -483,11 +483,11 @@ extension DayView {
.frame(width: 2) .frame(width: 2)
Text(Random.monthName(fromMonthInt: month)) Text(Random.monthName(fromMonthInt: month))
.font(.system(size: 18, weight: .regular, design: .serif)) .font(.headline.weight(.regular))
.foregroundColor(textColor) .foregroundColor(textColor)
Text(String(year)) Text(String(year))
.font(.system(size: 14, weight: .light, design: .serif)) .font(.subheadline.weight(.light))
.italic() .italic()
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
@@ -524,7 +524,7 @@ extension DayView {
return HStack(spacing: 0) { return HStack(spacing: 0) {
// Month number // Month number
Text(String(format: "%02d", month)) Text(String(format: "%02d", month))
.font(.system(size: 32, weight: .thin)) .font(.title.weight(.thin))
.foregroundColor(hasData ? barColor.opacity(0.6) : textColor.opacity(0.3)) .foregroundColor(hasData ? barColor.opacity(0.6) : textColor.opacity(0.3))
.frame(width: 50) .frame(width: 50)
@@ -554,16 +554,16 @@ extension DayView {
VStack(alignment: .trailing, spacing: 2) { VStack(alignment: .trailing, spacing: 2) {
Text(Random.monthName(fromMonthInt: month)) Text(Random.monthName(fromMonthInt: month))
.font(.system(size: 14, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundColor(textColor) .foregroundColor(textColor)
if hasData { if hasData {
Text(String(format: "%.1f avg", averageMood + 1)) // Display as 1-5 Text(String(format: "%.1f avg", averageMood + 1)) // Display as 1-5
.font(.system(size: 10, weight: .semibold)) .font(.caption2.weight(.semibold))
.foregroundColor(barColor) .foregroundColor(barColor)
} else { } else {
Text(String(year)) Text(String(year))
.font(.system(size: 11, weight: .regular)) .font(.caption2.weight(.regular))
.foregroundColor(textColor.opacity(0.4)) .foregroundColor(textColor.opacity(0.4))
} }
} }
@@ -576,11 +576,11 @@ extension DayView {
private func patternSectionHeader(month: Int, year: Int) -> some View { private func patternSectionHeader(month: Int, year: Int) -> some View {
HStack(spacing: 10) { HStack(spacing: 10) {
Image(systemName: "calendar") Image(systemName: "calendar")
.font(.system(size: 16, weight: .semibold)) .font(.body.weight(.semibold))
.foregroundColor(textColor.opacity(0.6)) .foregroundColor(textColor.opacity(0.6))
Text("\(Random.monthName(fromMonthInt: month)) \(String(year))") Text("\(Random.monthName(fromMonthInt: month)) \(String(year))")
.font(.system(size: 20, weight: .bold, design: .rounded)) .font(.title3.weight(.bold))
.foregroundColor(textColor) .foregroundColor(textColor)
Spacer() Spacer()
@@ -630,12 +630,12 @@ extension DayView {
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text(Random.monthName(fromMonthInt: month).uppercased()) Text(Random.monthName(fromMonthInt: month).uppercased())
.font(.system(size: 16, weight: .bold, design: .serif)) .font(.body.weight(.bold))
.foregroundColor(Color(red: 0.9, green: 0.85, blue: 0.75)) .foregroundColor(Color(red: 0.9, green: 0.85, blue: 0.75))
.shadow(color: .black.opacity(0.3), radius: 1, x: 0, y: 1) .shadow(color: .black.opacity(0.3), radius: 1, x: 0, y: 1)
Text(String(year)) Text(String(year))
.font(.system(size: 12, weight: .medium, design: .serif)) .font(.caption.weight(.medium))
.foregroundColor(Color(red: 0.8, green: 0.7, blue: 0.55)) .foregroundColor(Color(red: 0.8, green: 0.7, blue: 0.55))
} }
.padding(.leading, 12) .padding(.leading, 12)
@@ -726,17 +726,17 @@ extension DayView {
.blur(radius: 4) .blur(radius: 4)
Text(String(format: "%02d", month)) Text(String(format: "%02d", month))
.font(.system(size: 14, weight: .semibold, design: .rounded)) .font(.subheadline.weight(.semibold))
.foregroundColor(textColor) .foregroundColor(textColor)
} }
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text(Random.monthName(fromMonthInt: month)) Text(Random.monthName(fromMonthInt: month))
.font(.system(size: 18, weight: .medium)) .font(.headline.weight(.medium))
.foregroundColor(textColor) .foregroundColor(textColor)
Text(String(year)) Text(String(year))
.font(.system(size: 12, weight: .regular)) .font(.caption.weight(.regular))
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
} }
@@ -811,18 +811,18 @@ extension DayView {
.frame(width: 44, height: 44) .frame(width: 44, height: 44)
Image(systemName: "gyroscope") Image(systemName: "gyroscope")
.font(.system(size: 22, weight: .medium)) .font(.title2.weight(.medium))
.foregroundColor(.white) .foregroundColor(.white)
} }
.shadow(color: Color.purple.opacity(0.3), radius: 8, x: 0, y: 4) .shadow(color: Color.purple.opacity(0.3), radius: 8, x: 0, y: 4)
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text(Random.monthName(fromMonthInt: month)) Text(Random.monthName(fromMonthInt: month))
.font(.system(size: 20, weight: .semibold)) .font(.title3.weight(.semibold))
.foregroundColor(textColor) .foregroundColor(textColor)
Text(String(year)) Text(String(year))
.font(.system(size: 13, weight: .medium)) .font(.caption.weight(.medium))
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
} }
@@ -830,7 +830,7 @@ extension DayView {
// Tilt indicator // Tilt indicator
Image(systemName: "iphone.gen3.radiowaves.left.and.right") Image(systemName: "iphone.gen3.radiowaves.left.and.right")
.font(.system(size: 18)) .font(.headline)
.foregroundColor(textColor.opacity(0.3)) .foregroundColor(textColor.opacity(0.3))
} }
.padding(.horizontal, 18) .padding(.horizontal, 18)
@@ -852,15 +852,15 @@ extension DayView {
.frame(width: 3, height: 16) .frame(width: 3, height: 16)
Text("\(Random.monthName(fromMonthInt: month).prefix(3).uppercased())") Text("\(Random.monthName(fromMonthInt: month).prefix(3).uppercased())")
.font(.system(size: 11, weight: .bold, design: .monospaced)) .font(.caption2.weight(.bold).monospaced())
.foregroundColor(textColor.opacity(0.6)) .foregroundColor(textColor.opacity(0.6))
Text("") Text("")
.font(.system(size: 8)) .font(.caption2)
.foregroundColor(textColor.opacity(0.3)) .foregroundColor(textColor.opacity(0.3))
Text(String(year)) Text(String(year))
.font(.system(size: 11, weight: .medium, design: .monospaced)) .font(.caption2.weight(.medium).monospaced())
.foregroundColor(textColor.opacity(0.4)) .foregroundColor(textColor.opacity(0.4))
// Thin separator line // Thin separator line

View File

@@ -108,30 +108,31 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
.foregroundColor(isMissing ? .gray : .white) .foregroundColor(isMissing ? .gray : .white)
.accessibilityLabel(entry.mood.strValue)
} }
.shadow(color: isMissing ? .clear : moodColor.opacity(0.4), radius: 8, x: 0, y: 4) .shadow(color: isMissing ? .clear : moodColor.opacity(0.4), radius: 8, x: 0, y: 4)
VStack(alignment: .leading, spacing: 6) { VStack(alignment: .leading, spacing: 6) {
HStack(spacing: 6) { HStack(spacing: 6) {
Text(Random.weekdayName(fromDate: entry.forDate)) Text(Random.weekdayName(fromDate: entry.forDate))
.font(.system(size: 17, weight: .semibold)) .font(.body.weight(.semibold))
.foregroundColor(textColor) .foregroundColor(textColor)
Text("") Text("")
.foregroundColor(textColor.opacity(0.4)) .foregroundColor(textColor.opacity(0.4))
Text(Random.dayFormat(fromDate: entry.forDate)) Text(Random.dayFormat(fromDate: entry.forDate))
.font(.system(size: 17, weight: .medium)) .font(.body.weight(.medium))
.foregroundColor(textColor.opacity(0.8)) .foregroundColor(textColor.opacity(0.8))
} }
if isMissing { if isMissing {
Text(String(localized: "mood_value_missing_tap_to_add")) Text(String(localized: "mood_value_missing_tap_to_add"))
.font(.system(size: 14, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundColor(.gray) .foregroundColor(.gray)
} else { } else {
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 14, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(moodColor) .foregroundColor(moodColor)
.padding(.horizontal, 10) .padding(.horizontal, 10)
.padding(.vertical, 4) .padding(.vertical, 4)
@@ -145,7 +146,7 @@ struct EntryListView: View {
Spacer() Spacer()
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
.font(.system(size: 14, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(textColor.opacity(0.3)) .foregroundColor(textColor.opacity(0.3))
} }
.padding(.horizontal, 18) .padding(.horizontal, 18)
@@ -184,20 +185,21 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 22, height: 22) .frame(width: 22, height: 22)
.foregroundColor(isMissing ? .gray : moodColor) .foregroundColor(isMissing ? .gray : moodColor)
.accessibilityLabel(entry.mood.strValue)
) )
VStack(alignment: .leading, spacing: 3) { VStack(alignment: .leading, spacing: 3) {
Text(entry.forDate, format: .dateTime.weekday(.wide).day()) Text(entry.forDate, format: .dateTime.weekday(.wide).day())
.font(.system(size: 16, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundColor(textColor) .foregroundColor(textColor)
if isMissing { if isMissing {
Text(String(localized: "mood_value_missing_tap_to_add")) Text(String(localized: "mood_value_missing_tap_to_add"))
.font(.system(size: 13)) .font(.caption)
.foregroundColor(.gray) .foregroundColor(.gray)
} else { } else {
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 13, weight: .medium)) .font(.caption.weight(.medium))
.foregroundColor(moodColor) .foregroundColor(moodColor)
} }
} }
@@ -225,10 +227,10 @@ struct EntryListView: View {
// Date column // Date column
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
Text(entry.forDate, format: .dateTime.day()) Text(entry.forDate, format: .dateTime.day())
.font(.system(size: 20, weight: .bold, design: .rounded)) .font(.title3.weight(.bold))
.foregroundColor(textColor) .foregroundColor(textColor)
Text(entry.forDate, format: .dateTime.weekday(.abbreviated)) Text(entry.forDate, format: .dateTime.weekday(.abbreviated))
.font(.system(size: 11, weight: .medium)) .font(.caption2.weight(.medium))
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
.textCase(.uppercase) .textCase(.uppercase)
} }
@@ -241,7 +243,7 @@ struct EntryListView: View {
.frame(height: 32) .frame(height: 32)
.overlay( .overlay(
Text(String(localized: "mood_value_missing_tap_to_add")) Text(String(localized: "mood_value_missing_tap_to_add"))
.font(.system(size: 12, weight: .medium)) .font(.caption.weight(.medium))
.foregroundColor(.gray) .foregroundColor(.gray)
) )
} else { } else {
@@ -255,9 +257,10 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 16, height: 16) .frame(width: 16, height: 16)
.foregroundColor(moodColor) .foregroundColor(moodColor)
.accessibilityLabel(entry.mood.strValue)
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 13, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(moodColor) .foregroundColor(moodColor)
} }
) )
@@ -280,19 +283,20 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 28, height: 28) .frame(width: 28, height: 28)
.foregroundColor(isMissing ? .gray : .white) .foregroundColor(isMissing ? .gray : .white)
.accessibilityLabel(entry.mood.strValue)
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text(entry.forDate, format: .dateTime.weekday(.wide).day()) Text(entry.forDate, format: .dateTime.weekday(.wide).day())
.font(.system(size: 15, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(isMissing ? textColor : .white) .foregroundColor(isMissing ? textColor : .white)
if isMissing { if isMissing {
Text(String(localized: "mood_value_missing_tap_to_add")) Text(String(localized: "mood_value_missing_tap_to_add"))
.font(.system(size: 13)) .font(.caption)
.foregroundColor(.gray) .foregroundColor(.gray)
} else { } else {
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 13, weight: .medium)) .font(.caption.weight(.medium))
.foregroundColor(.white.opacity(0.85)) .foregroundColor(.white.opacity(0.85))
} }
} }
@@ -300,7 +304,7 @@ struct EntryListView: View {
Spacer() Spacer()
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
.font(.system(size: 12, weight: .semibold)) .font(.caption.weight(.semibold))
.foregroundColor(isMissing ? textColor.opacity(0.3) : .white.opacity(0.6)) .foregroundColor(isMissing ? textColor.opacity(0.3) : .white.opacity(0.6))
} }
.padding(.horizontal, 18) .padding(.horizontal, 18)
@@ -340,6 +344,7 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.padding(16) .padding(16)
.foregroundColor(isMissing ? .gray : .white) .foregroundColor(isMissing ? .gray : .white)
.accessibilityLabel(entry.mood.strValue)
} }
.shadow( .shadow(
color: isMissing ? .clear : moodColor.opacity(0.3), color: isMissing ? .clear : moodColor.opacity(0.3),
@@ -350,12 +355,12 @@ struct EntryListView: View {
// Day number // Day number
Text(entry.forDate, format: .dateTime.day()) Text(entry.forDate, format: .dateTime.day())
.font(.system(size: 16, weight: .bold, design: .rounded)) .font(.subheadline.weight(.bold))
.foregroundColor(textColor) .foregroundColor(textColor)
// Weekday abbreviation // Weekday abbreviation
Text(entry.forDate, format: .dateTime.weekday(.abbreviated)) Text(entry.forDate, format: .dateTime.weekday(.abbreviated))
.font(.system(size: 11, weight: .medium)) .font(.caption2.weight(.medium))
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
.textCase(.uppercase) .textCase(.uppercase)
} }
@@ -372,7 +377,7 @@ struct EntryListView: View {
HStack(spacing: 0) { HStack(spacing: 0) {
// Giant day number - the visual hero // Giant day number - the visual hero
Text(entry.forDate, format: .dateTime.day()) Text(entry.forDate, format: .dateTime.day())
.font(.system(size: 64, weight: .black, design: .rounded)) .font(.largeTitle.weight(.black))
.foregroundStyle( .foregroundStyle(
isMissing isMissing
? LinearGradient(colors: [Color.gray.opacity(0.3), Color.gray.opacity(0.15)], startPoint: .top, endPoint: .bottom) ? LinearGradient(colors: [Color.gray.opacity(0.3), Color.gray.opacity(0.15)], startPoint: .top, endPoint: .bottom)
@@ -385,7 +390,7 @@ struct EntryListView: View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
// Weekday with elegant typography // Weekday with elegant typography
Text(entry.forDate, format: .dateTime.weekday(.wide)) Text(entry.forDate, format: .dateTime.weekday(.wide))
.font(.system(size: 13, weight: .semibold, design: .rounded)) .font(.caption.weight(.semibold))
.textCase(.uppercase) .textCase(.uppercase)
.tracking(2) .tracking(2)
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
@@ -423,21 +428,22 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundColor(isMissing ? .gray : .white) .foregroundColor(isMissing ? .gray : .white)
.accessibilityLabel(entry.mood.strValue)
} }
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
if isMissing { if isMissing {
Text(String(localized: "mood_value_missing_tap_to_add")) Text(String(localized: "mood_value_missing_tap_to_add"))
.font(.system(size: 15, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundColor(.gray) .foregroundColor(.gray)
} else { } else {
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 20, weight: .bold, design: .rounded)) .font(.title3.weight(.bold))
.foregroundColor(textColor) .foregroundColor(textColor)
// Month context // Month context
Text(entry.forDate, format: .dateTime.month(.wide)) Text(entry.forDate, format: .dateTime.month(.wide))
.font(.system(size: 12, weight: .medium)) .font(.caption.weight(.medium))
.foregroundColor(textColor.opacity(0.4)) .foregroundColor(textColor.opacity(0.4))
} }
} }
@@ -510,12 +516,12 @@ struct EntryListView: View {
// Left column: Giant day number in serif // Left column: Giant day number in serif
VStack(alignment: .trailing, spacing: 0) { VStack(alignment: .trailing, spacing: 0) {
Text(entry.forDate, format: .dateTime.day()) Text(entry.forDate, format: .dateTime.day())
.font(.system(size: 72, weight: .regular, design: .serif)) .font(.largeTitle.weight(.regular))
.foregroundColor(textColor) .foregroundColor(textColor)
.frame(width: 80) .frame(width: 80)
Text(entry.forDate, format: .dateTime.weekday(.abbreviated).month(.abbreviated)) Text(entry.forDate, format: .dateTime.weekday(.abbreviated).month(.abbreviated))
.font(.system(size: 11, weight: .regular, design: .serif)) .font(.caption2.weight(.regular))
.italic() .italic()
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
.textCase(.uppercase) .textCase(.uppercase)
@@ -530,12 +536,12 @@ struct EntryListView: View {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
if isMissing { if isMissing {
Text("Entry Missing") Text("Entry Missing")
.font(.system(size: 24, weight: .regular, design: .serif)) .font(.title2.weight(.regular))
.italic() .italic()
.foregroundColor(.gray) .foregroundColor(.gray)
Text("Tap to record your mood for this day") Text("Tap to record your mood for this day")
.font(.system(size: 13, weight: .regular, design: .serif)) .font(.caption.weight(.regular))
.foregroundColor(.gray.opacity(0.7)) .foregroundColor(.gray.opacity(0.7))
} else { } else {
// Pull-quote style mood name // Pull-quote style mood name
@@ -545,7 +551,7 @@ struct EntryListView: View {
.frame(width: 4) .frame(width: 4)
Text("\"\(entry.moodString)\"") Text("\"\(entry.moodString)\"")
.font(.system(size: 28, weight: .regular, design: .serif)) .font(.title.weight(.regular))
.italic() .italic()
.foregroundColor(textColor) .foregroundColor(textColor)
} }
@@ -557,9 +563,10 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundColor(moodColor) .foregroundColor(moodColor)
.accessibilityLabel(entry.mood.strValue)
Text("Recorded mood entry") Text("Recorded mood entry")
.font(.system(size: 12, weight: .regular, design: .serif)) .font(.caption.weight(.regular))
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
.textCase(.uppercase) .textCase(.uppercase)
.tracking(1.5) .tracking(1.5)
@@ -618,23 +625,24 @@ struct EntryListView: View {
.frame(width: 28, height: 28) .frame(width: 28, height: 28)
.foregroundColor(isMissing ? .gray : moodColor) .foregroundColor(isMissing ? .gray : moodColor)
.shadow(color: isMissing ? .clear : moodColor, radius: 8, x: 0, y: 0) .shadow(color: isMissing ? .clear : moodColor, radius: 8, x: 0, y: 0)
.accessibilityLabel(entry.mood.strValue)
} }
.frame(width: 52, height: 52) .frame(width: 52, height: 52)
VStack(alignment: .leading, spacing: 6) { VStack(alignment: .leading, spacing: 6) {
// Date in monospace terminal style // Date in monospace terminal style
Text(entry.forDate, format: .dateTime.year().month(.twoDigits).day(.twoDigits)) Text(entry.forDate, format: .dateTime.year().month(.twoDigits).day(.twoDigits))
.font(.system(size: 13, weight: .medium, design: .monospaced)) .font(.caption.weight(.medium).monospaced())
.foregroundColor(Color(red: 0.4, green: 1.0, blue: 0.4)) // Terminal green .foregroundColor(Color(red: 0.4, green: 1.0, blue: 0.4)) // Terminal green
if isMissing { if isMissing {
Text("NO_DATA") Text("NO_DATA")
.font(.system(size: 18, weight: .bold, design: .monospaced)) .font(.headline.weight(.bold).monospaced())
.foregroundColor(.gray) .foregroundColor(.gray)
} else { } else {
// Mood in glowing text // Mood in glowing text
Text(entry.moodString.uppercased()) Text(entry.moodString.uppercased())
.font(.system(size: 18, weight: .black, design: .default)) .font(.headline.weight(.black))
.foregroundColor(moodColor) .foregroundColor(moodColor)
.shadow(color: moodColor.opacity(0.8), radius: 6, x: 0, y: 0) .shadow(color: moodColor.opacity(0.8), radius: 6, x: 0, y: 0)
.shadow(color: moodColor.opacity(0.4), radius: 12, x: 0, y: 0) .shadow(color: moodColor.opacity(0.4), radius: 12, x: 0, y: 0)
@@ -642,7 +650,7 @@ struct EntryListView: View {
// Weekday // Weekday
Text(entry.forDate, format: .dateTime.weekday(.wide)) Text(entry.forDate, format: .dateTime.weekday(.wide))
.font(.system(size: 11, weight: .medium, design: .monospaced)) .font(.caption2.weight(.medium).monospaced())
.foregroundColor(.white.opacity(0.4)) .foregroundColor(.white.opacity(0.4))
.textCase(.uppercase) .textCase(.uppercase)
} }
@@ -651,7 +659,7 @@ struct EntryListView: View {
// Chevron with glow // Chevron with glow
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
.font(.system(size: 14, weight: .bold)) .font(.subheadline.weight(.bold))
.foregroundColor(isMissing ? .gray : moodColor) .foregroundColor(isMissing ? .gray : moodColor)
.shadow(color: isMissing ? .clear : moodColor, radius: 4, x: 0, y: 0) .shadow(color: isMissing ? .clear : moodColor, radius: 4, x: 0, y: 0)
} }
@@ -713,36 +721,37 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 18, height: 18) .frame(width: 18, height: 18)
.foregroundColor(isMissing ? .gray.opacity(0.5) : moodColor.opacity(0.8)) .foregroundColor(isMissing ? .gray.opacity(0.5) : moodColor.opacity(0.8))
.accessibilityLabel(entry.mood.strValue)
} }
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
// Day number with brush-like weight variation // Day number with brush-like weight variation
HStack(alignment: .firstTextBaseline, spacing: 4) { HStack(alignment: .firstTextBaseline, spacing: 4) {
Text(entry.forDate, format: .dateTime.day()) Text(entry.forDate, format: .dateTime.day())
.font(.system(size: 36, weight: .thin)) .font(.title.weight(.thin))
.foregroundColor(textColor) .foregroundColor(textColor)
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text(entry.forDate, format: .dateTime.month(.wide)) Text(entry.forDate, format: .dateTime.month(.wide))
.font(.system(size: 11, weight: .light)) .font(.caption2.weight(.light))
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
.textCase(.uppercase) .textCase(.uppercase)
.tracking(2) .tracking(2)
Text(entry.forDate, format: .dateTime.weekday(.wide)) Text(entry.forDate, format: .dateTime.weekday(.wide))
.font(.system(size: 11, weight: .light)) .font(.caption2.weight(.light))
.foregroundColor(textColor.opacity(0.35)) .foregroundColor(textColor.opacity(0.35))
} }
} }
if isMissing { if isMissing {
Text("") Text("")
.font(.system(size: 20, weight: .ultraLight)) .font(.title3.weight(.ultraLight))
.foregroundColor(.gray.opacity(0.4)) .foregroundColor(.gray.opacity(0.4))
} else { } else {
// Mood in delicate typography // Mood in delicate typography
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 17, weight: .light)) .font(.body.weight(.light))
.foregroundColor(moodColor) .foregroundColor(moodColor)
.tracking(1) .tracking(1)
} }
@@ -862,16 +871,17 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 26, height: 26) .frame(width: 26, height: 26)
.foregroundColor(isMissing ? .gray : .white) .foregroundColor(isMissing ? .gray : .white)
.accessibilityLabel(entry.mood.strValue)
} }
VStack(alignment: .leading, spacing: 6) { VStack(alignment: .leading, spacing: 6) {
Text(entry.forDate, format: .dateTime.weekday(.wide)) Text(entry.forDate, format: .dateTime.weekday(.wide))
.font(.system(size: 15, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(textColor) .foregroundColor(textColor)
HStack(spacing: 8) { HStack(spacing: 8) {
Text(entry.forDate, format: .dateTime.month(.abbreviated).day()) Text(entry.forDate, format: .dateTime.month(.abbreviated).day())
.font(.system(size: 13, weight: .medium)) .font(.caption.weight(.medium))
.foregroundColor(textColor.opacity(0.6)) .foregroundColor(textColor.opacity(0.6))
if !isMissing { if !isMissing {
@@ -880,11 +890,11 @@ struct EntryListView: View {
.frame(width: 4, height: 4) .frame(width: 4, height: 4)
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 13, weight: .semibold)) .font(.caption.weight(.semibold))
.foregroundColor(moodColor) .foregroundColor(moodColor)
} else { } else {
Text("Tap to add") Text("Tap to add")
.font(.system(size: 13)) .font(.caption)
.foregroundColor(.gray) .foregroundColor(.gray)
} }
} }
@@ -894,7 +904,7 @@ struct EntryListView: View {
// Prismatic chevron // Prismatic chevron
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
.font(.system(size: 14, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundStyle( .foregroundStyle(
isMissing isMissing
? AnyShapeStyle(Color.gray.opacity(0.3)) ? AnyShapeStyle(Color.gray.opacity(0.3))
@@ -919,7 +929,7 @@ struct EntryListView: View {
// Track number column // Track number column
VStack { VStack {
Text(entry.forDate, format: .dateTime.day()) Text(entry.forDate, format: .dateTime.day())
.font(.system(size: 24, weight: .bold, design: .monospaced)) .font(.title2.weight(.bold).monospaced())
.foregroundColor(isMissing ? .gray : moodColor) .foregroundColor(isMissing ? .gray : moodColor)
} }
.frame(width: 50) .frame(width: 50)
@@ -949,17 +959,17 @@ struct EntryListView: View {
// Track info // Track info
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text(entry.forDate, format: .dateTime.weekday(.wide).month(.abbreviated)) Text(entry.forDate, format: .dateTime.weekday(.wide).month(.abbreviated))
.font(.system(size: 11, weight: .medium, design: .monospaced)) .font(.caption2.weight(.medium).monospaced())
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
.textCase(.uppercase) .textCase(.uppercase)
if isMissing { if isMissing {
Text("SIDE B - NO RECORDING") Text("SIDE B - NO RECORDING")
.font(.system(size: 14, weight: .bold, design: .rounded)) .font(.subheadline.weight(.bold))
.foregroundColor(.gray) .foregroundColor(.gray)
} else { } else {
Text(entry.moodString.uppercased()) Text(entry.moodString.uppercased())
.font(.system(size: 16, weight: .black, design: .rounded)) .font(.subheadline.weight(.black))
.foregroundColor(textColor) .foregroundColor(textColor)
.tracking(1) .tracking(1)
} }
@@ -992,6 +1002,7 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 16, height: 16) .frame(width: 16, height: 16)
.foregroundColor(isMissing ? .gray : moodColor) .foregroundColor(isMissing ? .gray : moodColor)
.accessibilityLabel(entry.mood.strValue)
} }
} }
.padding(.horizontal, 16) .padding(.horizontal, 16)
@@ -1068,21 +1079,22 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 24, height: 24) .frame(width: 24, height: 24)
.foregroundColor(.white) .foregroundColor(.white)
.accessibilityLabel(entry.mood.strValue)
} }
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
// Date with organic flow // Date with organic flow
HStack(spacing: 0) { HStack(spacing: 0) {
Text(entry.forDate, format: .dateTime.day()) Text(entry.forDate, format: .dateTime.day())
.font(.system(size: 32, weight: .light)) .font(.title.weight(.light))
.foregroundColor(textColor) .foregroundColor(textColor)
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
Text(entry.forDate, format: .dateTime.month(.abbreviated)) Text(entry.forDate, format: .dateTime.month(.abbreviated))
.font(.system(size: 12, weight: .medium)) .font(.caption.weight(.medium))
.foregroundColor(textColor.opacity(0.6)) .foregroundColor(textColor.opacity(0.6))
Text(entry.forDate, format: .dateTime.weekday(.abbreviated)) Text(entry.forDate, format: .dateTime.weekday(.abbreviated))
.font(.system(size: 11, weight: .regular)) .font(.caption2.weight(.regular))
.foregroundColor(textColor.opacity(0.4)) .foregroundColor(textColor.opacity(0.4))
} }
.padding(.leading, 6) .padding(.leading, 6)
@@ -1090,11 +1102,11 @@ struct EntryListView: View {
if isMissing { if isMissing {
Text("No mood recorded") Text("No mood recorded")
.font(.system(size: 14, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundColor(.gray) .foregroundColor(.gray)
} else { } else {
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 18, weight: .semibold)) .font(.headline.weight(.semibold))
.foregroundColor(moodColor) .foregroundColor(moodColor)
} }
} }
@@ -1139,11 +1151,11 @@ struct EntryListView: View {
// Handwritten-style date // Handwritten-style date
VStack(alignment: .center, spacing: 2) { VStack(alignment: .center, spacing: 2) {
Text(entry.forDate, format: .dateTime.day()) Text(entry.forDate, format: .dateTime.day())
.font(.system(size: 36, weight: .light, design: .serif)) .font(.title.weight(.light))
.foregroundColor(textColor) .foregroundColor(textColor)
Text(entry.forDate, format: .dateTime.weekday(.abbreviated)) Text(entry.forDate, format: .dateTime.weekday(.abbreviated))
.font(.system(size: 12, weight: .medium, design: .serif)) .font(.caption.weight(.medium))
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
.textCase(.uppercase) .textCase(.uppercase)
} }
@@ -1158,12 +1170,12 @@ struct EntryListView: View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
// Lined paper effect // Lined paper effect
Text(entry.forDate, format: .dateTime.month(.wide).year()) Text(entry.forDate, format: .dateTime.month(.wide).year())
.font(.system(size: 12, weight: .regular, design: .serif)) .font(.caption.weight(.regular))
.foregroundColor(textColor.opacity(0.4)) .foregroundColor(textColor.opacity(0.4))
if isMissing { if isMissing {
Text("nothing written...") Text("nothing written...")
.font(.system(size: 18, weight: .regular, design: .serif)) .font(.headline.weight(.regular))
.italic() .italic()
.foregroundColor(.gray) .foregroundColor(.gray)
} else { } else {
@@ -1173,9 +1185,10 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 24, height: 24) .frame(width: 24, height: 24)
.foregroundColor(moodColor) .foregroundColor(moodColor)
.accessibilityLabel(entry.mood.strValue)
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 20, weight: .medium, design: .serif)) .font(.title3.weight(.medium))
.foregroundColor(textColor) .foregroundColor(textColor)
} }
} }
@@ -1209,11 +1222,11 @@ struct EntryListView: View {
// Date column - minimal // Date column - minimal
VStack(alignment: .trailing, spacing: 2) { VStack(alignment: .trailing, spacing: 2) {
Text(entry.forDate, format: .dateTime.day()) Text(entry.forDate, format: .dateTime.day())
.font(.system(size: 28, weight: .thin)) .font(.title.weight(.thin))
.foregroundColor(textColor) .foregroundColor(textColor)
Text(entry.forDate, format: .dateTime.weekday(.abbreviated)) Text(entry.forDate, format: .dateTime.weekday(.abbreviated))
.font(.system(size: 10, weight: .medium)) .font(.caption2.weight(.medium))
.foregroundColor(textColor.opacity(0.4)) .foregroundColor(textColor.opacity(0.4))
.textCase(.uppercase) .textCase(.uppercase)
} }
@@ -1263,15 +1276,16 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 28, height: 28) .frame(width: 28, height: 28)
.foregroundColor(isMissing ? .gray : .white) .foregroundColor(isMissing ? .gray : .white)
.accessibilityLabel(entry.mood.strValue)
.padding(.leading, 16) .padding(.leading, 16)
if isMissing { if isMissing {
Text("No entry") Text("No entry")
.font(.system(size: 15, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundColor(.gray) .foregroundColor(.gray)
} else { } else {
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 17, weight: .semibold)) .font(.body.weight(.semibold))
.foregroundColor(.white) .foregroundColor(.white)
} }
@@ -1279,7 +1293,7 @@ struct EntryListView: View {
// Month indicator // Month indicator
Text(entry.forDate, format: .dateTime.month(.abbreviated)) Text(entry.forDate, format: .dateTime.month(.abbreviated))
.font(.system(size: 11, weight: .bold)) .font(.caption2.weight(.bold))
.foregroundColor(isMissing ? .gray : .white.opacity(0.7)) .foregroundColor(isMissing ? .gray : .white.opacity(0.7))
.padding(.trailing, 16) .padding(.trailing, 16)
} }
@@ -1309,20 +1323,21 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 28, height: 28) .frame(width: 28, height: 28)
.foregroundColor(.white) .foregroundColor(.white)
.accessibilityLabel(entry.mood.strValue)
} }
VStack(alignment: .leading, spacing: 6) { VStack(alignment: .leading, spacing: 6) {
Text(entry.forDate, format: .dateTime.weekday(.wide).day()) Text(entry.forDate, format: .dateTime.weekday(.wide).day())
.font(.system(size: 17, weight: .semibold)) .font(.body.weight(.semibold))
.foregroundColor(textColor) .foregroundColor(textColor)
if isMissing { if isMissing {
Text("No mood recorded") Text("No mood recorded")
.font(.system(size: 14)) .font(.subheadline)
.foregroundColor(.gray) .foregroundColor(.gray)
} else { } else {
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 15, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundColor(moodColor) .foregroundColor(moodColor)
} }
} }
@@ -1333,7 +1348,7 @@ struct EntryListView: View {
Spacer() Spacer()
Text(entry.forDate, format: .dateTime.month(.abbreviated)) Text(entry.forDate, format: .dateTime.month(.abbreviated))
.font(.system(size: 12, weight: .medium)) .font(.caption.weight(.medium))
.foregroundColor(textColor.opacity(0.6)) .foregroundColor(textColor.opacity(0.6))
.padding(.horizontal, 10) .padding(.horizontal, 10)
.padding(.vertical, 6) .padding(.vertical, 6)
@@ -1362,6 +1377,7 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: iconSize, height: iconSize) .frame(width: iconSize, height: iconSize)
.foregroundColor(isMissing ? Color.gray.opacity(0.15) : moodColor.opacity(0.2)) .foregroundColor(isMissing ? Color.gray.opacity(0.15) : moodColor.opacity(0.2))
.accessibilityHidden(true)
.position( .position(
x: CGFloat(col) * spacing + (row.isMultiple(of: 2) ? spacing/2 : 0), x: CGFloat(col) * spacing + (row.isMultiple(of: 2) ? spacing/2 : 0),
y: CGFloat(row) * spacing y: CGFloat(row) * spacing
@@ -1467,21 +1483,22 @@ struct EntryListView: View {
.frame(width: 22, height: 22) .frame(width: 22, height: 22)
.foregroundColor(.white) .foregroundColor(.white)
.shadow(color: Color.black.opacity(0.3), radius: 1, x: 0, y: 1) .shadow(color: Color.black.opacity(0.3), radius: 1, x: 0, y: 1)
.accessibilityLabel(entry.mood.strValue)
} }
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text(entry.forDate, format: .dateTime.weekday(.wide)) Text(entry.forDate, format: .dateTime.weekday(.wide))
.font(.system(size: 16, weight: .semibold, design: .serif)) .font(.subheadline.weight(.semibold))
.foregroundColor(Color(red: 0.95, green: 0.90, blue: 0.80)) .foregroundColor(Color(red: 0.95, green: 0.90, blue: 0.80))
.shadow(color: Color.black.opacity(0.5), radius: 1, x: 0, y: 1) .shadow(color: Color.black.opacity(0.5), radius: 1, x: 0, y: 1)
Text(entry.forDate, format: .dateTime.month(.abbreviated).day()) Text(entry.forDate, format: .dateTime.month(.abbreviated).day())
.font(.system(size: 12, weight: .medium, design: .serif)) .font(.caption.weight(.medium))
.foregroundColor(Color(red: 0.8, green: 0.7, blue: 0.55)) .foregroundColor(Color(red: 0.8, green: 0.7, blue: 0.55))
if !isMissing { if !isMissing {
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 14, weight: .bold, design: .serif)) .font(.subheadline.weight(.bold))
.foregroundColor(Color(red: 0.95, green: 0.90, blue: 0.80)) .foregroundColor(Color(red: 0.95, green: 0.90, blue: 0.80))
.shadow(color: Color.black.opacity(0.5), radius: 1, x: 0, y: 1) .shadow(color: Color.black.opacity(0.5), radius: 1, x: 0, y: 1)
} }
@@ -1598,22 +1615,23 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 24, height: 24) .frame(width: 24, height: 24)
.foregroundColor(isMissing ? .gray : .white) .foregroundColor(isMissing ? .gray : .white)
.accessibilityLabel(entry.mood.strValue)
} }
VStack(alignment: .leading, spacing: 6) { VStack(alignment: .leading, spacing: 6) {
Text(entry.forDate, format: .dateTime.weekday(.wide)) Text(entry.forDate, format: .dateTime.weekday(.wide))
.font(.system(size: 17, weight: .semibold)) .font(.body.weight(.semibold))
.foregroundColor(textColor) .foregroundColor(textColor)
HStack(spacing: 6) { HStack(spacing: 6) {
Text(entry.forDate, format: .dateTime.month(.abbreviated).day()) Text(entry.forDate, format: .dateTime.month(.abbreviated).day())
.font(.system(size: 13)) .font(.caption)
.foregroundColor(textColor.opacity(0.6)) .foregroundColor(textColor.opacity(0.6))
if !isMissing { if !isMissing {
// Glass pill for mood // Glass pill for mood
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 12, weight: .medium)) .font(.caption.weight(.medium))
.foregroundColor(moodColor) .foregroundColor(moodColor)
.padding(.horizontal, 10) .padding(.horizontal, 10)
.padding(.vertical, 4) .padding(.vertical, 4)
@@ -1633,7 +1651,7 @@ struct EntryListView: View {
// Glass chevron // Glass chevron
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
.font(.system(size: 14, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(textColor.opacity(0.4)) .foregroundColor(textColor.opacity(0.4))
} }
.padding(18) .padding(18)
@@ -1699,15 +1717,16 @@ struct EntryListView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 18, height: 18) .frame(width: 18, height: 18)
.foregroundColor(isMissing ? .gray : moodColor) .foregroundColor(isMissing ? .gray : moodColor)
.accessibilityLabel(entry.mood.strValue)
// Date - very compact // Date - very compact
Text(entry.forDate, format: .dateTime.month(.abbreviated).day()) Text(entry.forDate, format: .dateTime.month(.abbreviated).day())
.font(.system(size: 13, weight: .medium, design: .monospaced)) .font(.caption.weight(.medium).monospaced())
.foregroundColor(textColor.opacity(0.7)) .foregroundColor(textColor.opacity(0.7))
// Weekday initial // Weekday initial
Text(String(Random.weekdayName(fromDate: entry.forDate).prefix(3))) Text(String(Random.weekdayName(fromDate: entry.forDate).prefix(3)))
.font(.system(size: 11, weight: .semibold)) .font(.caption2.weight(.semibold))
.foregroundColor(textColor.opacity(0.4)) .foregroundColor(textColor.opacity(0.4))
.frame(width: 28) .frame(width: 28)
@@ -1716,7 +1735,7 @@ struct EntryListView: View {
// Mood as tiny pill // Mood as tiny pill
if !isMissing { if !isMissing {
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 11, weight: .semibold)) .font(.caption2.weight(.semibold))
.foregroundColor(moodColor) .foregroundColor(moodColor)
.padding(.horizontal, 8) .padding(.horizontal, 8)
.padding(.vertical, 3) .padding(.vertical, 3)
@@ -1726,12 +1745,12 @@ struct EntryListView: View {
) )
} else { } else {
Text("tap") Text("tap")
.font(.system(size: 10, weight: .medium)) .font(.caption2.weight(.medium))
.foregroundColor(.gray.opacity(0.6)) .foregroundColor(.gray.opacity(0.6))
} }
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
.font(.system(size: 10, weight: .semibold)) .font(.caption2.weight(.semibold))
.foregroundColor(textColor.opacity(0.25)) .foregroundColor(textColor.opacity(0.25))
} }
.padding(.horizontal, 14) .padding(.horizontal, 14)
@@ -1855,12 +1874,13 @@ struct MotionCardView: View {
x: -motionManager.xOffset * 0.3, x: -motionManager.xOffset * 0.3,
y: -motionManager.yOffset * 0.3 y: -motionManager.yOffset * 0.3
) )
.accessibilityLabel(entry.mood.strValue)
} }
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
// Day with motion // Day with motion
Text("\(dayNumber)") Text("\(dayNumber)")
.font(.system(size: 32, weight: .bold, design: .rounded)) .font(.title.weight(.bold))
.foregroundColor(isMissing ? .gray : moodColor) .foregroundColor(isMissing ? .gray : moodColor)
.offset( .offset(
x: motionManager.xOffset * 0.2, x: motionManager.xOffset * 0.2,
@@ -1869,7 +1889,7 @@ struct MotionCardView: View {
HStack(spacing: 6) { HStack(spacing: 6) {
Text(Random.weekdayName(fromDate: entry.forDate)) Text(Random.weekdayName(fromDate: entry.forDate))
.font(.system(size: 14, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundColor(textColor.opacity(0.7)) .foregroundColor(textColor.opacity(0.7))
if !isMissing { if !isMissing {
@@ -1877,7 +1897,7 @@ struct MotionCardView: View {
.fill(moodColor) .fill(moodColor)
.frame(width: 4, height: 4) .frame(width: 4, height: 4)
Text(entry.moodString) Text(entry.moodString)
.font(.system(size: 14, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(moodColor) .foregroundColor(moodColor)
} }
} }
@@ -1886,7 +1906,7 @@ struct MotionCardView: View {
Spacer() Spacer()
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
.font(.system(size: 14, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(textColor.opacity(0.3)) .foregroundColor(textColor.opacity(0.3))
.offset( .offset(
x: motionManager.xOffset * 0.5, x: motionManager.xOffset * 0.5,
@@ -1924,7 +1944,9 @@ class MotionManager: ObservableObject {
} }
private func startMotionUpdates() { private func startMotionUpdates() {
guard motionManager.isDeviceMotionAvailable else { return } // Respect Reduce Motion preference - skip parallax effect entirely
guard motionManager.isDeviceMotionAvailable,
!UIAccessibility.isReduceMotionEnabled else { return }
motionManager.deviceMotionUpdateInterval = 1/60 motionManager.deviceMotionUpdateInterval = 1/60
motionManager.startDeviceMotionUpdates(to: .main) { [weak self] motion, error in motionManager.startDeviceMotionUpdates(to: .main) { [weak self] motion, error in

View File

@@ -28,7 +28,7 @@ struct FeelsSubscriptionStoreView: View {
.frame(width: 100, height: 100) .frame(width: 100, height: 100)
Image(systemName: "heart.fill") Image(systemName: "heart.fill")
.font(.system(size: 44)) .font(.largeTitle)
.foregroundStyle( .foregroundStyle(
LinearGradient( LinearGradient(
colors: [.pink, .red], colors: [.pink, .red],
@@ -40,10 +40,10 @@ struct FeelsSubscriptionStoreView: View {
VStack(spacing: 8) { VStack(spacing: 8) {
Text("Unlock Premium") Text("Unlock Premium")
.font(.system(size: 28, weight: .bold, design: .rounded)) .font(.title.weight(.bold))
Text("Get unlimited access to all features") Text("Get unlimited access to all features")
.font(.system(size: 16)) .font(.body)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
} }
@@ -80,11 +80,11 @@ struct FeatureHighlight: View {
var body: some View { var body: some View {
HStack(spacing: 12) { HStack(spacing: 12) {
Image(systemName: "checkmark.circle.fill") Image(systemName: "checkmark.circle.fill")
.font(.system(size: 18)) .font(.headline)
.foregroundColor(.green) .foregroundColor(.green)
Text(text) Text(text)
.font(.system(size: 15, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundColor(.primary) .foregroundColor(.primary)
Spacer() Spacer()

View File

@@ -26,7 +26,7 @@ struct InsightsView: View {
// Header // Header
HStack { HStack {
Text("Insights") Text("Insights")
.font(.system(size: 28, weight: .bold, design: .rounded)) .font(.title.weight(.bold))
.foregroundColor(textColor) .foregroundColor(textColor)
Spacer() Spacer()
@@ -34,9 +34,9 @@ struct InsightsView: View {
if viewModel.isAIAvailable { if viewModel.isAIAvailable {
HStack(spacing: 4) { HStack(spacing: 4) {
Image(systemName: "sparkles") Image(systemName: "sparkles")
.font(.system(size: 12, weight: .medium)) .font(.caption.weight(.medium))
Text("AI") Text("AI")
.font(.system(size: 12, weight: .semibold)) .font(.caption.weight(.semibold))
} }
.foregroundColor(.white) .foregroundColor(.white)
.padding(.horizontal, 8) .padding(.horizontal, 8)
@@ -118,7 +118,7 @@ struct InsightsView: View {
.frame(width: 100, height: 100) .frame(width: 100, height: 100)
Image(systemName: "sparkles") Image(systemName: "sparkles")
.font(.system(size: 44)) .font(.largeTitle)
.foregroundStyle( .foregroundStyle(
LinearGradient( LinearGradient(
colors: [.purple, .blue], colors: [.purple, .blue],
@@ -131,12 +131,12 @@ struct InsightsView: View {
// Text // Text
VStack(spacing: 12) { VStack(spacing: 12) {
Text("Unlock AI-Powered Insights") Text("Unlock AI-Powered Insights")
.font(.system(size: 24, weight: .bold, design: .rounded)) .font(.title2.weight(.bold))
.foregroundColor(textColor) .foregroundColor(textColor)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
Text("Discover patterns in your mood, get personalized recommendations, and understand what affects how you feel.") Text("Discover patterns in your mood, get personalized recommendations, and understand what affects how you feel.")
.font(.system(size: 16)) .font(.body)
.foregroundColor(textColor.opacity(0.7)) .foregroundColor(textColor.opacity(0.7))
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.padding(.horizontal, 32) .padding(.horizontal, 32)
@@ -150,7 +150,7 @@ struct InsightsView: View {
Image(systemName: "sparkles") Image(systemName: "sparkles")
Text("Get Personal Insights") Text("Get Personal Insights")
} }
.font(.system(size: 18, weight: .bold)) .font(.headline.weight(.bold))
.foregroundColor(.white) .foregroundColor(.white)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.vertical, 16) .padding(.vertical, 16)
@@ -202,14 +202,20 @@ struct InsightsSectionView: View {
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
// Section Header // Section Header
Button(action: { withAnimation(.easeInOut(duration: 0.2)) { isExpanded.toggle() } }) { Button(action: {
if UIAccessibility.isReduceMotionEnabled {
isExpanded.toggle()
} else {
withAnimation(.easeInOut(duration: 0.2)) { isExpanded.toggle() }
}
}) {
HStack { HStack {
Image(systemName: icon) Image(systemName: icon)
.font(.system(size: 18, weight: .medium)) .font(.headline.weight(.medium))
.foregroundColor(textColor.opacity(0.6)) .foregroundColor(textColor.opacity(0.6))
Text(title) Text(title)
.font(.system(size: 20, weight: .bold)) .font(.title3.weight(.bold))
.foregroundColor(textColor) .foregroundColor(textColor)
// Loading indicator in header // Loading indicator in header
@@ -222,7 +228,7 @@ struct InsightsSectionView: View {
Spacer() Spacer()
Image(systemName: isExpanded ? "chevron.up" : "chevron.down") Image(systemName: isExpanded ? "chevron.up" : "chevron.down")
.font(.system(size: 12, weight: .semibold)) .font(.caption.weight(.semibold))
.foregroundColor(textColor.opacity(0.4)) .foregroundColor(textColor.opacity(0.4))
} }
.padding(.horizontal, 16) .padding(.horizontal, 16)
@@ -277,6 +283,7 @@ struct InsightsSectionView: View {
removal: .opacity removal: .opacity
)) ))
.animation( .animation(
UIAccessibility.isReduceMotionEnabled ? nil :
.spring(response: 0.4, dampingFraction: 0.8) .spring(response: 0.4, dampingFraction: 0.8)
.delay(Double(index) * 0.05), .delay(Double(index) * 0.05),
value: insights.count value: insights.count
@@ -294,7 +301,7 @@ struct InsightsSectionView: View {
.fill(colorScheme == .dark ? Color(.systemGray6) : .white) .fill(colorScheme == .dark ? Color(.systemGray6) : .white)
) )
.padding(.horizontal) .padding(.horizontal)
.animation(.easeInOut(duration: 0.2), value: isExpanded) .animation(UIAccessibility.isReduceMotionEnabled ? nil : .easeInOut(duration: 0.2), value: isExpanded)
} }
} }
@@ -336,12 +343,15 @@ struct InsightSkeletonView: View {
) )
.opacity(isAnimating ? 0.6 : 1.0) .opacity(isAnimating ? 0.6 : 1.0)
.animation( .animation(
UIAccessibility.isReduceMotionEnabled ? nil :
.easeInOut(duration: 0.8) .easeInOut(duration: 0.8)
.repeatForever(autoreverses: true), .repeatForever(autoreverses: true),
value: isAnimating value: isAnimating
) )
.onAppear { .onAppear {
isAnimating = true if !UIAccessibility.isReduceMotionEnabled {
isAnimating = true
}
} }
} }
} }
@@ -376,9 +386,10 @@ struct InsightCardView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 22, height: 22) .frame(width: 22, height: 22)
.foregroundColor(accentColor) .foregroundColor(accentColor)
.accessibilityLabel(mood.strValue)
} else { } else {
Image(systemName: insight.icon) Image(systemName: insight.icon)
.font(.system(size: 18, weight: .semibold)) .font(.headline.weight(.semibold))
.foregroundColor(accentColor) .foregroundColor(accentColor)
} }
} }
@@ -386,11 +397,11 @@ struct InsightCardView: View {
// Text Content // Text Content
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text(insight.title) Text(insight.title)
.font(.system(size: 15, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(textColor) .foregroundColor(textColor)
Text(insight.description) Text(insight.description)
.font(.system(size: 14)) .font(.subheadline)
.foregroundColor(textColor.opacity(0.7)) .foregroundColor(textColor.opacity(0.7))
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
} }

View File

@@ -31,7 +31,7 @@ struct LockScreenView: View {
// App icon / lock icon // App icon / lock icon
VStack(spacing: 20) { VStack(spacing: 20) {
Image(systemName: "lock.fill") Image(systemName: "lock.fill")
.font(.system(size: 60)) .font(.largeTitle)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
Text("Feels is Locked") Text("Feels is Locked")

View File

@@ -216,13 +216,13 @@ struct MonthCard: View {
VStack(spacing: 0) { VStack(spacing: 0) {
// Header with month/year // Header with month/year
Text("\(Random.monthName(fromMonthInt: month).uppercased()) \(String(year))") Text("\(Random.monthName(fromMonthInt: month).uppercased()) \(String(year))")
.font(.system(size: 32, weight: .heavy, design: .rounded)) .font(.title.weight(.heavy))
.foregroundColor(textColor) .foregroundColor(textColor)
.padding(.top, 40) .padding(.top, 40)
.padding(.bottom, 8) .padding(.bottom, 8)
Text("Monthly Mood Wrap") Text("Monthly Mood Wrap")
.font(.system(size: 16, weight: .medium, design: .rounded)) .font(.body.weight(.medium))
.foregroundColor(textColor.opacity(0.6)) .foregroundColor(textColor.opacity(0.6))
.padding(.bottom, 30) .padding(.bottom, 30)
@@ -238,15 +238,16 @@ struct MonthCard: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.foregroundColor(.white) .foregroundColor(.white)
.padding(24) .padding(24)
.accessibilityLabel(topMood.strValue)
) )
.shadow(color: moodTint.color(forMood: topMood).opacity(0.5), radius: 20, x: 0, y: 10) .shadow(color: moodTint.color(forMood: topMood).opacity(0.5), radius: 20, x: 0, y: 10)
Text("Top Mood") Text("Top Mood")
.font(.system(size: 14, weight: .medium, design: .rounded)) .font(.subheadline.weight(.medium))
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
Text(topMood.strValue.uppercased()) Text(topMood.strValue.uppercased())
.font(.system(size: 20, weight: .bold, design: .rounded)) .font(.title3.weight(.bold))
.foregroundColor(moodTint.color(forMood: topMood)) .foregroundColor(moodTint.color(forMood: topMood))
} }
.padding(.bottom, 30) .padding(.bottom, 30)
@@ -256,10 +257,10 @@ struct MonthCard: View {
HStack(spacing: 0) { HStack(spacing: 0) {
VStack(spacing: 4) { VStack(spacing: 4) {
Text("\(totalTrackedDays)") Text("\(totalTrackedDays)")
.font(.system(size: 36, weight: .bold, design: .rounded)) .font(.largeTitle.weight(.bold))
.foregroundColor(textColor) .foregroundColor(textColor)
Text("Days Tracked") Text("Days Tracked")
.font(.system(size: 12, weight: .medium, design: .rounded)) .font(.caption.weight(.medium))
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@@ -279,6 +280,7 @@ struct MonthCard: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.foregroundColor(.white) .foregroundColor(.white)
.padding(7) .padding(7)
.accessibilityLabel(metric.mood.strValue)
) )
GeometryReader { geo in GeometryReader { geo in
@@ -294,7 +296,7 @@ struct MonthCard: View {
.frame(height: 12) .frame(height: 12)
Text("\(Int(metric.percent))%") Text("\(Int(metric.percent))%")
.font(.system(size: 14, weight: .semibold, design: .rounded)) .font(.subheadline.weight(.semibold))
.foregroundColor(textColor) .foregroundColor(textColor)
.frame(width: 40, alignment: .trailing) .frame(width: 40, alignment: .trailing)
} }
@@ -305,7 +307,7 @@ struct MonthCard: View {
// App branding // App branding
Text("ifeel") Text("ifeel")
.font(.system(size: 14, weight: .medium, design: .rounded)) .font(.subheadline.weight(.medium))
.foregroundColor(textColor.opacity(0.3)) .foregroundColor(textColor.opacity(0.3))
.padding(.bottom, 20) .padding(.bottom, 20)
} }
@@ -317,7 +319,13 @@ struct MonthCard: View {
VStack(spacing: 0) { VStack(spacing: 0) {
// Month Header // Month Header
HStack { HStack {
Button(action: { withAnimation(.easeInOut(duration: 0.2)) { showStats.toggle() } }) { Button(action: {
if UIAccessibility.isReduceMotionEnabled {
showStats.toggle()
} else {
withAnimation(.easeInOut(duration: 0.2)) { showStats.toggle() }
}
}) {
HStack { HStack {
Text("\(Random.monthName(fromMonthInt: month)) \(String(year))") Text("\(Random.monthName(fromMonthInt: month)) \(String(year))")
.font(.title3.bold()) .font(.title3.bold())
@@ -453,6 +461,7 @@ struct MoodBarChart: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 18, height: 18) .frame(width: 18, height: 18)
.foregroundColor(moodTint.color(forMood: metric.mood)) .foregroundColor(moodTint.color(forMood: metric.mood))
.accessibilityLabel(metric.mood.strValue)
// Bar // Bar
GeometryReader { geo in GeometryReader { geo in
@@ -491,7 +500,7 @@ extension MonthView {
}, label: { }, label: {
Image(systemName: "gear") Image(systemName: "gear")
.foregroundColor(Color(UIColor.darkGray)) .foregroundColor(Color(UIColor.darkGray))
.font(.system(size: 20)) .font(.title3)
}).sheet(isPresented: $showingSheet) { }).sheet(isPresented: $showingSheet) {
SettingsView() SettingsView()
} }

View File

@@ -277,6 +277,7 @@ struct EntryDetailView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 34, height: 34) .frame(width: 34, height: 34)
.foregroundColor(.white) .foregroundColor(.white)
.accessibilityLabel(currentMood.strValue)
} }
.shadow(color: moodColor.opacity(0.4), radius: 8, x: 0, y: 4) .shadow(color: moodColor.opacity(0.4), radius: 8, x: 0, y: 4)
@@ -304,8 +305,12 @@ struct EntryDetailView: View {
ForEach(Mood.allValues) { mood in ForEach(Mood.allValues) { mood in
Button { Button {
// Update local state immediately for instant feedback // Update local state immediately for instant feedback
withAnimation(.easeInOut(duration: 0.15)) { if UIAccessibility.isReduceMotionEnabled {
selectedMood = mood selectedMood = mood
} else {
withAnimation(.easeInOut(duration: 0.15)) {
selectedMood = mood
}
} }
// Then persist the change // Then persist the change
onMoodUpdate(mood) onMoodUpdate(mood)
@@ -320,6 +325,7 @@ struct EntryDetailView: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 28, height: 28) .frame(width: 28, height: 28)
.foregroundColor(currentMood == mood ? .white : .gray) .foregroundColor(currentMood == mood ? .white : .gray)
.accessibilityLabel(mood.strValue)
) )
Text(mood.strValue) Text(mood.strValue)

View File

@@ -27,7 +27,7 @@ struct PhotoPickerView: View {
// Header // Header
VStack(spacing: 8) { VStack(spacing: 8) {
Image(systemName: "photo.on.rectangle.angled") Image(systemName: "photo.on.rectangle.angled")
.font(.system(size: 50)) .font(.largeTitle)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
Text("Add a Photo") Text("Add a Photo")
@@ -281,7 +281,7 @@ struct PhotoGalleryView: View {
} else { } else {
VStack(spacing: 16) { VStack(spacing: 16) {
Image(systemName: "photo.badge.exclamationmark") Image(systemName: "photo.badge.exclamationmark")
.font(.system(size: 50)) .font(.largeTitle)
.foregroundColor(.gray) .foregroundColor(.gray)
Text("Photo not found") Text("Photo not found")

View File

@@ -28,7 +28,7 @@ struct SettingsTabView: View {
VStack(spacing: 0) { VStack(spacing: 0) {
// Header // Header
Text("Settings") Text("Settings")
.font(.system(size: 28, weight: .bold, design: .rounded)) .font(.title.weight(.bold))
.foregroundColor(textColor) .foregroundColor(textColor)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 16) .padding(.horizontal, 16)
@@ -92,14 +92,14 @@ struct UpgradeBannerView: View {
// Countdown timer // Countdown timer
HStack(spacing: 6) { HStack(spacing: 6) {
Image(systemName: "clock") Image(systemName: "clock")
.font(.system(size: 14, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundColor(.orange) .foregroundColor(.orange)
if let expirationDate = trialExpirationDate { if let expirationDate = trialExpirationDate {
Text("\(Text("Trial expires in ").font(.system(size: 14, weight: .medium)).foregroundColor(textColor.opacity(0.8)))\(Text(expirationDate, style: .relative).font(.system(size: 14, weight: .bold)).foregroundColor(.orange))") Text("\(Text("Trial expires in ").font(.subheadline.weight(.medium)).foregroundColor(textColor.opacity(0.8)))\(Text(expirationDate, style: .relative).font(.subheadline.weight(.bold)).foregroundColor(.orange))")
} else { } else {
Text("Trial expired") Text("Trial expired")
.font(.system(size: 14, weight: .medium)) .font(.subheadline.weight(.medium))
.foregroundColor(.orange) .foregroundColor(.orange)
} }
} }
@@ -111,7 +111,7 @@ struct UpgradeBannerView: View {
showWhyUpgrade = true showWhyUpgrade = true
} label: { } label: {
Text("Why Upgrade?") Text("Why Upgrade?")
.font(.system(size: 14, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.vertical, 10) .padding(.vertical, 10)
@@ -126,7 +126,7 @@ struct UpgradeBannerView: View {
showSubscriptionStore = true showSubscriptionStore = true
} label: { } label: {
Text("Subscribe") Text("Subscribe")
.font(.system(size: 14, weight: .semibold)) .font(.subheadline.weight(.semibold))
.foregroundColor(.white) .foregroundColor(.white)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.vertical, 10) .padding(.vertical, 10)
@@ -157,7 +157,7 @@ struct WhyUpgradeView: View {
// Header // Header
VStack(spacing: 12) { VStack(spacing: 12) {
Image(systemName: "star.fill") Image(systemName: "star.fill")
.font(.system(size: 50)) .font(.largeTitle)
.foregroundStyle( .foregroundStyle(
LinearGradient( LinearGradient(
colors: [.orange, .pink], colors: [.orange, .pink],
@@ -167,7 +167,7 @@ struct WhyUpgradeView: View {
) )
Text("Unlock Premium") Text("Unlock Premium")
.font(.system(size: 28, weight: .bold)) .font(.title.weight(.bold))
Text("Get the most out of your mood tracking journey") Text("Get the most out of your mood tracking journey")
.font(.body) .font(.body)
@@ -262,7 +262,7 @@ struct PremiumBenefitRow: View {
var body: some View { var body: some View {
HStack(alignment: .top, spacing: 14) { HStack(alignment: .top, spacing: 14) {
Image(systemName: icon) Image(systemName: icon)
.font(.system(size: 22)) .font(.title3)
.foregroundColor(iconColor) .foregroundColor(iconColor)
.frame(width: 44, height: 44) .frame(width: 44, height: 44)
.background( .background(
@@ -272,10 +272,10 @@ struct PremiumBenefitRow: View {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text(title) Text(title)
.font(.system(size: 16, weight: .semibold)) .font(.body.weight(.semibold))
Text(description) Text(description)
.font(.system(size: 14)) .font(.subheadline)
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }

View File

@@ -174,13 +174,13 @@ struct YearCard: View {
VStack(spacing: 0) { VStack(spacing: 0) {
// Header with year // Header with year
Text(String(year)) Text(String(year))
.font(.system(size: 48, weight: .heavy, design: .rounded)) .font(.largeTitle.weight(.heavy))
.foregroundColor(textColor) .foregroundColor(textColor)
.padding(.top, 40) .padding(.top, 40)
.padding(.bottom, 8) .padding(.bottom, 8)
Text("Year in Review") Text("Year in Review")
.font(.system(size: 18, weight: .medium, design: .rounded)) .font(.headline.weight(.medium))
.foregroundColor(textColor.opacity(0.6)) .foregroundColor(textColor.opacity(0.6))
.padding(.bottom, 30) .padding(.bottom, 30)
@@ -196,15 +196,16 @@ struct YearCard: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.foregroundColor(.white) .foregroundColor(.white)
.padding(28) .padding(28)
.accessibilityLabel(topMood.strValue)
) )
.shadow(color: moodTint.color(forMood: topMood).opacity(0.5), radius: 25, x: 0, y: 12) .shadow(color: moodTint.color(forMood: topMood).opacity(0.5), radius: 25, x: 0, y: 12)
Text("Top Mood") Text("Top Mood")
.font(.system(size: 14, weight: .medium, design: .rounded)) .font(.subheadline.weight(.medium))
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
Text(topMood.strValue.uppercased()) Text(topMood.strValue.uppercased())
.font(.system(size: 24, weight: .bold, design: .rounded)) .font(.title2.weight(.bold))
.foregroundColor(moodTint.color(forMood: topMood)) .foregroundColor(moodTint.color(forMood: topMood))
} }
.padding(.bottom, 30) .padding(.bottom, 30)
@@ -214,10 +215,10 @@ struct YearCard: View {
HStack(spacing: 0) { HStack(spacing: 0) {
VStack(spacing: 4) { VStack(spacing: 4) {
Text("\(totalEntries)") Text("\(totalEntries)")
.font(.system(size: 42, weight: .bold, design: .rounded)) .font(.largeTitle.weight(.bold))
.foregroundColor(textColor) .foregroundColor(textColor)
Text("Days Tracked") Text("Days Tracked")
.font(.system(size: 13, weight: .medium, design: .rounded)) .font(.caption.weight(.medium))
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@@ -237,6 +238,7 @@ struct YearCard: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.foregroundColor(.white) .foregroundColor(.white)
.padding(8) .padding(8)
.accessibilityLabel(metric.mood.strValue)
) )
GeometryReader { geo in GeometryReader { geo in
@@ -252,7 +254,7 @@ struct YearCard: View {
.frame(height: 16) .frame(height: 16)
Text("\(Int(metric.percent))%") Text("\(Int(metric.percent))%")
.font(.system(size: 16, weight: .semibold, design: .rounded)) .font(.body.weight(.semibold))
.foregroundColor(textColor) .foregroundColor(textColor)
.frame(width: 45, alignment: .trailing) .frame(width: 45, alignment: .trailing)
} }
@@ -263,7 +265,7 @@ struct YearCard: View {
// App branding // App branding
Text("ifeel") Text("ifeel")
.font(.system(size: 14, weight: .medium, design: .rounded)) .font(.subheadline.weight(.medium))
.foregroundColor(textColor.opacity(0.3)) .foregroundColor(textColor.opacity(0.3))
.padding(.bottom, 20) .padding(.bottom, 20)
} }
@@ -275,7 +277,13 @@ struct YearCard: View {
VStack(spacing: 0) { VStack(spacing: 0) {
// Year Header // Year Header
HStack { HStack {
Button(action: { withAnimation(.easeInOut(duration: 0.2)) { showStats.toggle() } }) { Button(action: {
if UIAccessibility.isReduceMotionEnabled {
showStats.toggle()
} else {
withAnimation(.easeInOut(duration: 0.2)) { showStats.toggle() }
}
}) {
HStack { HStack {
Text(String(year)) Text(String(year))
.font(.title2.bold()) .font(.title2.bold())
@@ -324,6 +332,7 @@ struct YearCard: View {
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 16, height: 16) .frame(width: 16, height: 16)
.foregroundColor(moodTint.color(forMood: metric.mood)) .foregroundColor(moodTint.color(forMood: metric.mood))
.accessibilityLabel(metric.mood.strValue)
GeometryReader { geo in GeometryReader { geo in
ZStack(alignment: .leading) { ZStack(alignment: .leading) {
@@ -357,7 +366,7 @@ struct YearCard: View {
HStack(spacing: 2) { HStack(spacing: 2) {
ForEach(months.indices, id: \.self) { index in ForEach(months.indices, id: \.self) { index in
Text(months[index]) Text(months[index])
.font(.system(size: 9, weight: .medium)) .font(.caption2.weight(.medium))
.foregroundColor(textColor.opacity(0.5)) .foregroundColor(textColor.opacity(0.5))
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
} }

View File

@@ -554,6 +554,7 @@
.feature-card:nth-child(4) .feature-icon { background: rgba(239, 190, 154, 0.2); } .feature-card:nth-child(4) .feature-icon { background: rgba(239, 190, 154, 0.2); }
.feature-card:nth-child(5) .feature-icon { background: rgba(165, 196, 212, 0.2); } .feature-card:nth-child(5) .feature-icon { background: rgba(165, 196, 212, 0.2); }
.feature-card:nth-child(6) .feature-icon { background: rgba(229, 168, 154, 0.2); } .feature-card:nth-child(6) .feature-icon { background: rgba(229, 168, 154, 0.2); }
.feature-card:nth-child(7) .feature-icon { background: rgba(94, 186, 175, 0.2); }
.feature-card h3 { .feature-card h3 {
font-size: 1.25rem; font-size: 1.25rem;
@@ -1191,6 +1192,12 @@
<h3>Private by Design</h3> <h3>Private by Design</h3>
<p>Your feelings are yours alone. All data stays on your devices with iCloud sync you control.</p> <p>Your feelings are yours alone. All data stays on your devices with iCloud sync you control.</p>
</div> </div>
<div class="feature-card reveal">
<div class="feature-icon"></div>
<h3>WCAG 2.1 AA Accessible</h3>
<p>Built for everyone. Full VoiceOver support, Dynamic Type, and high contrast ensure no one is left behind.</p>
</div>
</div> </div>
</section> </section>

View File

@@ -1107,6 +1107,12 @@
<h3>Privacy</h3> <h3>Privacy</h3>
<p>Your data stays on your devices. iCloud sync with no third-party access.</p> <p>Your data stays on your devices. iCloud sync with no third-party access.</p>
</div> </div>
<div class="feature-card fade-in">
<div class="feature-number">07</div>
<h3>WCAG 2.1 AA</h3>
<p>Built for everyone. Full VoiceOver support, Dynamic Type, and high contrast ensure no one is left behind.</p>
</div>
</div> </div>
</section> </section>

View File

@@ -491,6 +491,7 @@
.feature-icon.purple { background: rgba(168, 85, 247, 0.15); } .feature-icon.purple { background: rgba(168, 85, 247, 0.15); }
.feature-icon.red { background: rgba(248, 113, 113, 0.15); } .feature-icon.red { background: rgba(248, 113, 113, 0.15); }
.feature-icon.gray { background: rgba(148, 163, 184, 0.15); } .feature-icon.gray { background: rgba(148, 163, 184, 0.15); }
.feature-icon.cyan { background: rgba(34, 211, 238, 0.15); }
.feature-card h3 { .feature-card h3 {
font-size: 1.125rem; font-size: 1.125rem;
@@ -956,6 +957,11 @@
<h3>Privacy First</h3> <h3>Privacy First</h3>
<p>Your feelings stay yours. All data lives on your devices with iCloud sync.</p> <p>Your feelings stay yours. All data lives on your devices with iCloud sync.</p>
</div> </div>
<div class="feature-card reveal">
<div class="feature-icon cyan"></div>
<h3>WCAG 2.1 AA Accessible</h3>
<p>Built for everyone. Full VoiceOver support, Dynamic Type, and high contrast ensure no one is left behind.</p>
</div>
</div> </div>
</div> </div>
</section> </section>