-
Notifications
You must be signed in to change notification settings - Fork 0
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
[GWL-70] Record 만들어진 UI ViewModel 구현 #95
Changes from all commits
5f551f7
7bd90a6
90e9983
82819e8
8835f05
93d7071
14ef606
a2f91f0
d964850
7394c94
3c6393c
ad958a1
2c84519
70d7adf
8a9fcbb
79e5a91
fd784f0
34d271e
f99c91e
db3c123
9e67b63
d90484b
c758411
ac92b6d
d9e33f8
6d4c5be
1ee9c01
db6ac0c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"code" : 0, | ||
"errorMessage": null, | ||
"data" : [ | ||
{ | ||
"description" : "혼자 진행하기", | ||
"icon" : "person.fill", | ||
"title" : "혼자 하기", | ||
"typeCode" : 1 | ||
}, | ||
{ | ||
"description" : "랜덤한 사용자와 같이 진행하세요", | ||
"icon" : "person.3.fill", | ||
"title" : "같이 하기", | ||
"typeCode" : 2 | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"code" : 0, | ||
"errorMessage": null, | ||
"data" : [ | ||
{ | ||
"icon": "figure.run", | ||
"description": "이거 가능?", | ||
"typeCode" : 1 | ||
}, | ||
{ | ||
"icon": "figure.pool.swim", | ||
"description": "수영", | ||
"typeCode" : 2 | ||
}, | ||
{ | ||
"icon": "figure.outdoor.cycle", | ||
"description": "이거 가능?", | ||
"typeCode" : 3 | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// | ||
// GWResponse.swift | ||
// RecordFeature | ||
// | ||
// Created by MaraMincho on 11/21/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
struct GWResponse<T>: Decodable where T: Decodable { | ||
let code: Int? | ||
let errorMessage: String? | ||
let data: T? | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// | ||
// PeerTypeDTO.swift | ||
// RecordFeature | ||
// | ||
// Created by MaraMincho on 11/21/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
struct PeerTypeDTO: Decodable { | ||
let icon: String | ||
let title: String | ||
let description: String | ||
let typeCode: Int | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// | ||
// WorkoutTypeDTO.swift | ||
// RecordFeature | ||
// | ||
// Created by MaraMincho on 11/21/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
struct WorkoutTypeDTO: Decodable { | ||
let icon: String | ||
let description: String | ||
let typeCode: Int | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// | ||
// DataLayerError.swift | ||
// RecordFeature | ||
// | ||
// Created by MaraMincho on 11/21/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
enum DataLayerError: LocalizedError { | ||
case noData | ||
case repositoryDidDeinit | ||
case curreptedData | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// | ||
// WorkoutEnvironmentSetupNetworkRepository.swift | ||
// WorkoutEnvironmentSetupNetworkRepository | ||
// | ||
// Created by MaraMincho on 11/21/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Combine | ||
import Foundation | ||
import Trinet | ||
|
||
// MARK: - WorkoutEnvironmentSetupNetworkRepository | ||
|
||
struct WorkoutEnvironmentSetupNetworkRepository: WorkoutEnvironmentSetupNetworkRepositoryRepresentable { | ||
private let decoder = JSONDecoder() | ||
private let provider: TNProvider<WorkoutEnvironmentSetupEndPoint> | ||
|
||
init(session: URLSessionProtocol) { | ||
provider = .init(session: session) | ||
} | ||
|
||
func workoutTypes() -> AnyPublisher<[WorkoutTypeDTO], Error> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p2: 지금은 Mock Data로 테스트해서 정상적으로 받아오지만, 서버에서는 GWResponse형태로 내려받기에 조심할 필요가 있습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아마 예전, MockData를 넣어서 Network에서 받아 올 때는 GWRresponse 포멧의 Json이었는데 현재는 내부 데이터라 [Type]에 대해서 기재했네요. 내부 Json 파일도 GWRresponse로 갖고 있어도 좋을 것 같다는 생각입니다. 따라서 Json도 수정 하겠습니다! |
||
return Future<[WorkoutTypeDTO], Error> { promise in | ||
|
||
guard | ||
let bundle = Bundle(identifier: PersistencyProperty.bundleIdentifier), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p3: App Target에서 실행하게 된다면 bundle이 App쪽을 따라가기 때문에 이렇게 하신건가요? |
||
let path = bundle.path(forResource: PersistencyProperty.workoutTypesFileName, ofType: PersistencyProperty.peerTypesFileNameOfType), | ||
let data = try? Data(contentsOf: URL(filePath: path)) | ||
else { | ||
return promise(.failure(DataLayerError.noData)) | ||
} | ||
|
||
Comment on lines
+28
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p3: 지금 보니 session을 안쓰고 Repository에서 Mock Data를 가져오는군요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 두가지 경우를 생각했습니다.
두가지를 커버하기 위해서, 한 파일에 넣었습니다. 현재는 API가 없어서, 일단 내부 파일로 구현했고 차후 API가 완성되면 통신 이후 Json을 내부 파일로 넣어서 처리할 예정이었습니다. "MockSession에서 할 일" 이란 무엇일까에 대해서 고민해봤는데 질문의 의도를 모르겠습니다. 혹시 보충 설명 해주실 수 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 레포지토리에서는 Session을 이용해서 데이터를 요청하고, 데이터가 어떻게 들어올 것인지는 Session에서 해야하는 역할이라고 생각했습니다. |
||
do { | ||
let workoutTypes = try decoder.decode(GWResponse<[WorkoutTypeDTO]>.self, from: data).data | ||
guard let workoutTypes else { | ||
promise(.failure(DataLayerError.curreptedData)) | ||
return | ||
} | ||
|
||
promise(.success(workoutTypes)) | ||
} catch { | ||
promise(.failure(error)) | ||
} | ||
}.eraseToAnyPublisher() | ||
} | ||
|
||
func peerType() -> AnyPublisher<[PeerTypeDTO], Error> { | ||
return Future<[PeerTypeDTO], Error> { promise in | ||
|
||
guard | ||
let bundle = Bundle(identifier: PersistencyProperty.bundleIdentifier), | ||
let path = bundle.path(forResource: PersistencyProperty.peerTypesFileName, ofType: PersistencyProperty.peerTypesFileNameOfType), | ||
let data = try? Data(contentsOf: URL(filePath: path)) | ||
else { | ||
return promise(.failure(DataLayerError.noData)) | ||
} | ||
|
||
do { | ||
let peerTypes = try decoder.decode(GWResponse<[PeerTypeDTO]>.self, from: data).data | ||
guard let peerTypes else { | ||
promise(.failure(DataLayerError.curreptedData)) | ||
return | ||
} | ||
|
||
promise(.success(peerTypes)) | ||
} catch { | ||
promise(.failure(error)) | ||
} | ||
}.eraseToAnyPublisher() | ||
} | ||
} | ||
|
||
// MARK: WorkoutEnvironmentSetupNetworkRepository.WorkoutEnvironmentSetupEndPoint | ||
|
||
private extension WorkoutEnvironmentSetupNetworkRepository { | ||
enum WorkoutEnvironmentSetupEndPoint: TNEndPoint { | ||
case exerciseTypes | ||
case peer | ||
|
||
// TODO: API에 맞게 수정 예정 | ||
var baseURL: String { | ||
switch self { | ||
case .exerciseTypes: | ||
return "https://www.naver.com" | ||
case .peer: | ||
return "https://www.naver.com" | ||
} | ||
} | ||
|
||
var path: String { | ||
switch self { | ||
case .exerciseTypes: | ||
return "" | ||
case .peer: | ||
return "" | ||
} | ||
} | ||
|
||
var method: TNMethod { return .get } | ||
var query: Encodable? { nil } | ||
var body: Encodable? { nil } | ||
var headers: Trinet.TNHeaders { .init(headers: []) } | ||
} | ||
|
||
enum PersistencyProperty { | ||
static let bundleIdentifier = "kr.codesquad.boostcamp8.RecordFeature" | ||
static let peerTypesFileName = "PeerTypes" | ||
static let workoutTypesFileName = "WorkoutTypes" | ||
static let peerTypesFileNameOfType = "json" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// | ||
// PeerType.swift | ||
// RecordFeature | ||
// | ||
// Created by MaraMincho on 11/21/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
struct PeerType: Hashable { | ||
let iconSystemImage: String | ||
let titleText: String | ||
let descriptionText: String | ||
let typeCode: Int | ||
|
||
init(icon: String, title: String, description: String, typeCode: Int) { | ||
iconSystemImage = icon | ||
titleText = title | ||
descriptionText = description | ||
self.typeCode = typeCode | ||
} | ||
|
||
init(peerTypeDTO dto: PeerTypeDTO) { | ||
iconSystemImage = dto.icon | ||
titleText = dto.title | ||
descriptionText = dto.description | ||
typeCode = dto.typeCode | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// | ||
// WorkoutType.swift | ||
// RecordFeature | ||
// | ||
// Created by MaraMincho on 11/21/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
public struct WorkoutType: Hashable { | ||
let workoutIcon: String | ||
let workoutIconDescription: String | ||
let typeCode: Int | ||
|
||
init(workoutIcon: String, workoutIconDescription: String, typeCode: Int) { | ||
self.workoutIcon = workoutIcon | ||
self.workoutIconDescription = workoutIconDescription | ||
self.typeCode = typeCode | ||
} | ||
|
||
init(workoutTypesDTO dto: WorkoutTypeDTO) { | ||
workoutIcon = dto.icon | ||
workoutIconDescription = dto.description | ||
typeCode = dto.typeCode | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// | ||
// DomainError.swift | ||
// RecordFeature | ||
// | ||
// Created by MaraMincho on 11/21/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
enum DomainError: LocalizedError { | ||
case didDeinitUseCase | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// | ||
// WorkoutEnvironmentSetupNetworkRepositoryRepresentable.swift | ||
// RecordFeature | ||
// | ||
// Created by MaraMincho on 11/22/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Combine | ||
import Foundation | ||
|
||
// MARK: - WorkoutEnvironmentSetupNetworkRepositoryRepresentable | ||
|
||
protocol WorkoutEnvironmentSetupNetworkRepositoryRepresentable { | ||
func workoutTypes() -> AnyPublisher<[WorkoutTypeDTO], Error> | ||
func peerType() -> AnyPublisher<[PeerTypeDTO], Error> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// | ||
// WorkoutEnvironmentSetupUseCase.swift | ||
// RecordFeature | ||
// | ||
// Created by MaraMincho on 11/21/23. | ||
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. | ||
// | ||
|
||
import Combine | ||
import Foundation | ||
|
||
// MARK: - WorkoutEnvironmentSetupUseCaseRepresentable | ||
|
||
protocol WorkoutEnvironmentSetupUseCaseRepresentable { | ||
func workoutTypes() -> AnyPublisher<Result<[WorkoutType], Error>, Never> | ||
func paerTypes() -> AnyPublisher<Result<[PeerType], Error>, Never> | ||
} | ||
|
||
// MARK: - WorkoutEnvironmentSetupUseCase | ||
|
||
final class WorkoutEnvironmentSetupUseCase: WorkoutEnvironmentSetupUseCaseRepresentable { | ||
let repository: WorkoutEnvironmentSetupNetworkRepositoryRepresentable | ||
|
||
init(repository: WorkoutEnvironmentSetupNetworkRepositoryRepresentable) { | ||
self.repository = repository | ||
} | ||
|
||
func paerTypes() -> AnyPublisher<Result<[PeerType], Error>, Never> { | ||
return repository | ||
.peerType() | ||
.map { dto -> Result<[PeerType], Error> in | ||
let peerTypes = dto.map { PeerType(peerTypeDTO: $0) } | ||
return .success(peerTypes) | ||
} | ||
.catch { error in | ||
Just(.failure(error)) | ||
} | ||
.eraseToAnyPublisher() | ||
} | ||
|
||
func workoutTypes() -> AnyPublisher<Result<[WorkoutType], Error>, Never> { | ||
return repository | ||
.workoutTypes() | ||
.map { dto -> Result<[WorkoutType], Error> in | ||
let workoutTypes = dto.map { WorkoutType(workoutTypesDTO: $0) } | ||
return .success(workoutTypes) | ||
} | ||
.catch { error in | ||
Just(.failure(error)) | ||
} | ||
.eraseToAnyPublisher() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3
이것도 DTO이지 않을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일단 이름이 애매해서 다음과 같이 사용했습니다. 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아니면 DTO를 제외하고 Response, Request로 구분지어서 작명하는 것도 좋을 것 같아요.