diff --git a/myApp/HaruDanim/Widgets/GoalWidgets.swift b/myApp/HaruDanim/Widgets/GoalWidgets.swift index 7e7e79b..0b0f578 100644 --- a/myApp/HaruDanim/Widgets/GoalWidgets.swift +++ b/myApp/HaruDanim/Widgets/GoalWidgets.swift @@ -181,35 +181,63 @@ struct QuestBarRow: View { } } +/// 다짐 하루 진행률 목록 카드 — 고정 개수 대신 주어진 높이에 들어가는 만큼 최대한 채운다. +/// 다 못 담으면 헤더 오른쪽에 "+N" 표기. (중형/대형 구성들이 공용) +struct QuestBarsColumn: View { + let quests: [QuestCellSnapshot] + + private let rowHeight: CGFloat = 14 + private let rowSpacing: CGFloat = 6 + private let headerHeight: CGFloat = 17 + + var body: some View { + GeometryReader { geo in + let fitCount = max(Int((geo.size.height - headerHeight + rowSpacing) / (rowHeight + rowSpacing)), 1) + let shown = Array(quests.prefix(fitCount)) + VStack(alignment: .leading, spacing: rowSpacing) { + HStack { + Text("다짐 (하루)") + .font(.system(size: 9, weight: .semibold)) + .foregroundStyle(.secondary) + Spacer() + if quests.count > shown.count { + Text(verbatim: "+\(quests.count - shown.count)") + .font(.system(size: 9, weight: .semibold).monospacedDigit()) + .foregroundStyle(.tertiary) + } + } + .frame(height: headerHeight - rowSpacing, alignment: .top) + if quests.isEmpty { + Spacer() + Text("다짐이 없어요") + .font(.caption2) + .foregroundStyle(.secondary) + .frame(maxWidth: .infinity) + Spacer() + } else { + ForEach(shown) { quest in + QuestBarRow(quest: quest) + .frame(height: rowHeight) + } + Spacer(minLength: 0) + } + } + } + .padding(10) + .background(WidgetCellBackground()) + } +} + /// 목표 1개 + 상위 다짐들의 하루 진행률 (중형 구성 2 / 대형 구성 2) struct GoalWithQuestsCellView: View { let goal: GoalSnapshot - var questCount = 3 var body: some View { HStack(alignment: .top, spacing: 10) { GoalBarsCellView(goal: goal) .frame(maxWidth: .infinity) - VStack(alignment: .leading, spacing: 7) { - Text("다짐 (하루)") - .font(.system(size: 9, weight: .semibold)) - .foregroundStyle(.secondary) - if goal.quests.isEmpty { - Spacer() - Text("다짐이 없어요") - .font(.caption2) - .foregroundStyle(.secondary) - Spacer() - } else { - ForEach(goal.quests.prefix(questCount)) { quest in - QuestBarRow(quest: quest) - } - Spacer(minLength: 0) - } - } - .padding(10) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .background(WidgetCellBackground()) + QuestBarsColumn(quests: goal.quests) + .frame(maxWidth: .infinity, maxHeight: .infinity) } } } @@ -258,7 +286,7 @@ struct GoalProgressWidgetView: View { } case .goalWithQuests: if let goal = entry.goals.first ?? nil { - GoalWithQuestsCellView(goal: goal, questCount: 3) + GoalWithQuestsCellView(goal: goal) } } } @@ -281,7 +309,7 @@ struct GoalProgressWidgetView: View { VStack(spacing: 8) { ForEach(0..<2, id: \.self) { index in if index < entry.goals.count, let goal = entry.goals[index] { - GoalWithQuestsCellView(goal: goal, questCount: 3) + GoalWithQuestsCellView(goal: goal) } else { WidgetBlankCellView() } @@ -292,24 +320,8 @@ struct GoalProgressWidgetView: View { VStack(spacing: 8) { GoalBarsCellView(goal: goal) .frame(maxHeight: 130) - VStack(alignment: .leading, spacing: 7) { - Text("다짐 (하루)") - .font(.system(size: 9, weight: .semibold)) - .foregroundStyle(.secondary) - if goal.quests.isEmpty { - Text("다짐이 없어요") - .font(.caption2) - .foregroundStyle(.secondary) - } else { - ForEach(goal.quests.prefix(8)) { quest in - QuestBarRow(quest: quest) - } - } - Spacer(minLength: 0) - } - .padding(10) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .background(WidgetCellBackground()) + QuestBarsColumn(quests: goal.quests) + .frame(maxWidth: .infinity, maxHeight: .infinity) } } }