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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user