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>
237 lines
7.1 KiB
Swift
237 lines
7.1 KiB
Swift
//
|
|
// ContentView.swift
|
|
// Reflect Watch App
|
|
//
|
|
// Main voting interface for logging moods on Apple Watch.
|
|
//
|
|
|
|
import SwiftUI
|
|
import WatchKit
|
|
|
|
struct ContentView: View {
|
|
@State private var showConfirmation = false
|
|
@State private var selectedMood: Mood?
|
|
@State private var todaysMood: Mood?
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
if let mood = todaysMood ?? selectedMood, showConfirmation || todaysMood != nil {
|
|
// Show "already rated" view
|
|
AlreadyRatedView(mood: mood)
|
|
} else {
|
|
// Show voting UI
|
|
VStack(spacing: 8) {
|
|
Text("How do you feel?")
|
|
.font(.system(size: 16, weight: .medium))
|
|
.foregroundColor(.secondary)
|
|
|
|
// Top row: Great, Good, Average
|
|
HStack(spacing: 8) {
|
|
MoodButton(mood: .great, action: { logMood(.great) })
|
|
MoodButton(mood: .good, action: { logMood(.good) })
|
|
MoodButton(mood: .average, action: { logMood(.average) })
|
|
}
|
|
|
|
// Bottom row: Bad, Horrible
|
|
HStack(spacing: 8) {
|
|
MoodButton(mood: .bad, action: { logMood(.bad) })
|
|
MoodButton(mood: .horrible, action: { logMood(.horrible) })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.onAppear {
|
|
checkTodaysEntry()
|
|
}
|
|
}
|
|
|
|
private func checkTodaysEntry() {
|
|
let entry = ExtensionDataProvider.shared.getTodayEntry()
|
|
if let entry = entry, entry.mood != .missing && entry.mood != .placeholder {
|
|
todaysMood = entry.mood
|
|
}
|
|
}
|
|
|
|
private func logMood(_ mood: Mood) {
|
|
selectedMood = mood
|
|
|
|
// Haptic feedback
|
|
WKInterfaceDevice.current().play(.success)
|
|
|
|
let date = Date()
|
|
|
|
// Send to iPhone for centralized logging (iOS handles all side effects)
|
|
// Also save locally as fallback and for immediate complication updates
|
|
Task { @MainActor in
|
|
// Always save locally for immediate complication display
|
|
ExtensionDataProvider.shared.add(mood: mood, forDate: date, entryType: .watch)
|
|
|
|
// Send to iPhone - it will handle HealthKit, Live Activity, etc.
|
|
_ = WatchConnectivityManager.shared.sendMoodToPhone(mood: mood.rawValue, date: date)
|
|
}
|
|
|
|
// Show confirmation and keep it (user already rated)
|
|
withAnimation(.easeInOut(duration: 0.2)) {
|
|
showConfirmation = true
|
|
todaysMood = mood
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Already Rated View
|
|
|
|
struct AlreadyRatedView: View {
|
|
let mood: Mood
|
|
|
|
var body: some View {
|
|
VStack(spacing: 12) {
|
|
Text(mood.watchEmoji)
|
|
.font(.system(size: 50))
|
|
|
|
Text("Logged!")
|
|
.font(.system(size: 18, weight: .semibold))
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Mood Button
|
|
|
|
struct MoodButton: View {
|
|
let mood: Mood
|
|
let action: () -> Void
|
|
|
|
var body: some View {
|
|
Button(action: action) {
|
|
Text(mood.watchEmoji)
|
|
.font(.system(size: 28))
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: 50)
|
|
.background(mood.watchColor.opacity(0.3))
|
|
.cornerRadius(12)
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|
|
|
|
|
|
// MARK: - Watch Mood Image Provider
|
|
|
|
/// Provides the appropriate emoji based on user's selected mood image style
|
|
enum WatchMoodImageStyle: Int {
|
|
case fontAwesome = 0
|
|
case emoji = 1
|
|
case handEmoji = 2
|
|
case weather = 3
|
|
case garden = 4
|
|
case hearts = 5
|
|
case cosmic = 6
|
|
|
|
static var current: WatchMoodImageStyle {
|
|
// Use optional chaining for preview safety - App Group may not exist in canvas
|
|
guard let defaults = UserDefaults(suiteName: Constants.currentGroupShareId) else {
|
|
return .emoji
|
|
}
|
|
let rawValue = defaults.integer(forKey: "moodImages")
|
|
return WatchMoodImageStyle(rawValue: rawValue) ?? .emoji
|
|
}
|
|
|
|
func emoji(for mood: Mood) -> String {
|
|
switch self {
|
|
case .fontAwesome:
|
|
// FontAwesome uses face icons - map to similar emoji
|
|
switch mood {
|
|
case .great: return "😁"
|
|
case .good: return "🙂"
|
|
case .average: return "😐"
|
|
case .bad: return "🙁"
|
|
case .horrible: return "😫"
|
|
case .missing, .placeholder: return "❓"
|
|
}
|
|
case .emoji:
|
|
switch mood {
|
|
case .great: return "😀"
|
|
case .good: return "🙂"
|
|
case .average: return "😑"
|
|
case .bad: return "😕"
|
|
case .horrible: return "💩"
|
|
case .missing, .placeholder: return "❓"
|
|
}
|
|
case .handEmoji:
|
|
switch mood {
|
|
case .great: return "🙏"
|
|
case .good: return "👍"
|
|
case .average: return "🖖"
|
|
case .bad: return "👎"
|
|
case .horrible: return "🖕"
|
|
case .missing, .placeholder: return "❓"
|
|
}
|
|
case .weather:
|
|
switch mood {
|
|
case .great: return "☀️"
|
|
case .good: return "⛅"
|
|
case .average: return "☁️"
|
|
case .bad: return "🌧️"
|
|
case .horrible: return "⛈️"
|
|
case .missing: return "🌫️"
|
|
case .placeholder: return "❓"
|
|
}
|
|
case .garden:
|
|
switch mood {
|
|
case .great: return "🌸"
|
|
case .good: return "🌿"
|
|
case .average: return "🌱"
|
|
case .bad: return "🍂"
|
|
case .horrible: return "🥀"
|
|
case .missing: return "🕳️"
|
|
case .placeholder: return "❓"
|
|
}
|
|
case .hearts:
|
|
switch mood {
|
|
case .great: return "💖"
|
|
case .good: return "🩷"
|
|
case .average: return "🤍"
|
|
case .bad: return "🩶"
|
|
case .horrible: return "💔"
|
|
case .missing: return "🖤"
|
|
case .placeholder: return "❓"
|
|
}
|
|
case .cosmic:
|
|
switch mood {
|
|
case .great: return "⭐"
|
|
case .good: return "🌕"
|
|
case .average: return "🌓"
|
|
case .bad: return "🌑"
|
|
case .horrible: return "🕳️"
|
|
case .missing: return "✧"
|
|
case .placeholder: return "❓"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Watch-Specific Mood Extensions
|
|
|
|
extension Mood {
|
|
/// Emoji representation for watch display based on user's selected style
|
|
var watchEmoji: String {
|
|
WatchMoodImageStyle.current.emoji(for: self)
|
|
}
|
|
|
|
/// Color for watch UI (simplified palette)
|
|
var watchColor: Color {
|
|
switch self {
|
|
case .great: return .green
|
|
case .good: return .mint
|
|
case .average: return .yellow
|
|
case .bad: return .orange
|
|
case .horrible: return .red
|
|
case .missing, .placeholder: return .gray
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
ContentView()
|
|
}
|