From 4bbf90d87e11f1ee75f8e728a58b85aae7020ebd Mon Sep 17 00:00:00 2001 From: HongSJae Date: Fri, 26 Apr 2024 09:55:54 +0900 Subject: [PATCH 01/11] =?UTF-8?q?=E2=9C=A8=20::=20find=20Password=20Featur?= =?UTF-8?q?e=20init?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectDescriptionHelpers/ModulePaths.swift | 1 + .../Interface/Interface.swift | 1 + .../Feature/FindPasswordFeature/Project.swift | 17 +++++++++++++++++ .../FindPasswordFeature/Sources/Sources.swift | 1 + .../Tests/FindPasswordFeatureTest.swift | 11 +++++++++++ 5 files changed, 31 insertions(+) create mode 100644 Projects/Feature/FindPasswordFeature/Interface/Interface.swift create mode 100644 Projects/Feature/FindPasswordFeature/Project.swift create mode 100644 Projects/Feature/FindPasswordFeature/Sources/Sources.swift create mode 100644 Projects/Feature/FindPasswordFeature/Tests/FindPasswordFeatureTest.swift diff --git a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift index e179a2d..7d3ed7c 100644 --- a/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift +++ b/Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift @@ -24,6 +24,7 @@ extension ModulePaths: MicroTargetPathConvertable { public extension ModulePaths { enum Feature: String, MicroTargetPathConvertable { + case FindPasswordFeature case SignupFeature case SigninFeature case SplashFeature diff --git a/Projects/Feature/FindPasswordFeature/Interface/Interface.swift b/Projects/Feature/FindPasswordFeature/Interface/Interface.swift new file mode 100644 index 0000000..b1853ce --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Interface/Interface.swift @@ -0,0 +1 @@ +// This is for Tuist diff --git a/Projects/Feature/FindPasswordFeature/Project.swift b/Projects/Feature/FindPasswordFeature/Project.swift new file mode 100644 index 0000000..f654b37 --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Project.swift @@ -0,0 +1,17 @@ +import DependencyPlugin +import ProjectDescription +import ProjectDescriptionHelpers + +let project = Project.module( + name: ModulePaths.Feature.FindPasswordFeature.rawValue, + targets: [ + .interface(module: .feature(.FindPasswordFeature)), + .implements(module: .feature(.FindPasswordFeature), dependencies: [ + .feature(target: .FindPasswordFeature, type: .interface), + .feature(target: .BaseFeature) + ]), + .tests(module: .feature(.FindPasswordFeature), dependencies: [ + .feature(target: .FindPasswordFeature) + ]) + ] +) diff --git a/Projects/Feature/FindPasswordFeature/Sources/Sources.swift b/Projects/Feature/FindPasswordFeature/Sources/Sources.swift new file mode 100644 index 0000000..b1853ce --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Sources/Sources.swift @@ -0,0 +1 @@ +// This is for Tuist diff --git a/Projects/Feature/FindPasswordFeature/Tests/FindPasswordFeatureTest.swift b/Projects/Feature/FindPasswordFeature/Tests/FindPasswordFeatureTest.swift new file mode 100644 index 0000000..ee17017 --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Tests/FindPasswordFeatureTest.swift @@ -0,0 +1,11 @@ +import XCTest + +final class FindPasswordFeatureTests: XCTestCase { + override func setUpWithError() throws {} + + override func tearDownWithError() throws {} + + func testExample() { + XCTAssertEqual(1, 1) + } +} From 4427fe1ace4b3c0eb2a9cd776d5343b200916683 Mon Sep 17 00:00:00 2001 From: HongSJae Date: Fri, 26 Apr 2024 19:42:16 +0900 Subject: [PATCH 02/11] =?UTF-8?q?=E2=9C=A8=20::=20onSuccess=20Extension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Shared/ViewUtil/Sources/View+onSuccess.swift | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Projects/Shared/ViewUtil/Sources/View+onSuccess.swift diff --git a/Projects/Shared/ViewUtil/Sources/View+onSuccess.swift b/Projects/Shared/ViewUtil/Sources/View+onSuccess.swift new file mode 100644 index 0000000..b882839 --- /dev/null +++ b/Projects/Shared/ViewUtil/Sources/View+onSuccess.swift @@ -0,0 +1,8 @@ +import SwiftUI + +public extension View { + func onSuccess(of value: Bool, _ action: () -> Void) -> some View { + if value { action() } + return self + } +} From 4e6ee769692b713869be009a7fcbd6d8d5ff95f9 Mon Sep 17 00:00:00 2001 From: HongSJae Date: Fri, 26 Apr 2024 19:42:47 +0900 Subject: [PATCH 03/11] =?UTF-8?q?=E2=9C=A8=20::=20Signup=20Component=20?= =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=EC=8B=9C=EC=8A=A4=ED=85=9C=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Extension/View+bottomButton.swift} | 3 +-- .../Sources/View}/NavigationTitleView.swift | 14 +++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) rename Projects/{Feature/SignupFeature/Sources/Components/BottomButton.swift => UserInterface/DesignSystem/Sources/Extension/View+bottomButton.swift} (95%) rename Projects/{Feature/SignupFeature/Sources/Components => UserInterface/DesignSystem/Sources/View}/NavigationTitleView.swift (59%) diff --git a/Projects/Feature/SignupFeature/Sources/Components/BottomButton.swift b/Projects/UserInterface/DesignSystem/Sources/Extension/View+bottomButton.swift similarity index 95% rename from Projects/Feature/SignupFeature/Sources/Components/BottomButton.swift rename to Projects/UserInterface/DesignSystem/Sources/Extension/View+bottomButton.swift index 6551884..80eb562 100644 --- a/Projects/Feature/SignupFeature/Sources/Components/BottomButton.swift +++ b/Projects/UserInterface/DesignSystem/Sources/Extension/View+bottomButton.swift @@ -1,10 +1,9 @@ import SwiftUI -import DesignSystem /// Vstack으로 할 경우 버튼이 잘리는 문제가 발생 /// ZStack을 사용해 SuperView의 상단으로 띄움 -struct BottomButton: ViewModifier { +private struct BottomButton: ViewModifier { let text: String let isEditing: Bool let isDisabled: Bool diff --git a/Projects/Feature/SignupFeature/Sources/Components/NavigationTitleView.swift b/Projects/UserInterface/DesignSystem/Sources/View/NavigationTitleView.swift similarity index 59% rename from Projects/Feature/SignupFeature/Sources/Components/NavigationTitleView.swift rename to Projects/UserInterface/DesignSystem/Sources/View/NavigationTitleView.swift index 6d6d621..1690ccc 100644 --- a/Projects/Feature/SignupFeature/Sources/Components/NavigationTitleView.swift +++ b/Projects/UserInterface/DesignSystem/Sources/View/NavigationTitleView.swift @@ -1,11 +1,15 @@ import SwiftUI -import DesignSystem -struct NavigationTitleView: View { - let title: String - let description: String +public struct NavigationTitleView: View { + private let title: String + private let description: String - var body: some View { + public init(title: String, description: String) { + self.title = title + self.description = description + } + + public var body: some View { VStack(alignment: .leading, spacing: 8) { Text(title) .kgFont(.h3, weight: .semiBold, color: .white) From f0d7c63bf21bc897ed215d08dafce6150e07295b Mon Sep 17 00:00:00 2001 From: HongSJae Date: Fri, 26 Apr 2024 19:43:05 +0900 Subject: [PATCH 04/11] =?UTF-8?q?=E2=9C=A8=20::=20FindPassword=20DI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Application/DI/AppComponent.swift | 13 ++++ .../Sources/Application/NeedleGenerated.swift | 64 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/Projects/App/Sources/Application/DI/AppComponent.swift b/Projects/App/Sources/Application/DI/AppComponent.swift index 90717f0..ad5a8d2 100644 --- a/Projects/App/Sources/Application/DI/AppComponent.swift +++ b/Projects/App/Sources/Application/DI/AppComponent.swift @@ -8,6 +8,8 @@ import SigninFeature import SigninFeatureInterface import SignupFeature import SignupFeatureInterface +import FindPasswordFeature +import FindPasswordFeatureInterface public final class AppComponent: BootstrapComponent { // private let _keychain: any Keychain @@ -63,4 +65,15 @@ public extension AppComponent { var signupCheckLevelFactory: any SignupCheckLevelFactory { SignupCheckLevelComponent(parent: self) } + + // Find Password + var inputEmailFactory: any InputEmailFactory { + InputEmailComponent(parent: self) + } + var verifyAuthCodeFactory: any VerifyAuthCodeFactory { + VerifyAuthCodeComponent(parent: self) + } + var inputNewPasswordFactory: any InputNewPasswordFactory { + InputNewPasswordComponent(parent: self) + } } diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index 13eed84..2fa268d 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -1,5 +1,7 @@ +import FindPasswordFeature +import FindPasswordFeatureInterface import NeedleFoundation import RootFeature import RootFeatureInterface @@ -162,6 +164,9 @@ private class SigninDependencyde06a9d0b22764487733Provider: SigninDependency { var signupEmailFactory: any SignupEmailFactory { return appComponent.signupEmailFactory } + var inputEmailFactory: any InputEmailFactory { + return appComponent.inputEmailFactory + } private let appComponent: AppComponent init(appComponent: AppComponent) { self.appComponent = appComponent @@ -171,6 +176,43 @@ private class SigninDependencyde06a9d0b22764487733Provider: SigninDependency { private func factory2882a056d84a613debccf47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { return SigninDependencyde06a9d0b22764487733Provider(appComponent: parent1(component) as! AppComponent) } +private class VerifyAuthCodeDependencya16ab19f97e0892b555bProvider: VerifyAuthCodeDependency { + var inputNewPasswordFactory: any InputNewPasswordFactory { + return appComponent.inputNewPasswordFactory + } + private let appComponent: AppComponent + init(appComponent: AppComponent) { + self.appComponent = appComponent + } +} +/// ^->AppComponent->VerifyAuthCodeComponent +private func factoryed5ce75de1bf576b84adf47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { + return VerifyAuthCodeDependencya16ab19f97e0892b555bProvider(appComponent: parent1(component) as! AppComponent) +} +private class InputNewPasswordDependency1149e32e41e1cfce6f6dProvider: InputNewPasswordDependency { + + + init() { + + } +} +/// ^->AppComponent->InputNewPasswordComponent +private func factory27615059458a0576f404e3b0c44298fc1c149afb(_ component: NeedleFoundation.Scope) -> AnyObject { + return InputNewPasswordDependency1149e32e41e1cfce6f6dProvider() +} +private class InputEmailDependency4102766a436592066e97Provider: InputEmailDependency { + var verifyAuthCodeFactory: any VerifyAuthCodeFactory { + return appComponent.verifyAuthCodeFactory + } + private let appComponent: AppComponent + init(appComponent: AppComponent) { + self.appComponent = appComponent + } +} +/// ^->AppComponent->InputEmailComponent +private func factoryf939e41ba3a1151e88f8f47b58f8f304c97af4d5(_ component: NeedleFoundation.Scope) -> AnyObject { + return InputEmailDependency4102766a436592066e97Provider(appComponent: parent1(component) as! AppComponent) +} #else extension AppComponent: Registration { @@ -186,6 +228,9 @@ extension AppComponent: Registration { localTable["signupStudentIDFactory-any SignupStudentIDFactory"] = { [unowned self] in self.signupStudentIDFactory as Any } localTable["signupGenderFactory-any SignupGenderFactory"] = { [unowned self] in self.signupGenderFactory as Any } localTable["signupCheckLevelFactory-any SignupCheckLevelFactory"] = { [unowned self] in self.signupCheckLevelFactory as Any } + localTable["inputEmailFactory-any InputEmailFactory"] = { [unowned self] in self.inputEmailFactory as Any } + localTable["verifyAuthCodeFactory-any VerifyAuthCodeFactory"] = { [unowned self] in self.verifyAuthCodeFactory as Any } + localTable["inputNewPasswordFactory-any InputNewPasswordFactory"] = { [unowned self] in self.inputNewPasswordFactory as Any } } } extension SplashComponent: Registration { @@ -245,6 +290,22 @@ extension RootComponent: Registration { extension SigninComponent: Registration { public func registerItems() { keyPathToName[\SigninDependency.signupEmailFactory] = "signupEmailFactory-any SignupEmailFactory" + keyPathToName[\SigninDependency.inputEmailFactory] = "inputEmailFactory-any InputEmailFactory" + } +} +extension VerifyAuthCodeComponent: Registration { + public func registerItems() { + keyPathToName[\VerifyAuthCodeDependency.inputNewPasswordFactory] = "inputNewPasswordFactory-any InputNewPasswordFactory" + } +} +extension InputNewPasswordComponent: Registration { + public func registerItems() { + + } +} +extension InputEmailComponent: Registration { + public func registerItems() { + keyPathToName[\InputEmailDependency.verifyAuthCodeFactory] = "verifyAuthCodeFactory-any VerifyAuthCodeFactory" } } @@ -275,6 +336,9 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi registerProviderFactory("^->AppComponent->SignupEmailComponent", factory4d1ddf658c5970ef6b47f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->RootComponent", factory264bfc4d4cb6b0629b40f47b58f8f304c97af4d5) registerProviderFactory("^->AppComponent->SigninComponent", factory2882a056d84a613debccf47b58f8f304c97af4d5) + registerProviderFactory("^->AppComponent->VerifyAuthCodeComponent", factoryed5ce75de1bf576b84adf47b58f8f304c97af4d5) + registerProviderFactory("^->AppComponent->InputNewPasswordComponent", factory27615059458a0576f404e3b0c44298fc1c149afb) + registerProviderFactory("^->AppComponent->InputEmailComponent", factoryf939e41ba3a1151e88f8f47b58f8f304c97af4d5) } #endif From 7b1ea9550f580b269124780426f796d490ff8dce Mon Sep 17 00:00:00 2001 From: HongSJae Date: Fri, 26 Apr 2024 19:43:20 +0900 Subject: [PATCH 05/11] =?UTF-8?q?=E2=9C=A8=20::=20RootPresentaionMode=20En?= =?UTF-8?q?v=20Value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RootPresentationMode.swift | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Projects/Feature/BaseFeature/Sources/EnvironmentValues/RootPresentationMode.swift diff --git a/Projects/Feature/BaseFeature/Sources/EnvironmentValues/RootPresentationMode.swift b/Projects/Feature/BaseFeature/Sources/EnvironmentValues/RootPresentationMode.swift new file mode 100644 index 0000000..619b788 --- /dev/null +++ b/Projects/Feature/BaseFeature/Sources/EnvironmentValues/RootPresentationMode.swift @@ -0,0 +1,20 @@ +import SwiftUI + +public struct RootPresentationModeKey: EnvironmentKey { + public static let defaultValue: Binding = .constant(RootPresentationMode()) +} + +public extension EnvironmentValues { + var rootPresentationMode: Binding { + get { return self[RootPresentationModeKey.self] } + set { self[RootPresentationModeKey.self] = newValue } + } +} + +public typealias RootPresentationMode = Bool + +public extension Binding { + func popToRootView() { + self.wrappedValue.toggle() + } +} From df950f85a774192e2d798fae3e98eb31480c728b Mon Sep 17 00:00:00 2001 From: HongSJae Date: Fri, 26 Apr 2024 19:43:41 +0900 Subject: [PATCH 06/11] =?UTF-8?q?=F0=9F=94=A5=20::=20Default=20File=20Remo?= =?UTF-8?q?ve?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Feature/FindPasswordFeature/Interface/Interface.swift | 1 - Projects/Feature/FindPasswordFeature/Sources/Sources.swift | 1 - 2 files changed, 2 deletions(-) delete mode 100644 Projects/Feature/FindPasswordFeature/Interface/Interface.swift delete mode 100644 Projects/Feature/FindPasswordFeature/Sources/Sources.swift diff --git a/Projects/Feature/FindPasswordFeature/Interface/Interface.swift b/Projects/Feature/FindPasswordFeature/Interface/Interface.swift deleted file mode 100644 index b1853ce..0000000 --- a/Projects/Feature/FindPasswordFeature/Interface/Interface.swift +++ /dev/null @@ -1 +0,0 @@ -// This is for Tuist diff --git a/Projects/Feature/FindPasswordFeature/Sources/Sources.swift b/Projects/Feature/FindPasswordFeature/Sources/Sources.swift deleted file mode 100644 index b1853ce..0000000 --- a/Projects/Feature/FindPasswordFeature/Sources/Sources.swift +++ /dev/null @@ -1 +0,0 @@ -// This is for Tuist From f86e4e94c03e0ab5141b785f062032cbdce98489 Mon Sep 17 00:00:00 2001 From: HongSJae Date: Fri, 26 Apr 2024 19:43:53 +0900 Subject: [PATCH 07/11] =?UTF-8?q?=E2=9C=A8=20::=20Input=20Email=20Publishi?= =?UTF-8?q?ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interface/InputEmailFactory.swift | 6 ++ .../Sources/Email/InputEmailComponent.swift | 16 +++++ .../Sources/Email/InputEmailViewModel.swift | 12 ++++ .../Sources/Email/InputSignupEmailView.swift | 61 +++++++++++++++++++ 4 files changed, 95 insertions(+) create mode 100644 Projects/Feature/FindPasswordFeature/Interface/InputEmailFactory.swift create mode 100644 Projects/Feature/FindPasswordFeature/Sources/Email/InputEmailComponent.swift create mode 100644 Projects/Feature/FindPasswordFeature/Sources/Email/InputEmailViewModel.swift create mode 100644 Projects/Feature/FindPasswordFeature/Sources/Email/InputSignupEmailView.swift diff --git a/Projects/Feature/FindPasswordFeature/Interface/InputEmailFactory.swift b/Projects/Feature/FindPasswordFeature/Interface/InputEmailFactory.swift new file mode 100644 index 0000000..ce19a83 --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Interface/InputEmailFactory.swift @@ -0,0 +1,6 @@ +import SwiftUI + +public protocol InputEmailFactory { + associatedtype SomeView: View + func makeView() -> SomeView +} diff --git a/Projects/Feature/FindPasswordFeature/Sources/Email/InputEmailComponent.swift b/Projects/Feature/FindPasswordFeature/Sources/Email/InputEmailComponent.swift new file mode 100644 index 0000000..b35cc94 --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Sources/Email/InputEmailComponent.swift @@ -0,0 +1,16 @@ +import SwiftUI +import NeedleFoundation +import FindPasswordFeatureInterface + +public protocol InputEmailDependency: Dependency { + var verifyAuthCodeFactory: any VerifyAuthCodeFactory { get } +} + +public final class InputEmailComponent: Component, InputEmailFactory { + public func makeView() -> some View { + InputEmailView( + viewModel: .init(), + verifyAuthCodeFactory: dependency.verifyAuthCodeFactory + ) + } +} diff --git a/Projects/Feature/FindPasswordFeature/Sources/Email/InputEmailViewModel.swift b/Projects/Feature/FindPasswordFeature/Sources/Email/InputEmailViewModel.swift new file mode 100644 index 0000000..5831b0f --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Sources/Email/InputEmailViewModel.swift @@ -0,0 +1,12 @@ +import BaseFeature +import Combine + +final class InputEmailViewModel: BaseViewModel { + @Published var email: String = "" + + @Published var isNavigatedToVerifyAuthCode: Bool = false + + func nextButtonDidTap() { + self.isNavigatedToVerifyAuthCode = true + } +} diff --git a/Projects/Feature/FindPasswordFeature/Sources/Email/InputSignupEmailView.swift b/Projects/Feature/FindPasswordFeature/Sources/Email/InputSignupEmailView.swift new file mode 100644 index 0000000..0b44eac --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Sources/Email/InputSignupEmailView.swift @@ -0,0 +1,61 @@ +import DesignSystem +import SwiftUI +import BaseFeature +import ViewUtil +import FindPasswordFeatureInterface + +struct InputEmailView: View { + private enum FocusField { + case email + } + @FocusState private var focusField: FocusField? + @StateObject var viewModel: InputEmailViewModel + @Environment(\.rootPresentationMode) var rootPresentationMode + + private let verifyAuthCodeFactory: any VerifyAuthCodeFactory + + init( + viewModel: InputEmailViewModel, + verifyAuthCodeFactory: any VerifyAuthCodeFactory + ) { + _viewModel = StateObject(wrappedValue: viewModel) + self.verifyAuthCodeFactory = verifyAuthCodeFactory + } + + var body: some View { + VStack(spacing: 0) { + NavigationTitleView( + title: "이메일을 입력해 주세요", + description: "이메일로 인증번호를 전송해 드릴게요" + ) + + KGTextField( + "이메일을 입력해주세요", + text: $viewModel.email, + title: "이메일", + isError: viewModel.isErrorOccurred, + errorMessage: viewModel.errorMessage, + onCommit: viewModel.nextButtonDidTap + ) + .textContentType(.emailAddress) + .keyboardType(.emailAddress) + .focused($focusField, equals: .email) + + Spacer() + } + .bottomButton( + text: "다음", + isEditing: focusField != nil, + isDisabled: viewModel.email.isEmpty, + action: viewModel.nextButtonDidTap + ) + .navigationBackButton() + .kgBackground() + .hideKeyboardWhenTap() + .navigate( + to: verifyAuthCodeFactory.makeView().eraseToAnyView() + .environment(\.rootPresentationMode, rootPresentationMode), + when: $viewModel.isNavigatedToVerifyAuthCode + ) + } +} From 715fb7c6d1c446bbf50fd3931c20c694d433d66f Mon Sep 17 00:00:00 2001 From: HongSJae Date: Fri, 26 Apr 2024 19:44:07 +0900 Subject: [PATCH 08/11] =?UTF-8?q?=E2=9C=A8=20::=20VerifyAuthCode=20Publish?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interface/VerifyAuthCodeFactory.swift | 6 ++ .../VerifyAuthCodeComponent.swift | 17 +++++ .../VerifyAuthCode/VerifyAuthCodeView.swift | 70 +++++++++++++++++++ .../VerifyAuthCodeViewModel.swift | 13 ++++ 4 files changed, 106 insertions(+) create mode 100644 Projects/Feature/FindPasswordFeature/Interface/VerifyAuthCodeFactory.swift create mode 100644 Projects/Feature/FindPasswordFeature/Sources/VerifyAuthCode/VerifyAuthCodeComponent.swift create mode 100644 Projects/Feature/FindPasswordFeature/Sources/VerifyAuthCode/VerifyAuthCodeView.swift create mode 100644 Projects/Feature/FindPasswordFeature/Sources/VerifyAuthCode/VerifyAuthCodeViewModel.swift diff --git a/Projects/Feature/FindPasswordFeature/Interface/VerifyAuthCodeFactory.swift b/Projects/Feature/FindPasswordFeature/Interface/VerifyAuthCodeFactory.swift new file mode 100644 index 0000000..b977a25 --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Interface/VerifyAuthCodeFactory.swift @@ -0,0 +1,6 @@ +import SwiftUI + +public protocol VerifyAuthCodeFactory { + associatedtype SomeView: View + func makeView() -> SomeView +} diff --git a/Projects/Feature/FindPasswordFeature/Sources/VerifyAuthCode/VerifyAuthCodeComponent.swift b/Projects/Feature/FindPasswordFeature/Sources/VerifyAuthCode/VerifyAuthCodeComponent.swift new file mode 100644 index 0000000..d713b57 --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Sources/VerifyAuthCode/VerifyAuthCodeComponent.swift @@ -0,0 +1,17 @@ +import SwiftUI +import NeedleFoundation +import FindPasswordFeatureInterface + +public protocol VerifyAuthCodeDependency: Dependency { + var inputNewPasswordFactory: any InputNewPasswordFactory { get } +} + +public final class VerifyAuthCodeComponent: Component, + VerifyAuthCodeFactory { + public func makeView() -> some View { + VerifyAuthCodeView( + viewModel: .init(), + inputNewPasswordFactory: dependency.inputNewPasswordFactory + ) + } +} diff --git a/Projects/Feature/FindPasswordFeature/Sources/VerifyAuthCode/VerifyAuthCodeView.swift b/Projects/Feature/FindPasswordFeature/Sources/VerifyAuthCode/VerifyAuthCodeView.swift new file mode 100644 index 0000000..9d0777d --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Sources/VerifyAuthCode/VerifyAuthCodeView.swift @@ -0,0 +1,70 @@ +import DesignSystem +import SwiftUI +import BaseFeature +import ViewUtil +import FindPasswordFeatureInterface + +struct VerifyAuthCodeView: View { + private enum FocusField { + case authCode + } + @FocusState private var focusField: FocusField? + @StateObject var viewModel: VerifyAuthCodeViewModel + @Environment(\.rootPresentationMode) var rootPresentationMode + + private let inputNewPasswordFactory: any InputNewPasswordFactory + + init( + viewModel: VerifyAuthCodeViewModel, + inputNewPasswordFactory: any InputNewPasswordFactory + ) { + _viewModel = StateObject(wrappedValue: viewModel) + self.inputNewPasswordFactory = inputNewPasswordFactory + } + + var body: some View { + VStack(spacing: 0) { + NavigationTitleView( + title: "인증번호를 입력해 주세요", + description: "입력해주신 이메일로 인증번호를 전송해 드렸어요" + ) + + VerifyCodeTextField( + $viewModel.authCode, + isError: viewModel.isErrorOccurred, + errorMessage: viewModel.errorMessage, + onCommit: viewModel.nextButtonDidTap + ) + .focused($focusField, equals: .authCode) + + HStack { + Text("5:00") + .kgFont(.label, weight: .regular, color: .Greens.main) + + Spacer() + + Text("인증번호 재전송") + .kgFont(.label, weight: .regular, color: .Grays.gray400) + .underlineText(color: .Grays.gray400) + } + .padding(.vertical, 16) + .padding(.horizontal, 24) + + Spacer() + } + .bottomButton( + text: "인증", + isEditing: focusField != nil, + isDisabled: viewModel.authCode.count < 4, + action: viewModel.nextButtonDidTap + ) + .navigationBackButton() + .kgBackground() + .hideKeyboardWhenTap() + .navigate( + to: inputNewPasswordFactory.makeView().eraseToAnyView() + .environment(\.rootPresentationMode, rootPresentationMode), + when: $viewModel.isNavigatedToNewPassword + ) + } +} diff --git a/Projects/Feature/FindPasswordFeature/Sources/VerifyAuthCode/VerifyAuthCodeViewModel.swift b/Projects/Feature/FindPasswordFeature/Sources/VerifyAuthCode/VerifyAuthCodeViewModel.swift new file mode 100644 index 0000000..9828a4e --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Sources/VerifyAuthCode/VerifyAuthCodeViewModel.swift @@ -0,0 +1,13 @@ +import BaseFeature +import Foundation +import Combine + +final class VerifyAuthCodeViewModel: BaseViewModel { + @Published var authCode: String = "" + + @Published var isNavigatedToNewPassword: Bool = false + + func nextButtonDidTap() { + self.isNavigatedToNewPassword = true + } +} From 8366ea51b42ddf8bee89a48acf169120c2392fca Mon Sep 17 00:00:00 2001 From: HongSJae Date: Fri, 26 Apr 2024 19:44:23 +0900 Subject: [PATCH 09/11] =?UTF-8?q?=E2=9C=A8=20::=20InputNewPassword=20Publi?= =?UTF-8?q?shing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Interface/InputNewPasswordFactory.swift | 6 ++ .../InputNewPasswordComponent.swift | 13 ++++ .../NewPassword/InputNewPasswordView.swift | 71 +++++++++++++++++++ .../InputNewPasswordViewModel.swift | 13 ++++ 4 files changed, 103 insertions(+) create mode 100644 Projects/Feature/FindPasswordFeature/Interface/InputNewPasswordFactory.swift create mode 100644 Projects/Feature/FindPasswordFeature/Sources/NewPassword/InputNewPasswordComponent.swift create mode 100644 Projects/Feature/FindPasswordFeature/Sources/NewPassword/InputNewPasswordView.swift create mode 100644 Projects/Feature/FindPasswordFeature/Sources/NewPassword/InputNewPasswordViewModel.swift diff --git a/Projects/Feature/FindPasswordFeature/Interface/InputNewPasswordFactory.swift b/Projects/Feature/FindPasswordFeature/Interface/InputNewPasswordFactory.swift new file mode 100644 index 0000000..f12aab8 --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Interface/InputNewPasswordFactory.swift @@ -0,0 +1,6 @@ +import SwiftUI + +public protocol InputNewPasswordFactory { + associatedtype SomeView: View + func makeView() -> SomeView +} diff --git a/Projects/Feature/FindPasswordFeature/Sources/NewPassword/InputNewPasswordComponent.swift b/Projects/Feature/FindPasswordFeature/Sources/NewPassword/InputNewPasswordComponent.swift new file mode 100644 index 0000000..742f013 --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Sources/NewPassword/InputNewPasswordComponent.swift @@ -0,0 +1,13 @@ +import SwiftUI +import NeedleFoundation +import FindPasswordFeatureInterface + +public protocol InputNewPasswordDependency: Dependency {} + +public final class InputNewPasswordComponent: Component, InputNewPasswordFactory { + public func makeView() -> some View { + InputNewPasswordView( + viewModel: .init() + ) + } +} diff --git a/Projects/Feature/FindPasswordFeature/Sources/NewPassword/InputNewPasswordView.swift b/Projects/Feature/FindPasswordFeature/Sources/NewPassword/InputNewPasswordView.swift new file mode 100644 index 0000000..194f8b9 --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Sources/NewPassword/InputNewPasswordView.swift @@ -0,0 +1,71 @@ +import DesignSystem +import SwiftUI +import BaseFeature +import SignupFeatureInterface +import ViewUtil + +struct InputNewPasswordView: View { + private enum FocusField { + case password + case checkPassword + } + @FocusState private var focusField: FocusField? + @StateObject var viewModel: InputNewPasswordViewModel + @Environment(\.rootPresentationMode) var rootPresentationMode + + init( + viewModel: InputNewPasswordViewModel + ) { + _viewModel = StateObject(wrappedValue: viewModel) + } + + var body: some View { + VStack(spacing: 0) { + NavigationTitleView( + title: "새 비밀번호를 입력해주세요", + description: "비밀번호는 영어와 숫자를 조합해 만들어 주세요" + ) + + KGTextField( + "비밀번호(8~12자)를 입력해 주세요", + text: $viewModel.password, + title: "비밀번호", + isError: viewModel.isErrorOccurred, + errorMessage: viewModel.errorMessage, + isSecure: true + ) { + self.focusField = .checkPassword + } + .textContentType(.password) + .focused($focusField, equals: .password) + + KGTextField( + "비밀번호 다시 입력해 주세요", + text: $viewModel.checkPassword, + title: "비밀번호 확인", + isError: viewModel.isErrorOccurred, + errorMessage: viewModel.errorMessage, + isSecure: true, + onCommit: viewModel.nextButtonDidTap + ) + .textContentType(.password) + .focused($focusField, equals: .checkPassword) + + Spacer() + } + .bottomButton( + text: "다음", + isEditing: focusField != nil, + isDisabled: viewModel.password.isEmpty || viewModel.checkPassword.isEmpty, + action: viewModel.nextButtonDidTap + ) + .navigationBar() + .kgBackground() + .hideKeyboardWhenTap() + .onSuccess(of: viewModel.isSuccessToChangePassword) { + DispatchQueue.main.async { + self.rootPresentationMode.popToRootView() + } + } + } +} diff --git a/Projects/Feature/FindPasswordFeature/Sources/NewPassword/InputNewPasswordViewModel.swift b/Projects/Feature/FindPasswordFeature/Sources/NewPassword/InputNewPasswordViewModel.swift new file mode 100644 index 0000000..c0aa1a4 --- /dev/null +++ b/Projects/Feature/FindPasswordFeature/Sources/NewPassword/InputNewPasswordViewModel.swift @@ -0,0 +1,13 @@ +import BaseFeature +import Combine + +final class InputNewPasswordViewModel: BaseViewModel { + @Published var password: String = "" + @Published var checkPassword: String = "" + + @Published var isSuccessToChangePassword: Bool = false + + func nextButtonDidTap() { + self.isSuccessToChangePassword = true + } +} From 3823cb75104bbc188b8168b6f398806cc80af40e Mon Sep 17 00:00:00 2001 From: HongSJae Date: Fri, 26 Apr 2024 19:44:42 +0900 Subject: [PATCH 10/11] =?UTF-8?q?=E2=9C=A8=20::=20Signin=20to=20FindPasswo?= =?UTF-8?q?rd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Feature/SigninFeature/Project.swift | 3 ++- .../Sources/SigninComponent.swift | 5 +++- .../SigninFeature/Sources/SigninView.swift | 23 +++++++++++++++---- .../Sources/SigninViewModel.swift | 5 ++++ ...wift => SignupVerifyAuthCodeFactory.swift} | 0 5 files changed, 30 insertions(+), 6 deletions(-) rename Projects/Feature/SignupFeature/Interface/{SignupAuthCodeVerifyFactory.swift => SignupVerifyAuthCodeFactory.swift} (100%) diff --git a/Projects/Feature/SigninFeature/Project.swift b/Projects/Feature/SigninFeature/Project.swift index 7bde5f1..048b497 100644 --- a/Projects/Feature/SigninFeature/Project.swift +++ b/Projects/Feature/SigninFeature/Project.swift @@ -9,7 +9,8 @@ let project = Project.module( .implements(module: .feature(.SigninFeature), dependencies: [ .feature(target: .SigninFeature, type: .interface), .feature(target: .BaseFeature), - .feature(target: .SignupFeature, type: .interface) + .feature(target: .SignupFeature, type: .interface), + .feature(target: .FindPasswordFeature, type: .interface) ]), .tests(module: .feature(.SigninFeature), dependencies: [ .feature(target: .SigninFeature) diff --git a/Projects/Feature/SigninFeature/Sources/SigninComponent.swift b/Projects/Feature/SigninFeature/Sources/SigninComponent.swift index d5d2bc9..1c3e783 100644 --- a/Projects/Feature/SigninFeature/Sources/SigninComponent.swift +++ b/Projects/Feature/SigninFeature/Sources/SigninComponent.swift @@ -2,16 +2,19 @@ import SwiftUI import NeedleFoundation import SigninFeatureInterface import SignupFeatureInterface +import FindPasswordFeatureInterface public protocol SigninDependency: Dependency { var signupEmailFactory: any SignupEmailFactory { get } + var inputEmailFactory: any InputEmailFactory { get } } public final class SigninComponent: Component, SigninFactory { public func makeView() -> some View { SigninView( viewModel: .init(), - signupEmailFactory: dependency.signupEmailFactory + signupEmailFactory: dependency.signupEmailFactory, + inputEmailFactory: dependency.inputEmailFactory ) } } diff --git a/Projects/Feature/SigninFeature/Sources/SigninView.swift b/Projects/Feature/SigninFeature/Sources/SigninView.swift index ad9b48f..0bee470 100644 --- a/Projects/Feature/SigninFeature/Sources/SigninView.swift +++ b/Projects/Feature/SigninFeature/Sources/SigninView.swift @@ -3,6 +3,7 @@ import SwiftUI import BaseFeature import ViewUtil import SignupFeatureInterface +import FindPasswordFeatureInterface struct SigninView: View { private enum FocusField { @@ -13,13 +14,16 @@ struct SigninView: View { @StateObject var viewModel: SigninViewModel private let signupEmailFactory: any SignupEmailFactory + private let inputEmailFactory: any InputEmailFactory init( viewModel: SigninViewModel, - signupEmailFactory: any SignupEmailFactory + signupEmailFactory: any SignupEmailFactory, + inputEmailFactory: any InputEmailFactory ) { _viewModel = StateObject(wrappedValue: viewModel) self.signupEmailFactory = signupEmailFactory + self.inputEmailFactory = inputEmailFactory } var body: some View { @@ -63,13 +67,24 @@ struct SigninView: View { Color.Grays.gray700 .frame(width: 1, height: 16) - Text("비밀번호 찾기") - .kgFont(.label, weight: .regular, color: .Grays.gray700) + Button(action: viewModel.findPasswordButtonDidTap) { + Text("비밀번호 찾기") + .kgFont(.label, weight: .regular, color: .Grays.gray700) + } } } Spacer() } - .navigate(to: signupEmailFactory.makeView().eraseToAnyView(), when: $viewModel.isNavigatedToSignup) + .hideKeyboardWhenTap() + .navigate( + to: signupEmailFactory.makeView().eraseToAnyView(), + when: $viewModel.isNavigatedToSignup + ) + .navigate( + to: inputEmailFactory.makeView().eraseToAnyView() + .environment(\.rootPresentationMode, $viewModel.isNavigatedToFindPassword), + when: $viewModel.isNavigatedToFindPassword + ) } } diff --git a/Projects/Feature/SigninFeature/Sources/SigninViewModel.swift b/Projects/Feature/SigninFeature/Sources/SigninViewModel.swift index ca3ec76..4e89503 100644 --- a/Projects/Feature/SigninFeature/Sources/SigninViewModel.swift +++ b/Projects/Feature/SigninFeature/Sources/SigninViewModel.swift @@ -6,6 +6,7 @@ final class SigninViewModel: BaseViewModel { @Published var password: String = "" @Published var isNavigatedToSignup: Bool = false + @Published var isNavigatedToFindPassword: Bool = false func signinButtonDidTap() { print("Signin") @@ -14,4 +15,8 @@ final class SigninViewModel: BaseViewModel { func signupButtonDidTap() { self.isNavigatedToSignup = true } + + func findPasswordButtonDidTap() { + self.isNavigatedToFindPassword = true + } } diff --git a/Projects/Feature/SignupFeature/Interface/SignupAuthCodeVerifyFactory.swift b/Projects/Feature/SignupFeature/Interface/SignupVerifyAuthCodeFactory.swift similarity index 100% rename from Projects/Feature/SignupFeature/Interface/SignupAuthCodeVerifyFactory.swift rename to Projects/Feature/SignupFeature/Interface/SignupVerifyAuthCodeFactory.swift From 42f064c3c4fb2f8bdf792e4dde29cd2871c54d9c Mon Sep 17 00:00:00 2001 From: HongSJae Date: Fri, 26 Apr 2024 19:57:59 +0900 Subject: [PATCH 11/11] =?UTF-8?q?=E2=9C=A8=20::=20onSuccess=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Shared/ViewUtil/Sources/View+onSuccess.swift | 9 ++++++--- .../DesignSystem/Sources/TextField/KGTextField.swift | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Projects/Shared/ViewUtil/Sources/View+onSuccess.swift b/Projects/Shared/ViewUtil/Sources/View+onSuccess.swift index b882839..75f369f 100644 --- a/Projects/Shared/ViewUtil/Sources/View+onSuccess.swift +++ b/Projects/Shared/ViewUtil/Sources/View+onSuccess.swift @@ -1,8 +1,11 @@ import SwiftUI public extension View { - func onSuccess(of value: Bool, _ action: () -> Void) -> some View { - if value { action() } - return self + func onSuccess(of value: Bool, _ action: @escaping () -> Void) -> some View { + self.onChange(of: value) { changedValue in + if changedValue { + action() + } + } } } diff --git a/Projects/UserInterface/DesignSystem/Sources/TextField/KGTextField.swift b/Projects/UserInterface/DesignSystem/Sources/TextField/KGTextField.swift index cea365c..9e19534 100644 --- a/Projects/UserInterface/DesignSystem/Sources/TextField/KGTextField.swift +++ b/Projects/UserInterface/DesignSystem/Sources/TextField/KGTextField.swift @@ -69,6 +69,9 @@ public struct KGTextField: View { lineWidth: 1 ) } + .onTapGesture { + self.isFocused = true + } if !description.isEmpty || isErrorAndNotEmpty { Text(isErrorAndNotEmpty ? errorMessage : description)