Skip to content

Commit ed6e840

Browse files
authored
Merge pull request #95 from YAPP-Github/feat/#84
feat: 디데이 등록 UI 구현
2 parents 37ebd09 + ea7f22d commit ed6e840

File tree

18 files changed

+621
-74
lines changed

18 files changed

+621
-74
lines changed

Projects/Feature/Onboarding/Sources/CodeInput/OnboardingCodeInputReducer.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,19 @@ public struct OnboardingCodeInputReducer {
4646
}
4747

4848
public enum Action: BindableAction {
49+
// MARK: - Binding
4950
case binding(BindingAction<State>)
51+
52+
// MARK: - User Action
5053
case backButtonTapped
5154
case codeInputChanged(String)
5255
case copyMyCodeButtonTapped
5356
case completeButtonTapped
5457
case codeFieldTapped
58+
59+
// MARK: - Delegate
5560
case delegate(Delegate)
5661

57-
@CasePathable
5862
public enum Delegate: Equatable {
5963
case navigateBack
6064
case coupleConnected

Projects/Feature/Onboarding/Sources/Connect/OnboardingConnectReducer.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,20 @@ public struct OnboardingConnectReducer {
3232
}
3333

3434
public enum Action: BindableAction {
35+
// MARK: - Binding
3536
case binding(BindingAction<State>)
37+
38+
// MARK: - User Action
3639
case backButtonTapped
3740
case directConnectCardTapped
3841
case sendInvitationButtonTapped
42+
43+
// MARK: - Update State
3944
case shareSheetDismissed
45+
46+
// MARK: - Delegate
4047
case delegate(Delegate)
4148

42-
@CasePathable
4349
public enum Delegate: Equatable {
4450
case navigateBack
4551
case navigateToCodeInput
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//
2+
// OnboardingDdayReducer.swift
3+
// FeatureOnboarding
4+
//
5+
// Created by Claude on 01/29/26.
6+
//
7+
8+
import ComposableArchitecture
9+
import Foundation
10+
import SharedDesignSystem
11+
12+
/// 기념일 등록 화면을 관리하는 Reducer입니다.
13+
///
14+
/// 커플의 기념일(D-day)을 선택하여 등록합니다.
15+
///
16+
/// ## 사용 예시
17+
/// ```swift
18+
/// let store = Store(
19+
/// initialState: OnboardingDdayReducer.State(),
20+
/// reducer: { OnboardingDdayReducer() }
21+
/// )
22+
/// ```
23+
@Reducer
24+
public struct OnboardingDdayReducer {
25+
/// 기념일 등록 화면의 상태입니다.
26+
@ObservableState
27+
public struct State: Equatable {
28+
/// 선택된 날짜
29+
var selectedDate: TXCalendarDate
30+
31+
/// 캘린더 시트 표시 여부
32+
var showCalendarSheet: Bool = false
33+
34+
public init() {
35+
self.selectedDate = TXCalendarDate()
36+
}
37+
}
38+
39+
public enum Action: BindableAction {
40+
// MARK: - Binding
41+
case binding(BindingAction<State>)
42+
43+
// MARK: - User Action
44+
case backButtonTapped
45+
case dateSelectorTapped
46+
case completeButtonTapped
47+
48+
// MARK: - Update State
49+
case calendarCompleted
50+
51+
// MARK: - Delegate
52+
case delegate(Delegate)
53+
54+
public enum Delegate: Equatable {
55+
case navigateBack
56+
case ddayCompleted(date: Date)
57+
}
58+
}
59+
60+
public init() {}
61+
62+
public var body: some ReducerOf<Self> {
63+
BindingReducer()
64+
Reduce { state, action in
65+
switch action {
66+
case .binding:
67+
return .none
68+
69+
case .backButtonTapped:
70+
return .send(.delegate(.navigateBack))
71+
72+
case .dateSelectorTapped:
73+
state.showCalendarSheet = true
74+
return .none
75+
76+
case .calendarCompleted:
77+
state.showCalendarSheet = false
78+
return .none
79+
80+
case .completeButtonTapped:
81+
guard let date = state.selectedDate.date else { return .none }
82+
return .send(.delegate(.ddayCompleted(date: date)))
83+
84+
case .delegate:
85+
return .none
86+
}
87+
}
88+
}
89+
}
90+
91+
// MARK: - Computed Properties
92+
93+
extension OnboardingDdayReducer.State {
94+
/// 날짜가 선택되었는지 여부
95+
var isDateSelected: Bool {
96+
selectedDate.day != nil
97+
}
98+
99+
/// 포맷된 날짜 문자열 (YYYY-MM-DD)
100+
var formattedDate: String? {
101+
guard let day = selectedDate.day else { return nil }
102+
return String(format: "%d-%02d-%02d", selectedDate.year, selectedDate.month, day)
103+
}
104+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//
2+
// OnboardingDdayView.swift
3+
// FeatureOnboarding
4+
//
5+
// Created by Claude on 01/29/26.
6+
//
7+
8+
import ComposableArchitecture
9+
import SharedDesignSystem
10+
import SwiftUI
11+
12+
/// 기념일 등록 화면입니다.
13+
public struct OnboardingDdayView: View {
14+
@Bindable var store: StoreOf<OnboardingDdayReducer>
15+
16+
public init(store: StoreOf<OnboardingDdayReducer>) {
17+
self.store = store
18+
}
19+
20+
public var body: some View {
21+
VStack(spacing: 0) {
22+
TXNavigationBar(style: .iconOnly(.back)) { action in
23+
if action == .backTapped {
24+
store.send(.backButtonTapped)
25+
}
26+
}
27+
28+
ScrollView {
29+
VStack(spacing: 0) {
30+
titleSection
31+
.padding(.horizontal, Spacing.spacing9)
32+
.padding(.bottom, 32)
33+
34+
dateSelectorSection
35+
.padding(.horizontal, Spacing.spacing8)
36+
}
37+
}
38+
.scrollDismissesKeyboard(.interactively)
39+
40+
Spacer()
41+
42+
bottomButton
43+
.padding(.horizontal, Spacing.spacing8)
44+
.padding(.vertical, Spacing.spacing5)
45+
}
46+
.frame(maxWidth: .infinity, maxHeight: .infinity)
47+
.background(Color.Common.white)
48+
.calendarSheet(
49+
isPresented: $store.showCalendarSheet,
50+
selectedDate: $store.selectedDate,
51+
onComplete: { store.send(.calendarCompleted) }
52+
)
53+
}
54+
}
55+
56+
// MARK: - Subviews
57+
58+
private extension OnboardingDdayView {
59+
var titleSection: some View {
60+
HStack {
61+
Text("우리 커플의 기념일은?")
62+
.typography(.h3_22eb)
63+
.foregroundStyle(Color.Gray.gray500)
64+
Spacer()
65+
}
66+
}
67+
68+
var dateSelectorSection: some View {
69+
Button {
70+
store.send(.dateSelectorTapped)
71+
} label: {
72+
dateSelectorContent
73+
}
74+
.buttonStyle(.plain)
75+
}
76+
77+
var dateSelectorContent: some View {
78+
VStack(spacing: 0) {
79+
HStack(spacing: 0) {
80+
dateText
81+
.padding(.leading, Spacing.spacing5)
82+
83+
Spacer()
84+
85+
calendarIcon
86+
.padding(.horizontal, 10)
87+
}
88+
.padding(.vertical, Spacing.spacing3)
89+
90+
underline
91+
}
92+
}
93+
94+
var dateText: some View {
95+
Group {
96+
if let formattedDate = store.formattedDate {
97+
Text(formattedDate)
98+
.typography(.t2_16b)
99+
.foregroundStyle(Color.Gray.gray500)
100+
} else {
101+
Text("YYYY-MM-DD")
102+
.typography(.t2_16b)
103+
.foregroundStyle(Color.Gray.gray200)
104+
}
105+
}
106+
.opacity(0.8)
107+
}
108+
109+
var calendarIcon: some View {
110+
Image.Icon.Symbol.calendar
111+
.resizable()
112+
.frame(width: 24, height: 24)
113+
.padding(2)
114+
.frame(width: 44, height: 44)
115+
}
116+
117+
var underline: some View {
118+
Rectangle()
119+
.foregroundStyle(Color.Gray.gray500)
120+
.frame(height: 1)
121+
}
122+
123+
var bottomButton: some View {
124+
TXRoundedRectangleButton(
125+
config: .long(
126+
text: "완료",
127+
colorStyle: store.isDateSelected ? .black : .disabled
128+
),
129+
action: { store.send(.completeButtonTapped) }
130+
)
131+
}
132+
}

0 commit comments

Comments
 (0)