// // TimeWidgetLiveActivity.swift // TimeWidget // // Created by 송예찬 on 6/13/26. // import ActivityKit import WidgetKit import SwiftUI struct TimeWidgetLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: TimerAttributes.self) { context in // Lock Screen / Notification Banner // Text(timerInterval:) is a system primitive that counts down in real time // without waking the app — no Timer or polling needed. HStack(spacing: 14) { Image(systemName: "timer") .font(.title2.weight(.semibold)) .foregroundStyle(.white) VStack(alignment: .leading, spacing: 2) { Text(context.attributes.sessionName) .font(.caption) .foregroundStyle(.white.opacity(0.75)) let safeEnd = max(Date.now, context.state.endDate) Text(timerInterval: Date.now...safeEnd, countsDown: true) .font(.title.monospacedDigit().bold()) .foregroundStyle(.white) } Spacer() } .padding(.horizontal, 20) .padding(.vertical, 14) .activityBackgroundTint(Color(red: 0.07, green: 0.07, blue: 0.18)) .activitySystemActionForegroundColor(.white) } dynamicIsland: { context in DynamicIsland { // Expanded — shown when the user long-presses the Dynamic Island. DynamicIslandExpandedRegion(.leading) { Label(context.attributes.sessionName, systemImage: "timer") .font(.caption) .foregroundStyle(.secondary) .lineLimit(1) } DynamicIslandExpandedRegion(.trailing) { EmptyView() } DynamicIslandExpandedRegion(.center) { let safeEnd = max(Date.now, context.state.endDate) Text(timerInterval: Date.now...safeEnd, countsDown: true) .font(.title2.monospacedDigit().bold()) .foregroundStyle(.primary) } DynamicIslandExpandedRegion(.bottom) { Text("Stay focused") .font(.caption2) .foregroundStyle(.tertiary) } } compactLeading: { Image(systemName: "timer") .foregroundStyle(.indigo) } compactTrailing: { let safeEnd = max(Date.now, context.state.endDate) Text(timerInterval: Date.now...safeEnd, countsDown: true) .font(.caption.monospacedDigit().bold()) .foregroundStyle(.primary) .frame(width: 52) } minimal: { Image(systemName: "timer") .foregroundStyle(.indigo) } .keylineTint(.indigo) } } } // MARK: - Previews extension TimerAttributes { fileprivate static var preview: TimerAttributes { TimerAttributes(sessionName: "Deep Work") } } extension TimerAttributes.ContentState { fileprivate static var active: TimerAttributes.ContentState { TimerAttributes.ContentState(endDate: Date.now.addingTimeInterval(22 * 60)) } fileprivate static var nearEnd: TimerAttributes.ContentState { TimerAttributes.ContentState(endDate: Date.now.addingTimeInterval(2 * 60)) } } #Preview("Notification", as: .content, using: TimerAttributes.preview) { TimeWidgetLiveActivity() } contentStates: { TimerAttributes.ContentState.active TimerAttributes.ContentState.nearEnd }