From 35a3af15f521c8edcf234f880c36221a7eb4b1ee Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Fri, 17 Jan 2025 07:16:39 +0900 Subject: [PATCH 01/16] =?UTF-8?q?[Feat]=20UserType=20Entity=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Sources/Entity/UserType.swift | 21 +++++++++++++++++++ .../UserTypeSelectionFeature.swift | 9 ++++++++ .../UserTypeSelectionView.swift | 16 ++++++++++++++ .../Onboarding/UserTypeSelectionView.swift | 9 ++++++++ 4 files changed, 55 insertions(+) create mode 100644 TnT/Projects/Domain/Sources/Entity/UserType.swift create mode 100644 TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift create mode 100644 TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift create mode 100644 TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelectionView.swift diff --git a/TnT/Projects/Domain/Sources/Entity/UserType.swift b/TnT/Projects/Domain/Sources/Entity/UserType.swift new file mode 100644 index 0000000..b63c9c8 --- /dev/null +++ b/TnT/Projects/Domain/Sources/Entity/UserType.swift @@ -0,0 +1,21 @@ +// +// UserType.swift +// Domain +// +// Created by 박민서 on 1/17/25. +// Copyright © 2025 yapp25thTeamTnT. All rights reserved. +// + +public enum UserType { + case trainer + case trainee + + public var koreanName: String { + switch self { + case .trainer: + return "트레이너" + case .trainee: + return "트레이니" + } + } +} diff --git a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift new file mode 100644 index 0000000..baa57cf --- /dev/null +++ b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift @@ -0,0 +1,9 @@ +// +// UserTypeSelectionFeature.swift +// Presentation +// +// Created by 박민서 on 1/17/25. +// Copyright © 2025 yapp25thTeamTnT. All rights reserved. +// + +import Foundation diff --git a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift new file mode 100644 index 0000000..15b81b5 --- /dev/null +++ b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift @@ -0,0 +1,16 @@ +// +// UserTypeSelectionView.swift +// Presentation +// +// Created by 박민서 on 1/17/25. +// Copyright © 2025 yapp25thTeamTnT. All rights reserved. +// + +import SwiftUI +import ComposableArchitecture + +import DesignSystem + +public struct UserTypeSelectionView: View { + +} diff --git a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelectionView.swift b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelectionView.swift new file mode 100644 index 0000000..9437c16 --- /dev/null +++ b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelectionView.swift @@ -0,0 +1,9 @@ +// +// UserTypeSelectionView.swift +// Presentation +// +// Created by 박민서 on 1/17/25. +// Copyright © 2025 yapp25thTeamTnT. All rights reserved. +// + +import Foundation From 4584e948b03a170e215bd331f1f7a96a936efeb1 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Fri, 17 Jan 2025 07:17:19 +0900 Subject: [PATCH 02/16] =?UTF-8?q?[Feat]=20UserTypeSelection=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserTypeSelectionFeature.swift | 74 +++++++++- .../UserTypeSelectionView.swift | 128 ++++++++++++++++++ 2 files changed, 201 insertions(+), 1 deletion(-) diff --git a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift index baa57cf..3388946 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift @@ -6,4 +6,76 @@ // Copyright © 2025 yapp25thTeamTnT. All rights reserved. // -import Foundation +import ComposableArchitecture + +import Domain + +/// 역할 선택 화면의 상태 및 로직을 관리하는 리듀서입니다. +@Reducer +public struct UserTypeSelectionFeature { + + @ObservableState + public struct State { + /// 현재 선택된 유저 타입 (트레이너/트레이니) + var userType: UserType + /// UI 관련 상태 + var viewState: ViewState + + /// `UserTypeSelectionFeature.State`의 생성자 + /// - Parameters: + /// - userType: 현재 선택된 유저 타입 (기본값: `.trainer`) + /// - viewState: UI 관련 상태 (기본값: `ViewState()`). + public init(userType: UserType = .trainer, viewState: ViewState = .init()) { + self.userType = userType + self.viewState = viewState + } + } + + /// UI 관련 상태를 관리하는 구조체입니다. + public struct ViewState: Equatable { + /// 다음 화면으로 이동 여부 + public var isNavigating: Bool + + /// `ViewState`의 생성자 + /// - Parameter isNavigating: 네비게이션 여부 (기본값: `false`) + public init(isNavigating: Bool = false) { + self.isNavigating = isNavigating + } + } + + + public enum Action: ViewAction { + /// 네비게이션 여부 설정 + case setNavigating(Bool) + /// 뷰에서 발생한 액션을 처리합니다. + case view(ViewAction) + + public enum ViewAction { + /// 유저 타입 선택 버튼이 눌렸을 때 + case tapUserTypeButton(UserType) + /// "다음으로" 버튼이 눌렸을 때 + case tapNextButton + } + } + + public init() {} + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .view(let action): + switch action { + case .tapUserTypeButton(let userType): + state.userType = userType + return .none + case .tapNextButton: + print("다음으로..") + return .send(.setNavigating(true)) + } + case .setNavigating(let isNavigating): + state.viewState.isNavigating = isNavigating + return .none + } + } + } +} diff --git a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift index 15b81b5..3507c7c 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift @@ -9,8 +9,136 @@ import SwiftUI import ComposableArchitecture +import Domain import DesignSystem +/// 역할 선택 화면을 담당하는 View입니다. +@ViewAction(for: UserTypeSelectionFeature.self) public struct UserTypeSelectionView: View { + public var store: StoreOf + + /// `UserTypeSelectionView`의 생성자 + /// - Parameter store: `UserTypeSelectionFeature`의 상태를 관리하는 `Store` + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + NavigationStack { + VStack { + + Spacer(minLength: 60) + + VStack(spacing: 48) { + Header + + ImageSection + + ButtonSection + } + + Spacer() + + BottomTempButton { + send(.tapNextButton) + } + } + .ignoresSafeArea(.container, edges: .bottom) + .navigationDestination( + isPresented: Binding(get: { store.viewState.isNavigating }, set: { store.send(.setNavigating($0))}) + ) { + DummyView() + } + } + } +} + +// MARK: - SubViews +private extension UserTypeSelectionView { + var Header: some View { + VStack(alignment: .leading, spacing: 12) { + Text("안녕하세요!\n어떤 회원으로 이용하시겠어요?") + .typographyStyle(.heading2, with: .neutral950) + Text("트레이너와 트레이니 중 선택해주세요.") + .typographyStyle(.body1Medium, with: .neutral500) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 24) + } + + var ImageSection: some View { + Group { + if store.userType == .trainer { + Image(.imgOnboardingTrainer) + .resizable() + } else { + Image(.imgOnboardingTrainee) + .resizable() + } + } + .frame(idealWidth: 310, idealHeight: 310) + .padding(.horizontal, 20) + } + + var ButtonSection: some View { + HStack(spacing: 8) { + TempButton( + isSelected: store.userType == UserType.trainer, + title: UserType.trainer.koreanName, + action: { send(.tapUserTypeButton(.trainer), animation: .easeInOut(duration: 0.5)) } + ) + TempButton( + isSelected: store.userType == UserType.trainee, + title: UserType.trainee.koreanName, + action: { send(.tapUserTypeButton(.trainee), animation: .easeInOut(duration: 0.5)) } + ) + } + .padding(.horizontal, 20) + } +} + +// TODO: 버튼 컴포넌트 나오면 대체 +private extension UserTypeSelectionView { + struct TempButton: View { + var isSelected: Bool = false + var title: String + let action: (() -> Void) + + var body: some View { + Button(action: { + action() + }) { + Text(title) + .typographyStyle(.body1Medium, with: isSelected ? .red600 : .neutral500) + .padding(.vertical, 16) + .frame(height: 58) + .frame(maxWidth: .infinity) + .background(isSelected ? Color.red50 : .clear) + .cornerRadius(16) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(isSelected ? Color.red400 : Color.neutral300, lineWidth: isSelected ? 1.5 : 1.0) + ) + } + } + } + + struct BottomTempButton: View { + let action: (() -> Void) + + var body: some View { + Button(action: { + action() + }) { + Text("다음") + .typographyStyle(.heading4, with: .neutral50) + .padding(.top, 20) + .padding(.bottom, 53) + .frame(height: 100) + .frame(maxWidth: .infinity) + .background(Color.neutral900) + } + } + } } From 5987a2b8826551dbeca519e75b693b3d76bcf834 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Fri, 17 Jan 2025 07:45:43 +0900 Subject: [PATCH 03/16] =?UTF-8?q?[Feat]=20=ED=8A=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=84=88=20=ED=8A=B8=EB=A0=88=EC=9D=B4=EB=8B=88=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=97=90=EC=85=8B=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../img_default_profile_image.png | Bin 4974 -> 0 bytes .../img_default_profile_image@2x.png | Bin 10219 -> 0 bytes .../img_default_profile_image@3x.png | Bin 15642 -> 0 bytes .../Contents.json | 6 ++--- .../basic profile_trainee.png | Bin 0 -> 4925 bytes .../basic profile_trainee@2x.png | Bin 0 -> 9994 bytes .../basic profile_trainee@3x.png | Bin 0 -> 15338 bytes .../Contents.json | 23 ++++++++++++++++++ .../basic profile_trainer.png | Bin 0 -> 5031 bytes .../basic profile_trainer@2x.png | Bin 0 -> 10238 bytes .../basic profile_trainer@3x.png | Bin 0 -> 15661 bytes .../DesignSystem/Image+DesignSystem.swift | 3 ++- .../CreateProfile/CreateProfileFeature.swift | 9 +++++++ .../CreateProfile/CreateProfileFeature.swift | 9 +++++++ .../CreateProfile/CreateProfileView.swift | 9 +++++++ 15 files changed, 55 insertions(+), 4 deletions(-) delete mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_profile_image.imageset/img_default_profile_image.png delete mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_profile_image.imageset/img_default_profile_image@2x.png delete mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_profile_image.imageset/img_default_profile_image@3x.png rename TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/{img_default_profile_image.imageset => img_default_trainee_image.imageset}/Contents.json (62%) create mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainee_image.imageset/basic profile_trainee.png create mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainee_image.imageset/basic profile_trainee@2x.png create mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainee_image.imageset/basic profile_trainee@3x.png create mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainer_image.imageset/Contents.json create mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainer_image.imageset/basic profile_trainer.png create mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainer_image.imageset/basic profile_trainer@2x.png create mode 100644 TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainer_image.imageset/basic profile_trainer@3x.png create mode 100644 TnT/Projects/Presentation/Sources/Onboarding/CreateProfile/CreateProfileFeature.swift create mode 100644 TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift create mode 100644 TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_profile_image.imageset/img_default_profile_image.png b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_profile_image.imageset/img_default_profile_image.png deleted file mode 100644 index 3b2d227b789313aa4f2c90cfac55ace1f24c5863..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4974 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 diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_profile_image.imageset/img_default_profile_image@2x.png b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_profile_image.imageset/img_default_profile_image@2x.png deleted file mode 100644 index 04943c3f10856bfa8e2e1b65c800f2c1dd90e808..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10219 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 diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_profile_image.imageset/img_default_profile_image@3x.png b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_profile_image.imageset/img_default_profile_image@3x.png deleted file mode 100644 index edb57e514ab4e8806699933bafa5f32eabdb007e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15642 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 literal 0 HcmV?d00001 diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainee_image.imageset/basic profile_trainee@2x.png b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainee_image.imageset/basic profile_trainee@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0b3afd5799317063166c72fe398d8ee43c33783f GIT binary patch literal 9994 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 Uoxj*Ay8r+H07*qoM6N<$f?)?SLjV8( literal 0 HcmV?d00001 diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainee_image.imageset/basic profile_trainee@3x.png b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainee_image.imageset/basic profile_trainee@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9cb8434ee84ea4603e2331b85e61b749bdfa342a GIT binary patch literal 15338 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 literal 0 HcmV?d00001 diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainer_image.imageset/basic profile_trainer@2x.png b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainer_image.imageset/basic profile_trainer@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9482a7cc0d8ca8c1da043004f2ad739d16bc6b33 GIT binary patch literal 10238 zcmV%PgT#1iNV21kb*)i8lof;mVyypfCOQjMK)Q)ma<62{504u^cPe zVK1^lN<4u`VF6;C$c~gqb|(%JBm{-6v=DI@6W)tB!_}{kt`E7NZ5V&ny zUVbbuC-U}**t2I_tgcQR|8~+796>sX76ORCSRS;G%7b)&@c$-(m_Q^T91s#I8sg5K zt5QHFVr6BE0@6Jw5*y2WUOf42n0V`b&4{Mw{&v>%E@%aDTLTLiBA zj1+`*(N#gn3WURTC-QPjUS5`#2Sj5_kk&wvXaQ0f)-6$37efZ|j?u^#jZMdQT}VSv zBpN_l3rDpu92I@G6qaRW1;Vifv5?mrHKB#!gbe8YR1Do(D2lN%AQ-Ew z=P4NVK#`~r3PXh$!Pv8BQ(jUqDug0Y0V>AeIH!a!40I)`2Ek9HT31&$q0$V+=0{3PW?OtqGM{_1%#TL6(Lhu_W4RI4$T-vv&dL8>rgc#q zibO%AI2^TH3er{}7RZpSt!+rRxfi^U}=7B4bpu^Wm+44tibMKs-VP3i?L z?tAv&YeW8z0Zt2wghq*9sSgfs`k^g&{ZR`{Y6L&bR;|_%Qpo(+v z-iiEdoE>^m)}Tc~3d9L*JuvP-lJQ-TOY(Cv*m%VtMM6z_^5YKcf{;a6E{GJ0vtnQj zP9&JMAca8|1hYC__sEc}iou8ktp`#H1GFjzAriD6Na?_;xa%&g73jTH(QA>Q^*|~G zR>j?Sh`ui%no z$IpDAj^apHG9R503HjlEUIu~S(@`L@AT0wAqVS*p2=V^=`2`^>(!y}Q@eSeJf4^|P z{&iX#SqO#VSQj!Moe&8v5F3Kb2f`pl!JABw_3_<^IRq{QF8u{I{-wcoMdYQjLKfhB z``iAP6b>>6UnnM`11Zwhiv$H?F*ta=fBsW=w{E2hgAxRJrdRF)∈q3`!`Zc)&^# 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 Ef|ux}i~s-t literal 0 HcmV?d00001 diff --git a/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainer_image.imageset/basic profile_trainer@3x.png b/TnT/Projects/DesignSystem/Resources/Assets.xcassets/Images/img_default_trainer_image.imageset/basic profile_trainer@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..d7ad29fc770e88ee34aea0bb14af3981d6a7f7f3 GIT binary patch literal 15661 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 Date: Fri, 17 Jan 2025 16:58:36 +0900 Subject: [PATCH 04/16] =?UTF-8?q?[Chore]=20UserTypeSelection=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Policy/UserPolicy.swift} | 4 +- .../Sources/Utility/TextValidator.swift | 19 +++ .../CreateProfile/CreateProfileFeature.swift | 74 +++++++++- .../CreateProfile/CreateProfileView.swift | 138 +++++++++++++++++- .../UserTypeSelectionFeature.swift | 3 +- .../UserTypeSelectionView.swift | 7 +- 6 files changed, 238 insertions(+), 7 deletions(-) rename TnT/Projects/{Presentation/Sources/Onboarding/UserTypeSelectionView.swift => Domain/Sources/Policy/UserPolicy.swift} (71%) create mode 100644 TnT/Projects/Domain/Sources/Utility/TextValidator.swift diff --git a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelectionView.swift b/TnT/Projects/Domain/Sources/Policy/UserPolicy.swift similarity index 71% rename from TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelectionView.swift rename to TnT/Projects/Domain/Sources/Policy/UserPolicy.swift index 9437c16..a513098 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelectionView.swift +++ b/TnT/Projects/Domain/Sources/Policy/UserPolicy.swift @@ -1,6 +1,6 @@ // -// UserTypeSelectionView.swift -// Presentation +// UserPolicy.swift +// Domain // // Created by 박민서 on 1/17/25. // Copyright © 2025 yapp25thTeamTnT. All rights reserved. diff --git a/TnT/Projects/Domain/Sources/Utility/TextValidator.swift b/TnT/Projects/Domain/Sources/Utility/TextValidator.swift new file mode 100644 index 0000000..ba56180 --- /dev/null +++ b/TnT/Projects/Domain/Sources/Utility/TextValidator.swift @@ -0,0 +1,19 @@ +// +// StringValidator.swift +// Domain +// +// Created by 박민서 on 1/17/25. +// Copyright © 2025 yapp25thTeamTnT. All rights reserved. +// + +import Foundation + +public struct UserPolicyValidator { + /// 입력값이 `UserPolicy.allowedCharactersRegex`에 맞는지 검사하는 함수 + public static func isValidName(_ name: String) -> Bool { + let pattern = UserPolicy.allowedCharactersRegex + guard let regex = try? NSRegularExpression(pattern: pattern) else { return false } + let range = NSRange(location: 0, length: name.utf16.count) + return regex.firstMatch(in: name, options: [], range: range) != nil + } +} diff --git a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift index fe1d006..9a98188 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift @@ -6,4 +6,76 @@ // Copyright © 2025 yapp25thTeamTnT. All rights reserved. // -import Foundation +import ComposableArchitecture + +import Domain + +/// 역할 선택 화면의 상태 및 로직을 관리하는 리듀서입니다. +@Reducer +public struct CreateProfileFeature { + + @ObservableState + public struct State { + /// 현재 선택된 유저 타입 (트레이너/트레이니) + var userType: UserType + /// UI 관련 상태 + var viewState: ViewState + + /// `UserTypeSelectionFeature.State`의 생성자 + /// - Parameters: + /// - userType: 현재 선택된 유저 타입 (기본값: `.trainer`) + /// - viewState: UI 관련 상태 (기본값: `ViewState()`). + public init(userType: UserType = .trainer, viewState: ViewState = .init()) { + self.userType = userType + self.viewState = viewState + } + } + + /// UI 관련 상태를 관리하는 구조체입니다. + public struct ViewState: Equatable { + /// 다음 화면으로 이동 여부 + public var isNavigating: Bool + + /// `ViewState`의 생성자 + /// - Parameter isNavigating: 네비게이션 여부 (기본값: `false`) + public init(isNavigating: Bool = false) { + self.isNavigating = isNavigating + } + } + + + public enum Action: ViewAction { + /// 네비게이션 여부 설정 + case setNavigating(Bool) + /// 뷰에서 발생한 액션을 처리합니다. + case view(ViewAction) + + public enum ViewAction { + /// 유저 타입 선택 버튼이 눌렸을 때 + case tapUserTypeButton(UserType) + /// "다음으로" 버튼이 눌렸을 때 + case tapNextButton + } + } + + public init() {} + + public var body: some ReducerOf { + Reduce { state, action in + switch action { + case .view(let action): + switch action { + case .tapUserTypeButton(let userType): + state.userType = userType + return .none + case .tapNextButton: + print("다음으로..") + return .send(.setNavigating(true)) + } + case .setNavigating(let isNavigating): + state.viewState.isNavigating = isNavigating + return .none + } + } + } +} diff --git a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift index 928ed44..ef9604a 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift @@ -6,4 +6,140 @@ // Copyright © 2025 yapp25thTeamTnT. All rights reserved. // -import Foundation +import SwiftUI +import ComposableArchitecture + +import Domain +import DesignSystem + +/// 역할 선택 화면을 담당하는 View입니다. +@ViewAction(for: CreateProfileFeature.self) +public struct CreateProfileView: View { + + public var store: StoreOf + + /// `CreateProfileView`의 생성자 + /// - Parameter store: `CreateProfileFeature`의 상태를 관리하는 `Store` + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + NavigationStack { + VStack { + + Spacer(minLength: 60) + + VStack(spacing: 48) { + Header + + ImageSection + + ButtonSection + } + + Spacer() + + BottomTempButton { + send(.tapNextButton) + } + } + .ignoresSafeArea(.container, edges: .bottom) + .navigationDestination( + isPresented: Binding(get: { store.viewState.isNavigating }, set: { store.send(.setNavigating($0))}) + ) { + DummyView() + } + } + } +} + +// MARK: - SubViews +private extension CreateProfileView { + var Header: some View { + VStack(alignment: .leading, spacing: 12) { + Text("안녕하세요!\n어떤 회원으로 이용하시겠어요?") + .typographyStyle(.heading2, with: .neutral950) + Text("트레이너와 트레이니 중 선택해주세요.") + .typographyStyle(.body1Medium, with: .neutral500) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 24) + } + + var ImageSection: some View { + Group { + if store.userType == .trainer { + Image(.imgOnboardingTrainer) + .resizable() + } else { + Image(.imgOnboardingTrainee) + .resizable() + } + } + .frame(idealWidth: 310, idealHeight: 310) + .padding(.horizontal, 20) + } + + var ButtonSection: some View { + HStack(spacing: 8) { + TempButton( + isSelected: store.userType == UserType.trainer, + title: UserType.trainer.koreanName, + action: { send(.tapUserTypeButton(.trainer), animation: .easeInOut(duration: 0.5)) } + ) + TempButton( + isSelected: store.userType == UserType.trainee, + title: UserType.trainee.koreanName, + action: { send(.tapUserTypeButton(.trainee), animation: .easeInOut(duration: 0.5)) } + ) + } + .padding(.horizontal, 20) + } +} + +// TODO: 버튼 컴포넌트 나오면 대체 +private extension CreateProfileView { + struct TempButton: View { + var isSelected: Bool = false + var title: String + let action: (() -> Void) + + var body: some View { + Button(action: { + action() + }) { + Text(title) + .typographyStyle(.body1Medium, with: isSelected ? .red600 : .neutral500) + .padding(.vertical, 16) + .frame(height: 58) + .frame(maxWidth: .infinity) + .background(isSelected ? Color.red50 : .clear) + .cornerRadius(16) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(isSelected ? Color.red400 : Color.neutral300, lineWidth: isSelected ? 1.5 : 1.0) + ) + } + } + } + + struct BottomTempButton: View { + let action: (() -> Void) + + var body: some View { + Button(action: { + action() + }) { + Text("다음") + .typographyStyle(.heading4, with: .neutral50) + .padding(.top, 20) + .padding(.bottom, 53) + .frame(height: 100) + .frame(maxWidth: .infinity) + .background(Color.neutral900) + } + } + } +} + diff --git a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift index 3388946..e4ce959 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift @@ -43,7 +43,6 @@ public struct UserTypeSelectionFeature { } } - public enum Action: ViewAction { /// 네비게이션 여부 설정 case setNavigating(Bool) @@ -68,10 +67,12 @@ public struct UserTypeSelectionFeature { case .tapUserTypeButton(let userType): state.userType = userType return .none + case .tapNextButton: print("다음으로..") return .send(.setNavigating(true)) } + case .setNavigating(let isNavigating): state.viewState.isNavigating = isNavigating return .none diff --git a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift index 3507c7c..6054e5f 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift @@ -48,7 +48,10 @@ public struct UserTypeSelectionView: View { .navigationDestination( isPresented: Binding(get: { store.viewState.isNavigating }, set: { store.send(.setNavigating($0))}) ) { - DummyView() + CreateProfileView( + store: .init(initialState: CreateProfileFeature.State(userType: .trainee)) { + CreateProfileFeature() + }) } } } @@ -77,7 +80,7 @@ private extension UserTypeSelectionView { .resizable() } } - .frame(idealWidth: 310, idealHeight: 310) + .frame(width: 310, height: 310) .padding(.horizontal, 20) } From 4fb4db7ecd120ac4af56d6b51f3ae21d7d8c75d8 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:59:26 +0900 Subject: [PATCH 05/16] =?UTF-8?q?[Feat]=20Domain=20-=20UserPolicy,TextVali?= =?UTF-8?q?dator=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Sources/Policy/UserPolicy.swift | 8 +++++- .../Sources/Utility/TextValidator.swift | 28 +++++++++++++------ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/TnT/Projects/Domain/Sources/Policy/UserPolicy.swift b/TnT/Projects/Domain/Sources/Policy/UserPolicy.swift index a513098..e3b8a39 100644 --- a/TnT/Projects/Domain/Sources/Policy/UserPolicy.swift +++ b/TnT/Projects/Domain/Sources/Policy/UserPolicy.swift @@ -6,4 +6,10 @@ // Copyright © 2025 yapp25thTeamTnT. All rights reserved. // -import Foundation +public struct UserPolicy { + /// 사용자 이름 최대 길이 제한 (공백 포함) + public static let maxNameLength: Int = 15 + + /// 사용자 이름 가능 문자: 한글/영어/공백만 허용 (특수문자 불가) + public static let allowedCharactersRegex = "^[ㄱ-ㅎㅏ-ㅣ가-힣a-zA-Z ]*$" +} diff --git a/TnT/Projects/Domain/Sources/Utility/TextValidator.swift b/TnT/Projects/Domain/Sources/Utility/TextValidator.swift index ba56180..be11db0 100644 --- a/TnT/Projects/Domain/Sources/Utility/TextValidator.swift +++ b/TnT/Projects/Domain/Sources/Utility/TextValidator.swift @@ -1,5 +1,5 @@ // -// StringValidator.swift +// TextValidator.swift // Domain // // Created by 박민서 on 1/17/25. @@ -8,12 +8,24 @@ import Foundation -public struct UserPolicyValidator { - /// 입력값이 `UserPolicy.allowedCharactersRegex`에 맞는지 검사하는 함수 - public static func isValidName(_ name: String) -> Bool { - let pattern = UserPolicy.allowedCharactersRegex - guard let regex = try? NSRegularExpression(pattern: pattern) else { return false } - let range = NSRange(location: 0, length: name.utf16.count) - return regex.firstMatch(in: name, options: [], range: range) != nil +public enum TextValidator { + /// 사용자가 입력할 때 즉각적인 유효성 검사를 수행하는 함수 + /// - Parameters: + /// - text: 검증할 문자열 + /// - maxLength: 최대 입력 가능 길이 + /// - regexPattern: 허용할 문자에 대한 정규식 + /// - Returns: 입력이 유효한지 여부 (`true` = 허용, `false` = 입력 불가) + public static func isValidInput( + _ text: String, + maxLength: Int, + regexPattern: String + ) -> Bool { + guard text.count <= maxLength, + let regex = try? NSRegularExpression(pattern: regexPattern) else { + return false + } + + let range: NSRange = .init(location: 0, length: text.utf16.count) + return regex.firstMatch(in: text, options: [], range: range) != nil } } From 364e1f77882791b4190afb044ed1061443965595 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:00:25 +0900 Subject: [PATCH 06/16] =?UTF-8?q?[Fix]=20TTextFiled=20=EB=82=B4=EB=B6=80?= =?UTF-8?q?=20TextColor=20State=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/TextField/TTextField.swift | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/TnT/Projects/DesignSystem/Sources/Components/TextField/TTextField.swift b/TnT/Projects/DesignSystem/Sources/Components/TextField/TTextField.swift index 473bee0..29bd5fd 100644 --- a/TnT/Projects/DesignSystem/Sources/Components/TextField/TTextField.swift +++ b/TnT/Projects/DesignSystem/Sources/Components/TextField/TTextField.swift @@ -20,6 +20,8 @@ public struct TTextField: View { @Binding private var text: String /// 텍스트 필드 상태 @Binding private var status: Status + /// 텍스트 컬러 상태 + @State private var textColor: Color = .neutral400 /// 텍스트 필드 포커스 상태 @FocusState var isFocused: Bool @@ -51,9 +53,12 @@ public struct TTextField: View { .lineSpacing(Typography.FontStyle.body1Medium.lineSpacing) .kerning(Typography.FontStyle.body1Medium.letterSpacing) .tint(Color.neutral800) - .foregroundStyle(status.textColor) + .foregroundStyle(textColor) .padding(8) .frame(height: 42) + .onChange(of: status) { + textColor = status.textColor(isFocused: isFocused) + } if let rightView { rightView @@ -66,7 +71,6 @@ public struct TTextField: View { } public extension TTextField.RightView { - /// enum Style { case unit(text: String, status: TTextField.Status) case button(title: String, tapAction: () -> Void) @@ -88,7 +92,7 @@ public extension TTextField { switch style { case let .unit(text, status): Text(text) - .typographyStyle(.body1Medium, with: status.textColor) + .typographyStyle(.body1Medium, with: status.textColor(isFocused: true)) .padding(.horizontal, 12) .padding(.vertical, 3) @@ -115,19 +119,19 @@ public extension TTextField { private let title: String /// 입력 가능한 글자 수 제한 private let limitCount: Int? - /// 입력된 텍스트 - @Binding private var text: String + /// 입력된 텍스트 카운트 + private var textCount: Int? public init( isRequired: Bool, title: String, limitCount: Int?, - text: Binding + textCount: Int? ) { self.isRequired = isRequired self.title = title self.limitCount = limitCount - self._text = text + self.textCount = textCount } public var body: some View { @@ -141,9 +145,9 @@ public extension TTextField { Spacer() - if let limitCount { - Text("\(text.count)/\(limitCount)자") - .typographyStyle(.label1Medium, with: .neutral400) + if let limitCount, let textCount { + Text("\(textCount)/\(limitCount)자") + .typographyStyle(.label1Medium, with: textCount > limitCount ? .red500 : .neutral400) .padding(.horizontal, 4) .padding(.vertical, 2) } @@ -156,11 +160,11 @@ public extension TTextField { /// 푸터 텍스트 private let footerText: String /// 텍스트 필드 상태 - @Binding private var status: Status + private var status: Status - public init(footerText: String, status: Binding) { + public init(footerText: String, status: Status) { self.footerText = footerText - self._status = status + self.status = status } public var body: some View { @@ -172,7 +176,7 @@ public extension TTextField { public extension TTextField { /// TextField에 표시되는 상태입니다 - enum Status { + enum Status: Equatable { case empty case filled case invalid @@ -181,9 +185,7 @@ public extension TTextField { /// 밑선 색상 설정 func underlineColor(isFocused: Bool) -> Color { switch self { - case .empty: - return isFocused ? .neutral600 : .neutral200 - case .filled: + case .empty, .filled: return isFocused ? .neutral600 : .neutral200 case .invalid: return .red500 @@ -193,7 +195,7 @@ public extension TTextField { } /// 텍스트 색상 설정 - var textColor: Color { + func textColor(isFocused: Bool) -> Color { switch self { case .empty: return .neutral400 From 33245a6b2766944f5f5ba71589e757a8884210dc Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:01:34 +0900 Subject: [PATCH 07/16] =?UTF-8?q?[Feat]=20CreateProfile=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreateProfile/CreateProfileFeature.swift | 118 +++++++++++-- .../CreateProfile/CreateProfileView.swift | 160 ++++++++++-------- 2 files changed, 192 insertions(+), 86 deletions(-) diff --git a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift index 9a98188..8ef9851 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift @@ -6,9 +6,12 @@ // Copyright © 2025 yapp25thTeamTnT. All rights reserved. // +import UIKit import ComposableArchitecture import Domain +import DesignSystem +import _PhotosUI_SwiftUI /// 역할 선택 화면의 상태 및 로직을 관리하는 리듀서입니다. @Reducer @@ -18,43 +21,92 @@ public struct CreateProfileFeature { public struct State { /// 현재 선택된 유저 타입 (트레이너/트레이니) var userType: UserType + /// 현재 입력된 사용자 이름 + var userName: String + /// 선택된 프로필 이미지 (데이터 형식) + var userImageData: Data? + /// UI 관련 상태 var viewState: ViewState - /// `UserTypeSelectionFeature.State`의 생성자 + /// `CreateProfileFeature.State`의 생성자 /// - Parameters: /// - userType: 현재 선택된 유저 타입 (기본값: `.trainer`) + /// - userName: 입력된 유저 이름 (기본값: 공백) + /// - userImageData: 선택된 이미지 데이터 (기본값: `nil`) /// - viewState: UI 관련 상태 (기본값: `ViewState()`). - public init(userType: UserType = .trainer, viewState: ViewState = .init()) { + public init( + userType: UserType, + userImageData: Data? = nil, + userName: String = "", + viewState: ViewState = ViewState() + ) { self.userType = userType + self.userImageData = userImageData + self.userName = userName self.viewState = viewState } } /// UI 관련 상태를 관리하는 구조체입니다. public struct ViewState: Equatable { - /// 다음 화면으로 이동 여부 - public var isNavigating: Bool + /// 텍스트 필드 상태 (빈 값 / 입력됨 / 유효하지 않음) + var textFieldStatus: TTextField.Status + /// 포토 피커 표시 여부 + var showPhotoPicker: Bool + /// "다음" 버튼 활성화 여부 + var isNextButtonEnabled: Bool + /// 네비게이션 여부 (다음 화면 이동) + var isNavigating: Bool + /// 현재 선택된 이미지 (PhotosPickerItem) + var photoPickerItem: PhotosPickerItem? + + /// 하단 푸터 텍스트 표시 여부 (이름이 유효하지 않을 경우 표시) + var showFooterText: Bool { + return textFieldStatus == .invalid + } /// `ViewState`의 생성자 - /// - Parameter isNavigating: 네비게이션 여부 (기본값: `false`) - public init(isNavigating: Bool = false) { + /// - Parameters: + /// - textFieldStatus: 텍스트 필드 상태 (기본값: `.empty`) + /// - showPhotoPicker: 포토 피커 표시 여부 (기본값: `false`) + /// - isNextButtonEnabled: "다음" 버튼 활성화 여부 (기본값: `false`) + /// - isNavigating: 네비게이션 여부 (기본값: `false`) + /// - photoPickerItem: 현재 선택된 이미지 아이템 (기본값: `nil`) + public init( + textFieldStatus: TTextField.Status = .empty, + showPhotoPicker: Bool = false, + isNextButtonEnabled: Bool = false, + isNavigating: Bool = false, + photoPickerItem: PhotosPickerItem? = nil + ) { + self.textFieldStatus = textFieldStatus + self.showPhotoPicker = showPhotoPicker + self.isNextButtonEnabled = isNextButtonEnabled self.isNavigating = isNavigating + self.photoPickerItem = photoPickerItem } } - public enum Action: ViewAction { /// 네비게이션 여부 설정 case setNavigating(Bool) + /// 선택된 이미지 데이터 저장 + case imagePicked(Data?) /// 뷰에서 발생한 액션을 처리합니다. case view(ViewAction) public enum ViewAction { - /// 유저 타입 선택 버튼이 눌렸을 때 - case tapUserTypeButton(UserType) + /// 프로필 사진 변경 버튼이 눌렸을 때 (사진 선택 모달 띄우기) + case tapWriteButton + /// 사용자 이름 입력 + case typeUserName(String) + /// 텍스트 필드 상태 업데이트 + case updateTextFieldStatus(TTextField.Status) /// "다음으로" 버튼이 눌렸을 때 case tapNextButton + /// 포토 피커에서 이미지가 선택되었을 때 + case tapImageInPicker(PhotosPickerItem?) } } @@ -65,17 +117,61 @@ public struct CreateProfileFeature { switch action { case .view(let action): switch action { - case .tapUserTypeButton(let userType): - state.userType = userType + case .tapWriteButton: + state.viewState.showPhotoPicker = true + return .none + + case .typeUserName(let userName): + state.userName = userName + return self.validate(&state) + + case .updateTextFieldStatus(let status): + state.viewState.textFieldStatus = status return .none + case .tapNextButton: print("다음으로..") return .send(.setNavigating(true)) + + case .tapImageInPicker(let item): + state.viewState.photoPickerItem = item + return .run { [item] send in + if let item, let data = try? await item.loadTransferable(type: Data.self) { + await send(.imagePicked(data)) + } + } } + case .setNavigating(let isNavigating): state.viewState.isNavigating = isNavigating return .none + + case .imagePicked(let imgData): + state.userImageData = imgData + return .none } } } } + +// MARK: Internal Logic +private extension CreateProfileFeature { + /// 사용자 입력값을 검증하고 상태를 업데이트합니다. + private func validate(_ state: inout State) -> Effect { + guard !(state.userName.isEmpty) else { + state.viewState.textFieldStatus = .empty + return .none + } + + let isNameValid: Bool = TextValidator.isValidInput( + state.userName, + maxLength: UserPolicy.maxNameLength, + regexPattern: UserPolicy.allowedCharactersRegex + ) + + state.viewState.textFieldStatus = isNameValid ? .filled : .invalid + state.viewState.isNextButtonEnabled = isNameValid + + return .none + } +} diff --git a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift index ef9604a..7c4c740 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift @@ -8,6 +8,7 @@ import SwiftUI import ComposableArchitecture +import PhotosUI import Domain import DesignSystem @@ -25,106 +26,115 @@ public struct CreateProfileView: View { } public var body: some View { - NavigationStack { - VStack { + VStack { + + Header + + VStack(spacing: 24) { - Spacer(minLength: 60) + ImageSection - VStack(spacing: 48) { - Header - - ImageSection - - ButtonSection - } - - Spacer() - - BottomTempButton { - send(.tapNextButton) - } + TextFieldSection } - .ignoresSafeArea(.container, edges: .bottom) - .navigationDestination( - isPresented: Binding(get: { store.viewState.isNavigating }, set: { store.send(.setNavigating($0))}) - ) { - DummyView() + + Spacer() + + BottomTempButton(isEnabled: store.viewState.isNextButtonEnabled) { + send(.tapNextButton) } } + .ignoresSafeArea(.container, edges: .bottom) } } // MARK: - SubViews private extension CreateProfileView { var Header: some View { - VStack(alignment: .leading, spacing: 12) { - Text("안녕하세요!\n어떤 회원으로 이용하시겠어요?") - .typographyStyle(.heading2, with: .neutral950) - Text("트레이너와 트레이니 중 선택해주세요.") - .typographyStyle(.body1Medium, with: .neutral500) - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 24) + Text("이름이 어떻게 되세요?") + .typographyStyle(.heading2, with: .neutral950) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 24) + .padding(.bottom, 48) } var ImageSection: some View { Group { - if store.userType == .trainer { - Image(.imgOnboardingTrainer) - .resizable() + if let imageData = store.userImageData, + let uiImage = UIImage(data: imageData) { + Image(uiImage: uiImage) + .resizable() + .clipShape(Circle()) } else { - Image(.imgOnboardingTrainee) - .resizable() + if store.userType == .trainer { + Image(.imgDefaultTrainerImage) + .resizable() + .clipShape(Circle()) + } else { + Image(.imgDefaultTraineeImage) + .resizable() + .clipShape(Circle()) + } + } + } + .frame(width: 132, height: 132) + .overlay(alignment: .bottomTrailing) { + PhotosPicker( + selection: Binding(get: { + store.viewState.photoPickerItem + }, set: { + send(.tapImageInPicker($0)) + }), + matching: .images, + photoLibrary: .shared() + ) { + ZStack { + Circle() + .fill(Color.neutral900) + .frame(width: 28, height: 28) + Image(.icnWriteWhite) + .resizable() + .frame(width: 16, height: 16) + } } } - .frame(idealWidth: 310, idealHeight: 310) - .padding(.horizontal, 20) } - var ButtonSection: some View { - HStack(spacing: 8) { - TempButton( - isSelected: store.userType == UserType.trainer, - title: UserType.trainer.koreanName, - action: { send(.tapUserTypeButton(.trainer), animation: .easeInOut(duration: 0.5)) } - ) - TempButton( - isSelected: store.userType == UserType.trainee, - title: UserType.trainee.koreanName, - action: { send(.tapUserTypeButton(.trainee), animation: .easeInOut(duration: 0.5)) } + var TextFieldSection: some View { + TTextField( + placeholder: "이름을 입력해주세요", + text: Binding(get: { + store.userName + }, set: { + send(.typeUserName($0)) + }), + textFieldStatus: Binding(get: { + store.viewState.textFieldStatus + }, set: { + send(.updateTextFieldStatus($0)) + }) + ) + .withSectionLayout( + header: .init( + isRequired: true, + title: "이름", + limitCount: UserPolicy.maxNameLength, + textCount: store.userName.count + ), + footer: .init( + footerText: store.viewState.showFooterText + ? "\(UserPolicy.maxNameLength)자 이하로 입력해주세요" + : "", + status: store.viewState.textFieldStatus ) - } + ) .padding(.horizontal, 20) } } // TODO: 버튼 컴포넌트 나오면 대체 -private extension CreateProfileView { - struct TempButton: View { - var isSelected: Bool = false - var title: String - let action: (() -> Void) - - var body: some View { - Button(action: { - action() - }) { - Text(title) - .typographyStyle(.body1Medium, with: isSelected ? .red600 : .neutral500) - .padding(.vertical, 16) - .frame(height: 58) - .frame(maxWidth: .infinity) - .background(isSelected ? Color.red50 : .clear) - .cornerRadius(16) - .overlay( - RoundedRectangle(cornerRadius: 16) - .stroke(isSelected ? Color.red400 : Color.neutral300, lineWidth: isSelected ? 1.5 : 1.0) - ) - } - } - } - +private extension CreateProfileView { struct BottomTempButton: View { + var isEnabled: Bool let action: (() -> Void) var body: some View { @@ -137,9 +147,9 @@ private extension CreateProfileView { .padding(.bottom, 53) .frame(height: 100) .frame(maxWidth: .infinity) - .background(Color.neutral900) + .background(isEnabled ? Color.neutral900 : Color.neutral300) + .disabled(!isEnabled) } } } } - From 503b4a8faddbeb0cfb6f1e5ec145fd3a131837af Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:03:36 +0900 Subject: [PATCH 08/16] =?UTF-8?q?[Feat]=20=ED=99=94=EB=A9=B4=20=EC=B5=9C?= =?UTF-8?q?=EC=B4=88=20=EC=8B=9C=EC=9E=91=20=EC=8B=9C=20=ED=8F=B0=ED=8A=B8?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TnT/Projects/TnTApp/Sources/TnTApp.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TnT/Projects/TnTApp/Sources/TnTApp.swift b/TnT/Projects/TnTApp/Sources/TnTApp.swift index 432ec86..ca9ab0f 100644 --- a/TnT/Projects/TnTApp/Sources/TnTApp.swift +++ b/TnT/Projects/TnTApp/Sources/TnTApp.swift @@ -8,8 +8,14 @@ import SwiftUI +import DesignSystem + @main struct ToyProjectApp: App { + + init() { + DesignSystemFontFamily.registerAllCustomFonts() + } var body: some Scene { WindowGroup { From 4f5c79c776068b851db5c9681b326bab5710e149 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:37:31 +0900 Subject: [PATCH 09/16] =?UTF-8?q?[Chore]=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=B9=84=EC=9C=A8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Onboarding/Trainee/CreateProfile/CreateProfileView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift index 7c4c740..b1388ab 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift @@ -63,6 +63,7 @@ private extension CreateProfileView { let uiImage = UIImage(data: imageData) { Image(uiImage: uiImage) .resizable() + .scaledToFill() .clipShape(Circle()) } else { if store.userType == .trainer { From 741f0f72351a2dd3a26864f9826ab4289401347f Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Sun, 19 Jan 2025 23:53:32 +0900 Subject: [PATCH 10/16] =?UTF-8?q?[Delete]=20=EC=A4=91=EB=B3=B5=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Onboarding/CreateProfile/CreateProfileFeature.swift | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 TnT/Projects/Presentation/Sources/Onboarding/CreateProfile/CreateProfileFeature.swift diff --git a/TnT/Projects/Presentation/Sources/Onboarding/CreateProfile/CreateProfileFeature.swift b/TnT/Projects/Presentation/Sources/Onboarding/CreateProfile/CreateProfileFeature.swift deleted file mode 100644 index fe1d006..0000000 --- a/TnT/Projects/Presentation/Sources/Onboarding/CreateProfile/CreateProfileFeature.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// CreateProfileFeature.swift -// Presentation -// -// Created by 박민서 on 1/17/25. -// Copyright © 2025 yapp25thTeamTnT. All rights reserved. -// - -import Foundation From 58d5b9ea1f410a629a97e3fad7bbedce2958dc52 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Mon, 20 Jan 2025 00:10:49 +0900 Subject: [PATCH 11/16] =?UTF-8?q?[Chore]=20=EC=82=AC=EC=9A=A9=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EB=A6=AC=EB=84=A4=EC=9D=B4=EB=B0=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreateProfile/CreateProfileFeature.swift | 16 ++++++++-------- .../CreateProfile/CreateProfileView.swift | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift index 8ef9851..66ce33e 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift @@ -53,7 +53,7 @@ public struct CreateProfileFeature { /// 텍스트 필드 상태 (빈 값 / 입력됨 / 유효하지 않음) var textFieldStatus: TTextField.Status /// 포토 피커 표시 여부 - var showPhotoPicker: Bool + var isPhotoPickerPresented: Bool /// "다음" 버튼 활성화 여부 var isNextButtonEnabled: Bool /// 네비게이션 여부 (다음 화면 이동) @@ -62,26 +62,26 @@ public struct CreateProfileFeature { var photoPickerItem: PhotosPickerItem? /// 하단 푸터 텍스트 표시 여부 (이름이 유효하지 않을 경우 표시) - var showFooterText: Bool { + var isFooterTextVisible: Bool { return textFieldStatus == .invalid } /// `ViewState`의 생성자 /// - Parameters: /// - textFieldStatus: 텍스트 필드 상태 (기본값: `.empty`) - /// - showPhotoPicker: 포토 피커 표시 여부 (기본값: `false`) + /// - isPhotoPickerPresented: 포토 피커 표시 여부 (기본값: `false`) /// - isNextButtonEnabled: "다음" 버튼 활성화 여부 (기본값: `false`) /// - isNavigating: 네비게이션 여부 (기본값: `false`) /// - photoPickerItem: 현재 선택된 이미지 아이템 (기본값: `nil`) public init( textFieldStatus: TTextField.Status = .empty, - showPhotoPicker: Bool = false, + isPhotoPickerPresented: Bool = false, isNextButtonEnabled: Bool = false, isNavigating: Bool = false, photoPickerItem: PhotosPickerItem? = nil ) { self.textFieldStatus = textFieldStatus - self.showPhotoPicker = showPhotoPicker + self.isPhotoPickerPresented = isPhotoPickerPresented self.isNextButtonEnabled = isNextButtonEnabled self.isNavigating = isNavigating self.photoPickerItem = photoPickerItem @@ -94,9 +94,9 @@ public struct CreateProfileFeature { /// 선택된 이미지 데이터 저장 case imagePicked(Data?) /// 뷰에서 발생한 액션을 처리합니다. - case view(ViewAction) + case view(View) - public enum ViewAction { + public enum View { /// 프로필 사진 변경 버튼이 눌렸을 때 (사진 선택 모달 띄우기) case tapWriteButton /// 사용자 이름 입력 @@ -118,7 +118,7 @@ public struct CreateProfileFeature { case .view(let action): switch action { case .tapWriteButton: - state.viewState.showPhotoPicker = true + state.viewState.isPhotoPickerPresented = true return .none case .typeUserName(let userName): diff --git a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift index b1388ab..1c77465 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift @@ -122,7 +122,7 @@ private extension CreateProfileView { textCount: store.userName.count ), footer: .init( - footerText: store.viewState.showFooterText + footerText: store.viewState.isFooterTextVisible ? "\(UserPolicy.maxNameLength)자 이하로 입력해주세요" : "", status: store.viewState.textFieldStatus From e640c175e09a8c3dcd7f8675a72c4a672e54fa5f Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Mon, 20 Jan 2025 00:13:58 +0900 Subject: [PATCH 12/16] =?UTF-8?q?[Chore]=20import,=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=EC=A0=9C=EC=96=B4=EC=9E=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Trainee/CreateProfile/CreateProfileFeature.swift | 4 ++-- .../Onboarding/Trainee/CreateProfile/CreateProfileView.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift index 66ce33e..e728a8c 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift @@ -7,11 +7,11 @@ // import UIKit +import _PhotosUI_SwiftUI import ComposableArchitecture import Domain import DesignSystem -import _PhotosUI_SwiftUI /// 역할 선택 화면의 상태 및 로직을 관리하는 리듀서입니다. @Reducer @@ -157,7 +157,7 @@ public struct CreateProfileFeature { // MARK: Internal Logic private extension CreateProfileFeature { /// 사용자 입력값을 검증하고 상태를 업데이트합니다. - private func validate(_ state: inout State) -> Effect { + func validate(_ state: inout State) -> Effect { guard !(state.userName.isEmpty) else { state.viewState.textFieldStatus = .empty return .none diff --git a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift index 1c77465..596f27c 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift @@ -7,8 +7,8 @@ // import SwiftUI -import ComposableArchitecture import PhotosUI +import ComposableArchitecture import Domain import DesignSystem From f3e4a434bffd885e5e93625aefe488c4eda2973d Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Mon, 20 Jan 2025 19:11:06 +0900 Subject: [PATCH 13/16] =?UTF-8?q?[Feat]=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreateProfile/CreateProfileFeature.swift | 88 ++++++++++++++----- .../CreateProfile/CreateProfileView.swift | 74 +++++++--------- .../UserTypeSelectionView.swift | 14 ++- 3 files changed, 102 insertions(+), 74 deletions(-) diff --git a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift index e728a8c..949502e 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift @@ -12,13 +12,14 @@ import ComposableArchitecture import Domain import DesignSystem +import SwiftUICore /// 역할 선택 화면의 상태 및 로직을 관리하는 리듀서입니다. @Reducer public struct CreateProfileFeature { @ObservableState - public struct State { + public struct State: Equatable { /// 현재 선택된 유저 타입 (트레이너/트레이니) var userType: UserType /// 현재 입력된 사용자 이름 @@ -27,7 +28,11 @@ public struct CreateProfileFeature { var userImageData: Data? /// UI 관련 상태 - var viewState: ViewState + var viewState: ViewState { + didSet { + print("changed") + } // 🔹 내부 변경이 발생할 때마다 id 업데이트 + } /// `CreateProfileFeature.State`의 생성자 /// - Parameters: @@ -59,7 +64,11 @@ public struct CreateProfileFeature { /// 네비게이션 여부 (다음 화면 이동) var isNavigating: Bool /// 현재 선택된 이미지 (PhotosPickerItem) - var photoPickerItem: PhotosPickerItem? + var photoPickerItem: PhotosPickerItem? { + didSet { + print("item picked") + } + } /// 하단 푸터 텍스트 표시 여부 (이름이 유효하지 않을 경우 표시) var isFooterTextVisible: Bool { @@ -88,7 +97,7 @@ public struct CreateProfileFeature { } } - public enum Action: ViewAction { + public enum Action: Sendable, ViewAction { /// 네비게이션 여부 설정 case setNavigating(Bool) /// 선택된 이미지 데이터 저장 @@ -96,16 +105,14 @@ public struct CreateProfileFeature { /// 뷰에서 발생한 액션을 처리합니다. case view(View) - public enum View { + @CasePathable + public enum View: Sendable, BindableAction { + /// 바인딩할 액션을 처리합니다 + case binding(BindingAction) /// 프로필 사진 변경 버튼이 눌렸을 때 (사진 선택 모달 띄우기) case tapWriteButton - /// 사용자 이름 입력 - case typeUserName(String) - /// 텍스트 필드 상태 업데이트 - case updateTextFieldStatus(TTextField.Status) /// "다음으로" 버튼이 눌렸을 때 case tapNextButton - /// 포토 피커에서 이미지가 선택되었을 때 case tapImageInPicker(PhotosPickerItem?) } } @@ -113,25 +120,29 @@ public struct CreateProfileFeature { public init() {} public var body: some ReducerOf { + BindingReducer(action: \.view) + Reduce { state, action in switch action { case .view(let action): switch action { - case .tapWriteButton: - state.viewState.isPhotoPickerPresented = true - return .none - - case .typeUserName(let userName): - state.userName = userName + case .binding(\.userName): return self.validate(&state) - case .updateTextFieldStatus(let status): - state.viewState.textFieldStatus = status + case .binding(\.viewState.photoPickerItem): + let item: PhotosPickerItem? = state.viewState.photoPickerItem + print("왜 안돼") + return .run { [item] send in + if let item, let data = try? await item.loadTransferable(type: Data.self) { + await send(.imagePicked(data)) + } + } + + case .binding(\.viewState): return .none - case .tapNextButton: - print("다음으로..") - return .send(.setNavigating(true)) + case .binding: + return .none case .tapImageInPicker(let item): state.viewState.photoPickerItem = item @@ -140,6 +151,14 @@ public struct CreateProfileFeature { await send(.imagePicked(data)) } } + + case .tapWriteButton: + state.viewState.isPhotoPickerPresented = true + return .none + + case .tapNextButton: + print("다음으로..") + return .send(.setNavigating(true)) } case .setNavigating(let isNavigating): @@ -175,3 +194,30 @@ private extension CreateProfileFeature { return .none } } + + +/// ✅ 자동으로 `id`를 변경하는 프로퍼티 래퍼 +@propertyWrapper +struct AutoIdentifiable: Equatable { + var id: UUID = UUID() // 변경 감지를 위한 ID + private var value: T + + var wrappedValue: T { + get { value } + set { + if value != newValue { + value = newValue + id = UUID() // 값이 변경되면 id도 변경 + } + } + } + + init(wrappedValue: T) { + self.value = wrappedValue + } + + // ✅ Equatable 수동 구현 + static func == (lhs: AutoIdentifiable, rhs: AutoIdentifiable) -> Bool { + lhs.value == rhs.value + } +} diff --git a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift index 596f27c..587b60a 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift @@ -17,7 +17,7 @@ import DesignSystem @ViewAction(for: CreateProfileFeature.self) public struct CreateProfileView: View { - public var store: StoreOf + @Bindable public var store: StoreOf /// `CreateProfileView`의 생성자 /// - Parameter store: `CreateProfileFeature`의 상태를 관리하는 `Store` @@ -58,61 +58,47 @@ private extension CreateProfileView { } var ImageSection: some View { - Group { - if let imageData = store.userImageData, - let uiImage = UIImage(data: imageData) { - Image(uiImage: uiImage) - .resizable() - .scaledToFill() - .clipShape(Circle()) - } else { - if store.userType == .trainer { - Image(.imgDefaultTrainerImage) + Group { + if let imageData = store.userImageData, + let uiImage = UIImage(data: imageData) { + Image(uiImage: uiImage) .resizable() + .scaledToFill() .clipShape(Circle()) } else { - Image(.imgDefaultTraineeImage) - .resizable() - .clipShape(Circle()) + Image(store.userType == .trainer + ? .imgDefaultTrainerImage + : .imgDefaultTraineeImage + ) + .resizable() + .clipShape(Circle()) } } - } - .frame(width: 132, height: 132) - .overlay(alignment: .bottomTrailing) { - PhotosPicker( - selection: Binding(get: { - store.viewState.photoPickerItem - }, set: { - send(.tapImageInPicker($0)) - }), - matching: .images, - photoLibrary: .shared() - ) { - ZStack { - Circle() - .fill(Color.neutral900) - .frame(width: 28, height: 28) - Image(.icnWriteWhite) - .resizable() - .frame(width: 16, height: 16) + .frame(width: 132, height: 132) + .overlay(alignment: .bottomTrailing) { + PhotosPicker( + selection: $store.viewState.photoPickerItem, +// .sending(\.view.tapImageInPicker), // -> 해당 액션은 수행됨 + matching: .images, + photoLibrary: .shared() + ) { + ZStack { + Circle() + .fill(Color.neutral900) + .frame(width: 28, height: 28) + Image(.icnWriteWhite) + .resizable() + .frame(width: 16, height: 16) + } } } - } } var TextFieldSection: some View { TTextField( placeholder: "이름을 입력해주세요", - text: Binding(get: { - store.userName - }, set: { - send(.typeUserName($0)) - }), - textFieldStatus: Binding(get: { - store.viewState.textFieldStatus - }, set: { - send(.updateTextFieldStatus($0)) - }) + text: $store.userName, + textFieldStatus: $store.viewState.textFieldStatus ) .withSectionLayout( header: .init( diff --git a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift index 6054e5f..81aba40 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift @@ -71,15 +71,11 @@ private extension UserTypeSelectionView { } var ImageSection: some View { - Group { - if store.userType == .trainer { - Image(.imgOnboardingTrainer) - .resizable() - } else { - Image(.imgOnboardingTrainee) - .resizable() - } - } + Image(store.userType == .trainer + ? .imgOnboardingTrainer + : .imgOnboardingTrainee + ) + .resizable() .frame(width: 310, height: 310) .padding(.horizontal, 20) } From 47f3616f85dd55718fd0d3d61db0007299840afc Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Tue, 21 Jan 2025 04:33:39 +0900 Subject: [PATCH 14/16] =?UTF-8?q?[Feat]=20UserTypeSelection=20=EB=A6=AC?= =?UTF-8?q?=EB=93=80=EC=84=9C=20=EA=B5=AC=EC=A1=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Sources/Entity/UserType.swift | 2 +- .../UserTypeSelectionFeature.swift | 48 ++++++++----------- .../UserTypeSelectionView.swift | 4 +- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/TnT/Projects/Domain/Sources/Entity/UserType.swift b/TnT/Projects/Domain/Sources/Entity/UserType.swift index b63c9c8..0bd1714 100644 --- a/TnT/Projects/Domain/Sources/Entity/UserType.swift +++ b/TnT/Projects/Domain/Sources/Entity/UserType.swift @@ -6,7 +6,7 @@ // Copyright © 2025 yapp25thTeamTnT. All rights reserved. // -public enum UserType { +public enum UserType: Sendable { case trainer case trainee diff --git a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift index e4ce959..78a4e18 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionFeature.swift @@ -15,41 +15,32 @@ import Domain public struct UserTypeSelectionFeature { @ObservableState - public struct State { + public struct State: Equatable { + // MARK: Data related state /// 현재 선택된 유저 타입 (트레이너/트레이니) var userType: UserType - /// UI 관련 상태 - var viewState: ViewState + + // MARK: UI related state + /// 다음 화면으로 이동 여부 + public var view_isNavigating: Bool /// `UserTypeSelectionFeature.State`의 생성자 /// - Parameters: /// - userType: 현재 선택된 유저 타입 (기본값: `.trainer`) - /// - viewState: UI 관련 상태 (기본값: `ViewState()`). - public init(userType: UserType = .trainer, viewState: ViewState = .init()) { + /// - isNavigating: 네비게이션 여부 (기본값: `false`) + public init(userType: UserType = .trainer, view_isNavigating: Bool = false) { self.userType = userType - self.viewState = viewState + self.view_isNavigating = view_isNavigating } } - /// UI 관련 상태를 관리하는 구조체입니다. - public struct ViewState: Equatable { - /// 다음 화면으로 이동 여부 - public var isNavigating: Bool - - /// `ViewState`의 생성자 - /// - Parameter isNavigating: 네비게이션 여부 (기본값: `false`) - public init(isNavigating: Bool = false) { - self.isNavigating = isNavigating - } - } - - public enum Action: ViewAction { - /// 네비게이션 여부 설정 - case setNavigating(Bool) + public enum Action: Sendable, ViewAction { /// 뷰에서 발생한 액션을 처리합니다. case view(ViewAction) - public enum ViewAction { + public enum ViewAction: Sendable, BindableAction { + /// 바인딩할 액션을 처리합니다 + case binding(BindingAction) /// 유저 타입 선택 버튼이 눌렸을 때 case tapUserTypeButton(UserType) /// "다음으로" 버튼이 눌렸을 때 @@ -60,22 +51,23 @@ public struct UserTypeSelectionFeature { public init() {} public var body: some ReducerOf { + BindingReducer(action: \.view) + Reduce { state, action in switch action { case .view(let action): switch action { + case .binding: + return .none + case .tapUserTypeButton(let userType): state.userType = userType return .none case .tapNextButton: - print("다음으로..") - return .send(.setNavigating(true)) + state.view_isNavigating = true + return .none } - - case .setNavigating(let isNavigating): - state.viewState.isNavigating = isNavigating - return .none } } } diff --git a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift index 81aba40..b4a9c3a 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/UserTypeSelection/UserTypeSelectionView.swift @@ -16,7 +16,7 @@ import DesignSystem @ViewAction(for: UserTypeSelectionFeature.self) public struct UserTypeSelectionView: View { - public var store: StoreOf + @Bindable public var store: StoreOf /// `UserTypeSelectionView`의 생성자 /// - Parameter store: `UserTypeSelectionFeature`의 상태를 관리하는 `Store` @@ -46,7 +46,7 @@ public struct UserTypeSelectionView: View { } .ignoresSafeArea(.container, edges: .bottom) .navigationDestination( - isPresented: Binding(get: { store.viewState.isNavigating }, set: { store.send(.setNavigating($0))}) + isPresented: $store.view_isNavigating ) { CreateProfileView( store: .init(initialState: CreateProfileFeature.State(userType: .trainee)) { From bea409b23d73410a412ad2da672a4a2d1a819147 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Tue, 21 Jan 2025 04:35:24 +0900 Subject: [PATCH 15/16] =?UTF-8?q?[Setting]=20Lint=20=EB=A3=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TnT/Tuist/Config/.swiftlint.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/TnT/Tuist/Config/.swiftlint.yml b/TnT/Tuist/Config/.swiftlint.yml index d3bb516..544f54e 100644 --- a/TnT/Tuist/Config/.swiftlint.yml +++ b/TnT/Tuist/Config/.swiftlint.yml @@ -19,7 +19,7 @@ disabled_rules: - inclusive_language # 포괄적 언어 사용 - identifier_name # 1~2글자 변수 이름 제한 - todo # TODO, FIXME 주석 제한 - + - multiple_closures_with_trailing_closure # 여러 후행 클로저 제한 # 옵트 인 룰 opt_in_rules: @@ -47,7 +47,13 @@ file_length: ignore_comment_only_lines: true # 주석은 줄 계산에서 무시됩니다 # 룰 적용 제외할 파일 -# excluded: -# - TnT/Application/AppDelegate.swift +excluded: + - Project.swift + - ./Project.swift + - Tuist/Project.swift + - "**/Project.swift" + - "*/Project.swift" + - "**/AppDelegate.swift" + - "**/SceneDelegate.swift" # 문서의 끝 From 64a161a86353d51d47ef8cf74b62f8eeb5094d80 Mon Sep 17 00:00:00 2001 From: Minseo Park <125115284+FpRaArNkK@users.noreply.github.com> Date: Tue, 21 Jan 2025 04:35:44 +0900 Subject: [PATCH 16/16] =?UTF-8?q?[Feat]=20CreateProfile=20=EB=A6=AC?= =?UTF-8?q?=EB=93=80=EC=84=9C=20=EA=B5=AC=EC=A1=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreateProfile/CreateProfileFeature.swift | 137 +++++------------- .../CreateProfile/CreateProfileView.swift | 11 +- 2 files changed, 42 insertions(+), 106 deletions(-) diff --git a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift index 949502e..6c0d6c5 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileFeature.swift @@ -12,7 +12,6 @@ import ComposableArchitecture import Domain import DesignSystem -import SwiftUICore /// 역할 선택 화면의 상태 및 로직을 관리하는 리듀서입니다. @Reducer @@ -20,6 +19,7 @@ public struct CreateProfileFeature { @ObservableState public struct State: Equatable { + // MARK: Data related state /// 현재 선택된 유저 타입 (트레이너/트레이니) var userType: UserType /// 현재 입력된 사용자 이름 @@ -27,11 +27,20 @@ public struct CreateProfileFeature { /// 선택된 프로필 이미지 (데이터 형식) var userImageData: Data? - /// UI 관련 상태 - var viewState: ViewState { - didSet { - print("changed") - } // 🔹 내부 변경이 발생할 때마다 id 업데이트 + // MARK: UI related state + /// 텍스트 필드 상태 (빈 값 / 입력됨 / 유효하지 않음) + var view_textFieldStatus: TTextField.Status + /// 포토 피커 표시 여부 + var view_isPhotoPickerPresented: Bool + /// "다음" 버튼 활성화 여부 + var view_isNextButtonEnabled: Bool + /// 네비게이션 여부 (다음 화면 이동) + var view_isNavigating: Bool + /// 현재 선택된 이미지 (PhotosPickerItem) + var view_photoPickerItem: PhotosPickerItem? + /// 하단 푸터 텍스트 표시 여부 (이름이 유효하지 않을 경우 표시) + var view_isFooterTextVisible: Bool { + return view_textFieldStatus == .invalid } /// `CreateProfileFeature.State`의 생성자 @@ -40,60 +49,29 @@ public struct CreateProfileFeature { /// - userName: 입력된 유저 이름 (기본값: 공백) /// - userImageData: 선택된 이미지 데이터 (기본값: `nil`) /// - viewState: UI 관련 상태 (기본값: `ViewState()`). + /// - view_textFieldStatus: 텍스트 필드 상태 (기본값: `.empty`) + /// - view_isPhotoPickerPresented: 포토 피커 표시 여부 (기본값: `false`) + /// - view_isNextButtonEnabled: "다음" 버튼 활성화 여부 (기본값: `false`) + /// - view_isNavigating: 네비게이션 여부 (기본값: `false`) + /// - view_photoPickerItem: 현재 선택된 이미지 아이템 (기본값: `nil`) public init( userType: UserType, userImageData: Data? = nil, userName: String = "", - viewState: ViewState = ViewState() + view_textFieldStatus: TTextField.Status = .empty, + view_isPhotoPickerPresented: Bool = false, + view_isNextButtonEnabled: Bool = false, + view_isNavigating: Bool = false, + view_photoPickerItem: PhotosPickerItem? = nil ) { self.userType = userType self.userImageData = userImageData self.userName = userName - self.viewState = viewState - } - } - - /// UI 관련 상태를 관리하는 구조체입니다. - public struct ViewState: Equatable { - /// 텍스트 필드 상태 (빈 값 / 입력됨 / 유효하지 않음) - var textFieldStatus: TTextField.Status - /// 포토 피커 표시 여부 - var isPhotoPickerPresented: Bool - /// "다음" 버튼 활성화 여부 - var isNextButtonEnabled: Bool - /// 네비게이션 여부 (다음 화면 이동) - var isNavigating: Bool - /// 현재 선택된 이미지 (PhotosPickerItem) - var photoPickerItem: PhotosPickerItem? { - didSet { - print("item picked") - } - } - - /// 하단 푸터 텍스트 표시 여부 (이름이 유효하지 않을 경우 표시) - var isFooterTextVisible: Bool { - return textFieldStatus == .invalid - } - - /// `ViewState`의 생성자 - /// - Parameters: - /// - textFieldStatus: 텍스트 필드 상태 (기본값: `.empty`) - /// - isPhotoPickerPresented: 포토 피커 표시 여부 (기본값: `false`) - /// - isNextButtonEnabled: "다음" 버튼 활성화 여부 (기본값: `false`) - /// - isNavigating: 네비게이션 여부 (기본값: `false`) - /// - photoPickerItem: 현재 선택된 이미지 아이템 (기본값: `nil`) - public init( - textFieldStatus: TTextField.Status = .empty, - isPhotoPickerPresented: Bool = false, - isNextButtonEnabled: Bool = false, - isNavigating: Bool = false, - photoPickerItem: PhotosPickerItem? = nil - ) { - self.textFieldStatus = textFieldStatus - self.isPhotoPickerPresented = isPhotoPickerPresented - self.isNextButtonEnabled = isNextButtonEnabled - self.isNavigating = isNavigating - self.photoPickerItem = photoPickerItem + self.view_textFieldStatus = view_textFieldStatus + self.view_isPhotoPickerPresented = view_isPhotoPickerPresented + self.view_isNextButtonEnabled = view_isNextButtonEnabled + self.view_isNavigating = view_isNavigating + self.view_photoPickerItem = view_photoPickerItem } } @@ -113,7 +91,6 @@ public struct CreateProfileFeature { case tapWriteButton /// "다음으로" 버튼이 눌렸을 때 case tapNextButton - case tapImageInPicker(PhotosPickerItem?) } } @@ -129,40 +106,27 @@ public struct CreateProfileFeature { case .binding(\.userName): return self.validate(&state) - case .binding(\.viewState.photoPickerItem): - let item: PhotosPickerItem? = state.viewState.photoPickerItem - print("왜 안돼") + case .binding(\.view_photoPickerItem): + let item: PhotosPickerItem? = state.view_photoPickerItem return .run { [item] send in if let item, let data = try? await item.loadTransferable(type: Data.self) { await send(.imagePicked(data)) } } - - case .binding(\.viewState): - return .none case .binding: return .none - case .tapImageInPicker(let item): - state.viewState.photoPickerItem = item - return .run { [item] send in - if let item, let data = try? await item.loadTransferable(type: Data.self) { - await send(.imagePicked(data)) - } - } - case .tapWriteButton: - state.viewState.isPhotoPickerPresented = true + state.view_isPhotoPickerPresented = true return .none case .tapNextButton: - print("다음으로..") return .send(.setNavigating(true)) } case .setNavigating(let isNavigating): - state.viewState.isNavigating = isNavigating + state.view_isNavigating = isNavigating return .none case .imagePicked(let imgData): @@ -178,7 +142,7 @@ private extension CreateProfileFeature { /// 사용자 입력값을 검증하고 상태를 업데이트합니다. func validate(_ state: inout State) -> Effect { guard !(state.userName.isEmpty) else { - state.viewState.textFieldStatus = .empty + state.view_textFieldStatus = .empty return .none } @@ -188,36 +152,9 @@ private extension CreateProfileFeature { regexPattern: UserPolicy.allowedCharactersRegex ) - state.viewState.textFieldStatus = isNameValid ? .filled : .invalid - state.viewState.isNextButtonEnabled = isNameValid + state.view_textFieldStatus = isNameValid ? .filled : .invalid + state.view_isNextButtonEnabled = isNameValid return .none } } - - -/// ✅ 자동으로 `id`를 변경하는 프로퍼티 래퍼 -@propertyWrapper -struct AutoIdentifiable: Equatable { - var id: UUID = UUID() // 변경 감지를 위한 ID - private var value: T - - var wrappedValue: T { - get { value } - set { - if value != newValue { - value = newValue - id = UUID() // 값이 변경되면 id도 변경 - } - } - } - - init(wrappedValue: T) { - self.value = wrappedValue - } - - // ✅ Equatable 수동 구현 - static func == (lhs: AutoIdentifiable, rhs: AutoIdentifiable) -> Bool { - lhs.value == rhs.value - } -} diff --git a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift index 587b60a..f62bfd7 100644 --- a/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift +++ b/TnT/Projects/Presentation/Sources/Onboarding/Trainee/CreateProfile/CreateProfileView.swift @@ -39,7 +39,7 @@ public struct CreateProfileView: View { Spacer() - BottomTempButton(isEnabled: store.viewState.isNextButtonEnabled) { + BottomTempButton(isEnabled: store.view_isNextButtonEnabled) { send(.tapNextButton) } } @@ -77,8 +77,7 @@ private extension CreateProfileView { .frame(width: 132, height: 132) .overlay(alignment: .bottomTrailing) { PhotosPicker( - selection: $store.viewState.photoPickerItem, -// .sending(\.view.tapImageInPicker), // -> 해당 액션은 수행됨 + selection: $store.view_photoPickerItem, matching: .images, photoLibrary: .shared() ) { @@ -98,7 +97,7 @@ private extension CreateProfileView { TTextField( placeholder: "이름을 입력해주세요", text: $store.userName, - textFieldStatus: $store.viewState.textFieldStatus + textFieldStatus: $store.view_textFieldStatus ) .withSectionLayout( header: .init( @@ -108,10 +107,10 @@ private extension CreateProfileView { textCount: store.userName.count ), footer: .init( - footerText: store.viewState.isFooterTextVisible + footerText: store.view_isFooterTextVisible ? "\(UserPolicy.maxNameLength)자 이하로 입력해주세요" : "", - status: store.viewState.textFieldStatus + status: store.view_textFieldStatus ) ) .padding(.horizontal, 20)