// // AddMoodHeaderView.swift // Feels // // Created by Trey Tartt on 1/5/22. // import Foundation import SwiftUI import CoreData struct AddMoodHeaderView: View { @AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system @AppStorage(UserDefaultsStore.Keys.moodTint.rawValue, store: GroupUserDefaults.groupDefaults) private var moodTint: MoodTints = .Default @AppStorage(UserDefaultsStore.Keys.textColor.rawValue, store: GroupUserDefaults.groupDefaults) private var textColor: Color = DefaultTextColor.textColor @AppStorage(UserDefaultsStore.Keys.votingLayoutStyle.rawValue, store: GroupUserDefaults.groupDefaults) private var votingLayoutStyle: Int = 0 @State var onboardingData = OnboardingDataDataManager.shared.savedOnboardingData let addItemHeaderClosure: ((Mood, Date) -> Void) init(addItemHeaderClosure: @escaping ((Mood, Date) -> Void)) { self.addItemHeaderClosure = addItemHeaderClosure } private var layoutStyle: VotingLayoutStyle { VotingLayoutStyle(rawValue: votingLayoutStyle) ?? .horizontal } var body: some View { ZStack { theme.currentTheme.secondaryBGColor VStack(spacing: 16) { Text(ShowBasedOnVoteLogics.getVotingTitle(onboardingData: onboardingData)) .font(.title2.bold()) .foregroundColor(textColor) .padding(.top) votingLayoutContent .padding(.bottom) } .padding(.horizontal) } .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) .frame(minWidth: 0, maxWidth: .infinity) } @ViewBuilder private var votingLayoutContent: some View { switch layoutStyle { case .horizontal: HorizontalVotingView(moodTint: moodTint, onMoodSelected: addItem) case .cards: CardVotingView(moodTint: moodTint, onMoodSelected: addItem) case .radial: RadialVotingView(moodTint: moodTint, onMoodSelected: addItem) case .stacked: StackedVotingView(moodTint: moodTint, onMoodSelected: addItem) } } private func addItem(withMood mood: Mood) { let impactFeedback = UIImpactFeedbackGenerator(style: .medium) impactFeedback.impactOccurred() let date = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: onboardingData) addItemHeaderClosure(mood, date) } } // MARK: - Layout 1: Horizontal (Polished version of current) struct HorizontalVotingView: View { let moodTint: MoodTints let onMoodSelected: (Mood) -> Void var body: some View { HStack(spacing: 8) { ForEach(Mood.allValues) { mood in Button(action: { onMoodSelected(mood) }) { mood.icon .resizable() .aspectRatio(contentMode: .fit) .frame(width: 55, height: 55) .foregroundColor(moodTint.color(forMood: mood)) } .buttonStyle(MoodButtonStyle()) .frame(maxWidth: .infinity) } } } } // MARK: - Layout 2: Cards Grid struct CardVotingView: View { let moodTint: MoodTints let onMoodSelected: (Mood) -> Void private let columns = [ GridItem(.flexible(), spacing: 12), GridItem(.flexible(), spacing: 12), GridItem(.flexible(), spacing: 12) ] var body: some View { LazyVGrid(columns: columns, spacing: 12) { ForEach(Mood.allValues) { mood in Button(action: { onMoodSelected(mood) }) { VStack(spacing: 8) { mood.icon .resizable() .aspectRatio(contentMode: .fit) .frame(width: 40, height: 40) .foregroundColor(moodTint.color(forMood: mood)) Text(mood.strValue) .font(.caption.weight(.medium)) .foregroundColor(moodTint.color(forMood: mood)) } .frame(maxWidth: .infinity) .padding(.vertical, 16) .background( RoundedRectangle(cornerRadius: 12) .fill(moodTint.color(forMood: mood).opacity(0.15)) ) .overlay( RoundedRectangle(cornerRadius: 12) .stroke(moodTint.color(forMood: mood).opacity(0.3), lineWidth: 1) ) } .buttonStyle(CardButtonStyle()) } } } } // MARK: - Layout 3: Radial/Semi-circle struct RadialVotingView: View { let moodTint: MoodTints let onMoodSelected: (Mood) -> Void var body: some View { GeometryReader { geometry in let center = CGPoint(x: geometry.size.width / 2, y: geometry.size.height * 0.9) let radius = min(geometry.size.width, geometry.size.height) * 0.65 let moods = Mood.allValues ZStack { ForEach(Array(moods.enumerated()), id: \.element.id) { index, mood in let angle = angleForIndex(index, total: moods.count) let position = positionForAngle(angle, radius: radius, center: center) Button(action: { onMoodSelected(mood) }) { VStack(spacing: 4) { mood.icon .resizable() .aspectRatio(contentMode: .fit) .frame(width: 44, height: 44) .foregroundColor(moodTint.color(forMood: mood)) Text(mood.strValue) .font(.caption2.weight(.medium)) .foregroundColor(moodTint.color(forMood: mood)) } .padding(8) .background( Circle() .fill(moodTint.color(forMood: mood).opacity(0.1)) ) } .buttonStyle(MoodButtonStyle()) .position(position) } } } .frame(height: 180) } private func angleForIndex(_ index: Int, total: Int) -> Double { // Spread moods across a semi-circle (180 degrees), from left to right let startAngle = Double.pi // 180 degrees (left) let endAngle = 0.0 // 0 degrees (right) let step = (startAngle - endAngle) / Double(total - 1) return startAngle - (step * Double(index)) } private func positionForAngle(_ angle: Double, radius: CGFloat, center: CGPoint) -> CGPoint { CGPoint( x: center.x + radius * CGFloat(cos(angle)), y: center.y - radius * CGFloat(sin(angle)) ) } } // MARK: - Layout 4: Stacked Full-width struct StackedVotingView: View { let moodTint: MoodTints let onMoodSelected: (Mood) -> Void var body: some View { VStack(spacing: 10) { ForEach(Mood.allValues) { mood in Button(action: { onMoodSelected(mood) }) { HStack(spacing: 16) { mood.icon .resizable() .aspectRatio(contentMode: .fit) .frame(width: 36, height: 36) .foregroundColor(moodTint.color(forMood: mood)) Text(mood.strValue) .font(.body.weight(.semibold)) .foregroundColor(moodTint.color(forMood: mood)) Spacer() Image(systemName: "chevron.right") .font(.caption.weight(.semibold)) .foregroundColor(moodTint.color(forMood: mood).opacity(0.5)) } .padding(.horizontal, 16) .padding(.vertical, 14) .background( RoundedRectangle(cornerRadius: 12) .fill(moodTint.color(forMood: mood).opacity(0.12)) ) } .buttonStyle(CardButtonStyle()) } } } } // MARK: - Button Styles struct MoodButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .scaleEffect(configuration.isPressed ? 0.9 : 1.0) .animation(.easeInOut(duration: 0.15), value: configuration.isPressed) } } struct CardButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .scaleEffect(configuration.isPressed ? 0.96 : 1.0) .opacity(configuration.isPressed ? 0.8 : 1.0) .animation(.easeInOut(duration: 0.15), value: configuration.isPressed) } } // MARK: - Previews struct AddMoodHeaderView_Previews: PreviewProvider { static var previews: some View { Group { AddMoodHeaderView(addItemHeaderClosure: { (_,_) in }).environment(\.managedObjectContext, PersistenceController.shared.viewContext) AddMoodHeaderView(addItemHeaderClosure: { (_,_) in }).preferredColorScheme(.dark).environment(\.managedObjectContext, PersistenceController.shared.viewContext) } } }