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-254] 앱 FCM 알람 딥링크 동작 구현 #96

Merged
merged 1 commit into from
Feb 14, 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
Expand Up @@ -50,7 +50,7 @@ public struct TrainerRepositoryImpl: TrainerRepository {
return try await networkService.request(TrainerTargetType.getMonthlyLessonList(year: year, month: month), decodingType: GetMonthlyLessonListResDTO.self)
}

public func getConnectedTraineeInfo(trainerId: Int, traineeId: Int) async throws -> GetConnectedTraineeInfoResponseDTO {
public func getConnectedTraineeInfo(trainerId: Int64, traineeId: Int64) async throws -> GetConnectedTraineeInfoResponseDTO {
return try await networkService.request(TrainerTargetType.getConnectedTraineeInfo(trainerId: trainerId, traineeId: traineeId), decodingType: GetConnectedTraineeInfoResponseDTO.self)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public enum TrainerTargetType {
/// 회원 조희
case getMemebersList
/// 연결 완료된 트레이니 최초로 정보 불러오기
case getConnectedTraineeInfo(trainerId: Int, traineeId: Int)
case getConnectedTraineeInfo(trainerId: Int64, traineeId: Int64)
/// 관리 중인 회원 목록 요청
case getActiveTraineesList
/// PT 수업 추가
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ public struct ConnectTraineeInfoDTO: Decodable {
/// 나이
let age: Int?
/// 키
let height: Double
let height: Double?
/// 몸무게
let weight: Double
let weight: Double?
/// PT 목표
let ptGoal: String
let ptGoal: String?
/// 주의 사항
let cautionNote: String?
}
2 changes: 2 additions & 0 deletions TnT/Projects/Domain/Sources/Extension/Notification+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public extension Notification.Name {
static let hideProgressNotification = Notification.Name("HideProgressNotification")
/// 세션 만료 팝업을 표시하기 위한 노티피케이션 이름
static let showSessionExpiredPopupNotification = Notification.Name("ShowSessionExpiredPopupNotification")
/// FCM 토큰 - 트레이너/트레이니 연결 완료 receive 노티피케이션 이름
static let fcmUserConnectedNotification = Notification.Name("FCMUserConnectedNotification")
}

public extension NotificationCenter {
Expand Down
2 changes: 1 addition & 1 deletion TnT/Projects/Domain/Sources/Mapper/TrainerMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public extension ConnectTraineeInfoDTO {
age: self.age,
height: height,
weight: weight,
ptGoal: self.ptGoal,
ptGoal: self.ptGoal ?? "",
cautionNote: self.cautionNote
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public protocol TrainerRepository {
func getMonthlyLessonList(year: Int, month: Int) async throws -> GetMonthlyLessonListResDTO

/// 연결 완료된 트레이니 정보 불러오기
func getConnectedTraineeInfo(trainerId: Int, traineeId: Int) async throws -> GetConnectedTraineeInfoResponseDTO
func getConnectedTraineeInfo(trainerId: Int64, traineeId: Int64) async throws -> GetConnectedTraineeInfoResponseDTO

/// 관리 중인 회원 목록 요청
func getActiveTraineesList() async throws -> GetActiveTraineesListResDTO
Expand Down
2 changes: 1 addition & 1 deletion TnT/Projects/Domain/Sources/UseCase/TrainerUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public struct DefaultTrainerUseCase: TrainerRepository {
return try await trainerRepository.getDateSessionList(date: date)
}

public func getConnectedTraineeInfo(trainerId: Int, traineeId: Int) async throws -> GetConnectedTraineeInfoResponseDTO {
public func getConnectedTraineeInfo(trainerId: Int64, traineeId: Int64) async throws -> GetConnectedTraineeInfoResponseDTO {
return try await trainerRepository.getConnectedTraineeInfo(trainerId: trainerId, traineeId: traineeId)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import Domain
import DIContainer

public enum AppFlow: Sendable {
case onboardingFlow
case traineeMainFlow
case trainerMainFlow
case onboardingFlow(OnboardingFlowFeature.State)
case traineeMainFlow(TraineeMainFlowFeature.State)
case trainerMainFlow(TrainerMainFlowFeature.State)
}

@Reducer
Expand Down Expand Up @@ -67,8 +67,6 @@ public struct AppFlowCoordinatorFeature {
case updateUserInfo(type: UserType?, isConnected: Bool)
/// 스플래시 표시 종료 시
case splashFinished
/// 세션 만료 팝업 표시
case showSessionExpiredPopup

@CasePathable
public enum View: Sendable, BindableAction {
Expand All @@ -78,6 +76,8 @@ public struct AppFlowCoordinatorFeature {
case onAppear
/// 세션 만료 팝업 확인 버튼 탭
case tapSessionExpiredPopupConfirmButton
/// Notification 액션 처리
case notification(NotificationAction)
}

@CasePathable
Expand All @@ -95,6 +95,14 @@ public struct AppFlowCoordinatorFeature {
/// 로그인 세션 유효 확인 API
case checkSession
}

@CasePathable
public enum NotificationAction: Sendable {
/// 세션 만료 팝업 표시
case showSessionExpiredPopup
/// 트레이너/트레이니 연결 완료 알림 탭 시
case showConnectCompletion(trainerId: Int64, traineeId: Int64)
}
}

@Dependency(\.userUseRepoCase) private var userUseCaseRepo: UserRepository
Expand All @@ -118,16 +126,27 @@ public struct AppFlowCoordinatorFeature {
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)
}
}
.send(.checkSessionInfo)
)

case .tapSessionExpiredPopupConfirmButton:
state.view_isPopUpPresented = false
return self.setFlow(.onboardingFlow, state: &state)
return self.setFlow(.onboardingFlow(.init()), state: &state)

case .notification(let action):
switch action {
case .showSessionExpiredPopup:
state.view_isPopUpPresented = true
return .none

case let .showConnectCompletion(trainerId, traineeId):
return self.setFlow(.trainerMainFlow(.init(path:
.init([
.mainTab(.home(.init())),
.connectionComplete(.init(traineeId: traineeId, trainerId: trainerId))
])
)), state: &state)
}
}

case .subFeature(let internalAction):
Expand Down Expand Up @@ -173,20 +192,16 @@ public struct AppFlowCoordinatorFeature {

switch userType {
case .trainee:
return self.setFlow(.traineeMainFlow, state: &state)
return self.setFlow(.traineeMainFlow(.init()), state: &state)
case .trainer:
return self.setFlow(.trainerMainFlow, state: &state)
return self.setFlow(.trainerMainFlow(.init()), state: &state)
default:
return self.setFlow(.onboardingFlow, state: &state)
return self.setFlow(.onboardingFlow(.init()), state: &state)
}

case .splashFinished:
state.view_isSplashActive = false
return .none

case .showSessionExpiredPopup:
state.view_isPopUpPresented = true
return .none
}
}
.ifLet(\.onboardingState, action: \.subFeature.onboardingFlow) { OnboardingFlowFeature() }
Expand All @@ -204,15 +219,15 @@ extension AppFlowCoordinatorFeature {
state.trainerMainState = nil

switch flow {
case .onboardingFlow:
case .onboardingFlow(let flowState):
state.userType = nil
state.onboardingState = .init()
case .traineeMainFlow:
state.onboardingState = flowState
case .traineeMainFlow(let flowState):
state.userType = .trainee
state.traineeMainState = .init()
case .trainerMainFlow:
state.traineeMainState = flowState
case .trainerMainFlow(let flowState):
state.userType = .trainer
state.trainerMainState = .init()
state.trainerMainState = flowState
}

return .none
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,30 @@ public struct AppFlowCoordinatorView: View {
)
)
}
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name.showSessionExpiredPopupNotification)) { notification in
send(.notification(.showSessionExpiredPopup))
}
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name.fcmUserConnectedNotification)) { notification in
guard let userInfo = notification.userInfo else { return }
let trainerId: Int64 = {
if let value = userInfo["trainerId"] as? Int64 {
return value
} else if let string = userInfo["trainerId"] as? String,
let value = Int64(string) {
return value
}
return 0
}()
let traineeId: Int64 = {
if let value = userInfo["traineeId"] as? Int64 {
return value
} else if let string = userInfo["traineeId"] as? String,
let value = Int64(string) {
return value
}
return 0
}()
send(.notification(.showConnectCompletion(trainerId: trainerId, traineeId: traineeId)))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Domain
@Reducer
public struct OnboardingFlowFeature {
@ObservableState
public struct State: Equatable {
public struct State: Equatable, Sendable {
public var path: StackState<Path.State>
@Shared var signUpEntity: PostSignUpEntity

Expand Down Expand Up @@ -47,9 +47,9 @@ public struct OnboardingFlowFeature {
case .element(id: _, action: .snsLogin(.setNavigating(let screen))):
switch screen {
case .traineeHome:
return .send(.switchFlow(.traineeMainFlow))
return .send(.switchFlow(.traineeMainFlow(.init())))
case .trainerHome:
return .send(.switchFlow(.trainerMainFlow))
return .send(.switchFlow(.trainerMainFlow(.init())))
case .userTypeSelection:
state.path.append(.userTypeSelection(.init(signUpEntity: state.$signUpEntity)))
}
Expand Down Expand Up @@ -83,7 +83,7 @@ public struct OnboardingFlowFeature {

/// 트레이너 초대 코드 생성 화면 -> 트레이너 홈 이동
case .element(id: _, action: .trainerMakeInvitationCode(.setNavigation)):
return .send(.switchFlow(.trainerMainFlow))
return .send(.switchFlow(.trainerMainFlow(.init())))

// MARK: Trainee
/// 트레이니 기본 정보 입력 -> PT 목적 설정 화면 이동
Expand All @@ -110,7 +110,7 @@ public struct OnboardingFlowFeature {
case .element(id: _, action: .traineeInvitationCodeInput(.setNavigating(let screen))):
switch screen {
case .traineeHome:
return .send(.switchFlow(.traineeMainFlow))
return .send(.switchFlow(.traineeMainFlow(.init())))
case let .trainingInfoInput(trainerName, invitationCode):
state.path.append(.traineeTrainingInfoInput(.init(trainerName: trainerName, invitationCode: invitationCode)))
return .none
Expand All @@ -133,7 +133,7 @@ public struct OnboardingFlowFeature {

/// 트레이니 트레이너 연결완료 -> 트레이니 홈화면
case .element(id: _, action: .traineeConnectionComplete(.setNavigating)):
return .send(.switchFlow(.traineeMainFlow))
return .send(.switchFlow(.traineeMainFlow(.init())))

default:
return .none
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public struct TraineeMainFlowFeature {

/// 마이페이지 로그아웃/회원탈퇴 -> 온보딩 로그인 화면 이동
case .onboardingLogin:
return .send(.switchFlow(.onboardingFlow))
return .send(.switchFlow(.onboardingFlow(.init())))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Domain
@Reducer
public struct TrainerMainFlowFeature {
@ObservableState
public struct State: Equatable {
public struct State: Equatable, Sendable {
public var path: StackState<Path.State>

public init(path: StackState<Path.State> = .init([.mainTab(.home(.init()))])) {
Expand Down Expand Up @@ -69,7 +69,7 @@ public struct TrainerMainFlowFeature {
case .trainerMyPage(let screen):
switch screen {
case .onboardingLogin:
return .send(.switchFlow(.onboardingFlow))
return .send(.switchFlow(.onboardingFlow(.init())))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,30 @@ public struct ConnectedTraineeProfileView: View {
}

public var body: some View {
NavigationStack {
ZStack {
Image(.imgConnectionCompleteBackground)
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.clipped()
.ignoresSafeArea()
ZStack {
Image(.imgConnectionCompleteBackground)
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.clipped()
.ignoresSafeArea()

VStack(spacing: 0) {
Spacer()

VStack(spacing: 0) {
Spacer()

traineeView()

Spacer()

TBottomButton(title: "시작하기", isEnable: true) {
send(.startButtonTapped)
}
.padding(.bottom, .safeAreaBottom)
.ignoresSafeArea(.all, edges: .bottom)
traineeView()

Spacer()

TBottomButton(title: "시작하기", isEnable: true) {
send(.startButtonTapped)
}
.padding(.bottom, .safeAreaBottom)
.ignoresSafeArea(.all, edges: .bottom)
}
.navigationBarBackButtonHidden()
}
.background(Color.neutral800)
.navigationBarBackButtonHidden()
}

@ViewBuilder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ import Domain
public struct ConnectionCompleteFeature {
@ObservableState
public struct State: Equatable {
var traineeId: Int?
var trainerId: Int?
var traineeId: Int64?
var trainerId: Int64?
var connectionInfo: ConnectionInfoEntity?
var traineeProfile: ConnectedTraineeProfileEntity?

public init(
traineeId: Int? = nil,
trainerId: Int? = nil,
traineeId: Int64? = nil,
trainerId: Int64? = nil,
connectionInfo: ConnectionInfoEntity? = nil,
traineeProfile: ConnectedTraineeProfileEntity? = nil
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public struct ConnectionCompleteView: View {
.ignoresSafeArea(.all, edges: .bottom)
}
}
.onAppear { send(.onAppear) }
.navigationBarBackButtonHidden()
}

Expand Down
Loading