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

@@ -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])
}) {