Skip to content

Commit

Permalink
Merge pull request #69 from YAPP-Github/TNT-221-appFlowUI
Browse files Browse the repository at this point in the history
[TNT-221] ์ „์ฒด ํ™”๋ฉด ํ๋ฆ„ ๊ด€๋ จ ๋ณด์™„ ๋กœ์ง ์ž‘์„ฑ
  • Loading branch information
FpRaArNkK authored Feb 10, 2025
2 parents 9654909 + 4cc643e commit f75cf83
Show file tree
Hide file tree
Showing 30 changed files with 447 additions and 70 deletions.
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

0 comments on commit f75cf83

Please sign in to comment.