From 7fc4dd0a7de5d1742bba7967eaa3bc72614c28a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=92=E1=85=A9=E1=86=BC=E1=84=89=E1=85=A5=E1=86=A8?= =?UTF-8?q?=E1=84=92=E1=85=A7=E1=86=AB?= Date: Tue, 16 Dec 2025 21:04:12 +0900 Subject: [PATCH 1/9] =?UTF-8?q?bugfix.=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20ass?= =?UTF-8?q?et=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...274 \354\230\244\355\233\204 02_55_30 1.png" | Bin 17285 -> 0 bytes .../EmptyTravelList.imageset/Contents.json | 2 +- .../EmptyTravelList.imageset/travelList.png | Bin 0 -> 17286 bytes 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 "DesignSystem/Resources/Assets.xcassets/Images/EmptyTravelList.imageset/ChatGPT Image 2025\353\205\204 12\354\233\224 12\354\235\274 \354\230\244\355\233\204 02_55_30 1.png" create mode 100644 DesignSystem/Resources/Assets.xcassets/Images/EmptyTravelList.imageset/travelList.png diff --git "a/DesignSystem/Resources/Assets.xcassets/Images/EmptyTravelList.imageset/ChatGPT Image 2025\353\205\204 12\354\233\224 12\354\235\274 \354\230\244\355\233\204 02_55_30 1.png" "b/DesignSystem/Resources/Assets.xcassets/Images/EmptyTravelList.imageset/ChatGPT Image 2025\353\205\204 12\354\233\224 12\354\235\274 \354\230\244\355\233\204 02_55_30 1.png" deleted file mode 100644 index 9b416ba505ac7f1278e26a62f1b5c99a2e962854..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17285 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 bk@Eimmidiu>;E@y00000NkvXXu0mjf4v;3w diff --git a/DesignSystem/Resources/Assets.xcassets/Images/EmptyTravelList.imageset/Contents.json b/DesignSystem/Resources/Assets.xcassets/Images/EmptyTravelList.imageset/Contents.json index 22114761..415e8627 100644 --- a/DesignSystem/Resources/Assets.xcassets/Images/EmptyTravelList.imageset/Contents.json +++ b/DesignSystem/Resources/Assets.xcassets/Images/EmptyTravelList.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "ChatGPT Image 2025년 12월 12일 오후 02_55_30 1.png", + "filename" : "travelList.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/DesignSystem/Resources/Assets.xcassets/Images/EmptyTravelList.imageset/travelList.png b/DesignSystem/Resources/Assets.xcassets/Images/EmptyTravelList.imageset/travelList.png new file mode 100644 index 0000000000000000000000000000000000000000..71d3b5f536f71b896e796ee7232931f36e09f88f GIT binary patch literal 17286 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 Date: Tue, 16 Dec 2025 21:04:26 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20EmptyCaseView=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DesignSystem/Sources/View/EmptyCaseView.swift | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 DesignSystem/Sources/View/EmptyCaseView.swift diff --git a/DesignSystem/Sources/View/EmptyCaseView.swift b/DesignSystem/Sources/View/EmptyCaseView.swift new file mode 100644 index 00000000..55b6f26f --- /dev/null +++ b/DesignSystem/Sources/View/EmptyCaseView.swift @@ -0,0 +1,34 @@ +// +// EmptyCaseView.swift +// DesignSystem +// +// Created by 홍석현 on 12/16/25. +// + +import SwiftUI + +public struct EmptyCaseView: View { + private let image: ImageAsset + private let message: String + + public init(image: ImageAsset, message: String) { + self.image = image + self.message = message + } + + public var body: some View { + VStack(spacing: 16) { + Image(asset: image) + .resizable() + .frame(width: 167, height: 167) + Text(message) + .font(.app(.title3, weight: .medium)) + .foregroundStyle(Color.gray6) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } +} + +#Preview { + EmptyCaseView(image: .expenseEmpty, message: "아직 지출이 없어요") +} From 55e960fcd954e63b7f03713e0b255b454949090a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=92=E1=85=A9=E1=86=BC=E1=84=89=E1=85=A5=E1=86=A8?= =?UTF-8?q?=E1=84=92=E1=85=A7=E1=86=AB?= Date: Tue, 16 Dec 2025 21:04:50 +0900 Subject: [PATCH 3/9] =?UTF-8?q?style:=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Components/ExpenseCardView.swift | 7 +- .../Components/SettlementHeaderView.swift | 2 +- .../ExpenseList/Sources/ExpenseListView.swift | 85 +++++---- .../Settlement/Sources/SettlementView.swift | 4 +- .../SettlementResultHeaderView.swift | 60 +------ .../Sources/Components/StatItemView.swift | 36 ++++ .../Sources/SettlementResultView.swift | 161 +++++++++++------- 7 files changed, 195 insertions(+), 160 deletions(-) create mode 100644 Features/SettlementResult/Sources/Components/StatItemView.swift diff --git a/Features/ExpenseList/Sources/Components/ExpenseCardView.swift b/Features/ExpenseList/Sources/Components/ExpenseCardView.swift index 4f957c23..26ce9c0c 100644 --- a/Features/ExpenseList/Sources/Components/ExpenseCardView.swift +++ b/Features/ExpenseList/Sources/Components/ExpenseCardView.swift @@ -71,14 +71,13 @@ public struct ExpenseCardView: View { .foregroundStyle(Color.gray7) } .padding(16) - .background(Color.white) + .background(Color.primary50) .clipShape(RoundedRectangle(cornerRadius: 16)) - .shadow(color: .black.opacity(0.25), radius: 5, x: 0, y: 4) .overlay( RoundedRectangle(cornerRadius: 16) - .stroke(Color.gray.opacity(0.1), lineWidth: 1) + .stroke(Color.gray1, lineWidth: 1) ) - .padding(.horizontal, 16) + .padding(.horizontal, 20) } } diff --git a/Features/ExpenseList/Sources/Components/SettlementHeaderView.swift b/Features/ExpenseList/Sources/Components/SettlementHeaderView.swift index 5d02d6b3..9ada9335 100644 --- a/Features/ExpenseList/Sources/Components/SettlementHeaderView.swift +++ b/Features/ExpenseList/Sources/Components/SettlementHeaderView.swift @@ -95,7 +95,7 @@ public struct SettlementHeaderView: View { selectedDateRange: $selectedDateRange, currentPage: $currentPage ) - .padding(.horizontal, 16) + .padding(.horizontal, 20) } } diff --git a/Features/ExpenseList/Sources/ExpenseListView.swift b/Features/ExpenseList/Sources/ExpenseListView.swift index a4e8f4f0..cc34fcd7 100644 --- a/Features/ExpenseList/Sources/ExpenseListView.swift +++ b/Features/ExpenseList/Sources/ExpenseListView.swift @@ -26,49 +26,64 @@ public struct ExpenseListView: View { totalAmount: store.formattedTotalAmount, startDate: store.startDate, endDate: store.endDate, - myExpenseAmount: store.formattedTotalAmount, // 임시로 동일 + myExpenseAmount: store.formattedTotalAmount, expenses: store.allExpenses, selectedDateRange: $store.selectedDateRange, currentPage: $store.currentPage ) - // 카테고리 필터 - CategoryFilterView( - selectedCategory: $store.selectedCategory - ) - .padding(.horizontal, 16) - .padding(.bottom, 16) - - // 지출 내역 리스트 - ScrollView { - LazyVStack(spacing: 16) { - ForEach(store.currentExpense) { expense in - ExpenseCardView(expense: expense) - .onTapGesture { - send(.onTapExpense(expense)) - } - .transition(.asymmetric( - insertion: .scale(scale: 0.95, anchor: .top) - .combined(with: .opacity) - .combined(with: .move(edge: .top)), - removal: .scale(scale: 0.95, anchor: .top) - .combined(with: .opacity) - )) + VStack(spacing: 0) { + // 카테고리 필터 + CategoryFilterView( + selectedCategory: $store.selectedCategory + ) + .padding(.horizontal, 20) + .padding(.top, 12) + .padding(.bottom, 8) + + // 지출 내역 리스트 + ScrollView { + LazyVStack(spacing: 16) { + ForEach(store.currentExpense) { expense in + ExpenseCardView(expense: expense) + .onTapGesture { + send(.onTapExpense(expense)) + } + .transition(.asymmetric( + insertion: .scale(scale: 0.95, anchor: .top) + .combined(with: .opacity) + .combined(with: .move(edge: .top)), + removal: .scale(scale: 0.95, anchor: .top) + .combined(with: .opacity) + )) + } + } + .padding(.bottom, 16) + .animation(.spring(response: 0.35, dampingFraction: 0.75), value: store.currentExpense.count) + } + .scrollIndicators(.hidden) + .overlay { + if store.currentExpense.isEmpty { + EmptyCaseView(image: .expenseEmpty, message: "아직 지출이 없어요") } } - .padding(.vertical, 10) - .animation(.spring(response: 0.35, dampingFraction: 0.75), value: store.currentExpense.count) } - .scrollIndicators(.hidden) + .background(Color.primary50) + .clipShape( + UnevenRoundedRectangle( + topLeadingRadius: 20, + topTrailingRadius: 20 + ) + ) + .overlay( + UnevenRoundedRectangle( + topLeadingRadius: 20, + topTrailingRadius: 20 + ) + .stroke(Color.gray1, lineWidth: 1) + ) } else { - VStack { - Image(asset: .expenseEmpty) - .resizable() - .frame(width: 167, height: 167) - Text("아직 지출이 없어요") - .font(.app(.title3, weight: .medium)) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) + EmptyCaseView(image: .expenseEmpty, message: "아직 지출이 없어요") } } .overlay { @@ -82,7 +97,7 @@ public struct ExpenseListView: View { send(.addExpenseButtonTapped) } .padding(.trailing, 20) - .padding(.bottom, 20) + .padding(.bottom, 54) } } .onAppear { diff --git a/Features/Settlement/Sources/SettlementView.swift b/Features/Settlement/Sources/SettlementView.swift index 66b9230a..9d957b31 100644 --- a/Features/Settlement/Sources/SettlementView.swift +++ b/Features/Settlement/Sources/SettlementView.swift @@ -33,7 +33,7 @@ public struct SettlementView: View { send(.settingsButtonTapped) } } - .padding(.horizontal, 16) + .padding(.horizontal, 20) // 탭 바 (Custom Segmented Control) HStack(spacing: 0) { TabButton(title: "지출 내역", isSelected: store.selectedTab == 0) { @@ -43,7 +43,7 @@ public struct SettlementView: View { send(.tabSelected(1)) } } - .padding(.horizontal, 16) + .padding(.horizontal, 20) // 컨텐츠 영역 if store.selectedTab == 0 { diff --git a/Features/SettlementResult/Sources/Components/SettlementResultHeaderView.swift b/Features/SettlementResult/Sources/Components/SettlementResultHeaderView.swift index 4afdda24..ef303251 100644 --- a/Features/SettlementResult/Sources/Components/SettlementResultHeaderView.swift +++ b/Features/SettlementResult/Sources/Components/SettlementResultHeaderView.swift @@ -10,71 +10,23 @@ import DesignSystem struct SettlementResultHeaderView: View { let totalExpenseAmount: String - let myExpenseAmount: String - let totalPersonCount: Int - let averageExpensePerPerson: String - - var body: some View { - VStack(spacing: 16) { - // 총 지출 - VStack(spacing: 8) { - Text("총 지출") - .font(.app(.body, weight: .medium)) - .foregroundStyle(Color.gray7) - - Text(totalExpenseAmount) - .font(.app(.title1, weight: .semibold)) - .foregroundStyle(Color.black) - } - .padding(12) - - // 통계 정보 - HStack(spacing: 0) { - StatItemView( - label: "내 지출", - value: myExpenseAmount - ) - - StatItemView( - label: "인원 수", - value: "\(totalPersonCount)명" - ) - - StatItemView( - label: "1인 평균", - value: averageExpensePerPerson - ) - } - .padding(.vertical, 20) - .frame(maxWidth: .infinity) - } - } -} - -// MARK: - Stat Item View -private struct StatItemView: View { - let label: String - let value: String var body: some View { VStack(spacing: 8) { - Text(label) - .font(.app(.caption1, weight: .semibold)) + Text("총 지출") + .font(.app(.body, weight: .medium)) .foregroundStyle(Color.gray7) - Text(value) - .font(.app(.title3, weight: .semibold)) + Text(totalExpenseAmount) + .font(.app(.title1, weight: .semibold)) .foregroundStyle(Color.black) } - .frame(maxWidth: .infinity) + .padding(.vertical, 12) } } #Preview { SettlementResultHeaderView( - totalExpenseAmount: "124만 5,000원", - myExpenseAmount: "52만원", - totalPersonCount: 5, - averageExpensePerPerson: "24만 9,000원" + totalExpenseAmount: "124만 5,000원" ) } diff --git a/Features/SettlementResult/Sources/Components/StatItemView.swift b/Features/SettlementResult/Sources/Components/StatItemView.swift new file mode 100644 index 00000000..5eddaf6d --- /dev/null +++ b/Features/SettlementResult/Sources/Components/StatItemView.swift @@ -0,0 +1,36 @@ +// +// StatItemView.swift +// SettlementResultFeature +// +// Created by 홍석현 on 12/16/25. +// + +import SwiftUI +import DesignSystem + +struct StatItemView: View { + let label: String + let value: String + + var body: some View { + VStack(spacing: 8) { + Text(label) + .font(.app(.caption1, weight: .semibold)) + .foregroundStyle(Color.gray7) + + Text(value) + .font(.app(.title3, weight: .semibold)) + .foregroundStyle(Color.black) + } + .frame(maxWidth: .infinity) + } +} + +#Preview { + HStack(spacing: 0) { + StatItemView(label: "내 지출", value: "52만원") + StatItemView(label: "인원 수", value: "5명") + StatItemView(label: "1인 평균", value: "24만 9,000원") + } + .padding() +} diff --git a/Features/SettlementResult/Sources/SettlementResultView.swift b/Features/SettlementResult/Sources/SettlementResultView.swift index 32045749..c6768954 100644 --- a/Features/SettlementResult/Sources/SettlementResultView.swift +++ b/Features/SettlementResult/Sources/SettlementResultView.swift @@ -23,76 +23,109 @@ public struct SettlementResultView: View { VStack(spacing: 0) { // 헤더 (총 지출, 통계) if !store.paymentsToMake.isEmpty || !store.paymentsToReceive.isEmpty { - SettlementResultHeaderView( - totalExpenseAmount: store.formattedTotalExpenseAmount, - myExpenseAmount: store.formattedMyExpenseAmount, - totalPersonCount: store.totalPersonCount, - averageExpensePerPerson: store.formattedAveragePerPerson - ) - - - // 지급/수령 예정 금액 섹션 - ScrollView { - VStack(spacing: 8) { - if !store.paymentsToMake.isEmpty { - PaymentSectionView( - title: "지급 예정 금액", - totalAmount: CurrencyFormatter.formatKoreanCurrency( - store.paymentsToMake.reduce(0.0) { $0 + $1.amount } - ), - amountColor: .red, - payments: store.paymentsToMake.map { - PaymentItem( - id: $0.id, - name: $0.memberName, - amount: CurrencyFormatter.formatKoreanCurrency($0.amount) - ) - } - ) - } - - if !store.paymentsToReceive.isEmpty { - PaymentSectionView( - title: "수령 예정 금액", - totalAmount: CurrencyFormatter.formatKoreanCurrency( - store.paymentsToReceive.reduce(0.0) { $0 + $1.amount } - ), - amountColor: .primary500, - payments: store.paymentsToReceive.map { - PaymentItem( - id: $0.id, - name: $0.memberName, - amount: CurrencyFormatter.formatKoreanCurrency($0.amount) - ) - } - ) + // 총 지출 (별도) + VStack(spacing: 8) { + Text("총 지출") + .font(.app(.body, weight: .medium)) + .foregroundStyle(Color.gray7) + + Text(store.formattedTotalExpenseAmount) + .font(.app(.title1, weight: .semibold)) + .foregroundStyle(Color.black) + } + .padding(.vertical, 24) + + VStack(spacing: 0) { + // 통계 정보 (내 지출, 인원수, 1인 평균) + HStack(spacing: 0) { + StatItemView( + label: "내 지출", + value: store.formattedMyExpenseAmount + ) + + StatItemView( + label: "인원 수", + value: "\(store.totalPersonCount)명" + ) + + StatItemView( + label: "1인 평균", + value: store.formattedAveragePerPerson + ) + } + .padding(.vertical, 20) + .frame(maxWidth: .infinity) + + // 지급/수령 예정 금액 섹션 + ScrollView { + VStack(spacing: 8) { + if !store.paymentsToMake.isEmpty { + PaymentSectionView( + title: "지급 예정 금액", + totalAmount: CurrencyFormatter.formatKoreanCurrency( + store.paymentsToMake.reduce(0.0) { $0 + $1.amount } + ), + amountColor: .red, + payments: store.paymentsToMake.map { + PaymentItem( + id: $0.id, + name: $0.memberName, + amount: CurrencyFormatter.formatKoreanCurrency($0.amount) + ) + } + ) + } + + if !store.paymentsToReceive.isEmpty { + PaymentSectionView( + title: "수령 예정 금액", + totalAmount: CurrencyFormatter.formatKoreanCurrency( + store.paymentsToReceive.reduce(0.0) { $0 + $1.amount } + ), + amountColor: .primary500, + payments: store.paymentsToReceive.map { + PaymentItem( + id: $0.id, + name: $0.memberName, + amount: CurrencyFormatter.formatKoreanCurrency($0.amount) + ) + } + ) + } } + .padding(.bottom, 16) } + .scrollIndicators(.hidden) + + // 상세보기 버튼 + Button { + send(.detailButtonTapped) + } label: { + Text("정산 내역 보기") + .font(.app(.body, weight: .medium)) + .foregroundStyle(Color.gray9) + } + .frame(maxWidth: .infinity, alignment: .center) + .padding(.vertical, 16) } - .scrollIndicators(.hidden) - - // 상세보기 버튼 - Button { - send(.detailButtonTapped) - } label: { - Text("정산 내역 보기") - .font(.app(.body, weight: .medium)) - .foregroundStyle(Color.gray9) - } - .frame(maxWidth: .infinity, alignment: .center) - .padding(16) + .padding(.horizontal, 20) + .clipShape( + UnevenRoundedRectangle( + topLeadingRadius: 20, + topTrailingRadius: 20 + ) + ) + .overlay( + UnevenRoundedRectangle( + topLeadingRadius: 20, + topTrailingRadius: 20 + ) + .stroke(Color.gray1, lineWidth: 1) + ) } else { - VStack { - Image(asset: .settlementEmpty) - .resizable() - .frame(width: 167, height: 167) - Text("정산 내역이 없습니다") - .font(.app(.title3, weight: .medium)) - } - .frame(maxHeight: .infinity) + EmptyCaseView(image: .settlementEmpty, message: "정산 내역이 없습니다") } } - .padding(.horizontal, 16) .background(Color.primary50) .onAppear { send(.onAppear) From eda6c5d26767b95711036c558c488dae7e44b7f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=92=E1=85=A9=E1=86=BC=E1=84=89=E1=85=A5=E1=86=A8?= =?UTF-8?q?=E1=84=92=E1=85=A7=E1=86=AB?= Date: Wed, 17 Dec 2025 08:10:42 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EC=A0=95=EC=82=B0=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EB=82=A0=EC=A7=9C=20=EB=B2=94=EC=9C=84=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Components/DateRangePicker.swift | 215 ++++++++++++++++++ .../Components/SettlementHeaderView.swift | 61 ++--- 2 files changed, 228 insertions(+), 48 deletions(-) create mode 100644 Features/ExpenseList/Sources/Components/DateRangePicker.swift diff --git a/Features/ExpenseList/Sources/Components/DateRangePicker.swift b/Features/ExpenseList/Sources/Components/DateRangePicker.swift new file mode 100644 index 00000000..ee92578c --- /dev/null +++ b/Features/ExpenseList/Sources/Components/DateRangePicker.swift @@ -0,0 +1,215 @@ +// +// DateRangePicker.swift +// ExpenseListFeature +// +// Created by Claude on 12/16/24. +// + +import SwiftUI +import UIKit + +// MARK: - DateRangePicker (SwiftUI Wrapper) + +struct DateRangePicker: View { + let startDate: Date + let endDate: Date + @Binding var selectedRange: ClosedRange? + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationView { + CalendarView( + startDate: startDate, + endDate: endDate, + selectedRange: $selectedRange + ) + .navigationTitle("기간 선택") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("닫기") { + dismiss() + } + } + } + } + .presentationDetents([.medium, .large]) + .presentationDragIndicator(.visible) + } +} + +// MARK: - CalendarView (UIViewRepresentable) + +struct CalendarView: UIViewRepresentable { + let startDate: Date + let endDate: Date + @Binding var selectedRange: ClosedRange? + + func makeCoordinator() -> Coordinator { + Coordinator(selectedRange: $selectedRange) + } + + func makeUIView(context: Context) -> UICalendarView { + let calendarView = UICalendarView() + calendarView.calendar = Calendar.current + calendarView.locale = Locale.current + calendarView.fontDesign = .rounded + + // 날짜 범위 제한 + let dateInterval = DateInterval(start: startDate, end: endDate) + calendarView.availableDateRange = dateInterval + + // MultiDate selection 설정 + let selection = UICalendarSelectionMultiDate(delegate: context.coordinator) + calendarView.selectionBehavior = selection + context.coordinator.selection = selection + + // 초기 선택 설정 + if let range = selectedRange { + context.coordinator.loadInitialRange(range) + } + + return calendarView + } + + func updateUIView(_ uiView: UICalendarView, context: Context) { + // selectedRange가 외부에서 변경되면 업데이트 + context.coordinator.externalRangeUpdate(selectedRange) + } + + // MARK: - Coordinator + + class Coordinator: NSObject, UICalendarSelectionMultiDateDelegate { + @Binding var selectedRange: ClosedRange? + var selection: UICalendarSelectionMultiDate? + + private var rangeStart: Date? + private var rangeEnd: Date? + private let calendar = Calendar.current + + init(selectedRange: Binding?>) { + self._selectedRange = selectedRange + } + + // 초기 범위 로드 + func loadInitialRange(_ range: ClosedRange) { + let start = calendar.startOfDay(for: range.lowerBound) + let end = calendar.startOfDay(for: range.upperBound) + + rangeStart = start + rangeEnd = end + + // 범위 내 모든 날짜 선택 + var dates: [DateComponents] = [] + var current = start + while current <= end { + let components = calendar.dateComponents([.year, .month, .day], from: current) + dates.append(components) + guard let next = calendar.date(byAdding: .day, value: 1, to: current) else { break } + current = next + } + + selection?.setSelectedDates(dates, animated: false) + } + + // 외부에서 selectedRange 변경 시 + func externalRangeUpdate(_ newRange: ClosedRange?) { + guard let range = newRange else { + rangeStart = nil + rangeEnd = nil + selection?.setSelectedDates([], animated: false) + return + } + + // 현재 내부 상태와 다를 때만 업데이트 + if rangeStart != range.lowerBound || rangeEnd != range.upperBound { + loadInitialRange(range) + } + } + + // 날짜 선택 가능 여부 + func multiDateSelection(_ selection: UICalendarSelectionMultiDate, canSelectDate dateComponents: DateComponents) -> Bool { + return true + } + + // 날짜 해제 가능 여부 + func multiDateSelection(_ selection: UICalendarSelectionMultiDate, canDeselectDate dateComponents: DateComponents) -> Bool { + return true + } + + // 날짜가 선택/해제되었을 때 + func multiDateSelection(_ selection: UICalendarSelectionMultiDate, didSelectDate dateComponents: DateComponents) { + guard let selectedDate = calendar.date(from: dateComponents) else { return } + let date = calendar.startOfDay(for: selectedDate) + + print("🔵 날짜 선택: \(date)") + + // 첫 번째 선택 (start) + if rangeStart == nil { + print(" 📍 첫 번째 선택 (start)") + rangeStart = date + rangeEnd = date + selection.setSelectedDates([dateComponents], animated: false) + updateBinding() + } + // 두 번째 선택 (end) + else if let start = rangeStart, calendar.isDate(start, inSameDayAs: rangeEnd ?? start) { + print(" 📍 두 번째 선택 (end)") + if date < start { + rangeStart = date + rangeEnd = start + } else { + rangeEnd = date + } + fillRange() + updateBinding() + } + // 세 번째 이후 선택 (리셋) + else { + print(" 🔄 세 번째 이후 선택 (리셋)") + rangeStart = date + rangeEnd = date + selection.setSelectedDates([dateComponents], animated: false) + updateBinding() + } + } + + func multiDateSelection(_ selection: UICalendarSelectionMultiDate, didDeselectDate dateComponents: DateComponents) { + print("❌ 날짜 해제") + // 날짜 해제 시 전체 리셋 + rangeStart = nil + rangeEnd = nil + selection.setSelectedDates([], animated: false) + selectedRange = nil + } + + // 범위 채우기 + private func fillRange() { + guard let start = rangeStart, let end = rangeEnd else { return } + + var dates: [DateComponents] = [] + var current = start + while current <= end { + let components = calendar.dateComponents([.year, .month, .day], from: current) + dates.append(components) + guard let next = calendar.date(byAdding: .day, value: 1, to: current) else { break } + current = next + } + + selection?.setSelectedDates(dates, animated: false) + print(" 🟢 범위 채우기 완료: \(dates.count)개 날짜") + } + + // Binding 업데이트 + private func updateBinding() { + guard let start = rangeStart, let end = rangeEnd else { + selectedRange = nil + print(" 🔴 selectedRange -> nil") + return + } + + selectedRange = start...end + print(" 🟢 selectedRange 업데이트: \(start) ~ \(end)") + } + } +} diff --git a/Features/ExpenseList/Sources/Components/SettlementHeaderView.swift b/Features/ExpenseList/Sources/Components/SettlementHeaderView.swift index 9ada9335..6dc9fe1f 100644 --- a/Features/ExpenseList/Sources/Components/SettlementHeaderView.swift +++ b/Features/ExpenseList/Sources/Components/SettlementHeaderView.swift @@ -18,6 +18,8 @@ public struct SettlementHeaderView: View { @Binding var selectedDateRange: ClosedRange? @Binding var currentPage: Int + @State private var showDatePicker = false + public init( totalAmount: String, startDate: Date, @@ -35,38 +37,13 @@ public struct SettlementHeaderView: View { self._selectedDateRange = selectedDateRange self._currentPage = currentPage } - + public var body: some View { VStack(spacing: 0) { VStack(spacing: 8) { - // 날짜 선택 (드롭다운 느낌) - Menu { - Button { - selectedDateRange = nil - } label: { - HStack { - Text("전체") - if selectedDateRange == nil { - Image(systemName: "checkmark") - } - } - } - - ForEach(datesRange, id: \.self) { date in - Button { - // 단일 날짜 선택 (같은 날짜의 범위) - selectedDateRange = date...date - } label: { - HStack { - Text(dateFormatter.string(from: date)) - if let range = selectedDateRange, - Calendar.current.isDate(range.lowerBound, inSameDayAs: date), - Calendar.current.isDate(range.upperBound, inSameDayAs: date) { - Image(systemName: "checkmark") - } - } - } - } + // 날짜 선택 버튼 + Button { + showDatePicker = true } label: { HStack(spacing: 4) { Text(selectedDateLabel) @@ -77,6 +54,13 @@ public struct SettlementHeaderView: View { .foregroundStyle(Color.gray5) } } + .sheet(isPresented: $showDatePicker) { + DateRangePicker( + startDate: startDate, + endDate: endDate, + selectedRange: $selectedDateRange + ) + } // 총 지출 금액 @@ -116,25 +100,6 @@ public struct SettlementHeaderView: View { } } - private var datesRange: [Date] { - var dates: [Date] = [] - let calendar = Calendar.current - // 시작일의 00:00:00으로 정규화 - let start = calendar.startOfDay(for: startDate) - let end = calendar.startOfDay(for: endDate) - - var currentDate = start - while currentDate <= end { - dates.append(currentDate) - if let nextDate = calendar.date(byAdding: .day, value: 1, to: currentDate) { - currentDate = nextDate - } else { - break - } - } - return dates - } - private var dateFormatter: DateFormatter { let formatter = DateFormatter() formatter.dateFormat = "yyyy.MM.dd" From 599c853538f050d7d211143bdbf7dcdd3169197e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=92=E1=85=A9=E1=86=BC=E1=84=89=E1=85=A5=E1=86=A8?= =?UTF-8?q?=E1=84=92=E1=85=A7=E1=86=AB?= Date: Wed, 17 Dec 2025 08:10:57 +0900 Subject: [PATCH 5/9] =?UTF-8?q?refactor:=20MainCoordinator=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Features/Main/Sources/MainCoordinator.swift | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Features/Main/Sources/MainCoordinator.swift b/Features/Main/Sources/MainCoordinator.swift index e29d1a67..4f4ded41 100644 --- a/Features/Main/Sources/MainCoordinator.swift +++ b/Features/Main/Sources/MainCoordinator.swift @@ -108,18 +108,6 @@ extension MainCoordinator { state.routes.push(.memberManage(.init(travelId: travelId))) return .none - -// case .routeAction(_, .travelSetting(.delegate(.navigateToTravelDetail(_)))): -// return .routeWithDelaysIfUnsupported(state.routes, action: \.router) { -// $0.goBackTo(\.travelList) -// } - -// case let .routeAction(_, .travelSetting(.delegate(.navigateToTravelDetail(travelId)))): -// return .routeWithDelaysIfUnsupported(state.routes, action: \.router) { -// $0.goBackTo(\.travelList) -// } - - case .routeAction(_, .memberManage(.delegate(.back))): state.routes.goBack() return .none From 21a7f8c509c51d0103a54ec0a0dfa0d006380948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=92=E1=85=A9=E1=86=BC=E1=84=89=E1=85=A5=E1=86=A8?= =?UTF-8?q?=E1=84=92=E1=85=A7=E1=86=AB?= Date: Wed, 17 Dec 2025 08:33:18 +0900 Subject: [PATCH 6/9] =?UTF-8?q?refactor:=20=EB=93=9C=EB=9E=98=EA=B7=B8=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EB=8B=A8?= =?UTF-8?q?=EC=9D=BC=20=EC=84=A0=ED=83=9D=20=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Components/ExpenseChartView.swift | 100 +++++------------- 1 file changed, 26 insertions(+), 74 deletions(-) diff --git a/Features/ExpenseList/Sources/Components/ExpenseChartView.swift b/Features/ExpenseList/Sources/Components/ExpenseChartView.swift index a596f190..22345c39 100644 --- a/Features/ExpenseList/Sources/Components/ExpenseChartView.swift +++ b/Features/ExpenseList/Sources/Components/ExpenseChartView.swift @@ -18,7 +18,6 @@ struct ExpenseChartView: View { @Binding var currentPage: Int private let barWidth: CGFloat = 18 - @State private var dragStartDateString: String? private static let mmddFormatter: DateFormatter = { let formatter = DateFormatter() @@ -102,6 +101,15 @@ struct ExpenseChartView: View { } } .frame(height: 126) + .onChange(of: selectedDateString) { newValue in + guard let dateStr = newValue, + let date = Expense.parseDate(dateStr) else { return } + + // 탭한 날짜만 선택 (단일 날짜 범위) + let calendar = Calendar.current + let selected = calendar.startOfDay(for: date) + selectedDateRange = selected...selected + } } private func chartView(for chunk: [String]) -> some View { @@ -130,91 +138,35 @@ struct ExpenseChartView: View { } } } - .chartXSelection(value: currentSelectionBinding) .chartGesture { proxy in - DragGesture(minimumDistance: 0) - .onChanged { value in - handleDragChange(value: value, proxy: proxy) - } + SpatialTapGesture() .onEnded { value in - handleDragEnd(value: value, proxy: proxy) + // 정확한 타입 추론을 위해 as: String.self 명시 + if let dateString = proxy.value(atX: value.location.x, as: String.self) { + selectedDateString = dateString + } } } .frame(height: 94) } - // 현재 선택 중인 날짜 (드래그 중일 수도 있음) - private var currentSelectionBinding: Binding { - Binding( - get: { - if let dragStart = dragStartDateString { - return dragStart - } - if let range = selectedDateRange { - return Expense.formatDate(range.lowerBound) - } - return nil - }, - set: { _ in } - ) - } - - private func handleDragChange(value: DragGesture.Value, proxy: ChartProxy) { - let location = value.location - if let dateStr: String = proxy.value(atX: location.x, as: String.self) { - if dragStartDateString == nil { - // 드래그 시작 - dragStartDateString = dateStr - } else { - // 드래그 중 - 범위 업데이트 - updateRangeSelection(from: dragStartDateString!, to: dateStr) - } - } - } + @State private var selectedDateString: String? - private func handleDragEnd(value: DragGesture.Value, proxy: ChartProxy) { - let location = value.location - if let endDateStr: String = proxy.value(atX: location.x, as: String.self), - let startDateStr = dragStartDateString { + private func barColor(for dateString: String) -> Color { + guard let date = Expense.parseDate(dateString) else { return .primary100 } - let dragDistance = value.translation.width + // 선택된 범위가 있으면 범위 체크 + if let range = selectedDateRange { + let calendar = Calendar.current + let dateDay = calendar.startOfDay(for: date) + let rangeStart = calendar.startOfDay(for: range.lowerBound) + let rangeEnd = calendar.startOfDay(for: range.upperBound) - // 드래그가 거의 없었으면 단일 선택 (탭) - if abs(dragDistance) < 10 { - // 단일 날짜 선택 - if let date = Expense.parseDate(startDateStr) { - selectedDateRange = date...date - } - } else { - // 범위 선택 - updateRangeSelection(from: startDateStr, to: endDateStr) - } + return (dateDay >= rangeStart && dateDay <= rangeEnd) ? .primary500 : .primary100 } - // 드래그 상태 초기화 - dragStartDateString = nil - } - - private func updateRangeSelection(from startStr: String, to endStr: String) { - guard let startDate = Expense.parseDate(startStr), - let endDate = Expense.parseDate(endStr) else { return } - - // 시작과 끝을 정렬 - let lower = min(startDate, endDate) - let upper = max(startDate, endDate) - selectedDateRange = lower...upper - } - - private func barColor(for dateString: String) -> Color { - guard let range = selectedDateRange else { return .primary500 } - guard let date = Expense.parseDate(dateString) else { return .primary500 } - - let calendar = Calendar.current - let dateDay = calendar.startOfDay(for: date) - let rangeStart = calendar.startOfDay(for: range.lowerBound) - let rangeEnd = calendar.startOfDay(for: range.upperBound) - // 범위 내에 있으면 primary500, 아니면 primary100 - return (dateDay >= rangeStart && dateDay <= rangeEnd) ? .primary500 : .primary100 + // 선택된 범위가 없으면 기본 색상 + return .primary100 } } From 436a12606b53a8274901f35d642dbb4249d45615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=92=E1=85=A9=E1=86=BC=E1=84=89=E1=85=A5=E1=86=A8?= =?UTF-8?q?=E1=84=92=E1=85=A7=E1=86=AB?= Date: Wed, 17 Dec 2025 09:18:53 +0900 Subject: [PATCH 7/9] =?UTF-8?q?style:=20emptycaseview=20=EA=B8=80=EC=9E=90?= =?UTF-8?q?=20=EC=83=89=EC=83=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DesignSystem/Sources/View/EmptyCaseView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DesignSystem/Sources/View/EmptyCaseView.swift b/DesignSystem/Sources/View/EmptyCaseView.swift index 55b6f26f..38ea567f 100644 --- a/DesignSystem/Sources/View/EmptyCaseView.swift +++ b/DesignSystem/Sources/View/EmptyCaseView.swift @@ -23,7 +23,7 @@ public struct EmptyCaseView: View { .frame(width: 167, height: 167) Text(message) .font(.app(.title3, weight: .medium)) - .foregroundStyle(Color.gray6) + .foregroundStyle(Color.black) } .frame(maxWidth: .infinity, maxHeight: .infinity) } From fead5324aebab63e9734af503f08d228dfbc68b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=92=E1=85=A9=E1=86=BC=E1=84=89=E1=85=A5=E1=86=A8?= =?UTF-8?q?=E1=84=92=E1=85=A7=E1=86=AB?= Date: Wed, 17 Dec 2025 09:19:18 +0900 Subject: [PATCH 8/9] =?UTF-8?q?refactor:=20=EB=82=A0=EC=A7=9C=20=EB=B2=94?= =?UTF-8?q?=EC=9C=84=20=EC=84=A0=ED=83=9D=20=EA=B0=80=EB=8A=A5=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20chart=20?= =?UTF-8?q?=EB=8B=A8=EC=9D=BC=20=EC=84=A0=ED=83=9D=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Components/ExpenseChartView.swift | 2 +- .../ExpenseList/Sources/ExpenseListFeature.swift | 7 +------ .../ExpenseList/Sources/ExpenseListView.swift | 3 ++- .../Settlement/Sources/SettlementFeature.swift | 15 ++++++++++++++- .../Sources/SettlementResultView.swift | 4 +++- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Features/ExpenseList/Sources/Components/ExpenseChartView.swift b/Features/ExpenseList/Sources/Components/ExpenseChartView.swift index 22345c39..a123b26e 100644 --- a/Features/ExpenseList/Sources/Components/ExpenseChartView.swift +++ b/Features/ExpenseList/Sources/Components/ExpenseChartView.swift @@ -101,7 +101,7 @@ struct ExpenseChartView: View { } } .frame(height: 126) - .onChange(of: selectedDateString) { newValue in + .onChange(of: selectedDateString) { _, newValue in guard let dateStr = newValue, let date = Expense.parseDate(dateStr) else { return } diff --git a/Features/ExpenseList/Sources/ExpenseListFeature.swift b/Features/ExpenseList/Sources/ExpenseListFeature.swift index ddc32bf7..07385670 100644 --- a/Features/ExpenseList/Sources/ExpenseListFeature.swift +++ b/Features/ExpenseList/Sources/ExpenseListFeature.swift @@ -26,7 +26,7 @@ public struct ExpenseListFeature { public var endDate: Date { return travel?.endDate ?? Date() } - public var selectedDateRange: ClosedRange? = nil + public var selectedDateRange: ClosedRange? public var currentPage: Int = 0 public var selectedCategory: ExpenseCategory? = nil public let travelId: String @@ -138,11 +138,6 @@ public struct ExpenseListFeature { } } return .none - case .binding(\.currentPage): - // 페이지 변경 시 선택된 날짜 초기화 및 해당 페이지 데이터로 필터링 - state.selectedDateRange = nil - applyFilters(&state) - return .none case .binding(\.selectedCategory): // 카테고리 변경 시 필터링 applyFilters(&state) diff --git a/Features/ExpenseList/Sources/ExpenseListView.swift b/Features/ExpenseList/Sources/ExpenseListView.swift index cc34fcd7..99070199 100644 --- a/Features/ExpenseList/Sources/ExpenseListView.swift +++ b/Features/ExpenseList/Sources/ExpenseListView.swift @@ -58,7 +58,7 @@ public struct ExpenseListView: View { )) } } - .padding(.bottom, 16) + .padding(.bottom, 16 + 54) .animation(.spring(response: 0.35, dampingFraction: 0.75), value: store.currentExpense.count) } .scrollIndicators(.hidden) @@ -100,6 +100,7 @@ public struct ExpenseListView: View { .padding(.bottom, 54) } } + .ignoresSafeArea() .onAppear { send(.onAppear) } diff --git a/Features/Settlement/Sources/SettlementFeature.swift b/Features/Settlement/Sources/SettlementFeature.swift index 3929863f..3f3e4ff4 100644 --- a/Features/Settlement/Sources/SettlementFeature.swift +++ b/Features/Settlement/Sources/SettlementFeature.swift @@ -164,13 +164,26 @@ extension SettlementFeature { state.$travel.withLock { $0 = travel } + // 초기 날짜 범위를 전체 여행 기간으로 설정 + if state.expenseList.selectedDateRange == nil { + let calendar = Calendar.current + let start = calendar.startOfDay(for: travel.startDate) + let end = calendar.startOfDay(for: travel.endDate) + state.expenseList.selectedDateRange = start...end + } return .none case let .travelDetailResponse(.success(travel)): state.$travel.withLock { $0 = travel } - + // 초기 날짜 범위를 전체 여행 기간으로 설정 + if state.expenseList.selectedDateRange == nil { + let calendar = Calendar.current + let start = calendar.startOfDay(for: travel.startDate) + let end = calendar.startOfDay(for: travel.endDate) + state.expenseList.selectedDateRange = start...end + } return .none case let .travelDetailResponse(.failure(error)): diff --git a/Features/SettlementResult/Sources/SettlementResultView.swift b/Features/SettlementResult/Sources/SettlementResultView.swift index c6768954..3e76c7bf 100644 --- a/Features/SettlementResult/Sources/SettlementResultView.swift +++ b/Features/SettlementResult/Sources/SettlementResultView.swift @@ -106,7 +106,8 @@ public struct SettlementResultView: View { .foregroundStyle(Color.gray9) } .frame(maxWidth: .infinity, alignment: .center) - .padding(.vertical, 16) + .padding(.top, 16) + .safeAreaPadding(34) } .padding(.horizontal, 20) .clipShape( @@ -130,6 +131,7 @@ public struct SettlementResultView: View { .onAppear { send(.onAppear) } + .ignoresSafeArea() .alert($store.scope(state: \.alert, action: \.scope.alert)) .sheet( item: $store.scope( From 59b8d9623ea06b07cb94834bf331336aa0f98d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=92=E1=85=A9=E1=86=BC=E1=84=89=E1=85=A5=E1=86=A8?= =?UTF-8?q?=E1=84=92=E1=85=A7=E1=86=AB?= Date: Wed, 17 Dec 2025 11:35:15 +0900 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20=EC=B4=88=EB=8C=80=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=B0=8F=20=EB=B3=B5=EC=82=AC=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EB=B9=84=EC=9A=A9=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20=EC=B4=88?= =?UTF-8?q?=EB=8C=80=20=EC=BD=94=EB=93=9C=20=EB=85=B8=EC=B6=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Images/copy.imageset/Contents.json | 23 +++++ .../Images/copy.imageset/lucide_files.png | Bin 0 -> 896 bytes .../Images/copy.imageset/lucide_files@2x.png | Bin 0 -> 1625 bytes .../Images/copy.imageset/lucide_files@3x.png | Bin 0 -> 2300 bytes .../Images/upload.imageset/Contents.json | 23 +++++ .../Images/upload.imageset/lucide_upload.png | Bin 0 -> 826 bytes .../upload.imageset/lucide_upload@2x.png | Bin 0 -> 1477 bytes .../upload.imageset/lucide_upload@3x.png | Bin 0 -> 2127 bytes DesignSystem/Sources/Image/ImageAsset.swift | 2 + .../Sources/Utilities/InviteCodeHelper.swift | 34 ++++++ Domain/Sources/Router/DeeplinkRouter.swift | 2 +- .../Components/InvitationCodeView.swift | 97 ++++++++++++++++++ .../Sources/ExpenseListFeature.swift | 3 +- .../ExpenseList/Sources/ExpenseListView.swift | 17 +++ .../Components/BasicSettingView.swift | 15 +-- 15 files changed, 201 insertions(+), 15 deletions(-) create mode 100644 DesignSystem/Resources/Assets.xcassets/Images/copy.imageset/Contents.json create mode 100644 DesignSystem/Resources/Assets.xcassets/Images/copy.imageset/lucide_files.png create mode 100644 DesignSystem/Resources/Assets.xcassets/Images/copy.imageset/lucide_files@2x.png create mode 100644 DesignSystem/Resources/Assets.xcassets/Images/copy.imageset/lucide_files@3x.png create mode 100644 DesignSystem/Resources/Assets.xcassets/Images/upload.imageset/Contents.json create mode 100644 DesignSystem/Resources/Assets.xcassets/Images/upload.imageset/lucide_upload.png create mode 100644 DesignSystem/Resources/Assets.xcassets/Images/upload.imageset/lucide_upload@2x.png create mode 100644 DesignSystem/Resources/Assets.xcassets/Images/upload.imageset/lucide_upload@3x.png create mode 100644 DesignSystem/Sources/Utilities/InviteCodeHelper.swift create mode 100644 Features/ExpenseList/Sources/Components/InvitationCodeView.swift diff --git a/DesignSystem/Resources/Assets.xcassets/Images/copy.imageset/Contents.json b/DesignSystem/Resources/Assets.xcassets/Images/copy.imageset/Contents.json new file mode 100644 index 00000000..745fe18c --- /dev/null +++ b/DesignSystem/Resources/Assets.xcassets/Images/copy.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "lucide_files.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lucide_files@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lucide_files@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DesignSystem/Resources/Assets.xcassets/Images/copy.imageset/lucide_files.png b/DesignSystem/Resources/Assets.xcassets/Images/copy.imageset/lucide_files.png new file mode 100644 index 0000000000000000000000000000000000000000..4e7cd4e45b8d9ab6c1dd8b9049d0401f536ca973 GIT binary patch literal 896 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 Xn(s1OgUj9{00000NkvXXu0mjf)hq(B literal 0 HcmV?d00001 diff --git a/DesignSystem/Resources/Assets.xcassets/Images/copy.imageset/lucide_files@3x.png b/DesignSystem/Resources/Assets.xcassets/Images/copy.imageset/lucide_files@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..b761496268b66257d654e52638a2d7d773dbd26d GIT binary patch literal 2300 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 Eg0AOzDgXcg literal 0 HcmV?d00001 diff --git a/DesignSystem/Resources/Assets.xcassets/Images/upload.imageset/lucide_upload@2x.png b/DesignSystem/Resources/Assets.xcassets/Images/upload.imageset/lucide_upload@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a5a2011b85b15cc3fcb32e8e1bbeda1f73e03633 GIT binary patch literal 1477 zcmV;$1v>hPP)1N3U=RfZ>}=>srSP2?@_nPBQIYGEP>2FB6~`MaM1-||rcpvpj3?}K z&J;>p>v|3miR1y>n|evHt>>Es87&sWo*Y_$-3|Rhl%7T#AV#f>jH{WD=eA~dQH z7}^NVA4Z-iyVZFu`vAA*+st^v-^}0M>bz?O_~}RetX$lnb-i@mIZc~@JH~4) zqXTd>=0_(~pe=y{xZ^yBx*M9{q>e1IiW+v%JRz_NoCB{q#&{yD&)UT83ReW!-q6Mt zGdIj+&_t=O3DEUf`&kI^$t}6bx8iMb!#{onPL5$7X$qhAVR+b92i~DN4{$6z&C>XS zEa!UrfMF^3--3ChDO8T@XR;R_kX7ybjrVgdjr8@;8F)g27hHJ2DZ^GbWIW-qOgy2% z3oKvZ84Plr@g7-;P>`nXm0bZC!!ud?LB@ah@3MUAD`z1BZKcgDy8=i@@Dzp!GXBl) zz;WgB^I@fW%p(&|kbo`UTmeiicA6P~@YmIIc`Qp2+^Dr+vG*4jNL>I9Z()u1$WjD0 z1MXQ^r-o|(?)_-SdjyHVYngx>HkMLm1gQLTSq^KwN23Upe_;e!C5{!v{YU8!X>%kU z+i#`nRF{*Ss89rUlyScw1~!AD#Px^FY>?FtbvN<-R>%nOkWHX-mc3CC+pP{=`i6qG zN8T9eG(HM|F2`~+GrAPoUX*pfL6Tu`U=a9husZJl`39MA_Y2_8=fL}S^%Jhhi0zg% zf;}YQ{zKsQ9pK&rpr;SU>Ax-a9ID>)%de3L2iUjdbsq`v&-Z&H2yp0I4j)xQe+`8~ z@V(=}{llVk0%Zr;!>O)Qo90FlAWfwCmx~{OwS+>(CQ%T>+`+2tOO&Wv4bltTwLwG}z~o;RTik4NZ};khreSeY zYb~ra9IIz*Q_o45XbByN^m8+fdBuV2K-o8Yg&d{ArU7ezWLyn59uEwNNGCB9rzzwX zu|>B3r-J?!C;&SNZ6S2NZQhaEU$mQKk~a;BjXqJ!qFm%rA#XSi7Dt-o2$GhupzU?N zBEzGx?Q@fGy8C!SO<#9Ts9@O6x-LO=X|gnVG@)#7@z4(Nw}vsEgbs_>Tq*w@qx4&? z+}=;5eF0fPEjSAExf~+pH%X9VUc5EZ6D_d<;>@h*$ta^VBx{_z*fAcOO0+S~yke%; z-Ix%pbzDEpwIXq50KGxh)YHr~%S?#sX3q1oek|L*yx%yamKs4J3e34N=n=IiMpx4? z+;`3NsU==cOQ$rNbpGHPtt4ox@iNyBPdCH$42>~28H|0_F<`DiDUX$PjvByiW3LVQyu^d@wXf|re;))E!EB?(Hb4v_3&3N0 zX<`Lq4%^`V9T_?9MT!B|#EOCu3c66%_VU^DBD76H3&35jX!-64152mRi5sE*C(MgE z;95Svz!Rf{>et_jdf?XweIXAurGc9$@WnK-#Vh6^$fvpr;Ad>^1={umkAOVX z19WcvqguMwJ%Iu;%?*|YpEg{c2sD96o1n(kS_FZMgHQDd@ZgSI)Uox4HL9*6ux&fL z0=x7HK$)tDOhggy_`4Z=L@ZBgD08ll37ItJ#$6&!JbakmHT>9SF_gP-v? z{~3O6Ycu?DvZ6N_cLkuXppEvO5XLh)$z7YHO0uILuT4PN=yN)%Xs6B1}voQ+QIwalj~7viJX+E~A>b z#&_@%;oT-Ohfl*TvoA6-K3TO*Gr&K8w{!gb`|H2F$MsQQ5zKb~@ppHSIf86oHZ;lo z@Z`#Ef|Zx!M}ggtL1B0r1({$AAjfZOxh|0RnKneR>mfS?rVO*_0hu9L05OJVw`c=3 z{@CY;6~vLP`0oM3ck3^UooED$?%D(5M-HShx%^y+Y|JQ(tXGoCa(;r$B zf3ag4XP%DDd{~>{&-}$HBnsD@FibA`3TElG84vxV4{N&}A*c0-l`Tvro0gBNgm z-5CGA{SLW9nan3&or2N*h*&m;!5v>p-qXZPtx|<4$eJ8m-K;G`dQWfR0p2$o}LqZB@2e$ zwk>gu<^?}N5xftR_hb%!L4KU|7I8m#cCC5?uj%4phSr2R7^_^QaW>xOjTcYUrZ~9u zyA}Q(5`crTG}Oid_N7lXUWn<18w`KetrS{6=*7+VJ9sWRknu0j-5M0aJz)2h+Q60vOrO!E=e{7T!jmj=L~#)UD}EP!@ZFfz z+QEfl-%xbe_3TOicU@@bVt`FbB(CaTr% z5-4BK@PTC!ls#eFgfTu7`uqS6ET+I^6!ak^E=Re@TjX=(@86Y|45=<_nrGPfrz2cH z#`<5xpr|ixyPY_fH;i2+f&=q}ftzDjWt~QZElH!DkUoD9C1pBl8**l1%r0eU4K;PR zlp(_(WN#bQv6wN_cigMcP*c&RZIu_Q;kaPz#|uA@%aJAW_lmDOpGc)3c1a~z=?m>D z@kT;mwKYj&d_|YA?LTya0WSHfsWBV|qB7lSLLU4=c zZ8_!vtR&%XJl->XxJliU3Rbf;dN0}tFi5Q?r^?%ywaRK56f;arbBQl5 zu_n37*I=P34xEC<>h_{hgQl3zOcM=0{i;<#T^EB7@;`Tb9qu?QodW;>002ovPDHLk FV1g?A_!9sC literal 0 HcmV?d00001 diff --git a/DesignSystem/Sources/Image/ImageAsset.swift b/DesignSystem/Sources/Image/ImageAsset.swift index 9e9fa01d..687c0913 100644 --- a/DesignSystem/Sources/Image/ImageAsset.swift +++ b/DesignSystem/Sources/Image/ImageAsset.swift @@ -34,4 +34,6 @@ public enum ImageAsset: String { case travelDetailMock case supportMaIl case settlementEmpty + case copy + case upload } diff --git a/DesignSystem/Sources/Utilities/InviteCodeHelper.swift b/DesignSystem/Sources/Utilities/InviteCodeHelper.swift new file mode 100644 index 00000000..4733e278 --- /dev/null +++ b/DesignSystem/Sources/Utilities/InviteCodeHelper.swift @@ -0,0 +1,34 @@ +// +// InviteCodeHelper.swift +// DesignSystem +// +// Created by Claude on 12/17/24. +// + +import UIKit + +/// 여행 초대 코드 복사 및 공유 기능을 제공하는 헬퍼 +@MainActor +public enum InviteCodeHelper { + + /// 초대 코드를 클립보드에 복사 + /// - Parameter code: 복사할 초대 코드 + public static func copyToClipboard(_ code: String) { + UIPasteboard.general.string = code + ToastManager.shared.showSuccess("클립보드에 복사되었습니다.") + } + + /// 딥링크를 시스템 공유 시트를 통해 공유 + /// - Parameter deepLink: 공유할 딥링크 URL + public static func shareDeepLink(_ deepLink: URL) { + let activityViewController = UIActivityViewController( + activityItems: [deepLink], + applicationActivities: nil + ) + + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first { + window.rootViewController?.present(activityViewController, animated: true) + } + } +} diff --git a/Domain/Sources/Router/DeeplinkRouter.swift b/Domain/Sources/Router/DeeplinkRouter.swift index bb6d6bbc..86b231e3 100644 --- a/Domain/Sources/Router/DeeplinkRouter.swift +++ b/Domain/Sources/Router/DeeplinkRouter.swift @@ -6,7 +6,7 @@ // import Foundation -import ComposableArchitecture +import Dependencies import LogMacro public struct DeeplinkRouter: Sendable { diff --git a/Features/ExpenseList/Sources/Components/InvitationCodeView.swift b/Features/ExpenseList/Sources/Components/InvitationCodeView.swift new file mode 100644 index 00000000..cb88e792 --- /dev/null +++ b/Features/ExpenseList/Sources/Components/InvitationCodeView.swift @@ -0,0 +1,97 @@ +// +// InvitationCodeView.swift +// ExpenseListFeature +// +// Created by 홍석현 on 12/17/25. +// + +import SwiftUI +import DesignSystem + +struct InvitationCodeView: View { + @State private var isExpanded: Bool = false + private let invitationCode: String + private let deepLinkURL: URL + + init( + invitationCode: String, + deepLinkURL: URL + ) { + self.invitationCode = invitationCode + self.deepLinkURL = deepLinkURL + } + + var body: some View { + VStack(spacing: 0) { + HStack { + Text("여행 초대코드") + .font(.app(.caption1, weight: .medium)) + .foregroundStyle(Color.gray5) + + Spacer() + + Button(action: { + withAnimation(.spring(response: 0.15, dampingFraction: 0.75)) { + isExpanded.toggle() + } + }, label: { + HStack(spacing: 2) { + Text(isExpanded ? "접기" : "펼치기") + .font(.app(.caption2, weight: .regular)) + + Image(systemName: "chevron.down") + .resizable() + .frame(width: 6, height: 3) + .rotationEffect(.degrees(isExpanded ? 180 : 0)) + } + .foregroundStyle(Color.gray4) + }) + } + + if isExpanded { + VStack(spacing: 4) { + Text("여행 코드를 공유해 여행 멤버를 초대하세요!") + .font(.app(.caption2, weight: .regular)) + .foregroundStyle(Color.gray4) + .frame(maxWidth: .infinity, alignment: .leading) + + HStack(spacing: 10) { + Text(invitationCode) + .font(.app(.title2, weight: .medium)) + .foregroundStyle(Color.gray8) + + Spacer() + + Button(action: { + InviteCodeHelper.copyToClipboard(invitationCode) + }, label: { + Image(asset: .copy) + .resizable() + .frame(width: 32, height: 32) + }) + + Button(action: { + InviteCodeHelper.shareDeepLink(deepLinkURL) + }, label: { + Image(asset: .upload) + .resizable() + .frame(width: 32, height: 32) + }) + } + } + .transition(.asymmetric( + insertion: .move(edge: .top).combined(with: .opacity), + removal: .move(edge: .top).combined(with: .opacity) + )) + } + } + .padding(20) + } +} + +#Preview { + InvitationCodeView( + invitationCode: "1A1A1A", + deepLinkURL: URL(string: "")! + ) +} diff --git a/Features/ExpenseList/Sources/ExpenseListFeature.swift b/Features/ExpenseList/Sources/ExpenseListFeature.swift index 07385670..2abedc6f 100644 --- a/Features/ExpenseList/Sources/ExpenseListFeature.swift +++ b/Features/ExpenseList/Sources/ExpenseListFeature.swift @@ -33,8 +33,7 @@ public struct ExpenseListFeature { public var isLoading: Bool = false @Presents public var alert: AlertState? public var pendingHighlightExpenseId: String? - /// 포맷팅된 총 지출 금액 문자열 - /// 포맷팅된 총 지출 금액 문자열 + public var formattedTotalAmount: String { // 날짜 범위가 선택되지 않았을 때(전체 기간)는 페이지네이션과 관계없이 전체 지출의 합계 표시 // 단, 카테고리 필터는 적용해야 함 diff --git a/Features/ExpenseList/Sources/ExpenseListView.swift b/Features/ExpenseList/Sources/ExpenseListView.swift index 99070199..77ca2019 100644 --- a/Features/ExpenseList/Sources/ExpenseListView.swift +++ b/Features/ExpenseList/Sources/ExpenseListView.swift @@ -32,6 +32,15 @@ public struct ExpenseListView: View { currentPage: $store.currentPage ) + if let inviteCode = store.travel?.inviteCode, + let url = store.travel?.deepLink, + let deepLinkURL = URL(string: url) { + InvitationCodeView( + invitationCode: inviteCode, + deepLinkURL: deepLinkURL + ) + } + VStack(spacing: 0) { // 카테고리 필터 CategoryFilterView( @@ -83,6 +92,14 @@ public struct ExpenseListView: View { .stroke(Color.gray1, lineWidth: 1) ) } else { + if let inviteCode = store.travel?.inviteCode, + let url = store.travel?.deepLink, + let deepLinkURL = URL(string: url) { + InvitationCodeView( + invitationCode: inviteCode, + deepLinkURL: deepLinkURL + ) + } EmptyCaseView(image: .expenseEmpty, message: "아직 지출이 없어요") } } diff --git a/Features/Travel/Sources/View/TravelSetting/Components/BasicSettingView.swift b/Features/Travel/Sources/View/TravelSetting/Components/BasicSettingView.swift index 718242d7..a838a9b1 100644 --- a/Features/Travel/Sources/View/TravelSetting/Components/BasicSettingView.swift +++ b/Features/Travel/Sources/View/TravelSetting/Components/BasicSettingView.swift @@ -158,8 +158,7 @@ struct BasicSettingView: View { private var inviteSection: some View { Button { guard let inviteCode = store.travel.inviteCode else { return } - UIPasteboard.general.string = inviteCode - ToastManager.shared.showSuccess("클립보드에 복사되었습니다.") + InviteCodeHelper.copyToClipboard(inviteCode) } label: { VStack(alignment: .leading, spacing: 8) { Text("초대 코드") @@ -180,16 +179,8 @@ struct BasicSettingView: View { Spacer() Button { - if let deepLink = store.travel.deepLink { - let activityViewController = UIActivityViewController( - activityItems: [deepLink], - applicationActivities: nil - ) - - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let window = windowScene.windows.first { - window.rootViewController?.present(activityViewController, animated: true) - } + if let deepLink = store.travel.deepLink, let url = URL(string: deepLink) { + InviteCodeHelper.shareDeepLink(url) } } label: { Image(systemName: "square.and.arrow.up")