Skip to content

Commit 000e4e0

Browse files
committed
ui: 실제 히트맵과 동일한 형태의 플레이드홀더 형태로 수정
1 parent 41ff16a commit 000e4e0

4 files changed

Lines changed: 150 additions & 12 deletions

File tree

DevLogWidget/Heatmap/HeatmapWidgetEntryView.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,21 +53,22 @@ struct HeatmapWidgetEntryView: View {
5353

5454
@ViewBuilder
5555
private var emptyState: some View {
56+
let shape = WidgetHeatmapPlaceholderShape(date: entry.date)
57+
5658
switch widgetFamily {
5759
case .systemSmall:
5860
VStack(alignment: .leading, spacing: 8) {
59-
Text("이번 달 히트맵")
60-
.font(.headline)
61+
header(title: "이번 달 히트맵")
6162
WidgetHeatmapPlaceholderGrid(
62-
weekCounts: [5],
63+
months: shape.currentMonths,
6364
showsMonthTitles: false
6465
)
6566
}
6667
case .systemMedium:
6768
VStack(alignment: .leading, spacing: 8) {
6869
header(title: "이번 분기 히트맵")
6970
WidgetHeatmapPlaceholderGrid(
70-
weekCounts: [5, 5, 5],
71+
months: shape.quarterMonths,
7172
showsMonthTitles: true
7273
)
7374
}

DevLogWidget/Heatmap/WidgetHeatmapGrid.swift

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@ struct WidgetHeatmapGrid: View {
4343
}
4444

4545
struct WidgetHeatmapPlaceholderGrid: View {
46-
let weekCounts: [Int]
46+
let months: [WidgetHeatmapPlaceholderMonthShape]
4747
let showsMonthTitles: Bool
4848

4949
var body: some View {
50+
let weekCounts = months.map(\.weeks.count)
51+
5052
GeometryReader { proxy in
5153
let layout = WidgetHeatmapLayout(
5254
availableWidth: proxy.size.width,
@@ -56,9 +58,9 @@ struct WidgetHeatmapPlaceholderGrid: View {
5658
)
5759

5860
HStack(alignment: .top, spacing: layout.monthSpacing) {
59-
ForEach(Array(weekCounts.enumerated()), id: \.offset) { _, weekCount in
61+
ForEach(months) { month in
6062
WidgetHeatmapPlaceholderMonthGrid(
61-
weekCount: weekCount,
63+
month: month,
6264
layout: layout,
6365
showsMonthTitle: showsMonthTitles
6466
)
@@ -187,7 +189,7 @@ private enum WidgetHeatmapActivityKind: String {
187189
}
188190

189191
private struct WidgetHeatmapPlaceholderMonthGrid: View {
190-
let weekCount: Int
192+
let month: WidgetHeatmapPlaceholderMonthShape
191193
let layout: WidgetHeatmapLayout
192194
let showsMonthTitle: Bool
193195
private let orderedWeekdays = Array(1...7)
@@ -204,9 +206,13 @@ private struct WidgetHeatmapPlaceholderMonthGrid: View {
204206
VStack(alignment: .leading, spacing: layout.cellSpacing) {
205207
ForEach(orderedWeekdays, id: \.self) { weekday in
206208
HStack(spacing: layout.cellSpacing) {
207-
ForEach(0..<weekCount, id: \.self) { weekIndex in
209+
ForEach(month.weeks) { week in
210+
let day = week.days.first {
211+
Calendar.current.component(.weekday, from: $0.date) == weekday
212+
}
213+
208214
RoundedRectangle(cornerRadius: layout.cellCornerRadius)
209-
.fill(Color.secondary.opacity(opacity(weekday: weekday, weekIndex: weekIndex)))
215+
.fill(fillColor(for: day))
210216
.frame(width: layout.cellSize, height: layout.cellSize)
211217
}
212218
}
@@ -215,8 +221,13 @@ private struct WidgetHeatmapPlaceholderMonthGrid: View {
215221
}
216222
}
217223

218-
private func opacity(weekday: Int, weekIndex: Int) -> Double {
219-
switch (weekday + weekIndex) % 4 {
224+
private func fillColor(for day: WidgetHeatmapPlaceholderDayShape?) -> Color {
225+
guard let day, day.isVisible else { return .clear }
226+
return Color.secondary.opacity(opacity(for: day))
227+
}
228+
229+
private func opacity(for day: WidgetHeatmapPlaceholderDayShape) -> Double {
230+
switch Calendar.current.component(.day, from: day.date) % 4 {
220231
case 0:
221232
return 1 / 8
222233
case 1:
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//
2+
// WidgetHeatmapPlaceholderShape.swift
3+
// DevLog
4+
//
5+
// Created by opfic on 4/30/26.
6+
//
7+
8+
import Foundation
9+
10+
struct WidgetHeatmapPlaceholderShape {
11+
let currentMonths: [WidgetHeatmapPlaceholderMonthShape]
12+
let quarterMonths: [WidgetHeatmapPlaceholderMonthShape]
13+
14+
var currentMonthWeekCounts: [Int] {
15+
currentMonths.map(\.weeks.count)
16+
}
17+
18+
var quarterWeekCounts: [Int] {
19+
quarterMonths.map(\.weeks.count)
20+
}
21+
22+
init(
23+
date: Date = Date(),
24+
calendar: Calendar = .current
25+
) {
26+
let quarterStart = calendar.startOfQuarter(for: date)
27+
let monthStarts = (0..<3).compactMap {
28+
calendar.date(byAdding: .month, value: $0, to: quarterStart)
29+
}
30+
let widgetHeatmapPlaceholderMonthShapes = monthStarts.map {
31+
Self.makeMonth(monthStart: $0, calendar: calendar)
32+
}
33+
34+
if let currentMonth = widgetHeatmapPlaceholderMonthShapes.first(where: {
35+
calendar.isDate($0.monthStart, equalTo: date, toGranularity: .month)
36+
}) {
37+
currentMonths = [currentMonth]
38+
} else {
39+
currentMonths = Array(widgetHeatmapPlaceholderMonthShapes.prefix(1))
40+
}
41+
42+
quarterMonths = widgetHeatmapPlaceholderMonthShapes
43+
}
44+
45+
private static func makeMonth(
46+
monthStart: Date,
47+
calendar: Calendar
48+
) -> WidgetHeatmapPlaceholderMonthShape {
49+
guard let monthInterval = calendar.dateInterval(of: .month, for: monthStart),
50+
let monthLastDay = calendar.date(byAdding: .day, value: -1, to: monthInterval.end),
51+
let firstWeekInterval = calendar.dateInterval(of: .weekOfYear, for: monthInterval.start),
52+
let lastWeekInterval = calendar.dateInterval(of: .weekOfYear, for: monthLastDay) else {
53+
return WidgetHeatmapPlaceholderMonthShape(monthStart: monthStart, weeks: [])
54+
}
55+
56+
var weeks = [WidgetHeatmapPlaceholderWeekShape]()
57+
var cursor = firstWeekInterval.start
58+
59+
while cursor < lastWeekInterval.end {
60+
weeks.append(
61+
WidgetHeatmapPlaceholderWeekShape(
62+
id: weeks.count,
63+
days: makeDays(
64+
weekStart: cursor,
65+
monthStart: monthStart,
66+
calendar: calendar
67+
)
68+
)
69+
)
70+
71+
guard let nextWeek = calendar.date(byAdding: .weekOfYear, value: 1, to: cursor) else {
72+
break
73+
}
74+
cursor = nextWeek
75+
}
76+
77+
return WidgetHeatmapPlaceholderMonthShape(monthStart: monthStart, weeks: weeks)
78+
}
79+
80+
private static func makeDays(
81+
weekStart: Date,
82+
monthStart: Date,
83+
calendar: Calendar
84+
) -> [WidgetHeatmapPlaceholderDayShape] {
85+
var days = [WidgetHeatmapPlaceholderDayShape]()
86+
var cursor = weekStart
87+
88+
for _ in 0..<7 {
89+
let normalizedDate = calendar.startOfDay(for: cursor)
90+
days.append(
91+
WidgetHeatmapPlaceholderDayShape(
92+
date: normalizedDate,
93+
isVisible: calendar.isDate(
94+
normalizedDate,
95+
equalTo: monthStart,
96+
toGranularity: .month
97+
)
98+
)
99+
)
100+
101+
guard let nextDay = calendar.date(byAdding: .day, value: 1, to: cursor) else {
102+
break
103+
}
104+
cursor = nextDay
105+
}
106+
107+
return days
108+
}
109+
}
110+
111+
struct WidgetHeatmapPlaceholderMonthShape: Identifiable, Hashable {
112+
var id: Date { monthStart }
113+
let monthStart: Date
114+
let weeks: [WidgetHeatmapPlaceholderWeekShape]
115+
}
116+
117+
struct WidgetHeatmapPlaceholderWeekShape: Identifiable, Hashable {
118+
let id: Int
119+
let days: [WidgetHeatmapPlaceholderDayShape]
120+
}
121+
122+
struct WidgetHeatmapPlaceholderDayShape: Identifiable, Hashable {
123+
var id: Date { date }
124+
let date: Date
125+
let isVisible: Bool
126+
}

0 commit comments

Comments
 (0)