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-232] 트레이니, 트레이너 마이페이지 + 앱 내 트레이너/트레이니 연결 여부 반영 #82

Merged
merged 7 commits into from
Feb 12, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,9 @@ public struct UserRepositoryImpl: UserRepository {
public func postWithdrawal() async throws -> PostWithdrawalResDTO {
return try await networkService.request(UserTargetType.postWithdrawal, decodingType: PostWithdrawalResDTO.self)
}

/// 마이페이지 요청을 수행
public func getMyPageInfo() async throws -> GetMyPageInfoResDTO {
return try await networkService.request(UserTargetType.getMyPageInfo, decodingType: GetMyPageInfoResDTO.self)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public enum UserTargetType {
case postLogout
/// 회원 탈퇴 요청
case postWithdrawal
/// 마이페이지 정보 요청
case getMyPageInfo
}

extension UserTargetType: TargetType {
Expand All @@ -45,12 +47,15 @@ extension UserTargetType: TargetType {

case .postWithdrawal:
return "/members/withdraw"

case .getMyPageInfo:
return "/members"
}
}

var method: HTTPMethod {
switch self {
case .getSessionCheck:
case .getSessionCheck, .getMyPageInfo:
return .get

case .postSocialLogin, .postSignUp, .postLogout, .postWithdrawal:
Expand All @@ -60,7 +65,7 @@ extension UserTargetType: TargetType {

var task: RequestTask {
switch self {
case .getSessionCheck, .postLogout, .postWithdrawal:
case .getSessionCheck, .postLogout, .postWithdrawal, .getMyPageInfo:
return .requestPlain

case .postSocialLogin(let reqDto):
Expand All @@ -80,7 +85,7 @@ extension UserTargetType: TargetType {

var headers: [String: String]? {
switch self {
case .getSessionCheck, .postLogout, .postWithdrawal:
case .getSessionCheck, .postLogout, .postWithdrawal, .getMyPageInfo:
return nil

case .postSocialLogin:
Expand All @@ -96,7 +101,7 @@ extension UserTargetType: TargetType {

var interceptors: [any Interceptor] {
switch self {
case .getSessionCheck, .postLogout, .postWithdrawal:
case .getSessionCheck, .postLogout, .postWithdrawal, .getMyPageInfo:
return [
LoggingInterceptor(),
AuthTokenInterceptor(),
Expand Down
47 changes: 47 additions & 0 deletions TnT/Projects/Domain/Sources/DTO/User/UserResponseDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import Foundation

/// 로그인 세션 유효 확인 응답 DTO
public struct GetSessionCheckResDTO: Decodable {
/// 트레이너/트레이니 연결 여부
public let isConnected: Bool
/// 멤버 유형
public let memberType: MemberTypeResDTO
}

Expand Down Expand Up @@ -104,6 +107,50 @@ public struct PostLogoutResDTO: Decodable {
/// 회원탈퇴 응답 DTO
public typealias PostWithdrawalResDTO = EmptyResponse

/// 마이페이지 정보 응답 DTO
public struct GetMyPageInfoResDTO: Decodable {
/// 회원 이름
public let name: String
/// 이메일
public let email: String
/// 프로필 사진 URL
public let profileImageUrl: String
/// 회원 타입
public let memberType: MemberTypeResDTO
/// 소셜 타입
public let socialType: String
/// 트레이너 DTO
public let trainer: TrainerInfoResDTO?
/// 트레이니 DTO
public let trainee: TraineeInfoResDTO?
}

/// 트레이너 정보 표시에 사용되는 TrainerInfoDTO
public struct TrainerInfoResDTO: Decodable {
/// 관리 중인 회원
public let activeTraineeCount: Int?
/// 함께했던 회원
public let totalTraineeCount: Int?
}

/// 트레이니 정보 표시에 사용되는 TraineeInfoDTO
public struct TraineeInfoResDTO: Decodable {
/// 트레이너 연결 여부
public let isConnected: Bool
/// 생년월일
public let birthday: String?
/// 나이
public let age: Int?
/// 진행한 PT 횟수
public let height: Double?
/// 총 PT 횟수
public let weight: Double?
/// 주의사항
public let cautionNote: String?
/// PT 목표
public let ptGoals: [String]
}

public extension PostSignUpResDTO {
func toEntity() -> PostSignUpResEntity {
return .init(
Expand Down
33 changes: 33 additions & 0 deletions TnT/Projects/Domain/Sources/Entity/MyPageEntity.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// MyPageEntity.swift
// Domain
//
// Created by 박민서 on 2/12/25.
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
//

import Foundation

public struct TraineeMyPageEntity: Equatable, Sendable {
/// 트레이니 연결 여부
public let isConnected: Bool
/// 트레이니 이름
public let name: String
/// 트레이니 프로필 이미지 URL
public let profileImageUrl: String
/// 소셜 타입
public let socialType: String
}

public struct TrainerMyPageEntity: Equatable, Sendable {
/// 트레이너 이름
public let name: String
/// 트레이너 프로필 이미지 URL
public let profileImageUrl: String
/// 소셜 타입
public let socialType: String
/// 관리 중인 회원 수
public let activeTraineeCount: Int?
/// 함께했던 회원 수
public let totalTraineeCount: Int?
}
30 changes: 30 additions & 0 deletions TnT/Projects/Domain/Sources/Mapper/UserMapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// UserMapper.swift
// Domain
//
// Created by 박민서 on 2/12/25.
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
//

import Foundation

public extension GetMyPageInfoResDTO {
func toEntity() -> TraineeMyPageEntity {
return .init(
isConnected: self.trainee?.isConnected ?? false,
name: self.name,
profileImageUrl: self.profileImageUrl,
socialType: self.socialType
)
}

func toEntity() -> TrainerMyPageEntity {
return .init(
name: self.name,
profileImageUrl: self.profileImageUrl,
socialType: self.socialType,
activeTraineeCount: self.trainer?.activeTraineeCount,
totalTraineeCount: self.trainer?.totalTraineeCount
)
}
}
1 change: 1 addition & 0 deletions TnT/Projects/Domain/Sources/Policy/AppStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ import Foundation

public enum AppStorage {
public static let hideHomePopupUntil: String = "hideHomePopupUntil"
public static let isConnected: String = "isConnected"
}
5 changes: 5 additions & 0 deletions TnT/Projects/Domain/Sources/Repository/UserRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,9 @@ public protocol UserRepository {
/// - Returns: 회원 탈퇴 완료 시 응답 DTO (`PostWithdrawalResDTO`)
/// - Throws: 네트워크 오류 또는 서버에서 반환한 오류를 발생시킬 수 있음
func postWithdrawal() async throws -> PostWithdrawalResDTO

/// 마이페이지 정보 요청
/// - Returns: 마이페이지 표시에 필요한 응답 DTO (`GetMyPageInfoResDTO`)
/// - Throws: 네트워크 오류 또는 서버에서 반환한 오류를 발생시킬 수 있음
func getMyPageInfo() async throws -> GetMyPageInfoResDTO
}
8 changes: 6 additions & 2 deletions TnT/Projects/Domain/Sources/UseCase/UserUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public protocol UserUseCase {
}

// MARK: - Default 구현체
public struct DefaultUserUseCase: UserRepository, UserUseCase {
public struct DefaultUserUseCase: UserUseCase {

public let userRepostiory: UserRepository

Expand Down Expand Up @@ -67,7 +67,7 @@ public struct DefaultUserUseCase: UserRepository, UserUseCase {
}

// MARK: - Repository
extension DefaultUserUseCase {
extension DefaultUserUseCase: UserRepository {
public func getSessionCheck() async throws -> GetSessionCheckResDTO {
return try await userRepostiory.getSessionCheck()
}
Expand All @@ -87,4 +87,8 @@ extension DefaultUserUseCase {
public func postWithdrawal() async throws -> PostWithdrawalResDTO {
return try await userRepostiory.postWithdrawal()
}

public func getMyPageInfo() async throws -> GetMyPageInfoResDTO {
return try await userRepostiory.getMyPageInfo()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@ public struct AppFlowCoordinatorFeature {
@ObservableState
public struct State: Equatable {
// MARK: Data related state
/// 트레이너/트레이니 연결 여부
@Shared(.appStorage(AppStorage.isConnected)) var isConnected: Bool = false
/// 유저 멤버 유형
var userType: UserType?
// MARK: UI related state
/// 스플래시 표시 여부
var view_isSplashActive: Bool
/// 팝업 표시 여부
var view_isPopUpPresented: Bool

// MARK: SubFeature state
Expand Down Expand Up @@ -59,7 +64,7 @@ public struct AppFlowCoordinatorFeature {
/// 저장 세션 정보 확인
case checkSessionInfo
/// 현재 유저 정보 업데이트
case updateUserInfo(UserType?)
case updateUserInfo(type: UserType?, isConnected: Bool)
/// 스플래시 표시 종료 시
case splashFinished
/// 세션 만료 팝업 표시
Expand Down Expand Up @@ -139,15 +144,20 @@ public struct AppFlowCoordinatorFeature {
switch action {
case .checkSession:
return .run { send in
let result = try? await userUseCaseRepo.getSessionCheck()
switch result?.memberType {
guard let result = try? await userUseCaseRepo.getSessionCheck() else {
try keyChainManager.delete(.sessionId)
await send(.updateUserInfo(type: nil, isConnected: false))
return
}

switch result.memberType {
case .trainer:
await send(.updateUserInfo(.trainer))
await send(.updateUserInfo(type: .trainer, isConnected: result.isConnected))
case .trainee:
await send(.updateUserInfo(.trainee))
await send(.updateUserInfo(type: .trainee, isConnected: result.isConnected))
default:
try keyChainManager.delete(.sessionId)
await send(.updateUserInfo(nil))
await send(.updateUserInfo(type: nil, isConnected: false))
}
}
}
Expand All @@ -156,9 +166,11 @@ public struct AppFlowCoordinatorFeature {
let session: String? = try? keyChainManager.read(for: .sessionId)
return session != nil
? .send(.api(.checkSession))
: .send(.updateUserInfo(nil))
: .send(.updateUserInfo(type: nil, isConnected: false))

case let .updateUserInfo(userType, isConnected):
state.$isConnected.withLock { $0 = isConnected }

case .updateUserInfo(let userType):
switch userType {
case .trainee:
return self.setFlow(.traineeMainFlow, state: &state)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ public struct TraineeMainFlowFeature {
)
return .none

/// 연결 완료 화면 -> 홈으로 이동
case .element(id: _, action: .traineeConnectionComplete(.setNavigating)):
state.path.removeLast(2)
return .none

default:
return .none
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,16 @@ public struct TrainerMainFlowFeature {
state.path.removeLast()
return .none

/// 연결 완료 -> 트레이니 정보
case .element(id: _, action: .connectionComplete(.setNavigating(let profile))):
state.path.append(.connectedTraineeProfile(.init(traineeProfile: profile)))
return .none

/// 트레이니 정보 -> 홈으로
case .element(id: _, action: .connectedTraineeProfile(.setNavigating)):
state.path.removeLast(2)
return.none

default:
return .none
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public struct TraineeHomeFeature {
// MARK: Data related state
/// 3일 동안 보지 않기 시작 날짜
@Shared(.appStorage(AppStorage.hideHomePopupUntil)) var hidePopupUntil: Date?
/// 트레이너 연결 여부
@Shared(.appStorage(AppStorage.isConnected)) var isConnected: Bool = false
/// 선택된 날짜
var selectedDate: Date
/// 캘린더 이벤트
Expand All @@ -29,8 +31,6 @@ public struct TraineeHomeFeature {
var records: [RecordListItemEntity]
/// 3일 동안 보지 않기 선택되었는지 여부
var isHideUntilSelected: Bool
/// 트레이너 연결 여부
var isConnected: Bool

// MARK: UI related state
/// 캘린더 표시 페이지
Expand All @@ -55,7 +55,6 @@ public struct TraineeHomeFeature {
sessionInfo: WorkoutListItemEntity? = nil,
records: [RecordListItemEntity] = [],
isHideUntilSelected: Bool = false,
isConnected: Bool = false,
view_currentPage: Date = .now,
view_isBottomSheetPresented: Bool = false,
view_isPopUpPresented: Bool = false
Expand All @@ -65,7 +64,6 @@ public struct TraineeHomeFeature {
self.sessionInfo = sessionInfo
self.records = records
self.isHideUntilSelected = isHideUntilSelected
self.isConnected = isConnected
self.view_currentPage = view_currentPage
self.view_isBottomSheetPresented = view_isBottomSheetPresented
self.view_isPopUpPresented = view_isPopUpPresented
Expand Down Expand Up @@ -172,11 +170,9 @@ public struct TraineeHomeFeature {
return .send(.setNavigating(.traineeInvitationCodeInput))

case .onAppear:
if let hideUntil = state.hidePopupUntil, hideUntil > Date() {
state.view_isPopUpPresented = false
} else {
state.view_isPopUpPresented = true
}
let hideUntil = state.hidePopupUntil ?? Date()
let hidePopUp = state.isConnected || hideUntil > Date()
state.view_isPopUpPresented = !hidePopUp
return .none
}

Expand Down
Loading