126 lines
4.3 KiB
Swift
126 lines
4.3 KiB
Swift
//
|
||
// LemonLimeWidgetLiveActivity.swift
|
||
// LemonLimeWidget
|
||
//
|
||
// Created by 송예찬 on 6/18/26.
|
||
//
|
||
|
||
import ActivityKit
|
||
import WidgetKit
|
||
import SwiftUI
|
||
|
||
private let timerEndDate = Date.distantFuture
|
||
|
||
struct LemonLimeWidgetLiveActivity: Widget {
|
||
var body: some WidgetConfiguration {
|
||
ActivityConfiguration(for: TimerActivityAttributes.self) { context in
|
||
// MARK: Lock Screen / Banner
|
||
HStack(spacing: 14) {
|
||
Image(systemName: "timer")
|
||
.font(.title2.weight(.semibold))
|
||
.foregroundStyle(.yellow)
|
||
|
||
VStack(alignment: .leading, spacing: 3) {
|
||
Text(context.attributes.taskName)
|
||
.font(.subheadline.weight(.semibold))
|
||
.foregroundStyle(.white)
|
||
.lineLimit(1)
|
||
|
||
Text(timerInterval: context.state.startDate...timerEndDate, countsDown: false)
|
||
.font(.caption.monospacedDigit())
|
||
.foregroundStyle(.yellow)
|
||
}
|
||
|
||
Spacer()
|
||
|
||
Text(context.attributes.taskIcon)
|
||
.font(.largeTitle)
|
||
}
|
||
.padding(.horizontal, 16)
|
||
.padding(.vertical, 12)
|
||
.activityBackgroundTint(Color(red: 0.08, green: 0.30, blue: 0.08))
|
||
.activitySystemActionForegroundColor(.yellow)
|
||
|
||
} dynamicIsland: { context in
|
||
DynamicIsland {
|
||
// MARK: Expanded – top row
|
||
DynamicIslandExpandedRegion(.leading) {
|
||
Label {
|
||
Text(context.attributes.taskName)
|
||
.font(.caption.weight(.semibold))
|
||
.lineLimit(1)
|
||
} icon: {
|
||
Text(context.attributes.taskIcon)
|
||
.font(.caption)
|
||
}
|
||
.foregroundStyle(.green)
|
||
}
|
||
|
||
DynamicIslandExpandedRegion(.trailing) {
|
||
Image(systemName: "circle.fill")
|
||
.font(.caption)
|
||
.foregroundStyle(.green)
|
||
.padding(.trailing, 4)
|
||
}
|
||
|
||
// MARK: Expanded – center (large timer)
|
||
DynamicIslandExpandedRegion(.bottom) {
|
||
Text(timerInterval: context.state.startDate...timerEndDate, countsDown: false)
|
||
.font(.system(size: 44, weight: .bold, design: .monospaced))
|
||
.foregroundStyle(.yellow)
|
||
.frame(maxWidth: .infinity, alignment: .center)
|
||
.padding(.top, 4)
|
||
}
|
||
|
||
} compactLeading: {
|
||
Image(systemName: "timer")
|
||
.foregroundStyle(.green)
|
||
.font(.caption.weight(.semibold))
|
||
|
||
} compactTrailing: {
|
||
Text(timerInterval: context.state.startDate...timerEndDate, countsDown: false)
|
||
.monospacedDigit()
|
||
.font(.caption2.weight(.medium))
|
||
.foregroundStyle(.yellow)
|
||
.frame(maxWidth: 52)
|
||
|
||
} minimal: {
|
||
ZStack {
|
||
Circle()
|
||
.fill(LinearGradient(
|
||
colors: [.green, Color(red: 0.8, green: 0.9, blue: 0.0)],
|
||
startPoint: .topLeading,
|
||
endPoint: .bottomTrailing
|
||
))
|
||
Text(context.attributes.taskIcon)
|
||
.font(.system(size: 11))
|
||
}
|
||
}
|
||
.keylineTint(.green)
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - Previews
|
||
|
||
extension TimerActivityAttributes {
|
||
fileprivate static var preview: TimerActivityAttributes {
|
||
TimerActivityAttributes(taskName: "운동하기", taskIcon: "🏃")
|
||
}
|
||
}
|
||
|
||
extension TimerActivityAttributes.ContentState {
|
||
fileprivate static var running: TimerActivityAttributes.ContentState {
|
||
TimerActivityAttributes.ContentState(
|
||
startDate: Date().addingTimeInterval(-125),
|
||
elapsedSeconds: 125
|
||
)
|
||
}
|
||
}
|
||
|
||
#Preview("Notification", as: .content, using: TimerActivityAttributes.preview) {
|
||
LemonLimeWidgetLiveActivity()
|
||
} contentStates: {
|
||
TimerActivityAttributes.ContentState.running
|
||
}
|