Skip to content

Commit 6934e6f

Browse files
authored
Feat: 감정 구슬 API 연동 및 추천 루틴 결과 화면 재사용 (#T3-127)
* Style: 클라에서 저장하고 있는 감정 구슬 값 삭제 (이미지 Asset, EmotionType 등) * Feat: Emotion 조회 및 등록 API 연동 (#T3-127) - EmotionEndpoint 정의 - EmotionRepositoryProtocol, EmotionRepository 정의 및 구현 (+ EmotionResponseDTO) - EmotionUseCaseProtocol, EmotionUseCase 정의 및 구현 (+ EmotionEntity) * Chore: Kingfisher 외부 라이브러리 의존성 추가 * Refatcor: 감정구슬 Cell, 감정구슬 등록 화면 수정 (#T3-127) - 서버 통신으로 받은 값들을 보여주도록 수정 - 감정 구슬 이미지 Kingfisher로 출력 * Docs: 그래픽 이미지 Asset 추가 및 BitnagilGraphic 케이스 추가 * Refactor: 기존 OnboardingUseCase 삭제 및 추천 루틴 받아오는 로직 이동 (#T3-127) - OnboardingUseCaseProtocol, OnboardingUseCase 삭제 - OnboardingRecommendedRoutineView 삭제 - 기존 온보딩 선택지 · 감정 값을 등록하고 추천 루틴을 받아오는 로직 이동 * Chore: Common View 폴더 생성 및 재사용 View 이동 (SplashView, TabBarView) * Feat: ResultRecommendedRoutineView · ResultRecommendedRoutineViewModel 구현 (#T3-127) - 추천 루틴 결과 값을 보여주는 화면(ResultRecommendedRoutineView) 구현 - 온보딩 선택지 or 감정 구슬 선택지에 따른 추천 루틴 받아오는 로직 - 선택한 추천 루틴 등록 로직 * Refactor: ResultRecommendedRoutineView 재사용을 위한 분기처리 (#T3-127) - MypageView, OnboardingResultView, EmotionRegisterView에서 재사용할 수 있도록 분기 처리 - OnboardingResultView에 그래픽 추가 * Feat: 의존성 조립 (Domain, DataSource, Presentation) * Refactor: 오타 수정 * Refactor: UIScreen.main 기준으로 height 계산하는 코드 수정
1 parent 6f9b3bf commit 6934e6f

63 files changed

Lines changed: 966 additions & 558 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,9 @@ public struct DataSourceDependencyAssembler: DependencyAssemblerProtocol {
2828
DIContainer.shared.register(type: RecommendedRoutineRepositoryProtocol.self) { _ in
2929
return RecommendedRoutineRepository()
3030
}
31+
32+
DIContainer.shared.register(type: EmotionRepositoryProtocol.self) { _ in
33+
return EmotionRepository()
34+
}
3135
}
3236
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// EmotionResponseDTO.swift
3+
// DataSource
4+
//
5+
// Created by 최정인 on 7/29/25.
6+
//
7+
8+
import Domain
9+
import Foundation
10+
11+
struct EmotionResponseDTO: Decodable {
12+
let type: String
13+
let name: String
14+
let imageUrl: String
15+
16+
enum CodingKeys: String, CodingKey {
17+
case type = "emotionMarbleType"
18+
case name = "emotionMarbleName"
19+
case imageUrl
20+
}
21+
}
22+
23+
extension EmotionResponseDTO {
24+
func toEmotionEntity() -> EmotionEntity {
25+
return EmotionEntity(
26+
emotionType: type,
27+
emotionName: name,
28+
emotionImageUrl: URL(string: imageUrl))
29+
}
30+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//
2+
// EmotionEndpoint.swift
3+
// DataSource
4+
//
5+
// Created by 최정인 on 7/28/25.
6+
//
7+
8+
enum EmotionEndpoint {
9+
case fetchEmotions
10+
case registerEmotion(emotion: String)
11+
}
12+
13+
extension EmotionEndpoint: Endpoint {
14+
var baseURL: String {
15+
return AppProperties.baseURL + "/api/v1/emotion-marbles"
16+
}
17+
18+
var path: String {
19+
return baseURL
20+
}
21+
22+
var method: HTTPMethod {
23+
switch self {
24+
case .fetchEmotions: .get
25+
case .registerEmotion: .post
26+
}
27+
}
28+
29+
var headers: [String : String] {
30+
let headers: [String: String] = [
31+
"Content-Type": "application/json",
32+
"accept": "*/*"
33+
]
34+
return headers
35+
}
36+
37+
var queryParameters: [String : String] {
38+
return [:]
39+
}
40+
41+
var bodyParameters: [String : Any] {
42+
switch self {
43+
case .fetchEmotions:
44+
return [:]
45+
case .registerEmotion(let emotion):
46+
return ["emotionMarbleType": emotion]
47+
}
48+
}
49+
50+
var isAuthorized: Bool {
51+
return true
52+
}
53+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// EmotionRepository.swift
3+
// DataSource
4+
//
5+
// Created by 최정인 on 7/28/25.
6+
//
7+
8+
import Domain
9+
10+
final class EmotionRepository: EmotionRepositoryProtocol {
11+
private let networkService = NetworkService.shared
12+
13+
func fetchEmotions() async throws -> [EmotionEntity] {
14+
let endpoint = EmotionEndpoint.fetchEmotions
15+
guard let response = try await networkService.request(endpoint: endpoint, type: [EmotionResponseDTO].self)
16+
else { return [] }
17+
18+
let emotionEntities = response.compactMap({ $0.toEmotionEntity() })
19+
return emotionEntities
20+
}
21+
22+
func registerEmotion(emotion: String) async throws -> [RecommendedRoutineEntity] {
23+
let endpoint = EmotionEndpoint.registerEmotion(emotion: emotion)
24+
guard let response = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineListResponseDTO.self)
25+
else { return [] }
26+
27+
let recommendedRoutineEntity = response.recommendedRoutines.compactMap({ $0.toRecommendedRoutineEntity() })
28+
return recommendedRoutineEntity
29+
}
30+
}

Projects/Domain/Sources/DomainDependencyAssembler.swift

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,25 @@ public struct DomainDependencyAssembler: DependencyAssemblerProtocol {
3232
return WithdrawUseCase(authRepository: authRepository)
3333
}
3434

35-
DIContainer.shared.register(type: OnboardingUseCaseProtocol.self) { container in
36-
guard let onboardingRepository = container.resolve(type: OnboardingRepositoryProtocol.self)
37-
else { fatalError("onboardingRepository 의존성이 등록되지 않았습니다.") }
38-
39-
return OnboardingUseCase(onboardingRepository: onboardingRepository)
40-
}
41-
4235
DIContainer.shared.register(type: RecommendedRoutineUseCaseProtocol.self) { container in
4336
guard let recommendedRoutineRepository = container.resolve(type: RecommendedRoutineRepositoryProtocol.self)
4437
else { fatalError("recommendedRoutineRepository 의존성이 등록되지 않았습니다.") }
4538

4639
return RecommendedRoutineUseCase(recommendedRoutineRepository: recommendedRoutineRepository)
4740
}
41+
42+
guard let emotionRepository = DIContainer.shared.resolve(type: EmotionRepositoryProtocol.self)
43+
else { fatalError("emotionRepository 의존성이 등록되지 않았습니다.") }
44+
45+
DIContainer.shared.register(type: EmotionUseCaseProtocol.self) { _ in
46+
return EmotionUseCase(emotionRepository: emotionRepository)
47+
}
48+
49+
DIContainer.shared.register(type: ResultRecommendedRoutineUseCaseProtocol.self) { container in
50+
guard let onboardingRepository = container.resolve(type: OnboardingRepositoryProtocol.self)
51+
else { fatalError("onboardingRepository 의존성이 등록되지 않았습니다.") }
52+
53+
return ResultRecommendedRoutineUseCase(onboardingRepository: onboardingRepository, emotionRepository: emotionRepository)
54+
}
4855
}
4956
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// EmotionEntity.swift
3+
// Domain
4+
//
5+
// Created by 최정인 on 7/29/25.
6+
//
7+
8+
import Foundation
9+
10+
public struct EmotionEntity {
11+
public let emotionType: String
12+
public let emotionName: String
13+
public let emotionImageUrl: URL?
14+
15+
public init(
16+
emotionType: String,
17+
emotionName: String,
18+
emotionImageUrl: URL?
19+
) {
20+
self.emotionType = emotionType
21+
self.emotionName = emotionName
22+
self.emotionImageUrl = emotionImageUrl
23+
}
24+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// EmotionRepositoryProtocol.swift
3+
// Domain
4+
//
5+
// Created by 최정인 on 7/28/25.
6+
//
7+
8+
/// 감정 구슬에 대한 로직을 처리하는 Repository
9+
public protocol EmotionRepositoryProtocol {
10+
/// 감정 구슬 목록을 불러옵니다.
11+
/// - Returns: 조회된 감정 구슬 목록
12+
func fetchEmotions() async throws -> [EmotionEntity]
13+
14+
/// 감정 구슬을 등록합니다.
15+
/// - Parameter emotion: 감정 구슬 String 값
16+
/// - Returns: 등록한 감정 구슬에 따른 추천 루틴 리스트
17+
func registerEmotion(emotion: String) async throws -> [RecommendedRoutineEntity]
18+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// EmotionUseCaseProtocol.swift
3+
// Domain
4+
//
5+
// Created by 최정인 on 7/28/25.
6+
//
7+
8+
public protocol EmotionUseCaseProtocol {
9+
/// 감정 구슬 목록을 불러옵니다.
10+
/// - Returns: 조회된 감정 구슬 목록
11+
func fetchEmotions() async throws -> [EmotionEntity]
12+
}

Projects/Domain/Sources/Protocol/UseCase/OnboardingUseCaseProtocol.swift

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// ResultRecommendedRoutineUseCaseProtocol.swift
3+
// Domain
4+
//
5+
// Created by 최정인 on 7/29/25.
6+
//
7+
8+
import Foundation
9+
10+
public protocol ResultRecommendedRoutineUseCaseProtocol {
11+
/// 선택한 온보딩 결과를 저장하고, 추천 루틴을 받습니다.
12+
/// - Parameter onboardingChoices: 선택한 온보딩 항목 list
13+
/// - Returns: 온보딩 결과를 바탕으로 받은 추천루틴 목록
14+
func fetchResultRecommendedRoutines(onboardingChoices: [OnboardingChoiceType]) async throws -> [RecommendedRoutineEntity]
15+
16+
/// 감정 구슬을 등록하고 그에 따른 추천 루틴 리스트를 받습니다.
17+
/// - Parameter emotion: 감정 구슬 타입 String
18+
/// - Returns: 등록한 감정 구슬에 따른 추천 루틴 리스트
19+
func fetchResultRecommendedRoutines(emotion: String) async throws -> [RecommendedRoutineEntity]
20+
21+
/// 선택한 추천 루틴을 등록합니다.
22+
/// - Parameter selectedRoutines: 선택한 추천 루틴 ID 목록
23+
func registerRecommendedRoutines(selectedRoutines: [Int]) async throws
24+
}

0 commit comments

Comments
 (0)