feat(export): add high-res image export of records and stats as report posters

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
This commit is contained in:
songyc macbook 2026-07-13 02:05:16 +09:00
parent b897db47db
commit cde4c4001b
4 changed files with 1350 additions and 1 deletions

View File

@ -600,6 +600,7 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "하루 다님";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "내보낸 리포트 이미지를 사진 앨범에 저장하기 위해 필요해요.";
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@ -639,6 +640,7 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "하루 다님";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "내보낸 리포트 이미지를 사진 앨범에 저장하기 위해 필요해요.";
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;

File diff suppressed because it is too large Load Diff

View File

@ -69,11 +69,18 @@ struct HistoryView: View {
#endif
return .list
}()
@State private var timetableWeekly = false
@State private var timetableWeekly: Bool = {
#if DEBUG
// : -historyWeekly YES
if UserDefaults.standard.bool(forKey: "historyWeekly") { return true }
#endif
return false
}()
@State private var editingSession: TimeSession?
@State private var editingEntry: CountEntry?
@State private var showingCalendar = false
@State private var showingFilter = false
@State private var showingExport = false
/// " " ( )
@State private var excludedActionIDs: Set<PersistentIdentifier> = []
@ -115,6 +122,13 @@ struct HistoryView: View {
.navigationTitle("기록")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
showingExport = true
} label: {
Image(systemName: "square.and.arrow.up")
}
}
ToolbarItem(placement: .topBarTrailing) {
Button {
showingFilter = true
@ -139,6 +153,18 @@ struct HistoryView: View {
excludedActionIDs: $excludedActionIDs
)
}
.sheet(isPresented: $showingExport) {
// / /
ExportImageSheet(snapshot: ExportBuilder.history(
dayKey: selectedDayKey,
weeklyTimetable: mode == .timetable && timetableWeekly,
sessions: sessions,
entries: entries,
orderedActions: allActions,
excludedActionIDs: excludedActionIDs,
math: math
))
}
.onAppear {
consumePendingFilter()
#if DEBUG
@ -152,6 +178,10 @@ struct HistoryView: View {
if UserDefaults.standard.bool(forKey: "historyShowFilter") {
showingFilter = true
}
// : -showExport YES
if UserDefaults.standard.bool(forKey: "showExport") {
showingExport = true
}
#endif
}
.onChange(of: router.pendingHistoryActionID) {

View File

@ -37,6 +37,7 @@ struct StatsTabView: View {
}()
@State private var anchorDayKey: Date = DayMath().dayKey(for: .now)
@State private var showingFilter = false
@State private var showingExport = false
/// " " , .
/// , .
@State private var excludedActionIDs: Set<PersistentIdentifier> = []
@ -101,6 +102,13 @@ struct StatsTabView: View {
.navigationTitle("통계")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
showingExport = true
} label: {
Image(systemName: "square.and.arrow.up")
}
}
ToolbarItem(placement: .topBarTrailing) {
Button {
showingFilter = true
@ -119,6 +127,18 @@ struct StatsTabView: View {
excludedActionIDs: $excludedActionIDs
)
}
.sheet(isPresented: $showingExport) {
// /
ExportImageSheet(snapshot: ExportBuilder.stats(
span: span,
anchorDayKey: anchorDayKey,
sessions: sessions,
entries: entries,
orderedActions: allActions,
excludedActionIDs: excludedActionIDs,
math: math
))
}
.onAppear {
consumePendingFilter()
#if DEBUG
@ -132,6 +152,10 @@ struct StatsTabView: View {
if UserDefaults.standard.bool(forKey: "statShowFilter") {
showingFilter = true
}
// : -showExport YES
if UserDefaults.standard.bool(forKey: "showExport") {
showingExport = true
}
#endif
}
.onChange(of: router.pendingStatsActionID) {