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>
This commit is contained in:
Trey t
2026-02-26 11:47:16 -06:00
parent b1a54d2844
commit 0442eab1f8
380 changed files with 858 additions and 1077 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 KiB

View File

@@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "AppIcon.png",
"idiom" : "universal",
"platform" : "watchos",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,236 @@
//
// 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()
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.88oakapps.reflect</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.88oakapps.reflect</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.88oakapps.reflect.debug</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.88oakapps.reflect.debug</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,232 @@
//
// ReflectComplication.swift
// Reflect Watch App
//
// WidgetKit complications for Apple Watch.
//
import WidgetKit
import SwiftUI
// MARK: - Timeline Provider
struct ReflectTimelineProvider: TimelineProvider {
func placeholder(in context: Context) -> ReflectEntry {
ReflectEntry(date: Date(), mood: nil, streak: 0)
}
func getSnapshot(in context: Context, completion: @escaping (ReflectEntry) -> Void) {
Task { @MainActor in
let entry = createEntry()
completion(entry)
}
}
func getTimeline(in context: Context, completion: @escaping (Timeline<ReflectEntry>) -> Void) {
Task { @MainActor in
let entry = createEntry()
// Refresh at midnight for the next day
let tomorrow = Calendar.current.startOfDay(
for: Calendar.current.date(byAdding: .day, value: 1, to: Date())!
)
let timeline = Timeline(entries: [entry], policy: .after(tomorrow))
completion(timeline)
}
}
@MainActor
private func createEntry() -> ReflectEntry {
let todayEntry = ExtensionDataProvider.shared.getTodayEntry()
let streak = ExtensionDataProvider.shared.getCurrentStreak()
return ReflectEntry(
date: Date(),
mood: todayEntry?.mood,
streak: streak
)
}
}
// MARK: - Timeline Entry
struct ReflectEntry: TimelineEntry {
let date: Date
let mood: Mood?
let streak: Int
}
// MARK: - Complication Views
struct ReflectComplicationEntryView: View {
var entry: ReflectEntry
@Environment(\.widgetFamily) var family
var body: some View {
switch family {
case .accessoryCircular:
CircularView(entry: entry)
case .accessoryCorner:
CornerView(entry: entry)
case .accessoryInline:
InlineView(entry: entry)
case .accessoryRectangular:
RectangularView(entry: entry)
default:
CircularView(entry: entry)
}
}
}
// MARK: - Circular Complication
struct CircularView: View {
let entry: ReflectEntry
var body: some View {
ZStack {
AccessoryWidgetBackground()
if let mood = entry.mood {
Text(mood.watchEmoji)
.font(.system(size: 24))
} else {
VStack(spacing: 0) {
Image(systemName: "face.smiling")
.font(.system(size: 18))
Text("Log")
.font(.system(size: 10))
}
}
}
}
}
// MARK: - Corner Complication
struct CornerView: View {
let entry: ReflectEntry
var body: some View {
if let mood = entry.mood {
Text(mood.watchEmoji)
.font(.system(size: 20))
.widgetLabel {
Text(mood.widgetDisplayName)
}
} else {
Image(systemName: "face.smiling")
.font(.system(size: 20))
.widgetLabel {
Text("Log mood")
}
}
}
}
// MARK: - Inline Complication
struct InlineView: View {
let entry: ReflectEntry
var body: some View {
if entry.streak > 0 {
Label("\(entry.streak) day streak", systemImage: "flame.fill")
} else if let mood = entry.mood {
Text("\(mood.watchEmoji) \(mood.widgetDisplayName)")
} else {
Label("Log your mood", systemImage: "face.smiling")
}
}
}
// MARK: - Rectangular Complication
struct RectangularView: View {
let entry: ReflectEntry
var body: some View {
HStack {
if let mood = entry.mood {
Text(mood.watchEmoji)
.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 entry.streak > 1 {
Label("\(entry.streak) days", systemImage: "flame.fill")
.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: - Widget Configuration
struct ReflectComplication: Widget {
let kind: String = "ReflectComplication"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: ReflectTimelineProvider()) { entry in
ReflectComplicationEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("Reflect")
.description("See today's mood and streak.")
.supportedFamilies([
.accessoryCircular,
.accessoryCorner,
.accessoryInline,
.accessoryRectangular
])
}
}
// MARK: - Preview
#Preview("Circular - Mood") {
CircularView(entry: ReflectEntry(date: Date(), mood: .great, streak: 5))
.previewContext(WidgetPreviewContext(family: .accessoryCircular))
}
#Preview("Circular - Empty") {
CircularView(entry: ReflectEntry(date: Date(), mood: nil, streak: 0))
.previewContext(WidgetPreviewContext(family: .accessoryCircular))
}
#Preview("Rectangular - Mood") {
RectangularView(entry: ReflectEntry(date: Date(), mood: .good, streak: 7))
.previewContext(WidgetPreviewContext(family: .accessoryRectangular))
}
#Preview("Inline - Streak") {
InlineView(entry: ReflectEntry(date: Date(), mood: .great, streak: 5))
.previewContext(WidgetPreviewContext(family: .accessoryInline))
}
#Preview("Corner - Mood") {
CornerView(entry: ReflectEntry(date: Date(), mood: .average, streak: 3))
.previewContext(WidgetPreviewContext(family: .accessoryCorner))
}

View File

@@ -0,0 +1,23 @@
//
// ReflectWatchApp.swift
// Reflect Watch App
//
// Entry point for the Apple Watch companion app.
//
import SwiftUI
@main
struct ReflectWatchApp: App {
init() {
// Initialize Watch Connectivity for cross-device widget updates
_ = WatchConnectivityManager.shared
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}