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-221] 전체 화면 흐름 관련 보완 로직 작성 #69

Merged
merged 10 commits into from
Feb 10, 2025
10 changes: 8 additions & 2 deletions TnT/Projects/Data/Sources/Network/NetworkService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,15 @@ public final class NetworkService {

// Data 디코딩
return try decodeData(data, as: decodingType)
} catch {
} catch let error as NetworkError {
// TODO: 추후 인터셉터 리팩토링 시 error middleWare로 분리
NotificationCenter.default.post(toast: .init(presentType: .text("⚠"), message: "서버 요청에 실패했어요"))
NotificationCenter.default.postProgress(visible: false)
switch error {
case .unauthorized:
NotificationCenter.default.postSessionExpired()
default:
NotificationCenter.default.post(toast: .init(presentType: .text("⚠"), message: "서버 요청에 실패했어요"))
}
throw error
}
}
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "AppIcon.png",
"filename" : "LOGO.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"images" : [
{
"filename" : "img_app_splash.png",
"filename" : "img_app_splash1x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "img_app_splash@2x.png",
"filename" : "img_app_splash2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "img_app_splash@3x.png",
"filename" : "img_app_splash3x.png",
"idiom" : "universal",
"scale" : "3x"
}
Expand Down
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public extension TPopUpAlertView {
let style: TPopupAlertState.ButtonState.Style
let action: () -> Void

init(
public init(
title: String,
style: TPopupAlertState.ButtonState.Style,
action: @escaping () -> Void
Expand Down
7 changes: 7 additions & 0 deletions TnT/Projects/Domain/Sources/Extension/Notification+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public extension Notification.Name {
static let showProgressNotification = Notification.Name("ShowProgressNotification")
/// ProgressView를 숨기기 위한 노티피케이션 이름
static let hideProgressNotification = Notification.Name("HideProgressNotification")
/// 세션 만료 팝업을 표시하기 위한 노티피케이션 이름
static let showSessionExpiredPopupNotification = Notification.Name("ShowSessionExpiredPopupNotification")
}

public extension NotificationCenter {
Expand All @@ -43,4 +45,9 @@ public extension NotificationCenter {
let name: Notification.Name = visible ? .showProgressNotification : .hideProgressNotification
self.post(name: name, object: nil)
}

/// 세션 만료 팝업 표시를 요청하는 편의 메서드
func postSessionExpired() {
NotificationCenter.default.post(name: .showSessionExpiredPopupNotification, object: nil)
}
}
15 changes: 15 additions & 0 deletions TnT/Projects/Domain/Sources/Policy/AppLinks.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// AppLinks.swift
// Domain
//
// Created by 박민서 on 2/10/25.
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
//

import Foundation

public enum AppLinks {
public static let termsOfService: String = "https://yapp25thteam.com/terms"
public static let privacyPolicy: String = "https://yapp25thteam.com/privacy"
public static let openSourceLicense: String = "https://yapp25thteam.com/license"
}
13 changes: 13 additions & 0 deletions TnT/Projects/Domain/Sources/Policy/AppStorage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// AppStorage.swift
// Domain
//
// Created by 박민서 on 2/10/25.
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
//

import Foundation

public enum AppStorage {
public static let hideHomePopupUntil: String = "hideHomePopupUntil"
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ public enum AppFlow: Sendable {
public struct AppFlowCoordinatorFeature {
@ObservableState
public struct State: Equatable {
// MARK: Data related state
var userType: UserType?
// MARK: UI related state
var view_isSplashActive: Bool
var view_isPopUpPresented: Bool

// MARK: SubFeature state
var trainerMainState: TrainerMainFlowFeature.State?
Expand All @@ -31,18 +35,23 @@ public struct AppFlowCoordinatorFeature {

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

public enum Action {
public enum Action: ViewAction {
case view(View)
/// 하위 코디네이터에서 일어나는 액션을 처리합니다
case subFeature(SubFeatureAction)
/// api 콜 액션을 처리합니다
Expand All @@ -51,8 +60,20 @@ public struct AppFlowCoordinatorFeature {
case checkSessionInfo
/// 현재 유저 정보 업데이트
case updateUserInfo(UserType?)
/// 첫 진입 시
case onAppear
/// 스플래시 표시 종료 시
case splashFinished
/// 세션 만료 팝업 표시
case showSessionExpiredPopup

@CasePathable
public enum View: Sendable, BindableAction {
/// 바인딩할 액션을 처리
case binding(BindingAction<State>)
/// 첫 진입 시
case onAppear
/// 세션 만료 팝업 확인 버튼 탭
case tapSessionExpiredPopupConfirmButton
}

@CasePathable
public enum SubFeatureAction: Sendable {
Expand All @@ -77,8 +98,33 @@ public struct AppFlowCoordinatorFeature {
public init() {}

public var body: some Reducer<State, Action> {
BindingReducer(action: \.view)

Reduce { state, action in
switch action {
case .view(let action):
switch action {
case .binding:
return .none

case .onAppear:
return .merge(
.run { send in
try await Task.sleep(for: .seconds(1))
await send(.splashFinished)
},
.send(.checkSessionInfo),
.run { send in
for await _ in NotificationCenter.default.notifications(named: .showSessionExpiredPopupNotification) {
await send(.showSessionExpiredPopup)
}
}
)
case .tapSessionExpiredPopupConfirmButton:
state.view_isPopUpPresented = false
return self.setFlow(.onboardingFlow, state: &state)
}

case .subFeature(let internalAction):
switch internalAction {
case .onboardingFlow(.switchFlow(let flow)),
Expand All @@ -88,7 +134,7 @@ public struct AppFlowCoordinatorFeature {
default:
return .none
}

case .api(let action):
switch action {
case .checkSession:
Expand All @@ -105,7 +151,7 @@ public struct AppFlowCoordinatorFeature {
}
}
}

case .checkSessionInfo:
let session: String? = try? keyChainManager.read(for: .sessionId)
return session != nil
Expand All @@ -122,8 +168,13 @@ public struct AppFlowCoordinatorFeature {
return self.setFlow(.onboardingFlow, state: &state)
}

case .onAppear:
return .send(.checkSessionInfo)
case .splashFinished:
state.view_isSplashActive = false
return .none

case .showSessionExpiredPopup:
state.view_isPopUpPresented = true
return .none
}
}
.ifLet(\.onboardingState, action: \.subFeature.onboardingFlow) { OnboardingFlowFeature() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,65 @@
import SwiftUI
import ComposableArchitecture

import DesignSystem

@ViewAction(for: AppFlowCoordinatorFeature.self)
public struct AppFlowCoordinatorView: View {
let store: StoreOf<AppFlowCoordinatorFeature>
@Bindable public var store: StoreOf<AppFlowCoordinatorFeature>

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

public var body: some View {
ZStack {
Group {
if let userType = store.userType {
switch userType {
case .trainee:
if let store = store.scope(state: \.traineeMainState, action: \.subFeature.traineeMainFlow) {
TraineeMainFlowView(store: store)
if store.view_isSplashActive {
SplashView()
.transition(.opacity)
} else {
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)
}
}
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)
}
}
} else {
if let store = store.scope(state: \.onboardingState, action: \.subFeature.onboardingFlow) {
OnboardingFlowView(store: store)
}
}
.animation(.easeInOut, value: store.userType)

OverlayContainer()
.environmentObject(OverlayManager.shared)
}
.animation(.easeInOut, value: store.userType)

OverlayContainer()
.environmentObject(OverlayManager.shared)
}
.onAppear {
store.send(.onAppear)
.animation(.easeInOut, value: store.view_isSplashActive)
.onAppear { send(.onAppear) }
.tPopUp(isPresented: $store.view_isPopUpPresented) {
.init(
alertState: .init(
title: "세션이 만료되었어요",
message: "장시간 미사용으로 로그인 화면으로 이동해요",
showAlertIcon: true,
buttons: [
TPopupAlertState.ButtonState(
title: "확인",
style: .secondary,
action: .init(action: {
send(.tapSessionExpiredPopupConfirmButton)
})
)
]
)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ public struct TraineeMainFlowFeature {
return .none
case .addMealRecordPage:
return .none
case .traineeInvitationCodeInput:
state.path.append(.traineeInvitationCodeInput(.init(view_navigationType: .existingUser)))
return .none
}
/// 트레이니 마이페이지
case .traineeMyPage(let screen):
Expand All @@ -64,7 +67,7 @@ public struct TraineeMainFlowFeature {

/// 마이페이지 초대코드 입력하기 버튼 탭-> 초대코드 입력 화면 이동
case .traineeInvitationCodeInput:
state.path.append(.traineeInvitationCodeInput(.init()))
state.path.append(.traineeInvitationCodeInput(.init(view_navigationType: .existingUser)))
return .none

/// 마이페이지 로그아웃/회원탈퇴 -> 온보딩 로그인 화면 이동
Expand All @@ -78,9 +81,14 @@ public struct TraineeMainFlowFeature {
// 특정 화면 append
return .none

/// 마이페이지 초대코드 입력화면 다음 버튼 탭 - > PT 정보 입력 화면 이동
case .element(_, action: .traineeInvitationCodeInput(.setNavigating)):
state.path.append(.traineeTrainingInfoInput(.init()))
/// 마이페이지 초대코드 입력화면 다음 버튼 탭 - > PT 정보 입력 화면 or 홈 이동
case .element(_, action: .traineeInvitationCodeInput(.setNavigating(let screen))):
switch screen {
case .traineeHome:
state.path.removeLast()
case .trainingInfoInput:
state.path.append(.traineeTrainingInfoInput(.init()))
}
return .none

/// PT 정보 입력 화면 다음 버튼 탭 -> 연결 완료 화면 이동
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ public struct TrainerMainFlowFeature {
case .addPTSessionPage:
state.path.append(.addPTSession(.init()))
return .none
case .trainerMakeInvitationCodePage:
state.path.append(.trainerMakeInvitationCodePage(.init()))
return .none
}

/// 트레이너 회원목록
Expand All @@ -63,6 +66,11 @@ public struct TrainerMainFlowFeature {
}
}

/// 트레이너 초대코드 발급 페이지 건너 뛰기 -> 홈으로
case .element(id: _, action: .trainerMakeInvitationCodePage(.setNavigation)):
state.path.removeLast()
return .none

default:
return .none
}
Expand Down Expand Up @@ -93,5 +101,7 @@ extension TrainerMainFlowFeature {
case addPTSession(TrainerAddPTSessionFeature)

// MARK: MyPage
/// 초대코드 발급
case trainerMakeInvitationCodePage(MakeInvitationCodeFeature)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public struct TrainerMainFlowView: View {
AlarmCheckView(store: store)

// MARK: MyPage
case .trainerMakeInvitationCodePage(let store):
MakeInvitationCodeView(store: store)
}
}
}
Expand Down
Loading