Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TNT-208] App Coordinator 로직 작성 #57

Merged
merged 4 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// AppFlowCoordinatorFeature.swift
// Presentation
//
// Created by 박민서 on 2/4/25.
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
//

import SwiftUI
import ComposableArchitecture

import Domain

@Reducer
public struct AppFlowCoordinatorFeature {
@ObservableState
public struct State: Equatable {
var userType: UserType?

// MARK: SubFeature state
var trainerMainState: TrainerMainFlowFeature.State?
var traineeMainState: TraineeMainFlowFeature.State?
var onboardingState: OnboardingFlowFeature.State?

public init(
userType: UserType? = nil,
onboardingState: OnboardingFlowFeature.State? = .init(),
trainerMainState: TrainerMainFlowFeature.State? = nil,
traineeMainState: TraineeMainFlowFeature.State? = nil
) {
self.userType = userType
self.onboardingState = onboardingState
self.trainerMainState = trainerMainState
self.traineeMainState = traineeMainState
}
}

public enum Action {
/// 하위 코디네이터에서 일어나는 액션을 처리합니다
case subFeature(SubFeatureAction)
case onAppear

@CasePathable
public enum SubFeatureAction: Sendable {
/// 온보딩 플로우 코디네이터에서 발생하는 액션 처리
case onboardingFlow(OnboardingFlowFeature.Action)
/// 트레이너 메인탭 플로우 코디네이터에서 발생하는 액션 처리
case trainerMainFlow(TrainerMainFlowFeature.Action)
/// 트레이니 메인탭 플로우 코디네이터에서 발생하는 액션 처리
case traineeMainFlow(TraineeMainFlowFeature.Action)
}
}

public init() {}

public var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .subFeature(let internalAction):
switch internalAction {
case .onboardingFlow:
return .none

case .trainerMainFlow:
return .none

case .traineeMainFlow:
return .none
}

case .onAppear:
return .none
}
}
.ifLet(\.onboardingState, action: \.subFeature.onboardingFlow) { OnboardingFlowFeature() }
.ifLet(\.trainerMainState, action: \.subFeature.trainerMainFlow) { TrainerMainFlowFeature() }
.ifLet(\.traineeMainState, action: \.subFeature.traineeMainFlow) { TraineeMainFlowFeature() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// AppFlowCoordinatorView.swift
// Presentation
//
// Created by 박민서 on 2/4/25.
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
//

import SwiftUI
import ComposableArchitecture

public struct AppFlowCoordinatorView: View {
let store: StoreOf<AppFlowCoordinatorFeature>

public init(store: StoreOf<AppFlowCoordinatorFeature>) {
self.store = store
}

public var body: some View {
Group {
if let userType = store.userType {
switch userType {
case .trainee:
if let store = store.scope(state: \.traineeMainState, action: \.subFeature.traineeMainFlow) {
TraineeMainFlowView(store: store)
}
case .trainer:
if let store = store.scope(state: \.trainerMainState, action: \.subFeature.trainerMainFlow) {
TrainerMainFlowView(store: store)
}
}
} else {
if let store = store.scope(state: \.onboardingState, action: \.subFeature.onboardingFlow) {
OnboardingFlowView(store: store)
}
}
}
.onAppear {
store.send(.onAppear)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// OnboardingNavigationFeature.swift
// Presentation
//
// Created by 박서연 on 1/24/25.
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
//

import SwiftUI
import ComposableArchitecture

import Domain

@Reducer
public struct OnboardingFlowFeature {
@ObservableState
public struct State: Equatable {
public var path: StackState<Path.State>

public init(path: StackState<Path.State> = .init([.snsLogin(.init())])) {
self.path = path
}
}

public enum Action: Sendable {
/// 현재 표시되고 있는 path 화면 내부에서 일어나는 액션을 처리합니다.
case path(StackActionOf<Path>)
case onAppear
}

public init() {}

public var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case let .path(action):
switch action {

case .element(id: _, action: .snsLogin(.view(.tappedAppleLogin))):
state.path.append(.trainerSignUpComplete(.init()))
return .none

/// 트레이너 프로필 생성 완료 -> 다음 버튼 tapped
case .element(id: _, action: .trainerSignUpComplete(.setNavigating)):
state.path.append(.trainerMakeInvitationCode(MakeInvitationCodeFeature.State()))
return .none

/// 트레이너의 초대코드 화면 -> 건너뛰기 버튼 tapped
case .element(id: _, action: .trainerMakeInvitationCode(.setNavigation)):
// 추후에 홈과 연결
return .none

/// 약관 화면 -> 트레이너/트레이니 선택 화면 이동
case .element(id: _, action: .userTypeSelection):
return .none

default:
return .none
}

case .onAppear:
return .none
}
}
.forEach(\.path, action: \.path)
}
}

extension OnboardingFlowFeature {
@Reducer(state: .equatable, .sendable)
public enum Path {
// MARK: Common
/// SNS 로그인 뷰
case snsLogin(LoginFeature)
/// 약관동의뷰
case term(TermFeature)
/// 트레이너/트레이니 선택 뷰
case userTypeSelection(UserTypeSelectionFeature)
/// 트레이너/트레이니의 이름 입력 뷰
case createProfile(CreateProfileFeature)

// MARK: Trainer
/// 트레이너 회원 가입 완료 뷰
/// TODO: 트레이너/트레이니 회원 가입 완료 화면으로 통합 필요
case trainerSignUpComplete(TrainerSignUpCompleteFeature)
/// 트레이너의 초대코드 발급 뷰
case trainerMakeInvitationCode(MakeInvitationCodeFeature)
/// 트레이너의 트레이니 프로필 확인 뷰
case trainerConnectedTraineeProfile(ConnectedTraineeProfileFeature)

// MARK: Trainee
/// 트레이니 기본 정보 입력
case traineeBasicInfoInput(TraineeBasicInfoInputFeature)
/// 트레이니 PT 목적 입력
case traineeTrainingPurpose(TraineeTrainingPurposeFeature)
/// 트레이니 주의사항 입력
case traineePrecautionInput(TraineePrecautionInputFeature)
/// 트레이니 프로필 생성 완료
/// TODO: 트레이너/트레이니 회원 가입 완료 화면으로 통합 필요
case traineeProfileCompletion(TraineeProfileCompletionFeature)
/// 트레이니 초대 코드입력
case traineeInvitationCodeInput(TraineeInvitationCodeInputFeature)
/// 트레이니 수업 정보 입력
case traineeTrainingInfoInput(TraineeTrainingInfoInputFeature)
/// 트레이니 연결 완료
/// TODO: 트레이너/트레이니 연결 완료 화면으로 통합 필요
case traineeConnectionComplete(TraineeConnectionCompleteFeature)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// OnboardingNavigationView.swift
// Presentation
//
// Created by 박민서 on 2/4/25.
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
//

import SwiftUI
import ComposableArchitecture

import DesignSystem

public struct OnboardingFlowView: View {
@Bindable var store: StoreOf<OnboardingFlowFeature>

public init(store: StoreOf<OnboardingFlowFeature>) {
self.store = store
}

public var body: some View {
NavigationStack(path: $store.scope(state: \.path, action: \.path)) {
EmptyView()
} destination: { store in
switch store.case {
// MARK: Common
case .snsLogin(let store):
LoginView(store: store)
case .term(let store):
TermView(store: store)
case .userTypeSelection(let store):
UserTypeSelectionView(store: store)
case .createProfile(let store):
CreateProfileView(store: store)

// MARK: Trainer
case .trainerSignUpComplete(let store):
TrainerSignUpCompleteView(store: store)
case .trainerMakeInvitationCode(let store):
MakeInvitationCodeView(store: store)
case .trainerConnectedTraineeProfile(let store):
ConnectedTraineeProfileView(store: store)

// MARK: Trainee
case .traineeBasicInfoInput(let store):
TraineeBasicInfoInputView(store: store)
case .traineeTrainingPurpose(let store):
TraineeTrainingPurposeView(store: store)
case .traineePrecautionInput(let store):
TraineePrecautionInputView(store: store)
case .traineeProfileCompletion(let store):
TraineeProfileCompletionView(store: store)
case .traineeInvitationCodeInput(let store):
TraineeInvitationCodeInputView(store: store)
case .traineeTrainingInfoInput(let store):
TraineeTrainingInfoInputView(store: store)
case .traineeConnectionComplete(let store):
TraineeConnectionCompleteView(store: store)
}
}
}
}
Loading