Files
Reflect/Shared/Onboarding/views/OnboardingStyle.swift
Trey t be84825aba 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>
2025-12-24 09:53:40 -06:00

224 lines
8.1 KiB
Swift

//
// OnboardingStyle.swift
// Feels
//
// Created by Claude Code on 12/10/24.
//
import SwiftUI
struct OnboardingStyle: View {
@ObservedObject var onboardingData: OnboardingData
@AppStorage(UserDefaultsStore.Keys.moodTint.rawValue, store: GroupUserDefaults.groupDefaults) private var moodTint: MoodTints = .Default
@AppStorage(UserDefaultsStore.Keys.moodImages.rawValue, store: GroupUserDefaults.groupDefaults) private var imagePack: MoodImages = .FontAwesome
var body: some View {
ZStack {
// Gradient background
LinearGradient(
colors: [Color(hex: "fa709a"), Color(hex: "fee140")],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
ScrollView(showsIndicators: false) {
VStack(spacing: 0) {
// Icon
ZStack {
Circle()
.fill(.white.opacity(0.15))
.frame(width: 100, height: 100)
Image(systemName: "paintpalette.fill")
.font(.largeTitle)
.foregroundColor(.white)
}
.padding(.top, 40)
.padding(.bottom, 20)
// Title
Text("Make it yours")
.font(.title.weight(.bold))
.foregroundColor(.white)
.padding(.bottom, 8)
// Subtitle
Text("Choose your favorite style")
.font(.body.weight(.medium))
.foregroundColor(.white.opacity(0.85))
.padding(.bottom, 20)
// Preview card
OnboardingStylePreview(moodTint: moodTint, imagePack: imagePack)
.padding(.horizontal, 24)
.padding(.bottom, 20)
// Icon Style Section
VStack(alignment: .leading, spacing: 10) {
Text("Icon Style")
.font(.subheadline.weight(.semibold))
.foregroundColor(.white.opacity(0.9))
.padding(.horizontal, 24)
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 10) {
ForEach(MoodImages.allCases, id: \.rawValue) { pack in
OnboardingIconPackOption(
pack: pack,
moodTint: moodTint,
isSelected: imagePack == pack,
action: {
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
imagePack = pack
}
)
}
}
.padding(.horizontal, 24)
}
.padding(.bottom, 16)
// Color Theme Section
VStack(alignment: .leading, spacing: 10) {
Text("Mood Colors")
.font(.subheadline.weight(.semibold))
.foregroundColor(.white.opacity(0.9))
.padding(.horizontal, 24)
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 10) {
ForEach(MoodTints.defaultOptions, id: \.rawValue) { tint in
OnboardingTintOption(
tint: tint,
isSelected: moodTint == tint,
action: {
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
moodTint = tint
}
)
}
}
.padding(.horizontal, 24)
}
// Hint
HStack(spacing: 8) {
Image(systemName: "arrow.left.arrow.right")
.font(.subheadline)
Text("You can change these anytime in Customize")
.font(.caption.weight(.medium))
}
.foregroundColor(.white.opacity(0.7))
.padding(.top, 20)
.padding(.bottom, 80)
}
}
}
}
}
// MARK: - Preview Card
struct OnboardingStylePreview: View {
let moodTint: MoodTints
let imagePack: MoodImages
var body: some View {
HStack(spacing: 16) {
imagePack.icon(forMood: .good)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 44, height: 44)
.foregroundColor(moodTint.color(forMood: .good))
.accessibilityLabel(Mood.good.strValue)
VStack(alignment: .leading, spacing: 4) {
Text("Wednesday - 10th")
.font(.body.weight(.semibold))
.foregroundColor(.white)
Text(Mood.good.strValue)
.font(.subheadline)
.foregroundColor(.white.opacity(0.8))
}
Spacer()
}
.padding(20)
.background(
RoundedRectangle(cornerRadius: 16)
.fill(.white.opacity(0.2))
)
}
}
// MARK: - Icon Pack Option
struct OnboardingIconPackOption: View {
let pack: MoodImages
let moodTint: MoodTints
let isSelected: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
HStack(spacing: 6) {
ForEach([Mood.great, .good, .average], id: \.self) { mood in
pack.icon(forMood: mood)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 24, height: 24)
.foregroundColor(moodTint.color(forMood: mood))
.accessibilityLabel(mood.strValue)
}
}
.padding(.horizontal, 16)
.padding(.vertical, 14)
.background(
RoundedRectangle(cornerRadius: 14)
.fill(isSelected ? .white : .white.opacity(0.2))
)
.overlay(
RoundedRectangle(cornerRadius: 14)
.stroke(isSelected ? Color.clear : .white.opacity(0.3), lineWidth: 1)
)
}
.buttonStyle(.plain)
}
}
// MARK: - Tint Option
struct OnboardingTintOption: View {
let tint: MoodTints
let isSelected: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
HStack(spacing: 4) {
ForEach([Mood.great, .good, .average, .bad, .horrible], id: \.self) { mood in
Circle()
.fill(tint.color(forMood: mood))
.frame(width: 20, height: 20)
}
}
.padding(.horizontal, 14)
.padding(.vertical, 12)
.background(
RoundedRectangle(cornerRadius: 14)
.fill(isSelected ? .white : .white.opacity(0.2))
)
.overlay(
RoundedRectangle(cornerRadius: 14)
.stroke(isSelected ? Color.clear : .white.opacity(0.3), lineWidth: 1)
)
}
.buttonStyle(.plain)
}
}
struct OnboardingStyle_Previews: PreviewProvider {
static var previews: some View {
OnboardingStyle(onboardingData: OnboardingData())
}
}