-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[GWL-15] 운동 세션 Flow와 운동 요약 화면 이동 처리 (#130)
* chore: ViewController를 프로퍼티로 갖도록 수정 * feat: LocationTrackingProtocol 구현 RouteMapViewController가 채택하고, Container가 protocol을 바라보게 함으로써 위치 정보를 제공받는 Publisher만 접근하도록 구현했습니다. * feat: location publisher를 container view controller와 연결 * add: WorkoutHealth 추가 - 운동 종료후 서버에게 요청보낼 건강 데이터입니다. - 아직 서버와 협의되지 않은 임시 모델입니다. * feat: Implement `HealthDataProtocol` in WorkoutSession Flow WorkoutSessionViewController에서 HealthKit을 이용해 데이터를 파싱할 예정입니다. healthData가 업데이트되면, publisher를 갖고있는 ContainerViewModel에게 이벤트가 전달되도록 구현했습니다. * chore: TODO 작성 * feat: WorkoutRecordRepository 구현 * feat: WorkoutRecordUseCase 추가 * feat: Workout Session Container Flow 구현 * chore: swiftformat 적용 * feat: WorkoutSessionCoordinating 구현 - WorkoutSessionContainer를 보여주기 위한 Coordinator * feat: coordinator로 요약화면 이동 * feat: Mock JSON 연결 * fix: UI - Main thread 오류 수정 * fix: Repository decode response model 수정 GWResponse를 묶어 decode하도록 수정했습니다. * chore: 버튼이 눌릴 때 이벤트가 실행되도록 변경 * feat: 종료 버튼 탭 시 요약화면으로 이동 * fix: locationManager 설정을 lazy var에서 let으로 수정 * chore: NavigationBar 숨김 처리 * add: deinit 코드와 preview 미비된 코드 추가
- Loading branch information
Showing
12 changed files
with
383 additions
and
27 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
iOS/Projects/Features/Record/Resources/MockJSON/WorkoutSession.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"code": null, | ||
"errorMessage": null, | ||
"data": { | ||
"recordId": 1 | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
iOS/Projects/Features/Record/Sources/Data/Repositories/WorkoutRecordRepository.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// | ||
// WorkoutRecordRepository.swift | ||
// RecordFeature | ||
// | ||
// Created by 홍승현 on 11/25/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Combine | ||
import Foundation | ||
import Trinet | ||
|
||
// MARK: - WorkoutRecordRepository | ||
|
||
public struct WorkoutRecordRepository: WorkoutRecordRepositoryRepresentable { | ||
private let provider: TNProvider<WorkoutRecordEndPoint> | ||
private let jsonDecoder: JSONDecoder = .init() | ||
|
||
init(session: URLSessionProtocol) { | ||
provider = .init(session: session) | ||
} | ||
|
||
/// 운동을 기록합니다. | ||
/// - Parameter locationData: 사용자의 위치 정보 | ||
/// - Parameter healthData: 사용자의 건강 정보 | ||
/// - Returns: 기록 고유 Identifier | ||
func record(usingLocation locationData: [LocationDTO], andHealthData healthData: WorkoutHealth) -> AnyPublisher<Int, Error> { | ||
return Deferred { | ||
Future<Data, Error> { promise in | ||
Task { | ||
do { | ||
let data = try await provider.request(.init(locationList: locationData, health: healthData)) | ||
promise(.success(data)) | ||
} catch { | ||
promise(.failure(error)) | ||
} | ||
} | ||
} | ||
} | ||
.decode(type: GWResponse<[String: Int]>.self, decoder: jsonDecoder) | ||
.tryMap { | ||
guard let dictionary = $0.data, | ||
let recordID = dictionary["recordId"] | ||
else { | ||
throw DataLayerError.noData | ||
} | ||
return recordID | ||
} | ||
.eraseToAnyPublisher() | ||
} | ||
} | ||
|
||
// MARK: WorkoutRecordRepository.WorkoutRecordEndPoint | ||
|
||
extension WorkoutRecordRepository { | ||
// TODO: 서버 값으로 세팅 | ||
struct WorkoutRecordEndPoint: TNEndPoint { | ||
var path: String = "api/v1/records" | ||
|
||
var method: TNMethod = .post | ||
|
||
var query: Encodable? = nil | ||
|
||
var body: Encodable? = nil | ||
|
||
var headers: TNHeaders = .init(headers: []) | ||
|
||
init(locationList _: [LocationDTO], health _: WorkoutHealth) { | ||
// TODO: 요청 모델 설정 필요 | ||
} | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
iOS/Projects/Features/Record/Sources/Domain/Entities/WorkoutHealth.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// | ||
// WorkoutHealth.swift | ||
// RecordFeature | ||
// | ||
// Created by 홍승현 on 11/25/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
// TODO: 서버 API와 맞추어야합니다. | ||
|
||
/// 건강 데이터를 body로 전달하기 위한 요청(request) 모델입니다. 운동 세션이 종료될 때 이 모델을 사용합니다. | ||
public struct WorkoutHealth: Encodable { | ||
/// 총 운동한 거리 | ||
let distance: Int? | ||
|
||
/// 소모한 칼로리 | ||
let calorie: Int? | ||
|
||
/// 평균 심박수 | ||
let averageHeartRate: Int? | ||
|
||
/// 운동 중에 기록한 최소 심박수 | ||
let minimumHeartRate: Int? | ||
|
||
/// 운동 중에 기록한 최대 심박수 | ||
let maximumHeartRate: Int? | ||
} |
14 changes: 14 additions & 0 deletions
14
.../Record/Sources/Domain/Interfaces/Repositories/WorkoutRecordRepositoryRepresentable.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// | ||
// WorkoutRecordRepositoryRepresentable.swift | ||
// RecordFeature | ||
// | ||
// Created by 홍승현 on 11/25/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Combine | ||
import Foundation | ||
|
||
protocol WorkoutRecordRepositoryRepresentable { | ||
func record(usingLocation locationData: [LocationDTO], andHealthData healthData: WorkoutHealth) -> AnyPublisher<Int, Error> | ||
} |
34 changes: 34 additions & 0 deletions
34
iOS/Projects/Features/Record/Sources/Domain/UseCases/WorkoutRecordUseCase.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// | ||
// WorkoutRecordUseCase.swift | ||
// RecordFeature | ||
// | ||
// Created by 홍승현 on 11/25/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Combine | ||
import Foundation | ||
|
||
// MARK: - WorkoutRecordUseCaseRepresentable | ||
|
||
protocol WorkoutRecordUseCaseRepresentable { | ||
func record(locations: [LocationDTO], healthData: WorkoutHealth) -> AnyPublisher<Int, Error> | ||
} | ||
|
||
// MARK: - WorkoutRecordUseCase | ||
|
||
struct WorkoutRecordUseCase { | ||
private let repository: WorkoutRecordRepositoryRepresentable | ||
|
||
init(repository: WorkoutRecordRepositoryRepresentable) { | ||
self.repository = repository | ||
} | ||
} | ||
|
||
// MARK: WorkoutRecordUseCaseRepresentable | ||
|
||
extension WorkoutRecordUseCase: WorkoutRecordUseCaseRepresentable { | ||
func record(locations: [LocationDTO], healthData: WorkoutHealth) -> AnyPublisher<Int, Error> { | ||
repository.record(usingLocation: locations, andHealthData: healthData) | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
.../Record/Sources/Presentation/Common/Coordinator/Protocol/WorkoutSessionCoordinating.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// | ||
// WorkoutSessionCoordinating.swift | ||
// RecordFeature | ||
// | ||
// Created by 홍승현 on 11/26/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Coordinator | ||
import Foundation | ||
|
||
protocol WorkoutSessionCoordinating: Coordinating { | ||
/// 운동 요약 화면으로 이동합니다. | ||
/// - Parameter recordID: 요약 화면을 보여주기 위한 기록 Identifier | ||
func pushWorkoutSummaryViewController(recordID: Int) | ||
} |
60 changes: 60 additions & 0 deletions
60
...s/Features/Record/Sources/Presentation/Common/Coordinator/WorkoutSessionCoordinator.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// | ||
// WorkoutSessionCoordinator.swift | ||
// RecordFeature | ||
// | ||
// Created by 홍승현 on 11/26/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Coordinator | ||
import Log | ||
import Trinet | ||
import UIKit | ||
|
||
// MARK: - WorkoutSessionCoordinator | ||
|
||
final class WorkoutSessionCoordinator: WorkoutSessionCoordinating { | ||
var navigationController: UINavigationController | ||
var childCoordinators: [Coordinating] = [] | ||
weak var finishDelegate: CoordinatorFinishDelegate? | ||
var flow: CoordinatorFlow = .workout | ||
private let isMockEnvironment: Bool | ||
|
||
init(navigationController: UINavigationController, isMockEnvironment: Bool) { | ||
self.navigationController = navigationController | ||
self.isMockEnvironment = isMockEnvironment | ||
} | ||
|
||
func start() { | ||
// TODO: Mock Data 연결 필요 | ||
guard let jsonPath = Bundle(for: Self.self).path(forResource: "WorkoutSession", ofType: "json"), | ||
let jsonData = try? Data(contentsOf: .init(filePath: jsonPath)) | ||
else { | ||
Log.make().error("WorkoutSession Mock Data를 생성할 수 없습니다.") | ||
return | ||
} | ||
|
||
let session: URLSessionProtocol = isMockEnvironment ? MockURLSession(mockData: jsonData) : URLSession.shared | ||
let repository = WorkoutRecordRepository(session: session) | ||
let useCase = WorkoutRecordUseCase(repository: repository) | ||
let viewModel = WorkoutSessionContainerViewModel(workoutRecordUseCase: useCase, coordinating: self) | ||
let viewController = WorkoutSessionContainerViewController(viewModel: viewModel) | ||
navigationController.pushViewController(viewController, animated: true) | ||
} | ||
|
||
func pushWorkoutSummaryViewController(recordID: Int) { | ||
guard let jsonPath = Bundle(for: Self.self).path(forResource: "WorkoutSummary", ofType: "json"), | ||
let jsonData = try? Data(contentsOf: .init(filePath: jsonPath)) | ||
else { | ||
Log.make().error("WorkoutSummary Mock Data를 생성할 수 없습니다.") | ||
return | ||
} | ||
|
||
let session: URLSessionProtocol = isMockEnvironment ? MockURLSession(mockData: jsonData, mockResponse: .init()) : URLSession.shared | ||
let repository = WorkoutSummaryRepository(session: session) | ||
let useCase = WorkoutSummaryUseCase(repository: repository, workoutRecordID: recordID) | ||
let viewModel = WorkoutSummaryViewModel(workoutSummaryUseCase: useCase) | ||
let workoutSummaryViewController = WorkoutSummaryViewController(viewModel: viewModel) | ||
navigationController.setViewControllers([workoutSummaryViewController], animated: true) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.