From d8b7a4722227d7e07b92af9ba99b514d016e5e3c Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 13:01:18 +0900 Subject: [PATCH 01/15] =?UTF-8?q?chore:=20l10n=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLogWidget/Heatmap/HeatmapWidget.swift | 2 +- .../HeatmapWidgetConfigurationIntent.swift | 2 +- .../Heatmap/HeatmapWidgetEntryView.swift | 10 +- DevLogWidget/Heatmap/WidgetHeatmapGrid.swift | 12 +- DevLogWidget/Resource/Localizable.xcstrings | 154 ++++++++++++++++++ DevLogWidget/Today/TodayTodoWidget.swift | 2 +- .../TodayTodoWidgetConfigurationIntent.swift | 2 +- .../Today/TodayTodoWidgetEntryView.swift | 4 +- 8 files changed, 171 insertions(+), 17 deletions(-) create mode 100644 DevLogWidget/Resource/Localizable.xcstrings diff --git a/DevLogWidget/Heatmap/HeatmapWidget.swift b/DevLogWidget/Heatmap/HeatmapWidget.swift index 0561e4d4..9f30cce4 100644 --- a/DevLogWidget/Heatmap/HeatmapWidget.swift +++ b/DevLogWidget/Heatmap/HeatmapWidget.swift @@ -23,7 +23,7 @@ struct HeatmapWidget: Widget { .widgetURL(WidgetDeepLink.heatmapURL) } .configurationDisplayName("Heatmap") - .description("활동 히트맵을 표시합니다.") + .description("widget_heatmap_description") .supportedFamilies([.systemSmall, .systemMedium]) } } diff --git a/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift b/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift index 159460e3..f14c7841 100644 --- a/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift +++ b/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift @@ -10,5 +10,5 @@ import WidgetKit struct HeatmapWidgetConfigurationIntent: WidgetConfigurationIntent { static var title: LocalizedStringResource = "Heatmap" - static var description = IntentDescription("활동 히트맵을 표시합니다.") + static var description = IntentDescription("widget_heatmap_description") } diff --git a/DevLogWidget/Heatmap/HeatmapWidgetEntryView.swift b/DevLogWidget/Heatmap/HeatmapWidgetEntryView.swift index 10c550c5..4a437923 100644 --- a/DevLogWidget/Heatmap/HeatmapWidgetEntryView.swift +++ b/DevLogWidget/Heatmap/HeatmapWidgetEntryView.swift @@ -28,7 +28,7 @@ struct HeatmapWidgetEntryView: View { switch widgetFamily { case .systemSmall: VStack(alignment: .leading, spacing: 4) { - header(title: "이번 달 히트맵") + header(title: "widget_heatmap_current_month_title") WidgetHeatmapGrid( months: currentMonths(from: snapshot), selectedActivityKindRawValues: snapshot.selectedActivityKindRawValues, @@ -38,7 +38,7 @@ struct HeatmapWidgetEntryView: View { } case .systemMedium: VStack(alignment: .leading, spacing: 8) { - header(title: "이번 분기 히트맵") + header(title: "widget_heatmap_current_quarter_title") WidgetHeatmapGrid( months: snapshot.months, selectedActivityKindRawValues: snapshot.selectedActivityKindRawValues, @@ -58,7 +58,7 @@ struct HeatmapWidgetEntryView: View { switch widgetFamily { case .systemSmall: VStack(alignment: .leading, spacing: 8) { - header(title: "이번 달 히트맵") + header(title: "widget_heatmap_current_month_title") WidgetHeatmapPlaceholderGrid( months: shape.currentMonths, showsMonthTitles: false @@ -66,7 +66,7 @@ struct HeatmapWidgetEntryView: View { } case .systemMedium: VStack(alignment: .leading, spacing: 8) { - header(title: "이번 분기 히트맵") + header(title: "widget_heatmap_current_quarter_title") WidgetHeatmapPlaceholderGrid( months: shape.quarterMonths, showsMonthTitles: true @@ -77,7 +77,7 @@ struct HeatmapWidgetEntryView: View { } } - private func header(title: String) -> some View { + private func header(title: LocalizedStringKey) -> some View { HStack(alignment: .firstTextBaseline, spacing: 6) { Text(title) .font(.headline) diff --git a/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift b/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift index b21a49c4..0ab3937c 100644 --- a/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift +++ b/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift @@ -74,10 +74,10 @@ struct WidgetHeatmapPlaceholderGrid: View { private struct WidgetHeatmapWeekdayLabels: View { let layout: WidgetHeatmapLayout private let orderedWeekdays = Array(1...7) - private let weekdayLabels = [ - 2: "월", - 4: "수", - 6: "금" + private let weekdayLocalizedStringKeys: [Int: LocalizedStringKey] = [ + 2: "widget_heatmap_weekday_monday", + 4: "widget_heatmap_weekday_wednesday", + 6: "widget_heatmap_weekday_friday" ] var body: some View { @@ -91,8 +91,8 @@ private struct WidgetHeatmapWeekdayLabels: View { @ViewBuilder private func weekdayLabel(for weekday: Int) -> some View { - if let label = weekdayLabels[weekday] { - Text(label) + if let localizedStringKey = weekdayLocalizedStringKeys[weekday] { + Text(localizedStringKey) .font(.caption2) .foregroundStyle(.secondary) .frame( diff --git a/DevLogWidget/Resource/Localizable.xcstrings b/DevLogWidget/Resource/Localizable.xcstrings new file mode 100644 index 00000000..39acec2c --- /dev/null +++ b/DevLogWidget/Resource/Localizable.xcstrings @@ -0,0 +1,154 @@ +{ + "sourceLanguage" : "ko", + "strings" : { + "#%lld" : { + + }, + "%lld" : { + + }, + "Heatmap" : { + + }, + "Today" : { + + }, + "widget_heatmap_current_month_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This Month Heatmap" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이번 달 히트맵" + } + } + } + }, + "widget_heatmap_current_quarter_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This Quarter Heatmap" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이번 분기 히트맵" + } + } + } + }, + "widget_heatmap_weekday_friday" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fri" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "금" + } + } + } + }, + "widget_heatmap_weekday_monday" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mon" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "월" + } + } + } + }, + "widget_heatmap_weekday_wednesday" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wed" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "수" + } + } + } + }, + "widget_heatmap_description" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Shows your activity heatmap." + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "활동 히트맵을 표시합니다." + } + } + } + }, + "widget_today_description" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Shows today's Todo list." + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "오늘 기준 Todo 목록을 표시합니다." + } + } + } + }, + "widget_today_empty_message" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No tasks for today.\nTake a short break!" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "오늘은 할 일이 없어요.\n잠시 휴식을 취해보세요!" + } + } + } + } + }, + "version" : "1.0" +} diff --git a/DevLogWidget/Today/TodayTodoWidget.swift b/DevLogWidget/Today/TodayTodoWidget.swift index 9e0a7c67..e002c9b5 100644 --- a/DevLogWidget/Today/TodayTodoWidget.swift +++ b/DevLogWidget/Today/TodayTodoWidget.swift @@ -22,7 +22,7 @@ struct TodayTodoWidget: Widget { .containerBackground(.fill.tertiary, for: .widget) .widgetURL(WidgetDeepLink.todayTodoURL) } - .description("오늘 기준 Todo 목록을 표시합니다.") + .description("widget_today_description") .configurationDisplayName("Today") .supportedFamilies([.systemSmall, .systemMedium]) } diff --git a/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift b/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift index 649d085e..111cf4a4 100644 --- a/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift +++ b/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift @@ -10,5 +10,5 @@ import WidgetKit struct TodayTodoWidgetConfigurationIntent: WidgetConfigurationIntent { static var title: LocalizedStringResource = "Today" - static var description = IntentDescription("오늘 기준 Todo 목록을 표시합니다.") + static var description = IntentDescription("widget_today_description") } diff --git a/DevLogWidget/Today/TodayTodoWidgetEntryView.swift b/DevLogWidget/Today/TodayTodoWidgetEntryView.swift index 9e4a949b..4b9f7177 100644 --- a/DevLogWidget/Today/TodayTodoWidgetEntryView.swift +++ b/DevLogWidget/Today/TodayTodoWidgetEntryView.swift @@ -41,7 +41,7 @@ struct TodayTodoWidgetEntryView: View { if let item = displayedItems(from: snapshot).first { todoRow(item) } else { - Text("오늘은 할 일이 없어요.\n잠시 휴식을 취해보세요!") + Text("widget_today_empty_message") .font(.caption) .foregroundStyle(.secondary) .lineLimit(2) @@ -51,7 +51,7 @@ struct TodayTodoWidgetEntryView: View { let items = displayedItems(from: snapshot) VStack(alignment: .leading, spacing: 6) { if items.isEmpty { - Text("오늘은 할 일이 없어요.\n잠시 휴식을 취해보세요!") + Text("widget_today_empty_message") .multilineTextAlignment(.center) .font(.caption) .foregroundStyle(.secondary) From 04a65cb155b0894af8320cd99ce44f64cdb5914c Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 13:29:11 +0900 Subject: [PATCH 02/15] =?UTF-8?q?chore:=20Xcode=20=EC=A7=80=EC=97=AD?= =?UTF-8?q?=ED=99=94=20=EC=84=A4=EC=A0=95=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog.xcodeproj/project.pbxproj | 5 ++++- .../xcshareddata/xcschemes/DevLog.xcscheme | 2 +- .../xcschemes/DevLogWidgetExtension.xcscheme | 16 +++++++++++++--- .../xcshareddata/xcschemes/DevLog_Unit.xcscheme | 2 +- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/DevLog.xcodeproj/project.pbxproj b/DevLog.xcodeproj/project.pbxproj index dcb2743b..74e9bf8b 100644 --- a/DevLog.xcodeproj/project.pbxproj +++ b/DevLog.xcodeproj/project.pbxproj @@ -275,7 +275,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 2640; - LastUpgradeCheck = 2600; + LastUpgradeCheck = 2640; TargetAttributes = { DF3416442E45F67C00F9312B = { CreatedOnToolsVersion = 16.3; @@ -295,6 +295,7 @@ knownRegions = ( ko, en, + Base, ); mainGroup = DFD48AF72DC4D6E2005905C5; minimizedProjectReferenceProxies = 1; @@ -603,6 +604,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -671,6 +673,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; diff --git a/DevLog.xcodeproj/xcshareddata/xcschemes/DevLog.xcscheme b/DevLog.xcodeproj/xcshareddata/xcschemes/DevLog.xcscheme index 5b1f7f8b..2a843515 100644 --- a/DevLog.xcodeproj/xcshareddata/xcschemes/DevLog.xcscheme +++ b/DevLog.xcodeproj/xcshareddata/xcschemes/DevLog.xcscheme @@ -1,6 +1,6 @@ - + + + + + - + Date: Fri, 1 May 2026 15:35:26 +0900 Subject: [PATCH 03/15] =?UTF-8?q?ui:=20'Heatmap'=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLogWidget/Resource/Localizable.xcstrings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DevLogWidget/Resource/Localizable.xcstrings b/DevLogWidget/Resource/Localizable.xcstrings index 39acec2c..556c302e 100644 --- a/DevLogWidget/Resource/Localizable.xcstrings +++ b/DevLogWidget/Resource/Localizable.xcstrings @@ -19,7 +19,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "This Month Heatmap" + "value" : "This Month" } }, "ko" : { @@ -36,7 +36,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "This Quarter Heatmap" + "value" : "This Quarter" } }, "ko" : { From c17c9975efe701d3ba4533721fd2933b1736d505 Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 15:40:38 +0900 Subject: [PATCH 04/15] =?UTF-8?q?ui:=20=EC=9A=94=EC=9D=BC=20=EB=9D=BC?= =?UTF-8?q?=EB=B2=A8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLogWidget/Heatmap/WidgetHeatmapGrid.swift | 61 +++---------------- .../Heatmap/WidgetHeatmapLayout.swift | 14 +---- DevLogWidget/Resource/Localizable.xcstrings | 51 ---------------- 3 files changed, 11 insertions(+), 115 deletions(-) diff --git a/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift b/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift index 0ab3937c..a7519d88 100644 --- a/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift +++ b/DevLogWidget/Heatmap/WidgetHeatmapGrid.swift @@ -22,19 +22,15 @@ struct WidgetHeatmapGrid: View { showsMonthTitles: showsMonthTitles ) - HStack(alignment: .top, spacing: layout.weekdayLabelSpacing) { - WidgetHeatmapWeekdayLabels(layout: layout) - - HStack(alignment: .top, spacing: layout.monthSpacing) { - ForEach(months, id: \.monthStart) { month in - WidgetHeatmapMonthGrid( - month: month, - layout: layout, - selectedActivityKindRawValues: selectedActivityKindRawValues, - maxCount: maxCount, - showsMonthTitle: showsMonthTitles - ) - } + HStack(alignment: .top, spacing: layout.monthSpacing) { + ForEach(months, id: \.monthStart) { month in + WidgetHeatmapMonthGrid( + month: month, + layout: layout, + selectedActivityKindRawValues: selectedActivityKindRawValues, + maxCount: maxCount, + showsMonthTitle: showsMonthTitles + ) } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) @@ -71,45 +67,6 @@ struct WidgetHeatmapPlaceholderGrid: View { } } -private struct WidgetHeatmapWeekdayLabels: View { - let layout: WidgetHeatmapLayout - private let orderedWeekdays = Array(1...7) - private let weekdayLocalizedStringKeys: [Int: LocalizedStringKey] = [ - 2: "widget_heatmap_weekday_monday", - 4: "widget_heatmap_weekday_wednesday", - 6: "widget_heatmap_weekday_friday" - ] - - var body: some View { - VStack(alignment: .leading, spacing: layout.cellSpacing) { - ForEach(orderedWeekdays, id: \.self) { weekday in - weekdayLabel(for: weekday) - } - } - .padding(.top, layout.weekdayTopPadding) - } - - @ViewBuilder - private func weekdayLabel(for weekday: Int) -> some View { - if let localizedStringKey = weekdayLocalizedStringKeys[weekday] { - Text(localizedStringKey) - .font(.caption2) - .foregroundStyle(.secondary) - .frame( - width: layout.weekdayLabelWidth, - height: layout.cellSize, - alignment: .leading - ) - } else { - Color.clear - .frame( - width: layout.weekdayLabelWidth, - height: layout.cellSize - ) - } - } -} - private struct WidgetHeatmapMonthGrid: View { let month: WidgetHeatmapMonthSnapshot let layout: WidgetHeatmapLayout diff --git a/DevLogWidget/Heatmap/WidgetHeatmapLayout.swift b/DevLogWidget/Heatmap/WidgetHeatmapLayout.swift index f5eff1e8..10b76a18 100644 --- a/DevLogWidget/Heatmap/WidgetHeatmapLayout.swift +++ b/DevLogWidget/Heatmap/WidgetHeatmapLayout.swift @@ -12,8 +12,6 @@ struct WidgetHeatmapLayout { let cellSpacing: CGFloat let monthSpacing: CGFloat let monthTitleSpacing: CGFloat - let weekdayLabelSpacing: CGFloat = Self.baseWeekdayLabelSpacing - let weekdayLabelWidth: CGFloat = Self.baseWeekdayLabelWidth let showsMonthTitles: Bool init( @@ -37,10 +35,6 @@ struct WidgetHeatmapLayout { ) } - var weekdayTopPadding: CGFloat { - showsMonthTitles ? cellSize + monthTitleSpacing : 0 - } - var cellCornerRadius: CGFloat { max(2, cellSize * 0.2) } @@ -49,8 +43,6 @@ struct WidgetHeatmapLayout { private static let baseMonthSpacing: CGFloat = 10 private static let maxMonthSpacing: CGFloat = 26 private static let baseMonthTitleSpacing: CGFloat = 4 - private static let baseWeekdayLabelSpacing: CGFloat = 5 - private static let baseWeekdayLabelWidth: CGFloat = 14 private static func resolvedMonthTitleSpacing(showsMonthTitles: Bool) -> CGFloat { // 월 제목을 표시하는 Medium에서만 제목과 셀 사이 간격을 확보한다. @@ -99,10 +91,8 @@ struct WidgetHeatmapLayout { ) -> CGFloat { // 셀 크기는 높이 기준으로 고정하고, 남는 가로폭만 월 간격 계산에 사용한다. let sanitizedWeekCounts = sanitizedWeekCounts(weekCounts) - // 요일 라벨 영역, 전체 셀 컬럼, 월 내부 주차 spacing을 더해 기본 너비를 구한다. - let contentWidth = baseWeekdayLabelWidth - + baseWeekdayLabelSpacing - + cellSize * CGFloat(totalColumns(in: sanitizedWeekCounts)) + // 전체 셀 컬럼과 월 내부 주차 spacing을 더해 기본 너비를 구한다. + let contentWidth = cellSize * CGFloat(totalColumns(in: sanitizedWeekCounts)) + baseCellSpacing * CGFloat(totalColumnSpacings(in: sanitizedWeekCounts)) // 기본 너비보다 위젯이 넓을 때만 월 간격에 분배할 여유 폭이 생긴다. return max(0, availableWidth - contentWidth) diff --git a/DevLogWidget/Resource/Localizable.xcstrings b/DevLogWidget/Resource/Localizable.xcstrings index 556c302e..3a61a290 100644 --- a/DevLogWidget/Resource/Localizable.xcstrings +++ b/DevLogWidget/Resource/Localizable.xcstrings @@ -47,57 +47,6 @@ } } }, - "widget_heatmap_weekday_friday" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fri" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "금" - } - } - } - }, - "widget_heatmap_weekday_monday" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mon" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "월" - } - } - } - }, - "widget_heatmap_weekday_wednesday" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wed" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "수" - } - } - } - }, "widget_heatmap_description" : { "extractionState" : "manual", "localizations" : { From db902492e50150637217ed544f832c29e5a89ca1 Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 15:54:06 +0900 Subject: [PATCH 05/15] =?UTF-8?q?refactor:=20key=20=ED=98=95=ED=83=9C?= =?UTF-8?q?=EB=A1=9C=20=ED=95=98=EB=93=9C=EC=BD=94=EB=94=A9=20=EC=9A=94?= =?UTF-8?q?=EC=86=8C=20=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLogWidget/Heatmap/HeatmapWidget.swift | 2 +- .../HeatmapWidgetConfigurationIntent.swift | 2 +- DevLogWidget/Resource/Localizable.xcstrings | 40 ++++++++++++++++--- DevLogWidget/Today/TodayTodoWidget.swift | 2 +- .../TodayTodoWidgetConfigurationIntent.swift | 2 +- .../Today/TodayTodoWidgetEntryView.swift | 2 +- 6 files changed, 39 insertions(+), 11 deletions(-) diff --git a/DevLogWidget/Heatmap/HeatmapWidget.swift b/DevLogWidget/Heatmap/HeatmapWidget.swift index 9f30cce4..4d70e4f2 100644 --- a/DevLogWidget/Heatmap/HeatmapWidget.swift +++ b/DevLogWidget/Heatmap/HeatmapWidget.swift @@ -22,7 +22,7 @@ struct HeatmapWidget: Widget { .containerBackground(.fill.tertiary, for: .widget) .widgetURL(WidgetDeepLink.heatmapURL) } - .configurationDisplayName("Heatmap") + .configurationDisplayName(LocalizedStringResource("widget_heatmap_title")) .description("widget_heatmap_description") .supportedFamilies([.systemSmall, .systemMedium]) } diff --git a/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift b/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift index f14c7841..134d20d9 100644 --- a/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift +++ b/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift @@ -9,6 +9,6 @@ import AppIntents import WidgetKit struct HeatmapWidgetConfigurationIntent: WidgetConfigurationIntent { - static var title: LocalizedStringResource = "Heatmap" + static var title: LocalizedStringResource = "widget_heatmap_title" static var description = IntentDescription("widget_heatmap_description") } diff --git a/DevLogWidget/Resource/Localizable.xcstrings b/DevLogWidget/Resource/Localizable.xcstrings index 3a61a290..7e5ea55a 100644 --- a/DevLogWidget/Resource/Localizable.xcstrings +++ b/DevLogWidget/Resource/Localizable.xcstrings @@ -6,12 +6,6 @@ }, "%lld" : { - }, - "Heatmap" : { - - }, - "Today" : { - }, "widget_heatmap_current_month_title" : { "extractionState" : "manual", @@ -64,6 +58,23 @@ } } }, + "widget_heatmap_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heatmap" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heatmap" + } + } + } + }, "widget_today_description" : { "extractionState" : "manual", "localizations" : { @@ -97,6 +108,23 @@ } } } + }, + "widget_today_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Today" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Today" + } + } + } } }, "version" : "1.0" diff --git a/DevLogWidget/Today/TodayTodoWidget.swift b/DevLogWidget/Today/TodayTodoWidget.swift index e002c9b5..57e15e61 100644 --- a/DevLogWidget/Today/TodayTodoWidget.swift +++ b/DevLogWidget/Today/TodayTodoWidget.swift @@ -23,7 +23,7 @@ struct TodayTodoWidget: Widget { .widgetURL(WidgetDeepLink.todayTodoURL) } .description("widget_today_description") - .configurationDisplayName("Today") + .configurationDisplayName(LocalizedStringResource("widget_today_title")) .supportedFamilies([.systemSmall, .systemMedium]) } } diff --git a/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift b/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift index 111cf4a4..8078ee87 100644 --- a/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift +++ b/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift @@ -9,6 +9,6 @@ import AppIntents import WidgetKit struct TodayTodoWidgetConfigurationIntent: WidgetConfigurationIntent { - static var title: LocalizedStringResource = "Today" + static var title: LocalizedStringResource = "widget_today_title" static var description = IntentDescription("widget_today_description") } diff --git a/DevLogWidget/Today/TodayTodoWidgetEntryView.swift b/DevLogWidget/Today/TodayTodoWidgetEntryView.swift index 4b9f7177..6e748abe 100644 --- a/DevLogWidget/Today/TodayTodoWidgetEntryView.swift +++ b/DevLogWidget/Today/TodayTodoWidgetEntryView.swift @@ -14,7 +14,7 @@ struct TodayTodoWidgetEntryView: View { var body: some View { VStack(alignment: .leading) { - Text("Today") + Text("widget_today_title") .font(.headline) Spacer() From a4d81755f2369018c41257ebe11e835c3d72e9c0 Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 16:25:23 +0900 Subject: [PATCH 06/15] =?UTF-8?q?feat:=20=EC=82=AD=EC=A0=9C=20=ED=82=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WidgetShared/WidgetSnapshotKey.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WidgetShared/WidgetSnapshotKey.swift b/WidgetShared/WidgetSnapshotKey.swift index 3cf46991..3f972d8b 100644 --- a/WidgetShared/WidgetSnapshotKey.swift +++ b/WidgetShared/WidgetSnapshotKey.swift @@ -10,4 +10,5 @@ import Foundation enum WidgetSnapshotKey { static let today = "Widget.today.snapshot" static let heatmap = "Widget.heatmap.snapshot" + static let snapshots = [today, heatmap] } From f8f6846be8747f6a692e34631f8b70eabf6beb72 Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 16:50:37 +0900 Subject: [PATCH 07/15] =?UTF-8?q?fix:=20=EC=9C=84=EC=A0=AF=20=EC=8A=A4?= =?UTF-8?q?=EB=83=85=EC=83=B7=20=EC=82=AD=EC=A0=9C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Persistence/WidgetSnapshotUpdater.swift | 6 ++++ .../Common/WidgetSharedDefaultsStore.swift | 4 +++ .../Widget/Common/WidgetSnapshotStore.swift | 6 ++++ .../Widget/WidgetSnapshotUpdaterTests.swift | 33 +++++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift b/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift index 38c54eb7..6ab8c99f 100644 --- a/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift +++ b/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift @@ -104,4 +104,10 @@ final class WidgetSnapshotUpdater { ) } } + + func clear() { + snapshotStore.clearSnapshots() + WidgetCenter.shared.reloadTimelines(ofKind: WidgetKind.todayTodo) + WidgetCenter.shared.reloadTimelines(ofKind: WidgetKind.heatmap) + } } diff --git a/DevLog/Widget/Common/WidgetSharedDefaultsStore.swift b/DevLog/Widget/Common/WidgetSharedDefaultsStore.swift index eedc63ac..b30ad1b0 100644 --- a/DevLog/Widget/Common/WidgetSharedDefaultsStore.swift +++ b/DevLog/Widget/Common/WidgetSharedDefaultsStore.swift @@ -21,4 +21,8 @@ final class WidgetSharedDefaultsStore { func setData(_ value: Data?, forKey key: String) { userDefaults.set(value, forKey: key) } + + func removeObject(forKey key: String) { + userDefaults.removeObject(forKey: key) + } } diff --git a/DevLog/Widget/Common/WidgetSnapshotStore.swift b/DevLog/Widget/Common/WidgetSnapshotStore.swift index 71abd4ff..5a887efd 100644 --- a/DevLog/Widget/Common/WidgetSnapshotStore.swift +++ b/DevLog/Widget/Common/WidgetSnapshotStore.swift @@ -35,4 +35,10 @@ final class WidgetSnapshotStore { guard let data = store.data(forKey: WidgetSnapshotKey.heatmap) else { return nil } return try decoder.decode(HeatmapWidgetSnapshot.self, from: data) } + + func clearSnapshots() { + WidgetSnapshotKey.snapshots.forEach { + store.removeObject(forKey: $0) + } + } } diff --git a/DevLog_Unit/Widget/WidgetSnapshotUpdaterTests.swift b/DevLog_Unit/Widget/WidgetSnapshotUpdaterTests.swift index 404b72ae..7bc4e087 100644 --- a/DevLog_Unit/Widget/WidgetSnapshotUpdaterTests.swift +++ b/DevLog_Unit/Widget/WidgetSnapshotUpdaterTests.swift @@ -66,6 +66,39 @@ struct WidgetSnapshotUpdaterTests { #expect(snapshot.maxCount == 1) } + @Test("WidgetSnapshotUpdater는 모든 위젯 스냅샷을 삭제한다") + func widgetSnapshotUpdater는_모든_위젯_스냅샷을_삭제한다() throws { + let calendar = Calendar(identifier: .gregorian) + let quarterStart = try #require(calendar.date(from: DateComponents(year: 2026, month: 4, day: 1))) + let now = try #require(calendar.date(from: DateComponents(year: 2026, month: 4, day: 30))) + let fixture = makeFixture(calendar: calendar) + let todo = try makeTodayTodoItem(now: now) + + fixture.updater.updateTodaySnapshot( + todos: [todo], + displayOptions: .default, + now: now + ) + fixture.updater.updateHeatmapSnapshot( + createdTodos: [ + makeTodo( + id: "created", + createdAt: now + ) + ], + completedTodos: [], + deletedTodos: [], + selectedActivityKinds: [.created], + quarterStart: quarterStart, + now: now + ) + + fixture.updater.clear() + + #expect(try fixture.snapshotStore.loadTodaySnapshot() == nil) + #expect(try fixture.snapshotStore.loadHeatmapSnapshot() == nil) + } + private func makeFixture( calendar: Calendar = .current ) -> (updater: WidgetSnapshotUpdater, snapshotStore: WidgetSnapshotStore) { From 32866f46617df54b11f6ec0c66c4f2cbe785ad0e Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 17:21:38 +0900 Subject: [PATCH 08/15] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EB=B0=8F=20=ED=9A=8C=EC=9B=90=ED=83=88=ED=87=B4=20?= =?UTF-8?q?=EC=8B=9C=20=EC=9C=84=EC=A0=AF=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/App/Assembler/DataAssembler.swift | 3 ++- .../Data/Repository/AuthenticationRepositoryImpl.swift | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/DevLog/App/Assembler/DataAssembler.swift b/DevLog/App/Assembler/DataAssembler.swift index 682b47f4..d62a519f 100644 --- a/DevLog/App/Assembler/DataAssembler.swift +++ b/DevLog/App/Assembler/DataAssembler.swift @@ -22,7 +22,8 @@ final class DataAssembler: Assembler { AuthenticationService.self, name: "GoogleAuthenticationService" ), - userService: container.resolve(UserService.self) + userService: container.resolve(UserService.self), + widgetSnapshotUpdater: container.resolve(WidgetSnapshotUpdater.self) ) } diff --git a/DevLog/Data/Repository/AuthenticationRepositoryImpl.swift b/DevLog/Data/Repository/AuthenticationRepositoryImpl.swift index a96b2862..ba7d7db8 100644 --- a/DevLog/Data/Repository/AuthenticationRepositoryImpl.swift +++ b/DevLog/Data/Repository/AuthenticationRepositoryImpl.swift @@ -11,19 +11,22 @@ final class AuthenticationRepositoryImpl: AuthenticationRepository { private let githubAuthService: AuthenticationService private let googleAuthService: AuthenticationService private let userService: UserService + private let widgetSnapshotUpdater: WidgetSnapshotUpdater init( authService: AuthService, appleAuthService: AuthenticationService, githubAuthService: AuthenticationService, googleAuthService: AuthenticationService, - userService: UserService + userService: UserService, + widgetSnapshotUpdater: WidgetSnapshotUpdater ) { self.authService = authService self.appleAuthService = appleAuthService self.githubAuthService = githubAuthService self.googleAuthService = googleAuthService self.userService = userService + self.widgetSnapshotUpdater = widgetSnapshotUpdater } func signIn(_ provider: AuthProvider) async throws { @@ -58,6 +61,7 @@ final class AuthenticationRepositoryImpl: AuthenticationRepository { let provider = AuthProvider(rawValue: providerID) else { try await authService.clearCurrentSession() + widgetSnapshotUpdater.clear() return } @@ -73,6 +77,8 @@ final class AuthenticationRepositoryImpl: AuthenticationRepository { } catch AuthError.notAuthenticated { try await authService.clearCurrentSession() } + + widgetSnapshotUpdater.clear() } func restore() -> Bool { @@ -100,5 +106,6 @@ final class AuthenticationRepositoryImpl: AuthenticationRepository { try await authService.deleteCurrentUser() try await authService.clearCurrentSession() + widgetSnapshotUpdater.clear() } } From b7b511411c6a8843c935064c28fddf33fba53908 Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 15:54:06 +0900 Subject: [PATCH 09/15] =?UTF-8?q?refactor:=20key=20=ED=98=95=ED=83=9C?= =?UTF-8?q?=EB=A1=9C=20=ED=95=98=EB=93=9C=EC=BD=94=EB=94=A9=20=EC=9A=94?= =?UTF-8?q?=EC=86=8C=20=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLogWidget/Heatmap/HeatmapWidget.swift | 2 +- .../HeatmapWidgetConfigurationIntent.swift | 2 +- DevLogWidget/Resource/Localizable.xcstrings | 40 ++++++++++++++++--- DevLogWidget/Today/TodayTodoWidget.swift | 2 +- .../TodayTodoWidgetConfigurationIntent.swift | 2 +- .../Today/TodayTodoWidgetEntryView.swift | 2 +- 6 files changed, 39 insertions(+), 11 deletions(-) diff --git a/DevLogWidget/Heatmap/HeatmapWidget.swift b/DevLogWidget/Heatmap/HeatmapWidget.swift index 9f30cce4..4d70e4f2 100644 --- a/DevLogWidget/Heatmap/HeatmapWidget.swift +++ b/DevLogWidget/Heatmap/HeatmapWidget.swift @@ -22,7 +22,7 @@ struct HeatmapWidget: Widget { .containerBackground(.fill.tertiary, for: .widget) .widgetURL(WidgetDeepLink.heatmapURL) } - .configurationDisplayName("Heatmap") + .configurationDisplayName(LocalizedStringResource("widget_heatmap_title")) .description("widget_heatmap_description") .supportedFamilies([.systemSmall, .systemMedium]) } diff --git a/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift b/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift index f14c7841..134d20d9 100644 --- a/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift +++ b/DevLogWidget/Heatmap/HeatmapWidgetConfigurationIntent.swift @@ -9,6 +9,6 @@ import AppIntents import WidgetKit struct HeatmapWidgetConfigurationIntent: WidgetConfigurationIntent { - static var title: LocalizedStringResource = "Heatmap" + static var title: LocalizedStringResource = "widget_heatmap_title" static var description = IntentDescription("widget_heatmap_description") } diff --git a/DevLogWidget/Resource/Localizable.xcstrings b/DevLogWidget/Resource/Localizable.xcstrings index 3a61a290..7e5ea55a 100644 --- a/DevLogWidget/Resource/Localizable.xcstrings +++ b/DevLogWidget/Resource/Localizable.xcstrings @@ -6,12 +6,6 @@ }, "%lld" : { - }, - "Heatmap" : { - - }, - "Today" : { - }, "widget_heatmap_current_month_title" : { "extractionState" : "manual", @@ -64,6 +58,23 @@ } } }, + "widget_heatmap_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heatmap" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heatmap" + } + } + } + }, "widget_today_description" : { "extractionState" : "manual", "localizations" : { @@ -97,6 +108,23 @@ } } } + }, + "widget_today_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Today" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Today" + } + } + } } }, "version" : "1.0" diff --git a/DevLogWidget/Today/TodayTodoWidget.swift b/DevLogWidget/Today/TodayTodoWidget.swift index e002c9b5..57e15e61 100644 --- a/DevLogWidget/Today/TodayTodoWidget.swift +++ b/DevLogWidget/Today/TodayTodoWidget.swift @@ -23,7 +23,7 @@ struct TodayTodoWidget: Widget { .widgetURL(WidgetDeepLink.todayTodoURL) } .description("widget_today_description") - .configurationDisplayName("Today") + .configurationDisplayName(LocalizedStringResource("widget_today_title")) .supportedFamilies([.systemSmall, .systemMedium]) } } diff --git a/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift b/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift index 111cf4a4..8078ee87 100644 --- a/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift +++ b/DevLogWidget/Today/TodayTodoWidgetConfigurationIntent.swift @@ -9,6 +9,6 @@ import AppIntents import WidgetKit struct TodayTodoWidgetConfigurationIntent: WidgetConfigurationIntent { - static var title: LocalizedStringResource = "Today" + static var title: LocalizedStringResource = "widget_today_title" static var description = IntentDescription("widget_today_description") } diff --git a/DevLogWidget/Today/TodayTodoWidgetEntryView.swift b/DevLogWidget/Today/TodayTodoWidgetEntryView.swift index 4b9f7177..6e748abe 100644 --- a/DevLogWidget/Today/TodayTodoWidgetEntryView.swift +++ b/DevLogWidget/Today/TodayTodoWidgetEntryView.swift @@ -14,7 +14,7 @@ struct TodayTodoWidgetEntryView: View { var body: some View { VStack(alignment: .leading) { - Text("Today") + Text("widget_today_title") .font(.headline) Spacer() From 1f10c3b03c57fc1ea8b0107eccc2c5f1da0ffe0f Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 16:25:23 +0900 Subject: [PATCH 10/15] =?UTF-8?q?feat:=20=EC=82=AD=EC=A0=9C=20=ED=82=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WidgetShared/WidgetSnapshotKey.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WidgetShared/WidgetSnapshotKey.swift b/WidgetShared/WidgetSnapshotKey.swift index 3cf46991..3f972d8b 100644 --- a/WidgetShared/WidgetSnapshotKey.swift +++ b/WidgetShared/WidgetSnapshotKey.swift @@ -10,4 +10,5 @@ import Foundation enum WidgetSnapshotKey { static let today = "Widget.today.snapshot" static let heatmap = "Widget.heatmap.snapshot" + static let snapshots = [today, heatmap] } From d59fc25d605989d58bb380ff3255704ece229c9a Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 16:50:37 +0900 Subject: [PATCH 11/15] =?UTF-8?q?fix:=20=EC=9C=84=EC=A0=AF=20=EC=8A=A4?= =?UTF-8?q?=EB=83=85=EC=83=B7=20=EC=82=AD=EC=A0=9C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Persistence/WidgetSnapshotUpdater.swift | 6 ++++ .../Common/WidgetSharedDefaultsStore.swift | 4 +++ .../Widget/Common/WidgetSnapshotStore.swift | 6 ++++ .../Widget/WidgetSnapshotUpdaterTests.swift | 33 +++++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift b/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift index 38c54eb7..6ab8c99f 100644 --- a/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift +++ b/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift @@ -104,4 +104,10 @@ final class WidgetSnapshotUpdater { ) } } + + func clear() { + snapshotStore.clearSnapshots() + WidgetCenter.shared.reloadTimelines(ofKind: WidgetKind.todayTodo) + WidgetCenter.shared.reloadTimelines(ofKind: WidgetKind.heatmap) + } } diff --git a/DevLog/Widget/Common/WidgetSharedDefaultsStore.swift b/DevLog/Widget/Common/WidgetSharedDefaultsStore.swift index eedc63ac..b30ad1b0 100644 --- a/DevLog/Widget/Common/WidgetSharedDefaultsStore.swift +++ b/DevLog/Widget/Common/WidgetSharedDefaultsStore.swift @@ -21,4 +21,8 @@ final class WidgetSharedDefaultsStore { func setData(_ value: Data?, forKey key: String) { userDefaults.set(value, forKey: key) } + + func removeObject(forKey key: String) { + userDefaults.removeObject(forKey: key) + } } diff --git a/DevLog/Widget/Common/WidgetSnapshotStore.swift b/DevLog/Widget/Common/WidgetSnapshotStore.swift index 71abd4ff..5a887efd 100644 --- a/DevLog/Widget/Common/WidgetSnapshotStore.swift +++ b/DevLog/Widget/Common/WidgetSnapshotStore.swift @@ -35,4 +35,10 @@ final class WidgetSnapshotStore { guard let data = store.data(forKey: WidgetSnapshotKey.heatmap) else { return nil } return try decoder.decode(HeatmapWidgetSnapshot.self, from: data) } + + func clearSnapshots() { + WidgetSnapshotKey.snapshots.forEach { + store.removeObject(forKey: $0) + } + } } diff --git a/DevLog_Unit/Widget/WidgetSnapshotUpdaterTests.swift b/DevLog_Unit/Widget/WidgetSnapshotUpdaterTests.swift index 404b72ae..7bc4e087 100644 --- a/DevLog_Unit/Widget/WidgetSnapshotUpdaterTests.swift +++ b/DevLog_Unit/Widget/WidgetSnapshotUpdaterTests.swift @@ -66,6 +66,39 @@ struct WidgetSnapshotUpdaterTests { #expect(snapshot.maxCount == 1) } + @Test("WidgetSnapshotUpdater는 모든 위젯 스냅샷을 삭제한다") + func widgetSnapshotUpdater는_모든_위젯_스냅샷을_삭제한다() throws { + let calendar = Calendar(identifier: .gregorian) + let quarterStart = try #require(calendar.date(from: DateComponents(year: 2026, month: 4, day: 1))) + let now = try #require(calendar.date(from: DateComponents(year: 2026, month: 4, day: 30))) + let fixture = makeFixture(calendar: calendar) + let todo = try makeTodayTodoItem(now: now) + + fixture.updater.updateTodaySnapshot( + todos: [todo], + displayOptions: .default, + now: now + ) + fixture.updater.updateHeatmapSnapshot( + createdTodos: [ + makeTodo( + id: "created", + createdAt: now + ) + ], + completedTodos: [], + deletedTodos: [], + selectedActivityKinds: [.created], + quarterStart: quarterStart, + now: now + ) + + fixture.updater.clear() + + #expect(try fixture.snapshotStore.loadTodaySnapshot() == nil) + #expect(try fixture.snapshotStore.loadHeatmapSnapshot() == nil) + } + private func makeFixture( calendar: Calendar = .current ) -> (updater: WidgetSnapshotUpdater, snapshotStore: WidgetSnapshotStore) { From 75dee3ba6e301cd9ee85bf7df9e838b76c4c9eec Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 17:21:38 +0900 Subject: [PATCH 12/15] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EB=B0=8F=20=ED=9A=8C=EC=9B=90=ED=83=88=ED=87=B4=20?= =?UTF-8?q?=EC=8B=9C=20=EC=9C=84=EC=A0=AF=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/App/Assembler/DataAssembler.swift | 3 ++- .../Data/Repository/AuthenticationRepositoryImpl.swift | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/DevLog/App/Assembler/DataAssembler.swift b/DevLog/App/Assembler/DataAssembler.swift index 682b47f4..d62a519f 100644 --- a/DevLog/App/Assembler/DataAssembler.swift +++ b/DevLog/App/Assembler/DataAssembler.swift @@ -22,7 +22,8 @@ final class DataAssembler: Assembler { AuthenticationService.self, name: "GoogleAuthenticationService" ), - userService: container.resolve(UserService.self) + userService: container.resolve(UserService.self), + widgetSnapshotUpdater: container.resolve(WidgetSnapshotUpdater.self) ) } diff --git a/DevLog/Data/Repository/AuthenticationRepositoryImpl.swift b/DevLog/Data/Repository/AuthenticationRepositoryImpl.swift index a96b2862..ba7d7db8 100644 --- a/DevLog/Data/Repository/AuthenticationRepositoryImpl.swift +++ b/DevLog/Data/Repository/AuthenticationRepositoryImpl.swift @@ -11,19 +11,22 @@ final class AuthenticationRepositoryImpl: AuthenticationRepository { private let githubAuthService: AuthenticationService private let googleAuthService: AuthenticationService private let userService: UserService + private let widgetSnapshotUpdater: WidgetSnapshotUpdater init( authService: AuthService, appleAuthService: AuthenticationService, githubAuthService: AuthenticationService, googleAuthService: AuthenticationService, - userService: UserService + userService: UserService, + widgetSnapshotUpdater: WidgetSnapshotUpdater ) { self.authService = authService self.appleAuthService = appleAuthService self.githubAuthService = githubAuthService self.googleAuthService = googleAuthService self.userService = userService + self.widgetSnapshotUpdater = widgetSnapshotUpdater } func signIn(_ provider: AuthProvider) async throws { @@ -58,6 +61,7 @@ final class AuthenticationRepositoryImpl: AuthenticationRepository { let provider = AuthProvider(rawValue: providerID) else { try await authService.clearCurrentSession() + widgetSnapshotUpdater.clear() return } @@ -73,6 +77,8 @@ final class AuthenticationRepositoryImpl: AuthenticationRepository { } catch AuthError.notAuthenticated { try await authService.clearCurrentSession() } + + widgetSnapshotUpdater.clear() } func restore() -> Bool { @@ -100,5 +106,6 @@ final class AuthenticationRepositoryImpl: AuthenticationRepository { try await authService.deleteCurrentUser() try await authService.clearCurrentSession() + widgetSnapshotUpdater.clear() } } From 3cce29b928b0729cfede699acc71e0184fe0603b Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 21:14:04 +0900 Subject: [PATCH 13/15] =?UTF-8?q?fix:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EB=B3=84=20Preference=EA=B0=80=20=EC=A7=80=EC=9B=8C=EC=A7=80?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=98=84=EC=83=81=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WidgetSnapshotPreferenceStore.swift | 26 ++++++++++++------- .../Persistence/WidgetSnapshotUpdater.swift | 1 + .../Widget/WidgetSnapshotUpdaterTests.swift | 17 ++++++++++-- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/DevLog/Storage/Persistence/WidgetSnapshotPreferenceStore.swift b/DevLog/Storage/Persistence/WidgetSnapshotPreferenceStore.swift index 56094f79..1eebbe82 100644 --- a/DevLog/Storage/Persistence/WidgetSnapshotPreferenceStore.swift +++ b/DevLog/Storage/Persistence/WidgetSnapshotPreferenceStore.swift @@ -8,10 +8,10 @@ import Foundation final class WidgetSnapshotPreferenceStore { - private enum Key { - static let heatmapActivityTypes = "Profile.heatmap.activityTypes" - static let todayDueDateVisibility = "Today.dueDateVisibility" - static let todayFocusVisibility = "Today.focusVisibility" + private enum Key: String, CaseIterable { + case heatmapActivityTypes = "Profile.heatmap.activityTypes" + case todayDueDateVisibility = "Today.dueDateVisibility" + case todayFocusVisibility = "Today.focusVisibility" } private let userDefaults: UserDefaults @@ -21,11 +21,11 @@ final class WidgetSnapshotPreferenceStore { } func heatmapActivityTypes() -> [String] { - userDefaults.stringArray(forKey: Key.heatmapActivityTypes) ?? [] + userDefaults.stringArray(forKey: Key.heatmapActivityTypes.rawValue) ?? [] } func setHeatmapActivityTypes(_ activityTypes: [String]) { - userDefaults.set(activityTypes, forKey: Key.heatmapActivityTypes) + userDefaults.set(activityTypes, forKey: Key.heatmapActivityTypes.rawValue) } func selectedActivityKinds() -> Set { @@ -41,8 +41,8 @@ final class WidgetSnapshotPreferenceStore { } func todayDisplayOptions() -> TodayDisplayOptions { - let dueDateVisibilityRawValue = userDefaults.string(forKey: Key.todayDueDateVisibility) - let focusVisibilityRawValue = userDefaults.string(forKey: Key.todayFocusVisibility) + let dueDateVisibilityRawValue = userDefaults.string(forKey: Key.todayDueDateVisibility.rawValue) + let focusVisibilityRawValue = userDefaults.string(forKey: Key.todayFocusVisibility.rawValue) return TodayDisplayOptions( dueDateVisibility: TodayDisplayOptions.DueDateVisibility( @@ -55,7 +55,13 @@ final class WidgetSnapshotPreferenceStore { } func setTodayDisplayOptions(_ options: TodayDisplayOptions) { - userDefaults.set(options.dueDateVisibility.rawValue, forKey: Key.todayDueDateVisibility) - userDefaults.set(options.focusVisibility.rawValue, forKey: Key.todayFocusVisibility) + userDefaults.set(options.dueDateVisibility.rawValue, forKey: Key.todayDueDateVisibility.rawValue) + userDefaults.set(options.focusVisibility.rawValue, forKey: Key.todayFocusVisibility.rawValue) + } + + func clear() { + Key.allCases.forEach { + userDefaults.removeObject(forKey: $0.rawValue) + } } } diff --git a/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift b/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift index 6ab8c99f..d0bbe570 100644 --- a/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift +++ b/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift @@ -107,6 +107,7 @@ final class WidgetSnapshotUpdater { func clear() { snapshotStore.clearSnapshots() + preferenceStore.clear() WidgetCenter.shared.reloadTimelines(ofKind: WidgetKind.todayTodo) WidgetCenter.shared.reloadTimelines(ofKind: WidgetKind.heatmap) } diff --git a/DevLog_Unit/Widget/WidgetSnapshotUpdaterTests.swift b/DevLog_Unit/Widget/WidgetSnapshotUpdaterTests.swift index 7bc4e087..4e5dd86d 100644 --- a/DevLog_Unit/Widget/WidgetSnapshotUpdaterTests.swift +++ b/DevLog_Unit/Widget/WidgetSnapshotUpdaterTests.swift @@ -73,6 +73,13 @@ struct WidgetSnapshotUpdaterTests { let now = try #require(calendar.date(from: DateComponents(year: 2026, month: 4, day: 30))) let fixture = makeFixture(calendar: calendar) let todo = try makeTodayTodoItem(now: now) + fixture.preferenceStore.setHeatmapActivityTypes(["created"]) + fixture.preferenceStore.setTodayDisplayOptions( + TodayDisplayOptions( + dueDateVisibility: .withDueDateOnly, + focusVisibility: .focusedOnly + ) + ) fixture.updater.updateTodaySnapshot( todos: [todo], @@ -97,11 +104,17 @@ struct WidgetSnapshotUpdaterTests { #expect(try fixture.snapshotStore.loadTodaySnapshot() == nil) #expect(try fixture.snapshotStore.loadHeatmapSnapshot() == nil) + #expect(fixture.preferenceStore.heatmapActivityTypes().isEmpty) + #expect(fixture.preferenceStore.todayDisplayOptions() == .default) } private func makeFixture( calendar: Calendar = .current - ) -> (updater: WidgetSnapshotUpdater, snapshotStore: WidgetSnapshotStore) { + ) -> ( + updater: WidgetSnapshotUpdater, + snapshotStore: WidgetSnapshotStore, + preferenceStore: WidgetSnapshotPreferenceStore + ) { let suiteName = "WidgetSnapshotUpdaterTests.\(UUID().uuidString)" let userDefaults = UserDefaults(suiteName: suiteName) ?? .standard userDefaults.removePersistentDomain(forName: suiteName) @@ -116,7 +129,7 @@ struct WidgetSnapshotUpdaterTests { preferenceStore: preferenceStore, heatmapFactory: HeatmapWidgetSnapshotFactory(calendar: calendar) ) - return (updater, snapshotStore) + return (updater, snapshotStore, preferenceStore) } private func makeTodayTodoItem(now: Date) throws -> TodayTodoItem { From 47ab19c6b48d5f56495efedc5c38f75c3a710e2d Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 21:16:58 +0900 Subject: [PATCH 14/15] =?UTF-8?q?fix:=20=EC=9C=84=EC=A0=AF=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=EC=84=A4=EC=A0=95=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift b/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift index d0bbe570..bfcdac41 100644 --- a/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift +++ b/DevLog/Storage/Persistence/WidgetSnapshotUpdater.swift @@ -108,7 +108,6 @@ final class WidgetSnapshotUpdater { func clear() { snapshotStore.clearSnapshots() preferenceStore.clear() - WidgetCenter.shared.reloadTimelines(ofKind: WidgetKind.todayTodo) - WidgetCenter.shared.reloadTimelines(ofKind: WidgetKind.heatmap) + WidgetCenter.shared.reloadAllTimelines() } } From 6554b82228482c9231fcf3e51be25846d0f2515b Mon Sep 17 00:00:00 2001 From: opficdev Date: Fri, 1 May 2026 21:21:24 +0900 Subject: [PATCH 15/15] =?UTF-8?q?chore:=20=EB=A7=88=EC=BC=80=ED=8C=85=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=201.1.0=EC=9C=BC=EB=A1=9C=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/DevLog.xcodeproj/project.pbxproj b/DevLog.xcodeproj/project.pbxproj index 74e9bf8b..18261118 100644 --- a/DevLog.xcodeproj/project.pbxproj +++ b/DevLog.xcodeproj/project.pbxproj @@ -396,7 +396,7 @@ DEVELOPMENT_TEAM = 4CPC6N38WA; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17; - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = opfic.DevLog_Unit; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = NO; @@ -421,7 +421,7 @@ DEVELOPMENT_TEAM = 4CPC6N38WA; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17; - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = opfic.DevLog_Unit; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = NO; @@ -457,7 +457,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = opfic.DevLog.DevLogWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -490,7 +490,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = opfic.DevLog.DevLogWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -536,7 +536,7 @@ "@executable_path/Frameworks", ); LOCALIZED_STRING_SWIFTUI_SUPPORT = YES; - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = opfic.DevLog; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -583,7 +583,7 @@ "@executable_path/Frameworks", ); LOCALIZED_STRING_SWIFTUI_SUPPORT = YES; - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = opfic.DevLog; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "";