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>
233 lines
6.4 KiB
Swift
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))
|
|
}
|