Skip to content

Commit b733379

Browse files
authored
Merge pull request #57 from YAPP-Github/TNT-208-appCoordinate
[TNT-208] App Coordinator 로직 작성
2 parents af7ea5e + e361e48 commit b733379

File tree

9 files changed

+617
-1
lines changed

9 files changed

+617
-1
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//
2+
// AppFlowCoordinatorFeature.swift
3+
// Presentation
4+
//
5+
// Created by 박민서 on 2/4/25.
6+
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
import ComposableArchitecture
11+
12+
import Domain
13+
14+
@Reducer
15+
public struct AppFlowCoordinatorFeature {
16+
@ObservableState
17+
public struct State: Equatable {
18+
var userType: UserType?
19+
20+
// MARK: SubFeature state
21+
var trainerMainState: TrainerMainFlowFeature.State?
22+
var traineeMainState: TraineeMainFlowFeature.State?
23+
var onboardingState: OnboardingFlowFeature.State?
24+
25+
public init(
26+
userType: UserType? = nil,
27+
onboardingState: OnboardingFlowFeature.State? = .init(),
28+
trainerMainState: TrainerMainFlowFeature.State? = nil,
29+
traineeMainState: TraineeMainFlowFeature.State? = nil
30+
) {
31+
self.userType = userType
32+
self.onboardingState = onboardingState
33+
self.trainerMainState = trainerMainState
34+
self.traineeMainState = traineeMainState
35+
}
36+
}
37+
38+
public enum Action {
39+
/// 하위 코디네이터에서 일어나는 액션을 처리합니다
40+
case subFeature(SubFeatureAction)
41+
case onAppear
42+
43+
@CasePathable
44+
public enum SubFeatureAction: Sendable {
45+
/// 온보딩 플로우 코디네이터에서 발생하는 액션 처리
46+
case onboardingFlow(OnboardingFlowFeature.Action)
47+
/// 트레이너 메인탭 플로우 코디네이터에서 발생하는 액션 처리
48+
case trainerMainFlow(TrainerMainFlowFeature.Action)
49+
/// 트레이니 메인탭 플로우 코디네이터에서 발생하는 액션 처리
50+
case traineeMainFlow(TraineeMainFlowFeature.Action)
51+
}
52+
}
53+
54+
public init() {}
55+
56+
public var body: some Reducer<State, Action> {
57+
Reduce { state, action in
58+
switch action {
59+
case .subFeature(let internalAction):
60+
switch internalAction {
61+
case .onboardingFlow:
62+
return .none
63+
64+
case .trainerMainFlow:
65+
return .none
66+
67+
case .traineeMainFlow:
68+
return .none
69+
}
70+
71+
case .onAppear:
72+
return .none
73+
}
74+
}
75+
.ifLet(\.onboardingState, action: \.subFeature.onboardingFlow) { OnboardingFlowFeature() }
76+
.ifLet(\.trainerMainState, action: \.subFeature.trainerMainFlow) { TrainerMainFlowFeature() }
77+
.ifLet(\.traineeMainState, action: \.subFeature.traineeMainFlow) { TraineeMainFlowFeature() }
78+
}
79+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// AppFlowCoordinatorView.swift
3+
// Presentation
4+
//
5+
// Created by 박민서 on 2/4/25.
6+
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
import ComposableArchitecture
11+
12+
public struct AppFlowCoordinatorView: View {
13+
let store: StoreOf<AppFlowCoordinatorFeature>
14+
15+
public init(store: StoreOf<AppFlowCoordinatorFeature>) {
16+
self.store = store
17+
}
18+
19+
public var body: some View {
20+
Group {
21+
if let userType = store.userType {
22+
switch userType {
23+
case .trainee:
24+
if let store = store.scope(state: \.traineeMainState, action: \.subFeature.traineeMainFlow) {
25+
TraineeMainFlowView(store: store)
26+
}
27+
case .trainer:
28+
if let store = store.scope(state: \.trainerMainState, action: \.subFeature.trainerMainFlow) {
29+
TrainerMainFlowView(store: store)
30+
}
31+
}
32+
} else {
33+
if let store = store.scope(state: \.onboardingState, action: \.subFeature.onboardingFlow) {
34+
OnboardingFlowView(store: store)
35+
}
36+
}
37+
}
38+
.onAppear {
39+
store.send(.onAppear)
40+
}
41+
}
42+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//
2+
// OnboardingNavigationFeature.swift
3+
// Presentation
4+
//
5+
// Created by 박서연 on 1/24/25.
6+
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
import ComposableArchitecture
11+
12+
import Domain
13+
14+
@Reducer
15+
public struct OnboardingFlowFeature {
16+
@ObservableState
17+
public struct State: Equatable {
18+
public var path: StackState<Path.State>
19+
20+
public init(path: StackState<Path.State> = .init([.snsLogin(.init())])) {
21+
self.path = path
22+
}
23+
}
24+
25+
public enum Action: Sendable {
26+
/// 현재 표시되고 있는 path 화면 내부에서 일어나는 액션을 처리합니다.
27+
case path(StackActionOf<Path>)
28+
case onAppear
29+
}
30+
31+
public init() {}
32+
33+
public var body: some ReducerOf<Self> {
34+
Reduce { state, action in
35+
switch action {
36+
case let .path(action):
37+
switch action {
38+
39+
case .element(id: _, action: .snsLogin(.view(.tappedAppleLogin))):
40+
state.path.append(.trainerSignUpComplete(.init()))
41+
return .none
42+
43+
/// 트레이너 프로필 생성 완료 -> 다음 버튼 tapped
44+
case .element(id: _, action: .trainerSignUpComplete(.setNavigating)):
45+
state.path.append(.trainerMakeInvitationCode(MakeInvitationCodeFeature.State()))
46+
return .none
47+
48+
/// 트레이너의 초대코드 화면 -> 건너뛰기 버튼 tapped
49+
case .element(id: _, action: .trainerMakeInvitationCode(.setNavigation)):
50+
// 추후에 홈과 연결
51+
return .none
52+
53+
/// 약관 화면 -> 트레이너/트레이니 선택 화면 이동
54+
case .element(id: _, action: .userTypeSelection):
55+
return .none
56+
57+
default:
58+
return .none
59+
}
60+
61+
case .onAppear:
62+
return .none
63+
}
64+
}
65+
.forEach(\.path, action: \.path)
66+
}
67+
}
68+
69+
extension OnboardingFlowFeature {
70+
@Reducer(state: .equatable, .sendable)
71+
public enum Path {
72+
// MARK: Common
73+
/// SNS 로그인 뷰
74+
case snsLogin(LoginFeature)
75+
/// 약관동의뷰
76+
case term(TermFeature)
77+
/// 트레이너/트레이니 선택 뷰
78+
case userTypeSelection(UserTypeSelectionFeature)
79+
/// 트레이너/트레이니의 이름 입력 뷰
80+
case createProfile(CreateProfileFeature)
81+
82+
// MARK: Trainer
83+
/// 트레이너 회원 가입 완료 뷰
84+
/// TODO: 트레이너/트레이니 회원 가입 완료 화면으로 통합 필요
85+
case trainerSignUpComplete(TrainerSignUpCompleteFeature)
86+
/// 트레이너의 초대코드 발급 뷰
87+
case trainerMakeInvitationCode(MakeInvitationCodeFeature)
88+
/// 트레이너의 트레이니 프로필 확인 뷰
89+
case trainerConnectedTraineeProfile(ConnectedTraineeProfileFeature)
90+
91+
// MARK: Trainee
92+
/// 트레이니 기본 정보 입력
93+
case traineeBasicInfoInput(TraineeBasicInfoInputFeature)
94+
/// 트레이니 PT 목적 입력
95+
case traineeTrainingPurpose(TraineeTrainingPurposeFeature)
96+
/// 트레이니 주의사항 입력
97+
case traineePrecautionInput(TraineePrecautionInputFeature)
98+
/// 트레이니 프로필 생성 완료
99+
/// TODO: 트레이너/트레이니 회원 가입 완료 화면으로 통합 필요
100+
case traineeProfileCompletion(TraineeProfileCompletionFeature)
101+
/// 트레이니 초대 코드입력
102+
case traineeInvitationCodeInput(TraineeInvitationCodeInputFeature)
103+
/// 트레이니 수업 정보 입력
104+
case traineeTrainingInfoInput(TraineeTrainingInfoInputFeature)
105+
/// 트레이니 연결 완료
106+
/// TODO: 트레이너/트레이니 연결 완료 화면으로 통합 필요
107+
case traineeConnectionComplete(TraineeConnectionCompleteFeature)
108+
}
109+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//
2+
// OnboardingNavigationView.swift
3+
// Presentation
4+
//
5+
// Created by 박민서 on 2/4/25.
6+
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
import ComposableArchitecture
11+
12+
import DesignSystem
13+
14+
public struct OnboardingFlowView: View {
15+
@Bindable var store: StoreOf<OnboardingFlowFeature>
16+
17+
public init(store: StoreOf<OnboardingFlowFeature>) {
18+
self.store = store
19+
}
20+
21+
public var body: some View {
22+
NavigationStack(path: $store.scope(state: \.path, action: \.path)) {
23+
EmptyView()
24+
} destination: { store in
25+
switch store.case {
26+
// MARK: Common
27+
case .snsLogin(let store):
28+
LoginView(store: store)
29+
case .term(let store):
30+
TermView(store: store)
31+
case .userTypeSelection(let store):
32+
UserTypeSelectionView(store: store)
33+
case .createProfile(let store):
34+
CreateProfileView(store: store)
35+
36+
// MARK: Trainer
37+
case .trainerSignUpComplete(let store):
38+
TrainerSignUpCompleteView(store: store)
39+
case .trainerMakeInvitationCode(let store):
40+
MakeInvitationCodeView(store: store)
41+
case .trainerConnectedTraineeProfile(let store):
42+
ConnectedTraineeProfileView(store: store)
43+
44+
// MARK: Trainee
45+
case .traineeBasicInfoInput(let store):
46+
TraineeBasicInfoInputView(store: store)
47+
case .traineeTrainingPurpose(let store):
48+
TraineeTrainingPurposeView(store: store)
49+
case .traineePrecautionInput(let store):
50+
TraineePrecautionInputView(store: store)
51+
case .traineeProfileCompletion(let store):
52+
TraineeProfileCompletionView(store: store)
53+
case .traineeInvitationCodeInput(let store):
54+
TraineeInvitationCodeInputView(store: store)
55+
case .traineeTrainingInfoInput(let store):
56+
TraineeTrainingInfoInputView(store: store)
57+
case .traineeConnectionComplete(let store):
58+
TraineeConnectionCompleteView(store: store)
59+
}
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)