Files
Reflect/Shared/Services/ExportableWatchViews.swift
Trey t 0442eab1f8 Rebrand entire project from Feels to Reflect
Complete rename across all bundle IDs, App Groups, CloudKit containers,
StoreKit product IDs, data store filenames, URL schemes, logger subsystems,
Swift identifiers, user-facing strings (7 languages), file names, directory
names, Xcode project, schemes, assets, and documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 11:47:16 -06:00

366 lines
11 KiB
Swift

//
// ExportableWatchViews.swift
// Reflect
//
// Exportable watch views that match the real watchOS layouts.
// These views accept tint/icon configuration as parameters for batch export.
//
#if DEBUG
import SwiftUI
// MARK: - Watch Export Configuration
/// Configuration for watch view export styling
struct WatchExportConfig {
let moodTint: MoodTintable.Type
let moodImages: MoodImagable.Type
/// Get emoji for a mood based on the image style
func emoji(for mood: Mood) -> String {
// Map MoodImagable type to WatchMoodImageStyle equivalent
switch String(describing: moodImages) {
case "FontAwesomeMoodImages":
return fontAwesomeEmoji(for: mood)
case "EmojiMoodImages":
return emojiStyle(for: mood)
case "HandEmojiMoodImages":
return handEmoji(for: mood)
case "WeatherMoodImages":
return weatherEmoji(for: mood)
case "GardenMoodImages":
return gardenEmoji(for: mood)
case "HeartsMoodImages":
return heartsEmoji(for: mood)
case "CosmicMoodImages":
return cosmicEmoji(for: mood)
default:
return emojiStyle(for: mood)
}
}
private func fontAwesomeEmoji(for mood: Mood) -> String {
switch mood {
case .great: return "😁"
case .good: return "🙂"
case .average: return "😐"
case .bad: return "🙁"
case .horrible: return "😫"
case .missing, .placeholder: return ""
}
}
private func emojiStyle(for mood: Mood) -> String {
switch mood {
case .great: return "😀"
case .good: return "🙂"
case .average: return "😑"
case .bad: return "😕"
case .horrible: return "💩"
case .missing, .placeholder: return ""
}
}
private func handEmoji(for mood: Mood) -> String {
switch mood {
case .great: return "🙏"
case .good: return "👍"
case .average: return "🖖"
case .bad: return "👎"
case .horrible: return "🖕"
case .missing, .placeholder: return ""
}
}
private func weatherEmoji(for mood: Mood) -> String {
switch mood {
case .great: return "☀️"
case .good: return ""
case .average: return "☁️"
case .bad: return "🌧️"
case .horrible: return "⛈️"
case .missing: return "🌫️"
case .placeholder: return ""
}
}
private func gardenEmoji(for mood: Mood) -> String {
switch mood {
case .great: return "🌸"
case .good: return "🌿"
case .average: return "🌱"
case .bad: return "🍂"
case .horrible: return "🥀"
case .missing: return "🕳️"
case .placeholder: return ""
}
}
private func heartsEmoji(for mood: Mood) -> String {
switch mood {
case .great: return "💖"
case .good: return "🩷"
case .average: return "🤍"
case .bad: return "🩶"
case .horrible: return "💔"
case .missing: return "🖤"
case .placeholder: return ""
}
}
private func cosmicEmoji(for mood: Mood) -> String {
switch mood {
case .great: return ""
case .good: return "🌕"
case .average: return "🌓"
case .bad: return "🌑"
case .horrible: return "🕳️"
case .missing: return ""
case .placeholder: return ""
}
}
}
// MARK: - Exportable Watch Voting View
/// Watch voting interface - matches ContentView from watch app
struct ExportableWatchVotingView: View {
let config: WatchExportConfig
var body: some View {
VStack(spacing: 8) {
Text("How do you feel?")
.font(.system(size: 16, weight: .medium))
.foregroundColor(.secondary)
// Top row: Great, Good, Average
HStack(spacing: 8) {
ExportableWatchMoodButton(mood: .great, config: config)
ExportableWatchMoodButton(mood: .good, config: config)
ExportableWatchMoodButton(mood: .average, config: config)
}
// Bottom row: Bad, Horrible
HStack(spacing: 8) {
ExportableWatchMoodButton(mood: .bad, config: config)
ExportableWatchMoodButton(mood: .horrible, config: config)
}
}
}
}
// MARK: - Exportable Watch Mood Button
struct ExportableWatchMoodButton: View {
let mood: Mood
let config: WatchExportConfig
var body: some View {
Text(config.emoji(for: mood))
.font(.system(size: 28))
.frame(maxWidth: .infinity)
.frame(height: 50)
.background(config.moodTint.color(forMood: mood).opacity(0.3))
.cornerRadius(12)
}
}
// MARK: - Exportable Watch Already Rated View
struct ExportableWatchAlreadyRatedView: View {
let mood: Mood
let config: WatchExportConfig
var body: some View {
VStack(spacing: 12) {
Text(config.emoji(for: mood))
.font(.system(size: 50))
Text("Logged!")
.font(.system(size: 18, weight: .semibold))
.foregroundColor(.secondary)
}
}
}
// MARK: - Exportable Circular Complication
struct ExportableCircularComplication: View {
let mood: Mood?
let config: WatchExportConfig
var body: some View {
ZStack {
Circle()
.fill(Color(white: 0.15))
if let mood = mood {
Text(config.emoji(for: mood))
.font(.system(size: 24))
} else {
VStack(spacing: 0) {
Image(systemName: "face.smiling")
.font(.system(size: 18))
Text("Log")
.font(.system(size: 10))
}
}
}
}
}
// MARK: - Exportable Corner Complication
struct ExportableCornerComplication: View {
let mood: Mood?
let config: WatchExportConfig
var body: some View {
HStack(spacing: 4) {
if let mood = mood {
Text(config.emoji(for: mood))
.font(.system(size: 20))
Text(mood.widgetDisplayName)
.font(.system(size: 12))
.foregroundColor(.secondary)
} else {
Image(systemName: "face.smiling")
.font(.system(size: 20))
Text("Log mood")
.font(.system(size: 12))
.foregroundColor(.secondary)
}
}
}
}
// MARK: - Exportable Inline Complication
struct ExportableInlineComplication: View {
let mood: Mood?
let streak: Int
let config: WatchExportConfig
var body: some View {
HStack(spacing: 4) {
if streak > 0 {
Image(systemName: "flame.fill")
.foregroundColor(.orange)
Text("\(streak) day streak")
} else if let mood = mood {
Text("\(config.emoji(for: mood)) \(mood.widgetDisplayName)")
} else {
Image(systemName: "face.smiling")
Text("Log your mood")
}
}
.font(.system(size: 14))
}
}
// MARK: - Exportable Rectangular Complication
struct ExportableRectangularComplication: View {
let mood: Mood?
let streak: Int
let config: WatchExportConfig
var body: some View {
HStack {
if let mood = mood {
Text(config.emoji(for: mood))
.font(.system(size: 28))
VStack(alignment: .leading, spacing: 2) {
Text("Today")
.font(.system(size: 12))
.foregroundColor(.secondary)
Text(mood.widgetDisplayName)
.font(.system(size: 14, weight: .semibold))
if streak > 1 {
HStack(spacing: 2) {
Image(systemName: "flame.fill")
Text("\(streak) days")
}
.font(.system(size: 10))
.foregroundColor(.orange)
}
}
} else {
Image(systemName: "face.smiling")
.font(.system(size: 24))
VStack(alignment: .leading, spacing: 2) {
Text("Reflect")
.font(.system(size: 14, weight: .semibold))
Text("Tap to log mood")
.font(.system(size: 12))
.foregroundColor(.secondary)
}
}
Spacer()
}
}
}
// MARK: - Watch Container for Export
struct ExportableWatchContainer<Content: View>: View {
let width: CGFloat
let height: CGFloat
let colorScheme: ColorScheme
let cornerRadius: CGFloat
let content: Content
init(width: CGFloat, height: CGFloat, colorScheme: ColorScheme, cornerRadius: CGFloat = 20, @ViewBuilder content: () -> Content) {
self.width = width
self.height = height
self.colorScheme = colorScheme
self.cornerRadius = cornerRadius
self.content = content()
}
private var backgroundColor: Color {
colorScheme == .dark ? Color.black : Color(red: 0.95, green: 0.95, blue: 0.97)
}
var body: some View {
content
.environment(\.colorScheme, colorScheme)
.frame(width: width, height: height)
.background(backgroundColor)
.clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
}
}
// MARK: - Watch Complication Container
struct ExportableComplicationContainer<Content: View>: View {
let size: CGSize
let colorScheme: ColorScheme
let isCircular: Bool
let content: Content
init(size: CGSize, colorScheme: ColorScheme, isCircular: Bool = false, @ViewBuilder content: () -> Content) {
self.size = size
self.colorScheme = colorScheme
self.isCircular = isCircular
self.content = content()
}
private var backgroundColor: Color {
colorScheme == .dark ? Color(white: 0.1) : Color(white: 0.95)
}
var body: some View {
content
.environment(\.colorScheme, colorScheme)
.frame(width: size.width, height: size.height)
.background(backgroundColor)
.clipShape(isCircular ? AnyShape(Circle()) : AnyShape(RoundedRectangle(cornerRadius: 12, style: .continuous)))
}
}
#endif