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

View File

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

View File

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

View File

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

View File

@@ -64,6 +64,8 @@ struct IconPickerView: View {
.frame(width: 50, height:50)
.cornerRadius(10)
})
.accessibilityLabel(String(localized: "Default app icon"))
.accessibilityHint(String(localized: "Double tap to select"))
ForEach(iconSets, id: \.self.0){ iconSet in
@@ -78,6 +80,8 @@ struct IconPickerView: View {
.frame(width: 50, height:50)
.cornerRadius(10)
})
.accessibilityLabel(String(localized: "App icon style \(iconSet.1.replacingOccurrences(of: "AppIcon", with: "").replacingOccurrences(of: "Image", with: ""))"))
.accessibilityHint(String(localized: "Double tap to select"))
}
}
.padding()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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