Files
Reflect/Reflect Watch App/ReflectComplication.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

233 lines
6.4 KiB
Swift

//
// 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))
}