From 1fab911627050565ba91d6e96292e2edafa4b74e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8F=84=ED=98=95?= <108233361+ShapeKim98@users.noreply.github.com> Date: Sat, 7 Sep 2024 18:59:29 +0900 Subject: [PATCH] =?UTF-8?q?[fix]=20#111=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=99=84=EB=A3=8C=20=ED=99=94=EB=A9=B4=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=92=A4=EB=A1=9C=EA=B0=80=EA=B8=B0=20=EB=B9=84?= =?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/App/Sources/Intro/IntroFeature.swift | 4 +- .../Sources/Login/LoginFeature.swift | 250 +++++++++++++++++ .../Sources/Login/LoginView.swift | 171 ++++++++++++ .../Sources/LoginRoot/LoginRootFeature.swift | 260 ++---------------- .../Sources/LoginRoot/LoginRootView.swift | 152 ++-------- .../SelectField/SelectFieldFeature.swift | 2 +- .../SignUpDone/SignUpDoneFeature.swift | 3 - .../Sources/SignUpDone/SignUpDoneView.swift | 21 +- .../Sources/FeatureLoginDemoApp.swift | 8 +- 9 files changed, 480 insertions(+), 391 deletions(-) create mode 100644 Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift create mode 100644 Projects/Feature/FeatureLogin/Sources/Login/LoginView.swift diff --git a/Projects/App/Sources/Intro/IntroFeature.swift b/Projects/App/Sources/Intro/IntroFeature.swift index 31efd3c6..e0f39160 100644 --- a/Projects/App/Sources/Intro/IntroFeature.swift +++ b/Projects/App/Sources/Intro/IntroFeature.swift @@ -16,7 +16,7 @@ public struct IntroFeature { @ObservableState public enum State { case splash(SplashFeature.State = .init()) - case login(LoginRootFeature.State = .init()) + case login(LoginRootFeature.State = .login(.init())) public init() { self = .splash() } } /// - Action @@ -42,7 +42,7 @@ public struct IntroFeature { case .splash(let splashAction): return splashDelegate(splashAction, state: &state) - case .login(.delegate(.dismissLoginRootView)): + case .login(.delegate(.로그인_루트_닫기)): return .run { send in await send(.delegate(.moveToTab), animation: .smooth) } case .delegate, .login: diff --git a/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift b/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift new file mode 100644 index 00000000..990b656a --- /dev/null +++ b/Projects/Feature/FeatureLogin/Sources/Login/LoginFeature.swift @@ -0,0 +1,250 @@ +// +// SignUpNavigationStackFeature.swift +// Feature +// +// Created by 김도형 on 7/7/24. + +import ComposableArchitecture +import CoreKit +import Util + +@Reducer +public struct LoginFeature { + /// - Dependency + @Dependency(\.dismiss) var dismiss + @Dependency(\.socialLogin) var socialLogin + @Dependency(\.authClient) var authClient + @Dependency(\.userClient) var userClient + @Dependency(\.userDefaults) var userDefaults + @Dependency(\.keychain) var keychain + /// - State + @ObservableState + public struct State { + var path = StackState() + + var nickName: String? + var interests: [String]? + + public init() {} + } + /// - Action + public enum Action: FeatureAction, ViewAction { + case view(View) + case inner(InnerAction) + case async(AsyncAction) + case scope(ScopeAction) + case delegate(DelegateAction) + case path(StackActionOf) + + @CasePathable + public enum View: Equatable { + /// - Button Tapped + case appleLoginButtonTapped + case googleLoginButtonTapped + } + public enum InnerAction: Equatable { + case pushAgreeToTermsView + case pushRegisterNicknameView + case pushSelectFieldView(nickname: String) + case pushSignUpDoneView + case 애플로그인(SocialLoginInfo) + case 구글로그인(SocialLoginInfo) + case 로그인_이후_화면이동(isRegistered: Bool) + } + public enum AsyncAction: Equatable { + case 회원가입 + case 로그인(SocialLoginInfo) + } + public enum ScopeAction { + case agreeToTerms(AgreeToTermsFeature.Action.DelegateAction) + case registerNickname(RegisterNicknameFeature.Action.DelegateAction) + case selectField(SelectFieldFeature.Action.DelegateAction) + } + public enum DelegateAction: Equatable { + case dismissLoginRootView + case 회원가입_완료_화면_이동 + } + } + /// initiallizer + public init() {} + /// - Reducer Core + private func core(into state: inout State, action: Action) -> Effect { + switch action { + /// - View + case .view(let viewAction): + return handleViewAction(viewAction, state: &state) + /// - Inner + case .inner(let innerAction): + return handleInnerAction(innerAction, state: &state) + /// - Async + case .async(let asyncAction): + return handleAsyncAction(asyncAction, state: &state) + /// - Scope + case .scope(let scopeAction): + return handleScopeAction(scopeAction, state: &state) + /// - Delegate + case .delegate(let delegateAction): + return handleDelegateAction(delegateAction, state: &state) + case .path(let pathAction): + return handlePathAction(pathAction, state: &state) + } + } + /// - Reducer body + public var body: some ReducerOf { + Reduce(self.core) + .forEach(\.path, action: \.path) + } +} +//MARK: - FeatureAction Effect +private extension LoginFeature { + /// - View Effect + func handleViewAction(_ action: Action.View, state: inout State) -> Effect { + switch action { + case .appleLoginButtonTapped: + return .run { send in + let response = try await socialLogin.appleLogin() + await send(.async(.로그인(response))) + } + + case .googleLoginButtonTapped: + return .run { send in + let response = try await socialLogin.googleLogin() + await send(.async(.로그인(response))) + } + } + } + /// - Inner Effect + func handleInnerAction(_ action: Action.InnerAction, state: inout State) -> Effect { + switch action { + case .pushAgreeToTermsView: + state.path.append(.agreeToTerms(AgreeToTermsFeature.State())) + return .none + case .pushRegisterNicknameView: + state.path.append(.registerNickname(RegisterNicknameFeature.State())) + return .none + case .pushSelectFieldView(let nickname): + state.path.append(.selecteField(SelectFieldFeature.State(nickname: nickname))) + return .none + case .pushSignUpDoneView: + return .send(.delegate(.회원가입_완료_화면_이동)) + case let .애플로그인(response): + return .run { send in + guard let idToken = response.idToken else { return } + guard let authCode = response.authCode else { return } + guard let jwt = response.jwt else { return } + + let platform = response.provider.description + let request = SignInRequest(authPlatform: platform, idToken: idToken) + let tokenResponse = try await authClient.로그인(request) + + /// [1]. UserDefaults에 최근 로그인한 애플로그인 `정보`저장 + await userDefaults.setString(platform, .authPlatform) + await userDefaults.setString(authCode, .authCode) + await userDefaults.setString(jwt, .jwt) + /// [2]. Keychain에 `access`, `refresh` 저장 + keychain.save(.accessToken, tokenResponse.accessToken) + keychain.save(.refreshToken, tokenResponse.refreshToken) + + let appleTokenRequest = AppleTokenRequest(authCode: authCode, jwt: jwt) + let appleTokenResponse = try await authClient.apple(appleTokenRequest) + keychain.save(.serverRefresh, appleTokenResponse.refresh_token) + + await send(.inner(.로그인_이후_화면이동(isRegistered: tokenResponse.isRegistered))) + } + case let .구글로그인(response): + return .run { send in + guard let idToken = response.idToken else { return } + let platform = response.provider.description + let request = SignInRequest(authPlatform: platform, idToken: idToken) + let tokenResponse = try await authClient.로그인(request) + + /// [1]. UserDefaults에 최근 로그인한 소셜로그인 `타입`저장 + await userDefaults.setString(platform, .authPlatform) + /// [2]. Keychain에 `access`, `refresh` 저장 + keychain.save(.accessToken, tokenResponse.accessToken) + keychain.save(.refreshToken, tokenResponse.refreshToken) + keychain.save(.serverRefresh, response.serverRefreshToken) + + await send(.inner(.로그인_이후_화면이동(isRegistered: tokenResponse.isRegistered))) + } + case let .로그인_이후_화면이동(isRegistered): + /// [3]. 이미 회원가입했던 유저라면 `메인`이동 + if isRegistered { + return .run { send in await send(.delegate(.dismissLoginRootView)) } + } else { + return .run { send in await send(.inner(.pushAgreeToTermsView)) } + } + } + } + /// - Async Effect + func handleAsyncAction(_ action: Action.AsyncAction, state: inout State) -> Effect { + switch action { + case .회원가입: + return .run { [nickName = state.nickName, interests = state.interests] send in + guard let nickName else { return } + guard let interests else { return } + let signUpRequest = SignupRequest(nickName: nickName, interests: interests) + let _ = try await userClient.회원등록(signUpRequest) + + await send(.inner(.pushSignUpDoneView)) + } + + case .로그인(let response): + switch response.provider { + case .apple: + return .run { send in await send(.inner(.애플로그인(response))) } + case .google: + return .run { send in await send(.inner(.구글로그인(response))) } + } + } + } + /// - Scope Effect + func handleScopeAction(_ action: Action.ScopeAction, state: inout State) -> Effect { + switch action { + case .agreeToTerms(let delegate): + switch delegate { + case .pushRegisterNicknameView: + return .send(.inner(.pushRegisterNicknameView)) + } + case .registerNickname(let delegate): + switch delegate { + case .pushSelectFieldView(let nickname): + state.nickName = nickname + return .send(.inner(.pushSelectFieldView(nickname: nickname))) + } + case .selectField(let delegate): + switch delegate { + case let .pushSignUpDoneView(interests): + state.interests = interests + return .send(.async(.회원가입)) + } + } + } + /// - Delegate Effect + func handleDelegateAction(_ action: Action.DelegateAction, state: inout State) -> Effect { + return .none + } + + func handlePathAction(_ action: StackActionOf, state: inout State) -> Effect { + switch action { + case .element(id: _, action: .agreeToTerms(.delegate(let delegate))): + return .send(.scope(.agreeToTerms(delegate))) + case .element(id: _, action: .registerNickname(.delegate(let delegate))): + return .send(.scope(.registerNickname(delegate))) + case .element(id: _, action: .selecteField(.delegate(let delegate))): + return .send(.scope(.selectField(delegate))) + case .element, .popFrom, .push: + return .none + } + } +} + +//MARK: - Path +extension LoginFeature { + @Reducer + public enum Path { + case agreeToTerms(AgreeToTermsFeature) + case registerNickname(RegisterNicknameFeature) + case selecteField(SelectFieldFeature) + } +} diff --git a/Projects/Feature/FeatureLogin/Sources/Login/LoginView.swift b/Projects/Feature/FeatureLogin/Sources/Login/LoginView.swift new file mode 100644 index 00000000..17519224 --- /dev/null +++ b/Projects/Feature/FeatureLogin/Sources/Login/LoginView.swift @@ -0,0 +1,171 @@ +// +// SignUpNavigationStackView.swift +// Feature +// +// Created by 김도형 on 7/7/24. + +import ComposableArchitecture +import SwiftUI + +import DSKit + +@ViewAction(for: LoginFeature.self) +public struct LoginView: View { + /// - Properties + @Perception.Bindable + public var store: StoreOf + /// - Initializer + public init(store: StoreOf) { + self.store = store + } +} +//MARK: - View +public extension LoginView { + var body: some View { + WithPerceptionTracking { + NavigationStack(path: $store.scope(state: \.path, action: \.path)) { + WithPerceptionTracking { + VStack(spacing: 8) { + logo + + Spacer() + + appleLoginButton + .pokitMaxWidth() + + googleLoginButton + .pokitMaxWidth() + } + .padding(.horizontal, 20) + .padding(.bottom, 36) + .background(.pokit(.bg(.base))) + .ignoresSafeArea(edges: .bottom) + .navigationBarBackButtonHidden() + } + } destination: { path in + WithPerceptionTracking { + switch path.case { + case .agreeToTerms(let store): + AgreeToTermsView(store: store) + case .registerNickname(let store): + RegisterNicknameView(store: store) + case .selecteField(let store): + SelectFieldView(store: store) + } + } + } + } + } +} +//MARK: - Configure View +extension LoginView { + private var logo: some View { + HStack { + Spacer() + + VStack(spacing: 24) { + Image(.logo(.pokit)) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 72) + .foregroundStyle(.pokit(.icon(.brand))) + + Text("다양한 링크들을 한 곳에") + .pokitFont(.b1(.b)) + .foregroundStyle(.pokit(.text(.secondary))) + } + + Spacer() + } + .padding(.top, 254) + } + + private var appleLoginButton: some View { + Button { + send(.appleLoginButtonTapped) + } label: { + appleLoginButtonLabel + } + } + + private var appleLoginButtonLabel: some View { + VStack { + Spacer() + + HStack(spacing: 12) { + Spacer() + + Image(systemName: "apple.logo") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 24, height: 24) + .foregroundColor(.pokit(.icon(.inverseWh))) + + Text("Apple로 계속하기") + .pokitFont(.l1(.r)) + .foregroundStyle(.pokit(.text(.inverseWh))) + + Spacer() + } + + Spacer() + } + .background { + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(.black) + .overlay { + RoundedRectangle(cornerRadius: 8, style: .continuous) + .stroke(.pokit(.border(.secondary)), lineWidth: 1) + } + } + .frame(height: 50) + } + + private var googleLoginButton: some View { + Button { + send(.googleLoginButtonTapped) + } label: { + googleLoginButtonLabel + } + } + + private var googleLoginButtonLabel: some View { + VStack { + Spacer() + + HStack(spacing: 12) { + Spacer() + + Image(.icon(.google)) + .resizable() + .frame(width: 24, height: 24) + + Text("Google로 계속하기") + .pokitFont(.l1(.r)) + .foregroundStyle(.pokit(.text(.primary))) + + Spacer() + } + + Spacer() + } + .background { + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(.pokit(.bg(.base))) + .overlay { + RoundedRectangle(cornerRadius: 8, style: .continuous) + .stroke(.pokit(.border(.secondary)), lineWidth: 1) + } + } + .frame(height: 50) + } +} +//MARK: - Preview +#Preview { + LoginView( + store: Store( + initialState: .init(), + reducer: { LoginFeature() } + ) + ) +} diff --git a/Projects/Feature/FeatureLogin/Sources/LoginRoot/LoginRootFeature.swift b/Projects/Feature/FeatureLogin/Sources/LoginRoot/LoginRootFeature.swift index 4c13e418..dec71f90 100644 --- a/Projects/Feature/FeatureLogin/Sources/LoginRoot/LoginRootFeature.swift +++ b/Projects/Feature/FeatureLogin/Sources/LoginRoot/LoginRootFeature.swift @@ -1,259 +1,59 @@ // -// SignUpNavigationStackFeature.swift +// LoginRootFeature.swift // Feature // -// Created by 김도형 on 7/7/24. +// Created by 김도형 on 9/7/24. import ComposableArchitecture -import CoreKit import Util @Reducer public struct LoginRootFeature { /// - Dependency - @Dependency(\.dismiss) var dismiss - @Dependency(\.socialLogin) var socialLogin - @Dependency(\.authClient) var authClient - @Dependency(\.userClient) var userClient - @Dependency(\.userDefaults) var userDefaults - @Dependency(\.keychain) var keychain + /// - State @ObservableState - public struct State { - var path = StackState() - - var nickName: String? - var interests: [String]? - - public init() {} + public enum State { + case login(LoginFeature.State) + case signUpDone(SignUpDoneFeature.State) } + /// - Action - public enum Action: FeatureAction, ViewAction { - case view(View) - case inner(InnerAction) - case async(AsyncAction) - case scope(ScopeAction) + public enum Action { + case login(LoginFeature.Action) + case signUpDone(SignUpDoneFeature.Action) case delegate(DelegateAction) - case path(StackActionOf) - - @CasePathable - public enum View: Equatable { - /// - Button Tapped - case appleLoginButtonTapped - case googleLoginButtonTapped - } - public enum InnerAction: Equatable { - case pushAgreeToTermsView - case pushRegisterNicknameView - case pushSelectFieldView(nickname: String) - case pushSignUpDoneView - case 애플로그인(SocialLoginInfo) - case 구글로그인(SocialLoginInfo) - case 로그인_이후_화면이동(isRegistered: Bool) - } - public enum AsyncAction: Equatable { - case 회원가입 - case 로그인(SocialLoginInfo) - } - public enum ScopeAction { - case agreeToTerms(AgreeToTermsFeature.Action.DelegateAction) - case registerNickname(RegisterNicknameFeature.Action.DelegateAction) - case selectField(SelectFieldFeature.Action.DelegateAction) - case signUpDone(SignUpDoneFeature.Action.DelegateAction) - } - public enum DelegateAction: Equatable { - case dismissLoginRootView - } } - /// initiallizer + + public enum DelegateAction { + case 로그인_루트_닫기 + } + + /// - Initiallizer public init() {} + /// - Reducer Core private func core(into state: inout State, action: Action) -> Effect { switch action { - /// - View - case .view(let viewAction): - return handleViewAction(viewAction, state: &state) - /// - Inner - case .inner(let innerAction): - return handleInnerAction(innerAction, state: &state) - /// - Async - case .async(let asyncAction): - return handleAsyncAction(asyncAction, state: &state) - /// - Scope - case .scope(let scopeAction): - return handleScopeAction(scopeAction, state: &state) - /// - Delegate - case .delegate(let delegateAction): - return handleDelegateAction(delegateAction, state: &state) - case .path(let pathAction): - return handlePathAction(pathAction, state: &state) + case .login(.delegate(.회원가입_완료_화면_이동)): + state = .signUpDone(.init()) + return .none + case .login(.delegate(.dismissLoginRootView)), + .signUpDone(.delegate(.dismissLoginRootView)): + return .send(.delegate(.로그인_루트_닫기)) + case .login, .signUpDone, .delegate: + return .none } } + /// - Reducer body public var body: some ReducerOf { Reduce(self.core) - .forEach(\.path, action: \.path) - } -} -//MARK: - FeatureAction Effect -private extension LoginRootFeature { - /// - View Effect - func handleViewAction(_ action: Action.View, state: inout State) -> Effect { - switch action { - case .appleLoginButtonTapped: - return .run { send in - let response = try await socialLogin.appleLogin() - await send(.async(.로그인(response))) - } - - case .googleLoginButtonTapped: - return .run { send in - let response = try await socialLogin.googleLogin() - await send(.async(.로그인(response))) - } - } - } - /// - Inner Effect - func handleInnerAction(_ action: Action.InnerAction, state: inout State) -> Effect { - switch action { - case .pushAgreeToTermsView: - state.path.append(.agreeToTerms(AgreeToTermsFeature.State())) - return .none - case .pushRegisterNicknameView: - state.path.append(.registerNickname(RegisterNicknameFeature.State())) - return .none - case .pushSelectFieldView(let nickname): - state.path.append(.selecteField(SelectFieldFeature.State(nickname: nickname))) - return .none - case .pushSignUpDoneView: - state.path.append(.signUpDone(SignUpDoneFeature.State())) - return .none - case let .애플로그인(response): - return .run { send in - guard let idToken = response.idToken else { return } - guard let authCode = response.authCode else { return } - guard let jwt = response.jwt else { return } - - let platform = response.provider.description - let request = SignInRequest(authPlatform: platform, idToken: idToken) - let tokenResponse = try await authClient.로그인(request) - - /// [1]. UserDefaults에 최근 로그인한 애플로그인 `정보`저장 - await userDefaults.setString(platform, .authPlatform) - await userDefaults.setString(authCode, .authCode) - await userDefaults.setString(jwt, .jwt) - /// [2]. Keychain에 `access`, `refresh` 저장 - keychain.save(.accessToken, tokenResponse.accessToken) - keychain.save(.refreshToken, tokenResponse.refreshToken) - - let appleTokenRequest = AppleTokenRequest(authCode: authCode, jwt: jwt) - let appleTokenResponse = try await authClient.apple(appleTokenRequest) - keychain.save(.serverRefresh, appleTokenResponse.refresh_token) - - await send(.inner(.로그인_이후_화면이동(isRegistered: tokenResponse.isRegistered))) + .ifCaseLet(\.login, action: \.login) { + LoginFeature() } - case let .구글로그인(response): - return .run { send in - guard let idToken = response.idToken else { return } - let platform = response.provider.description - let request = SignInRequest(authPlatform: platform, idToken: idToken) - let tokenResponse = try await authClient.로그인(request) - - /// [1]. UserDefaults에 최근 로그인한 소셜로그인 `타입`저장 - await userDefaults.setString(platform, .authPlatform) - /// [2]. Keychain에 `access`, `refresh` 저장 - keychain.save(.accessToken, tokenResponse.accessToken) - keychain.save(.refreshToken, tokenResponse.refreshToken) - keychain.save(.serverRefresh, response.serverRefreshToken) - - await send(.inner(.로그인_이후_화면이동(isRegistered: tokenResponse.isRegistered))) + .ifCaseLet(\.signUpDone, action: \.signUpDone) { + SignUpDoneFeature() } - case let .로그인_이후_화면이동(isRegistered): - /// [3]. 이미 회원가입했던 유저라면 `메인`이동 - if isRegistered { - return .run { send in await send(.delegate(.dismissLoginRootView)) } - } else { - return .run { send in await send(.inner(.pushAgreeToTermsView)) } - } - } - } - /// - Async Effect - func handleAsyncAction(_ action: Action.AsyncAction, state: inout State) -> Effect { - switch action { - case .회원가입: - return .run { [nickName = state.nickName, interests = state.interests] send in - guard let nickName else { return } - guard let interests else { return } - let signUpRequest = SignupRequest(nickName: nickName, interests: interests) - let _ = try await userClient.회원등록(signUpRequest) - - await send(.inner(.pushSignUpDoneView)) - } - - case .로그인(let response): - switch response.provider { - case .apple: - return .run { send in await send(.inner(.애플로그인(response))) } - case .google: - return .run { send in await send(.inner(.구글로그인(response))) } - } - } - } - /// - Scope Effect - func handleScopeAction(_ action: Action.ScopeAction, state: inout State) -> Effect { - switch action { - case .agreeToTerms(let delegate): - switch delegate { - case .pushRegisterNicknameView: - return .send(.inner(.pushRegisterNicknameView)) - } - case .registerNickname(let delegate): - switch delegate { - case .pushSelectFieldView(let nickname): - state.nickName = nickname - return .send(.inner(.pushSelectFieldView(nickname: nickname))) - } - case .selectField(let delegate): - switch delegate { - case let .pushSignUpDoneView(interests): - state.interests = interests - return .send(.async(.회원가입)) - } - case .signUpDone(let delegate): - switch delegate { - case .dismissLoginRootView: - return .send(.delegate(.dismissLoginRootView)) - } - } - } - /// - Delegate Effect - func handleDelegateAction(_ action: Action.DelegateAction, state: inout State) -> Effect { - return .none - } - - func handlePathAction(_ action: StackActionOf, state: inout State) -> Effect { - switch action { - case .element(id: _, action: .agreeToTerms(.delegate(let delegate))): - return .send(.scope(.agreeToTerms(delegate))) - case .element(id: _, action: .registerNickname(.delegate(let delegate))): - return .send(.scope(.registerNickname(delegate))) - case .element(id: _, action: .selecteField(.delegate(let delegate))): - return .send(.scope(.selectField(delegate))) - case .element(id: _, action: .signUpDone(.delegate(let delegate))): - return .send(.scope(.signUpDone(delegate))) - case .element, .popFrom, .push: - return .none - } - } -} - -//MARK: - Path -extension LoginRootFeature { - @Reducer - public enum Path { - case agreeToTerms(AgreeToTermsFeature) - case registerNickname(RegisterNicknameFeature) - case selecteField(SelectFieldFeature) - case signUpDone(SignUpDoneFeature) } } diff --git a/Projects/Feature/FeatureLogin/Sources/LoginRoot/LoginRootView.swift b/Projects/Feature/FeatureLogin/Sources/LoginRoot/LoginRootView.swift index ff8a9bf7..f62c1974 100644 --- a/Projects/Feature/FeatureLogin/Sources/LoginRoot/LoginRootView.swift +++ b/Projects/Feature/FeatureLogin/Sources/LoginRoot/LoginRootView.swift @@ -1,19 +1,17 @@ // -// SignUpNavigationStackView.swift +// LoginRootView.swift // Feature // -// Created by 김도형 on 7/7/24. +// Created by 김도형 on 9/7/24. -import ComposableArchitecture import SwiftUI -import DSKit +import ComposableArchitecture -@ViewAction(for: LoginRootFeature.self) public struct LoginRootView: View { /// - Properties - @Perception.Bindable - public var store: StoreOf + public let store: StoreOf + /// - Initializer public init(store: StoreOf) { self.store = store @@ -23,35 +21,14 @@ public struct LoginRootView: View { public extension LoginRootView { var body: some View { WithPerceptionTracking { - NavigationStack(path: $store.scope(state: \.path, action: \.path)) { - WithPerceptionTracking { - VStack(spacing: 8) { - logo - - Spacer() - - appleLoginButton - .pokitMaxWidth() - - googleLoginButton - .pokitMaxWidth() + Group { + switch store.state { + case .login: + if let store = store.scope(state: \.login, action: \.login) { + LoginView(store: store) } - .padding(.horizontal, 20) - .padding(.bottom, 36) - .background(.pokit(.bg(.base))) - .ignoresSafeArea(edges: .bottom) - .navigationBarBackButtonHidden() - } - } destination: { path in - WithPerceptionTracking { - switch path.case { - case .agreeToTerms(let store): - AgreeToTermsView(store: store) - case .registerNickname(let store): - RegisterNicknameView(store: store) - case .selecteField(let store): - SelectFieldView(store: store) - case .signUpDone(let store): + case .signUpDone: + if let store = store.scope(state: \.signUpDone, action: \.signUpDone) { SignUpDoneView(store: store) } } @@ -60,114 +37,17 @@ public extension LoginRootView { } } //MARK: - Configure View -extension LoginRootView { - private var logo: some View { - HStack { - Spacer() - - VStack(spacing: 24) { - Image(.logo(.pokit)) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(height: 72) - .foregroundStyle(.pokit(.icon(.brand))) - - Text("다양한 링크들을 한 곳에") - .pokitFont(.b1(.b)) - .foregroundStyle(.pokit(.text(.secondary))) - } - - Spacer() - } - .padding(.top, 254) - } - - private var appleLoginButton: some View { - Button { - send(.appleLoginButtonTapped) - } label: { - appleLoginButtonLabel - } - } - - private var appleLoginButtonLabel: some View { - VStack { - Spacer() - - HStack(spacing: 12) { - Spacer() - - Image(systemName: "apple.logo") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 24, height: 24) - .foregroundColor(.pokit(.icon(.inverseWh))) - - Text("Apple로 계속하기") - .pokitFont(.l1(.r)) - .foregroundStyle(.pokit(.text(.inverseWh))) - - Spacer() - } - - Spacer() - } - .background { - RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(.black) - .overlay { - RoundedRectangle(cornerRadius: 8, style: .continuous) - .stroke(.pokit(.border(.secondary)), lineWidth: 1) - } - } - .frame(height: 50) - } +private extension LoginRootView { - private var googleLoginButton: some View { - Button { - send(.googleLoginButtonTapped) - } label: { - googleLoginButtonLabel - } - } - - private var googleLoginButtonLabel: some View { - VStack { - Spacer() - - HStack(spacing: 12) { - Spacer() - - Image(.icon(.google)) - .resizable() - .frame(width: 24, height: 24) - - Text("Google로 계속하기") - .pokitFont(.l1(.r)) - .foregroundStyle(.pokit(.text(.primary))) - - Spacer() - } - - Spacer() - } - .background { - RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(.pokit(.bg(.base))) - .overlay { - RoundedRectangle(cornerRadius: 8, style: .continuous) - .stroke(.pokit(.border(.secondary)), lineWidth: 1) - } - } - .frame(height: 50) - } } //MARK: - Preview #Preview { LoginRootView( store: Store( - initialState: .init(), + initialState: .login(.init()), reducer: { LoginRootFeature() } ) ) } + + diff --git a/Projects/Feature/FeatureLogin/Sources/SelectField/SelectFieldFeature.swift b/Projects/Feature/FeatureLogin/Sources/SelectField/SelectFieldFeature.swift index 9f6d0375..30bb71eb 100644 --- a/Projects/Feature/FeatureLogin/Sources/SelectField/SelectFieldFeature.swift +++ b/Projects/Feature/FeatureLogin/Sources/SelectField/SelectFieldFeature.swift @@ -108,7 +108,7 @@ private extension SelectFieldFeature { func handleInnerAction(_ action: Action.InnerAction, state: inout State) -> Effect { switch action { case let .관심사_목록_조회_결과(interests): - interests.forEach { state.fields.append($0.description) } + state.fields = interests.map { $0.description } return .none } } diff --git a/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneFeature.swift b/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneFeature.swift index d3dcea62..df3fa547 100644 --- a/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneFeature.swift +++ b/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneFeature.swift @@ -36,7 +36,6 @@ public struct SignUpDoneFeature { public enum View: Equatable { /// - Button Tapped case startButtonTapped - case backButtonTapped case firecrackerOnAppeared case titleOnAppeared @@ -84,8 +83,6 @@ private extension SignUpDoneFeature { switch action { case .startButtonTapped: return .send(.delegate(.dismissLoginRootView), animation: .pokitDissolve) - case .backButtonTapped: - return .run { _ in await self.dismiss() } case .firecrackerOnAppeared: state.firecrackIsAppear = true return .none diff --git a/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneView.swift b/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneView.swift index c40c921a..d5f5c0de 100644 --- a/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneView.swift +++ b/Projects/Feature/FeatureLogin/Sources/SignUpDone/SignUpDoneView.swift @@ -25,10 +25,10 @@ public extension SignUpDoneView { VStack(spacing: 0) { Spacer() - logo - - title - .padding(.top, 4) + logo + + title + .padding(.top, 4) Spacer() @@ -36,6 +36,7 @@ public extension SignUpDoneView { .padding(.top, 78) } .padding(.horizontal, 20) + .background(.pokit(.bg(.base))) .overlay(alignment: .bottom) { PokitBottomButton( "시작하기", @@ -46,16 +47,8 @@ public extension SignUpDoneView { .padding(.horizontal, 20) .background(.pokit(.bg(.base))) } - .pokitNavigationBar { - PokitHeader { - PokitHeaderItems(placement: .leading) { - PokitToolbarButton(.icon(.arrowLeft)) { - send(.backButtonTapped) - } - } - } - } .ignoresSafeArea(edges: .bottom) + .navigationBarBackButtonHidden() } } } @@ -127,5 +120,3 @@ extension SignUpDoneView { ) ) } - - diff --git a/Projects/Feature/FeatureLoginDemo/Sources/FeatureLoginDemoApp.swift b/Projects/Feature/FeatureLoginDemo/Sources/FeatureLoginDemoApp.swift index 81527b99..0e3b13e3 100644 --- a/Projects/Feature/FeatureLoginDemo/Sources/FeatureLoginDemoApp.swift +++ b/Projects/Feature/FeatureLoginDemo/Sources/FeatureLoginDemoApp.swift @@ -14,10 +14,10 @@ struct FeatureLoginDemoApp: App { var body: some Scene { WindowGroup { // TODO: 루트 뷰 추가 - LoginRootView( + LoginView( store: .init( initialState: .init(), - reducer: { LoginRootFeature() } + reducer: { LoginFeature() } ) ) } @@ -25,10 +25,10 @@ struct FeatureLoginDemoApp: App { } #Preview { - LoginRootView( + LoginView( store: .init( initialState: .init(), - reducer: { LoginRootFeature() } + reducer: { LoginFeature() } ) ) }