Skip to content

Commit e882bbd

Browse files
ShapeKim98stealmh
authored andcommitted
[fix] #111 회원가입 완료 화면에서 뒤로가기 비활성화
1 parent c1fc993 commit e882bbd

File tree

9 files changed

+480
-391
lines changed

9 files changed

+480
-391
lines changed

Projects/App/Sources/Intro/IntroFeature.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public struct IntroFeature {
1616
@ObservableState
1717
public enum State {
1818
case splash(SplashFeature.State = .init())
19-
case login(LoginRootFeature.State = .init())
19+
case login(LoginRootFeature.State = .login(.init()))
2020
public init() { self = .splash() }
2121
}
2222
/// - Action
@@ -42,7 +42,7 @@ public struct IntroFeature {
4242
case .splash(let splashAction):
4343
return splashDelegate(splashAction, state: &state)
4444

45-
case .login(.delegate(.dismissLoginRootView)):
45+
case .login(.delegate(.로그인_루트_닫기)):
4646
return .run { send in await send(.delegate(.moveToTab), animation: .smooth) }
4747

4848
case .delegate, .login:
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
//
2+
// SignUpNavigationStackFeature.swift
3+
// Feature
4+
//
5+
// Created by 김도형 on 7/7/24.
6+
7+
import ComposableArchitecture
8+
import CoreKit
9+
import Util
10+
11+
@Reducer
12+
public struct LoginFeature {
13+
/// - Dependency
14+
@Dependency(\.dismiss) var dismiss
15+
@Dependency(\.socialLogin) var socialLogin
16+
@Dependency(\.authClient) var authClient
17+
@Dependency(\.userClient) var userClient
18+
@Dependency(\.userDefaults) var userDefaults
19+
@Dependency(\.keychain) var keychain
20+
/// - State
21+
@ObservableState
22+
public struct State {
23+
var path = StackState<Path.State>()
24+
25+
var nickName: String?
26+
var interests: [String]?
27+
28+
public init() {}
29+
}
30+
/// - Action
31+
public enum Action: FeatureAction, ViewAction {
32+
case view(View)
33+
case inner(InnerAction)
34+
case async(AsyncAction)
35+
case scope(ScopeAction)
36+
case delegate(DelegateAction)
37+
case path(StackActionOf<Path>)
38+
39+
@CasePathable
40+
public enum View: Equatable {
41+
/// - Button Tapped
42+
case appleLoginButtonTapped
43+
case googleLoginButtonTapped
44+
}
45+
public enum InnerAction: Equatable {
46+
case pushAgreeToTermsView
47+
case pushRegisterNicknameView
48+
case pushSelectFieldView(nickname: String)
49+
case pushSignUpDoneView
50+
case 애플로그인(SocialLoginInfo)
51+
case 구글로그인(SocialLoginInfo)
52+
case 로그인_이후_화면이동(isRegistered: Bool)
53+
}
54+
public enum AsyncAction: Equatable {
55+
case 회원가입
56+
case 로그인(SocialLoginInfo)
57+
}
58+
public enum ScopeAction {
59+
case agreeToTerms(AgreeToTermsFeature.Action.DelegateAction)
60+
case registerNickname(RegisterNicknameFeature.Action.DelegateAction)
61+
case selectField(SelectFieldFeature.Action.DelegateAction)
62+
}
63+
public enum DelegateAction: Equatable {
64+
case dismissLoginRootView
65+
case 회원가입_완료_화면_이동
66+
}
67+
}
68+
/// initiallizer
69+
public init() {}
70+
/// - Reducer Core
71+
private func core(into state: inout State, action: Action) -> Effect<Action> {
72+
switch action {
73+
/// - View
74+
case .view(let viewAction):
75+
return handleViewAction(viewAction, state: &state)
76+
/// - Inner
77+
case .inner(let innerAction):
78+
return handleInnerAction(innerAction, state: &state)
79+
/// - Async
80+
case .async(let asyncAction):
81+
return handleAsyncAction(asyncAction, state: &state)
82+
/// - Scope
83+
case .scope(let scopeAction):
84+
return handleScopeAction(scopeAction, state: &state)
85+
/// - Delegate
86+
case .delegate(let delegateAction):
87+
return handleDelegateAction(delegateAction, state: &state)
88+
case .path(let pathAction):
89+
return handlePathAction(pathAction, state: &state)
90+
}
91+
}
92+
/// - Reducer body
93+
public var body: some ReducerOf<Self> {
94+
Reduce(self.core)
95+
.forEach(\.path, action: \.path)
96+
}
97+
}
98+
//MARK: - FeatureAction Effect
99+
private extension LoginFeature {
100+
/// - View Effect
101+
func handleViewAction(_ action: Action.View, state: inout State) -> Effect<Action> {
102+
switch action {
103+
case .appleLoginButtonTapped:
104+
return .run { send in
105+
let response = try await socialLogin.appleLogin()
106+
await send(.async(.로그인(response)))
107+
}
108+
109+
case .googleLoginButtonTapped:
110+
return .run { send in
111+
let response = try await socialLogin.googleLogin()
112+
await send(.async(.로그인(response)))
113+
}
114+
}
115+
}
116+
/// - Inner Effect
117+
func handleInnerAction(_ action: Action.InnerAction, state: inout State) -> Effect<Action> {
118+
switch action {
119+
case .pushAgreeToTermsView:
120+
state.path.append(.agreeToTerms(AgreeToTermsFeature.State()))
121+
return .none
122+
case .pushRegisterNicknameView:
123+
state.path.append(.registerNickname(RegisterNicknameFeature.State()))
124+
return .none
125+
case .pushSelectFieldView(let nickname):
126+
state.path.append(.selecteField(SelectFieldFeature.State(nickname: nickname)))
127+
return .none
128+
case .pushSignUpDoneView:
129+
return .send(.delegate(.회원가입_완료_화면_이동))
130+
case let .애플로그인(response):
131+
return .run { send in
132+
guard let idToken = response.idToken else { return }
133+
guard let authCode = response.authCode else { return }
134+
guard let jwt = response.jwt else { return }
135+
136+
let platform = response.provider.description
137+
let request = SignInRequest(authPlatform: platform, idToken: idToken)
138+
let tokenResponse = try await authClient.로그인(request)
139+
140+
/// [1]. UserDefaults에 최근 로그인한 애플로그인 `정보`저장
141+
await userDefaults.setString(platform, .authPlatform)
142+
await userDefaults.setString(authCode, .authCode)
143+
await userDefaults.setString(jwt, .jwt)
144+
/// [2]. Keychain에 `access`, `refresh` 저장
145+
keychain.save(.accessToken, tokenResponse.accessToken)
146+
keychain.save(.refreshToken, tokenResponse.refreshToken)
147+
148+
let appleTokenRequest = AppleTokenRequest(authCode: authCode, jwt: jwt)
149+
let appleTokenResponse = try await authClient.apple(appleTokenRequest)
150+
keychain.save(.serverRefresh, appleTokenResponse.refresh_token)
151+
152+
await send(.inner(.로그인_이후_화면이동(isRegistered: tokenResponse.isRegistered)))
153+
}
154+
case let .구글로그인(response):
155+
return .run { send in
156+
guard let idToken = response.idToken else { return }
157+
let platform = response.provider.description
158+
let request = SignInRequest(authPlatform: platform, idToken: idToken)
159+
let tokenResponse = try await authClient.로그인(request)
160+
161+
/// [1]. UserDefaults에 최근 로그인한 소셜로그인 `타입`저장
162+
await userDefaults.setString(platform, .authPlatform)
163+
/// [2]. Keychain에 `access`, `refresh` 저장
164+
keychain.save(.accessToken, tokenResponse.accessToken)
165+
keychain.save(.refreshToken, tokenResponse.refreshToken)
166+
keychain.save(.serverRefresh, response.serverRefreshToken)
167+
168+
await send(.inner(.로그인_이후_화면이동(isRegistered: tokenResponse.isRegistered)))
169+
}
170+
case let .로그인_이후_화면이동(isRegistered):
171+
/// [3]. 이미 회원가입했던 유저라면 `메인`이동
172+
if isRegistered {
173+
return .run { send in await send(.delegate(.dismissLoginRootView)) }
174+
} else {
175+
return .run { send in await send(.inner(.pushAgreeToTermsView)) }
176+
}
177+
}
178+
}
179+
/// - Async Effect
180+
func handleAsyncAction(_ action: Action.AsyncAction, state: inout State) -> Effect<Action> {
181+
switch action {
182+
case .회원가입:
183+
return .run { [nickName = state.nickName, interests = state.interests] send in
184+
guard let nickName else { return }
185+
guard let interests else { return }
186+
let signUpRequest = SignupRequest(nickName: nickName, interests: interests)
187+
let _ = try await userClient.회원등록(signUpRequest)
188+
189+
await send(.inner(.pushSignUpDoneView))
190+
}
191+
192+
case .로그인(let response):
193+
switch response.provider {
194+
case .apple:
195+
return .run { send in await send(.inner(.애플로그인(response))) }
196+
case .google:
197+
return .run { send in await send(.inner(.구글로그인(response))) }
198+
}
199+
}
200+
}
201+
/// - Scope Effect
202+
func handleScopeAction(_ action: Action.ScopeAction, state: inout State) -> Effect<Action> {
203+
switch action {
204+
case .agreeToTerms(let delegate):
205+
switch delegate {
206+
case .pushRegisterNicknameView:
207+
return .send(.inner(.pushRegisterNicknameView))
208+
}
209+
case .registerNickname(let delegate):
210+
switch delegate {
211+
case .pushSelectFieldView(let nickname):
212+
state.nickName = nickname
213+
return .send(.inner(.pushSelectFieldView(nickname: nickname)))
214+
}
215+
case .selectField(let delegate):
216+
switch delegate {
217+
case let .pushSignUpDoneView(interests):
218+
state.interests = interests
219+
return .send(.async(.회원가입))
220+
}
221+
}
222+
}
223+
/// - Delegate Effect
224+
func handleDelegateAction(_ action: Action.DelegateAction, state: inout State) -> Effect<Action> {
225+
return .none
226+
}
227+
228+
func handlePathAction(_ action: StackActionOf<Path>, state: inout State) -> Effect<Action> {
229+
switch action {
230+
case .element(id: _, action: .agreeToTerms(.delegate(let delegate))):
231+
return .send(.scope(.agreeToTerms(delegate)))
232+
case .element(id: _, action: .registerNickname(.delegate(let delegate))):
233+
return .send(.scope(.registerNickname(delegate)))
234+
case .element(id: _, action: .selecteField(.delegate(let delegate))):
235+
return .send(.scope(.selectField(delegate)))
236+
case .element, .popFrom, .push:
237+
return .none
238+
}
239+
}
240+
}
241+
242+
//MARK: - Path
243+
extension LoginFeature {
244+
@Reducer
245+
public enum Path {
246+
case agreeToTerms(AgreeToTermsFeature)
247+
case registerNickname(RegisterNicknameFeature)
248+
case selecteField(SelectFieldFeature)
249+
}
250+
}

0 commit comments

Comments
 (0)