Files
Reflect/FeelsWidget2/WidgetSharedViews.swift
Trey t 37fdb4db29 Fix small widget layout and settings light mode contrast
- Remove prompt text from small widget before voting
- Show date instead of "Logged!" after voting on small widget
- Fix light mode settings: section=gray, inner rows=white for contrast
- Remove redundant backgrounds from Browse Themes and subscription banner

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 13:39:25 -06:00

265 lines
8.2 KiB
Swift

//
// 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 (no text - just mood buttons)
private var smallLayout: some View {
VStack(spacing: 8) {
// Top row: Great, Good, Average
HStack(spacing: 12) {
ForEach([Mood.great, .good, .average], id: \.rawValue) { mood in
moodButton(for: mood, size: 40)
}
}
// Bottom row: Bad, Horrible
HStack(spacing: 12) {
ForEach([Mood.bad, .horrible], id: \.rawValue) { mood in
moodButton(for: mood, size: 40)
}
}
}
.padding(.horizontal, 8)
.padding(.vertical, 8)
}
// 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 icons only (accessibility labels provide screen reader support)
let content = moodImages.icon(forMood: mood)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 36, height: 36)
.foregroundColor(moodTint.color(forMood: mood))
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 {
// Large widget uses icons only (accessibility labels provide screen reader support)
moodImages.icon(forMood: mood)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 44, height: 44)
.foregroundColor(moodTint.color(forMood: mood))
.padding(10)
.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))
}
}