Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion DevLog/Widget/Heatmap/HeatmapWidgetSnapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ import Foundation

struct HeatmapWidgetSnapshot: Codable, Equatable {
let generatedAt: Date
let monthStart: Date
let quarterStart: Date
let selectedActivityKindRawValues: [String]
let maxCount: Int
let months: [WidgetHeatmapMonthSnapshot]
}

struct WidgetHeatmapMonthSnapshot: Codable, Equatable {
let monthStart: Date
let weeks: [WidgetHeatmapWeekSnapshot]
}

Expand Down
90 changes: 58 additions & 32 deletions DevLog/Widget/Heatmap/HeatmapWidgetSnapshotFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,40 @@ struct HeatmapWidgetSnapshotFactory {
completedTodos: [Todo],
deletedTodos: [Todo],
selectedActivityKinds: Set<ActivityKind>,
monthStart: Date,
quarterStart: Date,
now: Date = Date()
) -> HeatmapWidgetSnapshot {
let normalizedMonthStart = startOfMonth(for: monthStart)
let normalizedQuarterStart = startOfQuarter(for: quarterStart)
guard let nextQuarterStart = calendar.date(byAdding: .month, value: 3, to: normalizedQuarterStart) else {
return HeatmapWidgetSnapshot(
generatedAt: now,
quarterStart: normalizedQuarterStart,
selectedActivityKindRawValues: orderedActivityKinds(from: selectedActivityKinds).map(\.rawValue),
maxCount: 0,
months: []
)
}
let dailyCountsByDate = makeDailyCountsByDate(
createdTodos: createdTodos,
completedTodos: completedTodos,
deletedTodos: deletedTodos,
monthStart: normalizedMonthStart
quarterStart: normalizedQuarterStart,
nextQuarterStart: nextQuarterStart
)
let weeks = makeWeeks(
monthStart: normalizedMonthStart,
let months = makeMonths(
quarterStart: normalizedQuarterStart,
dailyCountsByDate: dailyCountsByDate
)

return HeatmapWidgetSnapshot(
generatedAt: now,
monthStart: normalizedMonthStart,
quarterStart: normalizedQuarterStart,
selectedActivityKindRawValues: orderedActivityKinds(from: selectedActivityKinds).map(\.rawValue),
maxCount: maxCount(
from: weeks,
from: months,
selectedActivityKinds: selectedActivityKinds
),
weeks: weeks
months: months
)
}
}
Expand All @@ -69,15 +79,17 @@ private extension HeatmapWidgetSnapshotFactory {
createdTodos: [Todo],
completedTodos: [Todo],
deletedTodos: [Todo],
monthStart: Date
quarterStart: Date,
nextQuarterStart: Date
) -> [Date: DailyCounts] {
var dailyCountsByDate = [Date: DailyCounts]()

for todo in createdTodos {
appendCount(
activityKind: .created,
occurredAt: todo.createdAt,
monthStart: monthStart,
quarterStart: quarterStart,
nextQuarterStart: nextQuarterStart,
dailyCountsByDate: &dailyCountsByDate
)
}
Expand All @@ -87,7 +99,8 @@ private extension HeatmapWidgetSnapshotFactory {
appendCount(
activityKind: .completed,
occurredAt: completedAt,
monthStart: monthStart,
quarterStart: quarterStart,
nextQuarterStart: nextQuarterStart,
dailyCountsByDate: &dailyCountsByDate
)
}
Expand All @@ -97,7 +110,8 @@ private extension HeatmapWidgetSnapshotFactory {
appendCount(
activityKind: .deleted,
occurredAt: deletedAt,
monthStart: monthStart,
quarterStart: quarterStart,
nextQuarterStart: nextQuarterStart,
dailyCountsByDate: &dailyCountsByDate
)
}
Expand All @@ -108,17 +122,37 @@ private extension HeatmapWidgetSnapshotFactory {
func appendCount(
activityKind: ActivityKind,
occurredAt: Date,
monthStart: Date,
quarterStart: Date,
nextQuarterStart: Date,
dailyCountsByDate: inout [Date: DailyCounts]
) {
guard isDateInMonth(occurredAt, monthStart: monthStart) else { return }
guard quarterStart <= occurredAt && occurredAt < nextQuarterStart else { return }

let dayStart = calendar.startOfDay(for: occurredAt)
var dailyCounts = dailyCountsByDate[dayStart] ?? DailyCounts()
dailyCounts.increment(activityKind)
dailyCountsByDate[dayStart] = dailyCounts
}

func makeMonths(
quarterStart: Date,
dailyCountsByDate: [Date: DailyCounts]
) -> [WidgetHeatmapMonthSnapshot] {
let monthStarts = (0..<3).compactMap {
calendar.date(byAdding: .month, value: $0, to: quarterStart)
}

return monthStarts.map { monthStart in
WidgetHeatmapMonthSnapshot(
monthStart: monthStart,
weeks: makeWeeks(
monthStart: monthStart,
dailyCountsByDate: dailyCountsByDate
)
)
}
}

func makeWeeks(
monthStart: Date,
dailyCountsByDate: [Date: DailyCounts]
Expand Down Expand Up @@ -173,29 +207,21 @@ private extension HeatmapWidgetSnapshotFactory {
return weeks
}

func startOfMonth(for date: Date) -> Date {
guard let monthInterval = calendar.dateInterval(of: .month, for: date) else {
return calendar.startOfDay(for: date)
}
return monthInterval.start
}

func isDateInMonth(
_ date: Date,
monthStart: Date
) -> Bool {
calendar.isDate(
calendar.startOfDay(for: date),
equalTo: monthStart,
toGranularity: .month
)
func startOfQuarter(for date: Date) -> Date {
let month = calendar.component(.month, from: date)
let startMonth = ((month - 1) / 3) * 3 + 1
var components = calendar.dateComponents([.year], from: date)
components.month = startMonth
components.day = 1
return calendar.date(from: components) ?? calendar.startOfDay(for: date)
}

func maxCount(
from weeks: [WidgetHeatmapWeekSnapshot],
from months: [WidgetHeatmapMonthSnapshot],
selectedActivityKinds: Set<ActivityKind>
) -> Int {
weeks
months
.flatMap(\.weeks)
.flatMap(\.days)
.filter(\.isVisible)
.map { day in
Expand Down
31 changes: 16 additions & 15 deletions DevLog/Widget/Heatmap/HeatmapWidgetSyncCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,16 @@ final class HeatmapWidgetSyncCoordinator {
selectedActivityKinds: Set<ActivityKind>,
now: Date = Date()
) async {
let monthStart = startOfMonth(for: now)
guard let nextMonthStart = calendar.date(byAdding: .month, value: 1, to: monthStart) else {
let quarterStart = startOfQuarter(for: now)
guard let nextQuarterStart = calendar.date(byAdding: .month, value: 3, to: quarterStart) else {
return
}

do {
async let createdTodoPage = fetchTodosUseCase.execute(
TodoQuery(
sortDateFrom: monthStart,
sortDateTo: nextMonthStart,
sortDateFrom: quarterStart,
sortDateTo: nextQuarterStart,
includesDeleted: true,
sortTarget: .createdAt,
pageSize: 100,
Expand All @@ -50,8 +50,8 @@ final class HeatmapWidgetSyncCoordinator {
)
async let completedTodoPage = fetchTodosUseCase.execute(
TodoQuery(
sortDateFrom: monthStart,
sortDateTo: nextMonthStart,
sortDateFrom: quarterStart,
sortDateTo: nextQuarterStart,
includesDeleted: true,
sortTarget: .completedAt,
pageSize: 100,
Expand All @@ -61,8 +61,8 @@ final class HeatmapWidgetSyncCoordinator {
)
async let deletedTodoPage = fetchTodosUseCase.execute(
TodoQuery(
sortDateFrom: monthStart,
sortDateTo: nextMonthStart,
sortDateFrom: quarterStart,
sortDateTo: nextQuarterStart,
includesDeleted: true,
sortTarget: .deletedAt,
pageSize: 100,
Expand All @@ -76,7 +76,7 @@ final class HeatmapWidgetSyncCoordinator {
completedTodos: try await completedTodoPage.items,
deletedTodos: try await deletedTodoPage.items,
selectedActivityKinds: selectedActivityKinds,
monthStart: monthStart,
quarterStart: quarterStart,
now: now
)

Expand All @@ -94,11 +94,12 @@ final class HeatmapWidgetSyncCoordinator {
}

private extension HeatmapWidgetSyncCoordinator {
func startOfMonth(for date: Date) -> Date {
guard let monthInterval = calendar.dateInterval(of: .month, for: date) else {
return calendar.startOfDay(for: date)
}

return monthInterval.start
func startOfQuarter(for date: Date) -> Date {
let month = calendar.component(.month, from: date)
let startMonth = ((month - 1) / 3) * 3 + 1
var components = calendar.dateComponents([.year], from: date)
components.month = startMonth
components.day = 1
return calendar.date(from: components) ?? calendar.startOfDay(for: date)
}
Comment thread
opficdev marked this conversation as resolved.
}
7 changes: 1 addition & 6 deletions DevLogWidget/Common/WidgetPlaceholderCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import SwiftUI

struct WidgetPlaceholderCard: View {
let title: String
let message: String

var body: some View {
Expand All @@ -18,11 +17,7 @@ struct WidgetPlaceholderCard: View {
Text(message)
.font(.caption)
.foregroundStyle(.secondary)
}
.overlay(alignment: .topLeading) {
Text(title)
.font(.headline)
.padding(12)
.multilineTextAlignment(.center)
}
}
}
4 changes: 2 additions & 2 deletions DevLogWidget/Heatmap/HeatmapWidget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ struct HeatmapWidget: Widget {
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("Heatmap")
.description("이번 달 활동 히트맵을 표시합니다.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
.description("활동 히트맵을 표시합니다.")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ import WidgetKit

struct HeatmapWidgetConfigurationIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Heatmap"
static var description = IntentDescription("이번 달 활동 히트맵을 표시합니다.")
static var description = IntentDescription("활동 히트맵을 표시합니다.")
}
Loading