// // WidgetSharedViews.swift // FeelsWidget // // Shared voting views used across multiple widgets // import WidgetKit import SwiftUI import AppIntents // MARK: - Voting View struct VotingView: View { let family: WidgetFamily let promptText: String let hasSubscription: Bool private var moodTint: MoodTintable.Type { UserDefaultsStore.moodTintable() } private var moodImages: MoodImagable.Type { UserDefaultsStore.moodMoodImagable() } var body: some View { if family == .systemSmall { smallLayout } else { mediumLayout } } // MARK: - Small Widget: 3 over 2 grid private var smallLayout: some View { VStack(spacing: 0) { Text(hasSubscription ? promptText : "Tap to open app") .font(.caption) .foregroundStyle(.primary) .multilineTextAlignment(.center) .lineLimit(1) .minimumScaleFactor(0.7) .padding(.bottom, 10) // Top row: Great, Good, Average HStack(spacing: 12) { ForEach([Mood.great, .good, .average], id: \.rawValue) { mood in moodButton(for: mood, size: 36) } } .padding(.bottom, 6) // Bottom row: Bad, Horrible HStack(spacing: 12) { ForEach([Mood.bad, .horrible], id: \.rawValue) { mood in moodButton(for: mood, size: 36) } } } .padding(.horizontal, 8) .padding(.vertical, 4) } // MARK: - Medium Widget: Single row private var mediumLayout: some View { VStack(spacing: 12) { Text(hasSubscription ? promptText : "Subscribe to track your mood") .font(.headline) .foregroundStyle(.primary) .multilineTextAlignment(.center) .lineLimit(2) .minimumScaleFactor(0.8) HStack(spacing: 0) { ForEach([Mood.great, .good, .average, .bad, .horrible], id: \.rawValue) { mood in moodButtonMedium(for: mood) .frame(maxWidth: .infinity) } } } .padding(.horizontal, 12) .padding(.vertical, 16) } @ViewBuilder private func moodButton(for mood: Mood, size: CGFloat) -> some View { // Used for small widget let touchSize = max(size, 44) if hasSubscription { Button(intent: VoteMoodIntent(mood: mood)) { moodIcon(for: mood, size: size) .frame(minWidth: touchSize, minHeight: touchSize) } .buttonStyle(.plain) .accessibilityLabel(mood.strValue) .accessibilityHint(String(localized: "Log this mood")) } else { Link(destination: URL(string: "feels://subscribe")!) { moodIcon(for: mood, size: size) .frame(minWidth: touchSize, minHeight: touchSize) } .accessibilityLabel(mood.strValue) .accessibilityHint(String(localized: "Open app to subscribe")) } } @ViewBuilder private func moodButtonMedium(for mood: Mood) -> some View { // Medium widget uses smaller icons with labels, flexible width let content = VStack(spacing: 4) { moodImages.icon(forMood: mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 32, height: 32) .foregroundColor(moodTint.color(forMood: mood)) Text(mood.widgetDisplayName) .font(.caption2) .foregroundColor(moodTint.color(forMood: mood)) .lineLimit(1) .minimumScaleFactor(0.8) } if hasSubscription { Button(intent: VoteMoodIntent(mood: mood)) { content } .buttonStyle(.plain) .accessibilityLabel(mood.strValue) .accessibilityHint(String(localized: "Log this mood")) } else { Link(destination: URL(string: "feels://subscribe")!) { content } .accessibilityLabel(mood.strValue) .accessibilityHint(String(localized: "Open app to subscribe")) } } private func moodIcon(for mood: Mood, size: CGFloat) -> some View { moodImages.icon(forMood: mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: size, height: size) .foregroundColor(moodTint.color(forMood: mood)) } } // MARK: - Large Voting View struct LargeVotingView: View { let promptText: String let hasSubscription: Bool private var moodTint: MoodTintable.Type { UserDefaultsStore.moodTintable() } private var moodImages: MoodImagable.Type { UserDefaultsStore.moodMoodImagable() } var body: some View { VStack(spacing: 16) { Spacer() Text(hasSubscription ? promptText : "Subscribe to track your mood") .font(.title3.weight(.semibold)) .foregroundStyle(.primary) .multilineTextAlignment(.center) .lineLimit(2) .minimumScaleFactor(0.8) .padding(.horizontal, 8) // Large mood buttons in a row - flexible spacing HStack(spacing: 0) { ForEach([Mood.great, .good, .average, .bad, .horrible], id: \.rawValue) { mood in moodButton(for: mood) .frame(maxWidth: .infinity) } } Spacer() } .padding(.horizontal, 12) .padding(.vertical, 16) } @ViewBuilder private func moodButton(for mood: Mood) -> some View { if hasSubscription { Button(intent: VoteMoodIntent(mood: mood)) { moodButtonContent(for: mood) } .buttonStyle(.plain) .accessibilityLabel(mood.strValue) .accessibilityHint(String(localized: "Log this mood")) } else { Link(destination: URL(string: "feels://subscribe")!) { moodButtonContent(for: mood) } .accessibilityLabel(mood.strValue) .accessibilityHint(String(localized: "Open app to subscribe")) } } private func moodButtonContent(for mood: Mood) -> some View { VStack(spacing: 4) { moodImages.icon(forMood: mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 40, height: 40) .foregroundColor(moodTint.color(forMood: mood)) Text(mood.widgetDisplayName) .font(.caption2.weight(.medium)) .foregroundColor(moodTint.color(forMood: mood)) .lineLimit(1) .minimumScaleFactor(0.8) } .padding(.vertical, 8) .padding(.horizontal, 4) .background( RoundedRectangle(cornerRadius: 12) .fill(moodTint.color(forMood: mood).opacity(0.15)) ) } } // MARK: - Inline Voting View (compact mood buttons for timeline widget) struct InlineVotingView: View { let promptText: String let hasSubscription: Bool let moods: [Mood] = [.horrible, .bad, .average, .good, .great] private var moodTint: MoodTintable.Type { UserDefaultsStore.moodTintable() } private var moodImages: MoodImagable.Type { UserDefaultsStore.moodMoodImagable() } var body: some View { VStack(spacing: 8) { Text(hasSubscription ? promptText : "Tap to open app") .font(.subheadline) .foregroundStyle(.primary) .multilineTextAlignment(.center) .lineLimit(2) .minimumScaleFactor(0.7) HStack(spacing: 8) { ForEach(moods, id: \.rawValue) { mood in moodButton(for: mood) } } } } @ViewBuilder private func moodButton(for mood: Mood) -> some View { if hasSubscription { Button(intent: VoteMoodIntent(mood: mood)) { moodIcon(for: mood) } .buttonStyle(.plain) .accessibilityLabel(mood.strValue) .accessibilityHint(String(localized: "Log this mood")) } else { Link(destination: URL(string: "feels://subscribe")!) { moodIcon(for: mood) } .accessibilityLabel(mood.strValue) .accessibilityHint(String(localized: "Open app to subscribe")) } } private func moodIcon(for mood: Mood) -> some View { moodImages.icon(forMood: mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 44, height: 44) .foregroundColor(moodTint.color(forMood: mood)) } }