From 352be3e29bc6fab0ec6e7fc08514d646ae6641f2 Mon Sep 17 00:00:00 2001 From: songyc macbook Date: Fri, 10 Jul 2026 03:52:51 +0900 Subject: [PATCH] feat(main): allow displaying up to 3 goals on the top progress widget - Update the goal progress widget in the main tab to support up to 3 selected goals - Add multi-selection capabilities to the widget settings menu - Implement a horizontal or scrollable layout for multiple goals to optimize screen space - Maintain existing routing to specific goal detail screens upon tap --- .../UserInterfaceState.xcuserstate | Bin 29822 -> 29483 bytes myApp/HaruDanim/IOS/ContentView.swift | 1 + myApp/HaruDanim/IOS/Core/DebugSeed.swift | 28 +++++++ myApp/HaruDanim/IOS/Core/Settings.swift | 6 ++ myApp/HaruDanim/IOS/Views/MainView.swift | 50 +++++++++--- myApp/HaruDanim/IOS/Views/SettingsView.swift | 74 ++++++++++++++---- 6 files changed, 134 insertions(+), 25 deletions(-) diff --git a/myApp/HaruDanim/Haru_Danim.xcodeproj/project.xcworkspace/xcuserdata/ceuak.xcuserdatad/UserInterfaceState.xcuserstate b/myApp/HaruDanim/Haru_Danim.xcodeproj/project.xcworkspace/xcuserdata/ceuak.xcuserdatad/UserInterfaceState.xcuserstate index 232515be04182b043b0119f33149db4b85276f89..153e7ada266dc86b8e4eadffc74a186e3d798728 100644 GIT binary patch delta 13635 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 IzjB-Z0UG%DX#fBK delta 13904 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 P-!|` 런치 인자: 생성순으로 목표 N개를 모음 탭에 표시 (다중 목표 카드 검증용) + static func pinGoalsIfRequested(context: ModelContext) { + let n = UserDefaults.standard.integer(forKey: "pinGoals") + guard n > 0 else { return } + let descriptor = FetchDescriptor(sortBy: [SortDescriptor(\.createdAt)]) + guard let goals = try? context.fetch(descriptor) else { return } + for (index, goal) in goals.enumerated() { + goal.showsOnMain = index < n + } + } + static func seedIfRequested(context: ModelContext) { guard UserDefaults.standard.bool(forKey: "seedDemo") else { return } let existing = (try? context.fetchCount(FetchDescriptor())) ?? 0 @@ -148,6 +159,23 @@ enum DebugSeed { videoQuest.targetSeconds = 3600 videoQuest.direction = .atMost context.insert(videoQuest) + + // 세 번째 목표 (모음 탭 다중 목표 카드 검증용) + let habit = Goal( + title: "매일 밤 독서 습관", + symbolName: "book.fill", + colorHex: "#9D5B4A", + startDate: cal.date(byAdding: .day, value: -5, to: now)!, + endDate: cal.date(byAdding: .day, value: 60, to: now)! + ) + context.insert(habit) + let readingQuest = Quest(goal: habit) + readingQuest.targetAction = reading + readingQuest.measure = .time + readingQuest.period = .daily + readingQuest.targetSeconds = 45 * 60 + readingQuest.direction = .atLeast + context.insert(readingQuest) } } #endif diff --git a/myApp/HaruDanim/IOS/Core/Settings.swift b/myApp/HaruDanim/IOS/Core/Settings.swift index 6afd6ff..6653d44 100644 --- a/myApp/HaruDanim/IOS/Core/Settings.swift +++ b/myApp/HaruDanim/IOS/Core/Settings.swift @@ -30,6 +30,12 @@ enum SettingsKeys { static let goalCardStyle = "settings.goalCardStyle" } +/// 모음 탭 상단 목표 카드 구성 +enum MainGoalCard { + /// 동시에 표시할 수 있는 목표 최대 개수 + static let maxPinned = 3 +} + /// 모음 탭 목표 진행 현황 카드의 표시 방식 enum GoalCardStyle: String, CaseIterable, Identifiable { /// 다짐(최대 3개)별로 하루/주간/월간 진행률을 각각 표시 diff --git a/myApp/HaruDanim/IOS/Views/MainView.swift b/myApp/HaruDanim/IOS/Views/MainView.swift index d731c4e..ee2583c 100644 --- a/myApp/HaruDanim/IOS/Views/MainView.swift +++ b/myApp/HaruDanim/IOS/Views/MainView.swift @@ -14,7 +14,7 @@ struct MainView: View { @Query(sort: \Action.sortOrder) private var actions: [Action] @Query(filter: #Predicate { $0.endAt == nil }, sort: \TimeSession.startAt) private var runningSessions: [TimeSession] - @Query(filter: #Predicate { $0.showsOnMain }) private var pinnedGoals: [Goal] + @Query(filter: #Predicate { $0.showsOnMain }, sort: \Goal.createdAt) private var pinnedGoals: [Goal] @Environment(AppRouter.self) private var router @@ -41,8 +41,8 @@ struct MainView: View { var body: some View { ScrollView { VStack(alignment: .leading, spacing: 16) { - if let goal = pinnedGoals.first, !isEditing { - GoalSummaryCard(goal: goal) + if !pinnedGoals.isEmpty && !isEditing { + goalArea } if anyRunning && !isEditing { runningArea @@ -118,6 +118,30 @@ struct MainView: View { #endif } + // MARK: 목표 진행 현황 영역 (1개=상세 카드, 2개 이상=가로 슬라이드 요약 카드) + + @ViewBuilder + private var goalArea: some View { + if pinnedGoals.count == 1, let goal = pinnedGoals.first { + GoalSummaryCard(goal: goal) + } else { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 10) { + ForEach(pinnedGoals.prefix(MainGoalCard.maxPinned)) { goal in + GoalSummaryCard(goal: goal, compact: true) + // 좁은 화면에선 옆 카드가 살짝 보이는 슬라이드, 넓은 화면(아이패드 등)에선 한 줄에 나란히 들어옴 + .containerRelativeFrame(.horizontal) { length, _ in + min(length * 0.72, 300) + } + } + } + .scrollTargetLayout() + } + .scrollTargetBehavior(.viewAligned) + .scrollClipDisabled() + } + } + // MARK: 현재 진행 중 영역 private var runningArea: some View { @@ -308,6 +332,8 @@ struct MainView: View { struct GoalSummaryCard: View { let goal: Goal + /// 여러 목표를 가로 슬라이드로 보여줄 때의 축약 모드 (전체 다짐 평균 한 줄만 표시) + var compact: Bool = false @AppStorage(SettingsKeys.goalCardStyle) private var cardStyleRaw = GoalCardStyle.perQuest.rawValue @@ -350,18 +376,22 @@ struct GoalSummaryCard: View { } } if !goal.sortedQuests.isEmpty { - switch cardStyle { - case .perQuest: - ForEach(goal.sortedQuests.prefix(3)) { quest in - questSpanBlock(quest) - } - case .combined: + if compact { combinedBlock + } else { + switch cardStyle { + case .perQuest: + ForEach(goal.sortedQuests.prefix(3)) { quest in + questSpanBlock(quest) + } + case .combined: + combinedBlock + } } } } .padding(14) - .frame(maxWidth: .infinity, alignment: .leading) + .frame(maxWidth: .infinity, maxHeight: compact ? .infinity : nil, alignment: .topLeading) .background(AppTheme.surface, in: RoundedRectangle(cornerRadius: 18, style: .continuous)) .contentShape(RoundedRectangle(cornerRadius: 18, style: .continuous)) } diff --git a/myApp/HaruDanim/IOS/Views/SettingsView.swift b/myApp/HaruDanim/IOS/Views/SettingsView.swift index 625c762..bceb8b7 100644 --- a/myApp/HaruDanim/IOS/Views/SettingsView.swift +++ b/myApp/HaruDanim/IOS/Views/SettingsView.swift @@ -36,18 +36,12 @@ struct SettingsView: View { } } - /// 모음 탭에 표시할 목표 선택 ↔ Goal.showsOnMain 플래그 변환 (최대 1개 유지) - private var pinnedGoalSelection: Binding { - Binding { - goals.first { $0.showsOnMain }?.persistentModelID - } set: { newValue in - for goal in goals { - goal.showsOnMain = goal.persistentModelID == newValue - } - } + private var pinnedGoals: [Goal] { + goals.filter(\.showsOnMain) } var body: some View { + ScrollViewReader { proxy in Form { Section("화면") { Picker("테마", selection: $theme) { @@ -70,13 +64,16 @@ struct SettingsView: View { Text("선택한 탭(최대 \(TabBarConfig.maxVisible)개)이 하단 탭바에 바로 표시되고, 나머지 탭은 ‘더보기’에서 열 수 있어요.") } Section { - Picker("목표 진행 현황 표시", selection: pinnedGoalSelection) { - Text("표시 안 함").tag(nil as PersistentIdentifier?) + if goals.isEmpty { + Text("목표 탭에서 목표를 만들면 여기서 선택할 수 있어요.") + .font(.caption) + .foregroundStyle(.secondary) + } else { ForEach(goals) { goal in - Text(goal.title).tag(Optional(goal.persistentModelID)) + pinnedGoalRow(goal) } } - if goals.contains(where: \.showsOnMain) { + if pinnedGoals.count == 1 { Picker("표시 방식", selection: $goalCardStyle) { ForEach(GoalCardStyle.allCases) { style in Text(style.label).tag(style.rawValue) @@ -84,10 +81,11 @@ struct SettingsView: View { } } } header: { - Text("모음 탭") + Text("모음 탭 목표 진행 현황 (최대 \(MainGoalCard.maxPinned)개)") } footer: { - Text("선택한 목표의 진행 현황이 모음 탭 상단에 표시돼요. 탭하면 목표 상세로 이동해요. ‘다짐별로 각각’은 다짐(최대 3개)마다 하루·주간·월간 진행률을, ‘전체 다짐 합산’은 소속 다짐 전체의 평균 진행률을 한 줄로 보여줘요.") + Text("선택한 목표의 진행 현황이 모음 탭 상단에 표시돼요. 탭하면 목표 상세로 이동해요. 2개 이상 선택하면 옆으로 넘겨보는 요약 카드(전체 다짐 평균)로 표시되고, 1개만 선택하면 표시 방식(다짐별로 각각 / 전체 다짐 합산)을 고를 수 있어요.") } + .id("goalSection") Section { Picker("주 시작 요일", selection: $weekStartWeekday) { ForEach(1...7, id: \.self) { weekday in @@ -147,6 +145,52 @@ struct SettingsView: View { .scrollContentBackground(.hidden) .background(AppTheme.background) .navigationTitle("설정") + #if DEBUG + // 검증용: -settingsScrollGoal YES → 모음 탭 목표 섹션까지 자동 스크롤 + .onAppear { + if UserDefaults.standard.bool(forKey: "settingsScrollGoal") { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { + withAnimation { proxy.scrollTo("goalSection", anchor: .top) } + } + } + } + #endif + } + } + + // MARK: 모음 탭 목표 선택 (최대 MainGoalCard.maxPinned개 다중 선택) + + private func pinnedGoalRow(_ goal: Goal) -> some View { + let isPinned = goal.showsOnMain + let atMax = pinnedGoals.count >= MainGoalCard.maxPinned + return Button { + if isPinned { + goal.showsOnMain = false + } else if !atMax { + goal.showsOnMain = true + } + } label: { + HStack { + Label { + Text(goal.title) + .foregroundStyle(.primary) + .lineLimit(1) + } icon: { + Image(systemName: goal.symbolName) + .foregroundStyle(goal.color) + } + Spacer() + if isPinned { + Image(systemName: "checkmark") + .foregroundStyle(AppTheme.green) + .fontWeight(.semibold) + } + } + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .disabled(!isPinned && atMax) + .opacity(!isPinned && atMax ? 0.5 : 1) } // MARK: 탭바 구성