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:
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -31,6 +31,7 @@ struct ImagePackPickerView: View {
|
||||
.foregroundColor(
|
||||
moodTint.color(forMood: mood)
|
||||
)
|
||||
.accessibilityLabel(mood.strValue)
|
||||
}
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}) {
|
||||
|
||||
Reference in New Issue
Block a user