Skip to content

Commit 60cb690

Browse files
committed
πŸƒ Phase 1: ActivityKit (Live Activities) νŠœν† λ¦¬μ–Ό μΆ”κ°€
DocC νŠœν† λ¦¬μ–Ό 8개 챕터: - 01: Live Activity μ΄ν•΄ν•˜κΈ° (HIG 원칙) - 02: ActivityAttributes μ •μ˜ - 03: ActivityConfiguration μ„€μ • - 04: Dynamic Island Compact - 05: Dynamic Island Expanded - 06: 잠금 ν™”λ©΄ λ ˆμ΄μ•„μ›ƒ - 07: Activity 생λͺ…μ£ΌκΈ° (μ‹œμž‘/μ—…λ°μ΄νŠΈ/μ’…λ£Œ) - 08: Push Token μ—…λ°μ΄νŠΈ λ¦¬μ†ŒμŠ€ 파일 27개 + λΈ”λ‘œκ·Έ 포슀트
1 parent 7b2d64c commit 60cb690

41 files changed

Lines changed: 1682 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
<!DOCTYPE html>
2+
<html lang="ko">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>πŸƒ 배달 좔적 Live Activity λ§Œλ“€κΈ° β€” HIG Lab</title>
7+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;600;700;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
8+
<style>
9+
:root{--bg:#fafafa;--surface:#fff;--text:#1d1d1f;--text-2:#6e6e73;--text-3:#86868b;--accent:#0071e3;--green:#34c759;--red:#ff3b30;--border:#d2d2d7;--code-bg:#1e1e2e;--tag-bg:#f5f5f7;--radius:16px}
10+
*{margin:0;padding:0;box-sizing:border-box}
11+
body{font-family:'Noto Sans KR',-apple-system,sans-serif;background:var(--bg);color:var(--text);line-height:1.8;-webkit-font-smoothing:antialiased}
12+
.top-bar{position:sticky;top:0;z-index:100;background:rgba(250,250,250,0.88);backdrop-filter:saturate(180%) blur(20px);border-bottom:.5px solid var(--border)}
13+
.top-bar-inner{max-width:800px;margin:0 auto;padding:12px 24px;display:flex;align-items:center;justify-content:space-between}
14+
.top-logo{font-size:16px;font-weight:700;text-decoration:none;color:var(--text)}
15+
.top-logo span{color:var(--accent)}
16+
.top-nav{display:flex;gap:20px}
17+
.top-nav a{font-size:13px;color:var(--text-2);text-decoration:none;font-weight:500}
18+
article{max-width:800px;margin:0 auto;padding:48px 24px 120px}
19+
.hero-section{margin-bottom:56px}
20+
.breadcrumb{font-size:13px;color:var(--text-3);margin-bottom:20px}
21+
.breadcrumb a{color:var(--accent);text-decoration:none}
22+
.article-title{font-size:clamp(32px,5vw,44px);font-weight:900;letter-spacing:-1.5px;line-height:1.2;margin-bottom:16px}
23+
.article-subtitle{font-size:18px;color:var(--text-2);line-height:1.7;margin-bottom:28px}
24+
.meta-row{display:flex;flex-wrap:wrap;gap:16px;padding-bottom:28px;border-bottom:1px solid var(--border)}
25+
.meta-item{font-size:13px;color:var(--text-2)}
26+
h2{font-size:26px;font-weight:800;margin:56px 0 16px;padding-top:24px}
27+
h3{font-size:20px;font-weight:700;margin:36px 0 12px}
28+
p{margin-bottom:16px;font-size:16px}
29+
.hig-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin:24px 0}
30+
.hig-box-header{padding:16px 20px;background:var(--tag-bg);border-bottom:1px solid var(--border);font-size:14px;font-weight:700}
31+
.hig-box-body{padding:20px}
32+
.code-block{background:var(--code-bg);border-radius:12px;margin:20px 0;overflow:hidden}
33+
.code-header{padding:10px 16px;display:flex;justify-content:space-between;align-items:center;background:rgba(255,255,255,0.05);border-bottom:1px solid rgba(255,255,255,0.08)}
34+
.code-filename{font-size:13px;color:#cdd6f4;font-family:'JetBrains Mono',monospace;font-weight:500}
35+
.code-lang{font-size:11px;color:#6c7086;background:rgba(255,255,255,0.06);padding:2px 8px;border-radius:4px}
36+
pre.code-body{margin:0;padding:16px;overflow-x:auto;font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.6;color:#cdd6f4;white-space:pre;tab-size:4}
37+
.code-body .kw{color:#cba6f7}
38+
.code-body .type{color:#89b4fa}
39+
.code-body .str{color:#a6e3a1}
40+
.code-body .num{color:#fab387}
41+
.code-body .comment{color:#6c7086;font-style:italic}
42+
.code-body .func{color:#89dceb}
43+
.code-body .prop{color:#f5c2e7}
44+
.checkpoint{background:linear-gradient(135deg,#0071e308,#5856d608);border:1px solid #0071e320;border-radius:12px;padding:20px 24px;margin:32px 0;display:flex;gap:12px;align-items:flex-start}
45+
.checkpoint-icon{font-size:24px;flex-shrink:0}
46+
.checkpoint h4{font-size:15px;font-weight:700;margin-bottom:4px}
47+
.checkpoint p{font-size:14px;color:var(--text-2);margin:0}
48+
.resource-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin:24px 0}
49+
.resource-card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:20px;text-decoration:none;color:inherit;transition:all .2s;text-align:center}
50+
.resource-card:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(0,0,0,0.08)}
51+
.resource-icon{font-size:28px;margin-bottom:8px}
52+
.resource-label{font-size:13px;font-weight:600;color:var(--accent)}
53+
.resource-desc{font-size:12px;color:var(--text-3);margin-top:2px}
54+
@media(max-width:640px){.resource-grid{grid-template-columns:1fr}}
55+
</style>
56+
</head>
57+
<body>
58+
59+
<div class="top-bar">
60+
<div class="top-bar-inner">
61+
<a href="../index.html" class="top-logo">HIG <span>Lab</span></a>
62+
<div class="top-nav">
63+
<a href="https://github.com/M1zz/HIGLab">GitHub</a>
64+
<a href="https://youtube.com/@devjari">개발자리</a>
65+
</div>
66+
</div>
67+
</div>
68+
69+
<article>
70+
<div class="hero-section">
71+
<div class="breadcrumb">
72+
<a href="../index.html">HIG Lab</a> β†’ <a href="../index.html#phase1">Phase 1</a> β†’ ActivityKit
73+
</div>
74+
<h1 class="article-title">πŸƒ 배달 좔적 Live Activity<br>λ§Œλ“€κΈ°</h1>
75+
<p class="article-subtitle">
76+
ActivityKit을 ν™œμš©ν•΄ 배달 ν˜„ν™©μ„ 잠금 ν™”λ©΄κ³Ό Dynamic Islandμ—μ„œ
77+
μ‹€μ‹œκ°„μœΌλ‘œ 확인할 수 μžˆλŠ” Live Activityλ₯Ό κ΅¬ν˜„ν•©λ‹ˆλ‹€.
78+
</p>
79+
<div class="meta-row">
80+
<span class="meta-item">πŸ“… 2025.02</span>
81+
<span class="meta-item">⏱ 총 μ‹€μŠ΅ 2μ‹œκ°„</span>
82+
<span class="meta-item">πŸ“± iOS 16.1+ / Xcode 15+</span>
83+
<span class="meta-item">🎯 쀑급</span>
84+
</div>
85+
</div>
86+
87+
<h2>πŸ“– Live Activityλž€?</h2>
88+
89+
<p>
90+
Live ActivityλŠ” <strong>μ§„ν–‰ 쀑인 μž‘μ—…μ˜ μ‹€μ‹œκ°„ μƒνƒœ</strong>λ₯Ό 잠금 ν™”λ©΄κ³Ό Dynamic Island에 ν‘œμ‹œν•©λ‹ˆλ‹€.
91+
μœ„μ ―κ³Ό 달리 μ‹œκ°„ μ œν•œ(μ΅œλŒ€ 8μ‹œκ°„)이 있고, μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈκ°€ κ°€λŠ₯ν•©λ‹ˆλ‹€.
92+
</p>
93+
94+
<div class="hig-box">
95+
<div class="hig-box-header">🍎 Live Activity 적합 사둀</div>
96+
<div class="hig-box-body">
97+
<p><strong>βœ… 쒋은 사둀</strong><br>배달/νƒμ‹œ 좔적, 슀포츠 κ²½κΈ°, 타이머, μŒμ•… μž¬μƒ, ν•­κ³΅νŽΈ 정보</p>
98+
<p><strong>❌ λ‚˜μœ 사둀</strong><br>날씨(β†’ Widget), μΊ˜λ¦°λ” μ•Œλ¦Ό(β†’ Notification), μ£Όκ°€(β†’ Widget)</p>
99+
</div>
100+
</div>
101+
102+
<h2>πŸ—οΈ ActivityAttributes μ •μ˜</h2>
103+
104+
<div class="code-block">
105+
<div class="code-header">
106+
<span class="code-filename">DeliveryAttributes.swift</span>
107+
<span class="code-lang">Swift</span>
108+
</div>
109+
<pre class="code-body"><span class="kw">import</span> <span class="type">ActivityKit</span>
110+
111+
<span class="kw">struct</span> <span class="type">DeliveryAttributes</span>: <span class="type">ActivityAttributes</span> {
112+
<span class="comment">// Static: λ³€κ²½ λΆˆκ°€</span>
113+
<span class="kw">let</span> <span class="prop">orderNumber</span>: <span class="type">String</span>
114+
<span class="kw">let</span> <span class="prop">storeName</span>: <span class="type">String</span>
115+
116+
<span class="comment">// ContentState: μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈ</span>
117+
<span class="kw">struct</span> <span class="type">ContentState</span>: <span class="type">Codable</span>, <span class="type">Hashable</span> {
118+
<span class="kw">let</span> <span class="prop">status</span>: <span class="type">DeliveryStatus</span>
119+
<span class="kw">let</span> <span class="prop">estimatedArrival</span>: <span class="type">Date</span>
120+
<span class="kw">let</span> <span class="prop">driverName</span>: <span class="type">String</span>?
121+
}
122+
}
123+
124+
<span class="kw">enum</span> <span class="type">DeliveryStatus</span>: <span class="type">String</span>, <span class="type">Codable</span> {
125+
<span class="kw">case</span> <span class="prop">preparing</span> <span class="comment">// μ€€λΉ„ 쀑</span>
126+
<span class="kw">case</span> <span class="prop">pickedUp</span> <span class="comment">// ν”½μ—… μ™„λ£Œ</span>
127+
<span class="kw">case</span> <span class="prop">nearby</span> <span class="comment">// 근처 도착</span>
128+
<span class="kw">case</span> <span class="prop">delivered</span> <span class="comment">// 배달 μ™„λ£Œ</span>
129+
}</pre>
130+
</div>
131+
132+
<h2>🏝️ Dynamic Island λ ˆμ΄μ•„μ›ƒ</h2>
133+
134+
<p>Dynamic IslandλŠ” 3κ°€μ§€ μƒνƒœκ°€ μžˆμŠ΅λ‹ˆλ‹€: Compact, Minimal, Expanded</p>
135+
136+
<div class="code-block">
137+
<div class="code-header">
138+
<span class="code-filename">DeliveryLiveActivity.swift</span>
139+
<span class="code-lang">Swift</span>
140+
</div>
141+
<pre class="code-body"><span class="kw">struct</span> <span class="type">DeliveryLiveActivity</span>: <span class="type">Widget</span> {
142+
<span class="kw">var</span> <span class="prop">body</span>: <span class="kw">some</span> <span class="type">WidgetConfiguration</span> {
143+
<span class="type">ActivityConfiguration</span>(<span class="kw">for</span>: <span class="type">DeliveryAttributes</span>.<span class="kw">self</span>) { context <span class="kw">in</span>
144+
<span class="comment">// 잠금 ν™”λ©΄</span>
145+
<span class="type">LockScreenView</span>(context: context)
146+
} dynamicIsland: { context <span class="kw">in</span>
147+
<span class="type">DynamicIsland</span> {
148+
<span class="comment">// Expanded</span>
149+
<span class="type">DynamicIslandExpandedRegion</span>(.leading) {
150+
<span class="type">Image</span>(systemName: <span class="str">"person.circle"</span>)
151+
}
152+
<span class="type">DynamicIslandExpandedRegion</span>(.trailing) {
153+
<span class="type">Image</span>(systemName: context.state.status.<span class="prop">symbol</span>)
154+
}
155+
<span class="type">DynamicIslandExpandedRegion</span>(.center) {
156+
<span class="type">Text</span>(context.attributes.<span class="prop">storeName</span>)
157+
}
158+
<span class="type">DynamicIslandExpandedRegion</span>(.bottom) {
159+
<span class="type">ProgressView</span>(value: context.state.<span class="prop">progress</span>)
160+
}
161+
} compactLeading: {
162+
<span class="type">Image</span>(systemName: <span class="str">"bicycle"</span>)
163+
} compactTrailing: {
164+
<span class="type">Text</span>(context.state.<span class="prop">estimatedArrival</span>, style: .timer)
165+
} minimal: {
166+
<span class="type">Image</span>(systemName: <span class="str">"bicycle"</span>)
167+
}
168+
}
169+
}
170+
}</pre>
171+
</div>
172+
173+
<h2>⚑ Activity 생λͺ…μ£ΌκΈ°</h2>
174+
175+
<div class="code-block">
176+
<div class="code-header">
177+
<span class="code-filename">ActivityManager.swift</span>
178+
<span class="code-lang">Swift</span>
179+
</div>
180+
<pre class="code-body"><span class="comment">// μ‹œμž‘</span>
181+
<span class="kw">let</span> activity = <span class="kw">try</span> <span class="type">Activity</span>.request(
182+
attributes: attributes,
183+
content: <span class="type">ActivityContent</span>(state: initialState, staleDate: <span class="kw">nil</span>),
184+
pushType: .token
185+
)
186+
187+
<span class="comment">// μ—…λ°μ΄νŠΈ</span>
188+
<span class="kw">await</span> activity.update(<span class="type">ActivityContent</span>(state: newState, staleDate: <span class="kw">nil</span>))
189+
190+
<span class="comment">// μ’…λ£Œ</span>
191+
<span class="kw">await</span> activity.end(finalContent, dismissalPolicy: .after(<span class="type">Date</span>().addingTimeInterval(<span class="num">3600</span>)))</pre>
192+
</div>
193+
194+
<div class="checkpoint">
195+
<div class="checkpoint-icon">πŸŽ‰</div>
196+
<div class="checkpoint-content">
197+
<h4>μ±Œλ¦°μ§€ μ™„λ£Œ!</h4>
198+
<p>Dynamic Island와 잠금 ν™”λ©΄μ—μ„œ 배달 ν˜„ν™©μ„ μ‹€μ‹œκ°„μœΌλ‘œ 확인할 수 μžˆλŠ” Live Activityλ₯Ό μ™„μ„±ν–ˆμŠ΅λ‹ˆλ‹€!</p>
199+
</div>
200+
</div>
201+
202+
<h2>πŸ“¦ ν•™μŠ΅ 자료</h2>
203+
204+
<div class="resource-grid">
205+
<a href="../activitykit/documentation/higactivitykit/tutorials/table-of-contents" class="resource-card">
206+
<div class="resource-icon">πŸ“š</div>
207+
<div class="resource-label">DocC νŠœν† λ¦¬μ–Ό</div>
208+
<div class="resource-desc">Xcodeμ—μ„œ λ°”λ‘œ μ‹€μŠ΅</div>
209+
</a>
210+
<a href="https://github.com/M1zz/HIGLab" class="resource-card">
211+
<div class="resource-icon">πŸ’»</div>
212+
<div class="resource-label">GitHub ν”„λ‘œμ νŠΈ</div>
213+
<div class="resource-desc">전체 μ†ŒμŠ€μ½”λ“œ</div>
214+
</a>
215+
<a href="https://developer.apple.com/design/human-interface-guidelines/live-activities" class="resource-card">
216+
<div class="resource-icon">🍎</div>
217+
<div class="resource-label">Apple HIG 원문</div>
218+
<div class="resource-desc">Live Activities κ°€μ΄λ“œ</div>
219+
</a>
220+
</div>
221+
222+
</article>
223+
</body>
224+
</html>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// swift-tools-version: 5.9
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "HIGActivityKit",
6+
platforms: [.iOS(.v17), .macOS(.v14)],
7+
products: [
8+
.library(name: "HIGActivityKit", targets: ["HIGActivityKit"]),
9+
],
10+
dependencies: [
11+
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
12+
],
13+
targets: [
14+
.target(name: "HIGActivityKit"),
15+
]
16+
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# ``HIGActivityKit``
2+
3+
Live Activities와 Dynamic Islandλ₯Ό ν™œμš©ν•œ μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈ κ²½ν—˜μ„ λ§Œλ“­λ‹ˆλ‹€.
4+
5+
## Overview
6+
7+
이 νŠœν† λ¦¬μ–Όμ—μ„œλŠ” 배달 좔적 앱을 λ§Œλ“€λ©° ActivityKit의 핡심 κ°œλ…μ„ ν•™μŠ΅ν•©λ‹ˆλ‹€.
8+
9+
- Live Activity둜 잠금 화면에 μ‹€μ‹œκ°„ 정보 ν‘œμ‹œ
10+
- Dynamic Islandμ—μ„œ 컴팩트/ν™•μž₯ λ ˆμ΄μ•„μ›ƒ κ΅¬ν˜„
11+
- Push Token을 ν™œμš©ν•œ 원격 μ—…λ°μ΄νŠΈ
12+
13+
## Topics
14+
15+
### Tutorials
16+
17+
- <doc:tutorials/Table-of-Contents>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Dynamic Island - Compact λ ˆμ΄μ•„μ›ƒ
2+
// Leading(쒌)κ³Ό Trailing(우) μ˜μ—­
3+
4+
// Compact Leading: 쒌츑 - μ•„μ΄μ½˜ λ˜λŠ” 이미지
5+
struct CompactLeadingView: View {
6+
var body: some View {
7+
Image(systemName: "bicycle")
8+
.font(.system(size: 14, weight: .semibold))
9+
}
10+
}
11+
12+
// Compact Trailing: 우츑 - 핡심 숫자/ν…μŠ€νŠΈ
13+
struct CompactTrailingView: View {
14+
let minutesRemaining: Int
15+
16+
var body: some View {
17+
Text("\(minutesRemaining)λΆ„")
18+
.font(.system(size: 14, weight: .bold, design: .rounded))
19+
.monospacedDigit()
20+
}
21+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Dynamic Island - Expanded λ ˆμ΄μ•„μ›ƒ
2+
// 길게 λˆŒλ €μ„ λ•Œ ν‘œμ‹œ
3+
4+
struct ExpandedView: View {
5+
let context: ActivityViewContext<DeliveryAttributes>
6+
7+
var body: some View {
8+
VStack(spacing: 12) {
9+
// 상단: κ°€κ²Œ 정보 + 배달 μƒνƒœ
10+
HStack {
11+
// Leading
12+
Image(systemName: "storefront")
13+
.font(.title2)
14+
15+
// Center
16+
VStack(alignment: .leading) {
17+
Text(context.attributes.storeName)
18+
.font(.headline)
19+
Text(context.state.statusMessage)
20+
.font(.caption)
21+
.foregroundStyle(.secondary)
22+
}
23+
24+
Spacer()
25+
26+
// Trailing
27+
Image(systemName: context.state.status.symbolName)
28+
.font(.title2)
29+
.foregroundStyle(.green)
30+
}
31+
32+
// Bottom: μ§„ν–‰ λ°”
33+
DeliveryProgressBar(progress: context.state.progress)
34+
}
35+
.padding()
36+
}
37+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// HIG Live Activity 핡심 원칙
2+
3+
// 1. Glanceable β€” ν•œλˆˆμ— νŒŒμ•…
4+
struct GlanceableDesign {
5+
// βœ… Do: 핡심 μ •λ³΄λ§Œ 크게
6+
let prominentInfo = "λ„μ°©κΉŒμ§€ 12λΆ„"
7+
8+
// ❌ Don't: λΆˆν•„μš”ν•œ 정보 λ‚˜μ—΄
9+
// "주문번호 #12345, κ°€κ²Œλͺ…, 메뉴, 배달원, μ—°λ½μ²˜..."
10+
}
11+
12+
// 2. Real-time β€” μ‹€μ‹œκ°„ 반영
13+
struct RealtimeUpdates {
14+
// βœ… Do: μ‹€μ œ μƒνƒœ 반영
15+
func updateDeliveryStatus(_ status: DeliveryStatus) {
16+
// 배달원 μœ„μΉ˜, μ˜ˆμƒ μ‹œκ°„ μ—…λ°μ΄νŠΈ
17+
}
18+
19+
// ❌ Don't: 였래된 정보 방치
20+
}
21+
22+
// 3. Actionable β€” νƒ­ν•˜λ©΄ μ•±μœΌλ‘œ
23+
struct ActionableLink {
24+
// νƒ­ μ‹œ μ•±μ˜ κ΄€λ ¨ ν™”λ©΄μœΌλ‘œ 이동
25+
let deepLink = URL(string: "myapp://order/12345")
26+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Dynamic Island - Minimal λ ˆμ΄μ•„μ›ƒ
2+
// λ‹€λ₯Έ Live Activity와 ν•¨κ»˜ ν‘œμ‹œλ  λ•Œ
3+
4+
struct MinimalView: View {
5+
let status: DeliveryStatus
6+
7+
var body: some View {
8+
// μ›ν˜• μ˜μ—­ μ•ˆμ— 핡심 μ•„μ΄μ½˜λ§Œ
9+
Image(systemName: status.symbolName)
10+
.font(.system(size: 12, weight: .semibold))
11+
}
12+
}
13+
14+
extension DeliveryStatus {
15+
var symbolName: String {
16+
switch self {
17+
case .preparing: "bag"
18+
case .pickedUp: "bicycle"
19+
case .nearby: "location.fill"
20+
case .delivered: "checkmark.circle.fill"
21+
}
22+
}
23+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Live Activity 적합 사둀
2+
enum LiveActivityUseCases {
3+
// βœ… 쒋은 사둀: μ‹œκ°„ μ œν•œμ΄ μžˆλŠ” μ§„ν–‰ 쀑인 μž‘μ—…
4+
case deliveryTracking // 배달 좔적
5+
case rideShare // νƒμ‹œ/μ°¨λŸ‰ 곡유
6+
case sportsScore // 슀포츠 경기 점수
7+
case flightStatus // ν•­κ³΅νŽΈ μƒνƒœ
8+
case timer // 타이머/μŠ€ν†±μ›ŒμΉ˜
9+
case musicPlayback // μŒμ•… μž¬μƒ
10+
case workoutSession // μš΄λ™ μ„Έμ…˜
11+
12+
// ❌ λ‚˜μœ 사둀: 지속적/정적 정보
13+
// case weatherWidget // β†’ Widget μ‚¬μš©
14+
// case calendarReminder // β†’ Notification μ‚¬μš©
15+
// case stockPrice // β†’ Widget μ‚¬μš©
16+
}

0 commit comments

Comments
Β (0)