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

[FEAT/#62] 스티커 API 연결 및 Entity에 맞게 일부 코드 수정 #65

Merged
merged 10 commits into from
Nov 21, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Combine
import Foundation
import PhotoGetherDomainInterface

public final class LocalShapeDataSourceImpl: ShapeDataSource {
public func fetchStickerData() -> AnyPublisher<[StickerDTO], Error> {
return Empty().eraseToAnyPublisher()
}

public init() { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Combine
import Foundation
import PhotoGetherNetwork

public final class RemoteShapeDataSourceImpl: ShapeDataSource {
// TODO: 페이징 적용 필요
public func fetchStickerData() -> AnyPublisher<[StickerDTO], Error> {
return Request.requestJSON(StickerEndPoint())
}

public init() { }
}

private struct StickerEndPoint: EndPoint {
var baseURL: URL { URL(string: "https://api.api-ninjas.com")! }
var path: String { "v1/emoji" }
var method: HTTPMethod { .get }
// 시작 인덱스, 1회 호출당 30개씩 호출
var parameters: [String: Any]? { ["group": "objects", "offset": 0] }
0Hooni marked this conversation as resolved.
Show resolved Hide resolved
var headers: [String: String]? {
let apiKey = Bundle.main.object(forInfoDictionaryKey: "EMOJI_API_KEY") as? String ?? ""
return ["X-Api-Key": apiKey]
0Hooni marked this conversation as resolved.
Show resolved Hide resolved
}
var body: Encodable? { nil }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Combine
import Foundation

public protocol ShapeDataSource {
func fetchStickerData() -> AnyPublisher<[StickerDTO], Error>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Combine
import Foundation
import PhotoGetherDomainInterface

final public class ShapeRepositoryImpl: ShapeRepository {
// TODO: local 먼저 확인 -> remote 데이터 확인하도록 수정
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분 다른 팀원분들 의견도 궁금합니다!
local에는 캐시가 있고 먼저 확인할 예정입니다.

// MARK: JSON 데이터 내부의 URL을 어느 시점에 다운로드 할것인가...?
public func fetchStickerList() -> AnyPublisher<[StickerEntity], Never> {
return remoteDataSource.fetchStickerData()
.map { $0.map { $0.toEntity() } }
.replaceError(with: [])
.eraseToAnyPublisher()
}

private let localDataSource: ShapeDataSource
private let remoteDataSource: ShapeDataSource

public init(
localDataSource: ShapeDataSource,
remoteDataSource: ShapeDataSource
) {
self.localDataSource = localDataSource
self.remoteDataSource = remoteDataSource
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation
import PhotoGetherDomainInterface

public struct StickerDTO: Decodable {
let code: String
let character: String
let image: String
let name: String
let group: String
let subgroup: String
}

extension StickerDTO {
func toEntity() -> StickerEntity {
return .init(
image: self.image,
name: self.name
)
}
}

This file was deleted.

0Hooni marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@ final class FetchStickerListUseCaseTests: XCTestCase {

sut = FetchStickerListUseCaseImpl(shapeRepository: shapeRepositoryMock)

var targetDataList: [Data] = []
let beforeDataListCount = 0
var targetEntityList: [StickerEntity] = []
let beforeEntityListCount = 0

//Act 실행 단계: SUT 메소드를 호출하면서 의존성을 전달해서 결과를 저장하기
let cancellable = sut.execute()
.sink { datas in
targetDataList.append(contentsOf: datas)
.sink { stickerEntities in
targetEntityList.append(contentsOf: stickerEntities)
expectation.fulfill()
}

//Assert 검증 단계: 결과와 기대치를 비교해서 검증하기
wait(for: [expectation], timeout: 2.0)
XCTAssertEqual(beforeDataListCount, 0)
XCTAssertEqual(targetDataList.count, 12)
XCTAssertEqual(beforeEntityListCount, 0)
XCTAssertEqual(targetEntityList.count, 12)

cancellable.cancel()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Foundation
import PhotoGetherDomainInterface

public final class FetchStickerListUseCaseImpl: FetchStickerListUseCase {
public func execute() -> AnyPublisher<[Data], Never> {
public func execute() -> AnyPublisher<[StickerEntity], Never> {
return shapeRepository.fetchStickerList()
}

Expand Down
0Hooni marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

public struct StickerEntity: Decodable {
public let image: String
public let name: String

public init(image: String, name: String) {
self.image = image
self.name = name
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import Combine
import Foundation

public protocol ShapeRepository {
func fetchStickerList() -> AnyPublisher<[Data], Never>
func fetchStickerList() -> AnyPublisher<[StickerEntity], Never>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import Combine
import Foundation

public protocol FetchStickerListUseCase {
func execute() -> AnyPublisher<[Data], Never>
func execute() -> AnyPublisher<[StickerEntity], Never>
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,22 @@ public final class ShapeRepositoryMock: ShapeRepository {
self.imageNameList = imageNameList
}

public func fetchStickerList() -> AnyPublisher<[Data], Never> {
let imageDataList = imageNameList.map { imageData(named: $0) }
public func fetchStickerList() -> AnyPublisher<[StickerEntity], Never> {
let stickerEntities: [StickerEntity] = imageNameList.map {
.init(
image: imageData(named: $0), // 이미지 주소(or 경로)
name: $0
)
}

return Just(imageDataList.compactMap { $0 })
.eraseToAnyPublisher()
return Just(stickerEntities).eraseToAnyPublisher()
}

private func imageData(named: String) -> Data? {
private func imageData(named: String) -> String {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data 리턴에서 path 리턴으로 바뀌었군요! 그럼 메서드 이름도 imagePath로 바꾸는건 어떨까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

포토게더

해당 커밋에서 수정하였습니다!

let bundle = Bundle(for: Self.self) // 해당 클래스가 존재하는 Bundle을 의미
guard let imageURL = bundle.url(forResource: named, withExtension: "png"),
let imageData = try? Data(contentsOf: imageURL)
else { debugPrint("bundle Image to Data error"); return nil }
guard let path = bundle.url(forResource: named, withExtension: "png")?.absoluteString
else { return "" }

return imageData
return path
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
050F6E122CEE19A2002F5473 /* PhotoGetherData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 050F6E112CEE19A2002F5473 /* PhotoGetherData.framework */; };
050F6E132CEE19A2002F5473 /* PhotoGetherData.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 050F6E112CEE19A2002F5473 /* PhotoGetherData.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
053DC8532CE32AE900DC9F35 /* DesignSystem.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 053DC8522CE32AE900DC9F35 /* DesignSystem.framework */; };
053DC8542CE32AE900DC9F35 /* DesignSystem.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 053DC8522CE32AE900DC9F35 /* DesignSystem.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
055AF9732CEC6E7A0060E408 /* PhotoGetherDomain.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 055AF9722CEC6E7A0060E408 /* PhotoGetherDomain.framework */; };
Expand Down Expand Up @@ -65,6 +67,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
050F6E132CEE19A2002F5473 /* PhotoGetherData.framework in Embed Frameworks */,
7B59510D2CDB598600B89C85 /* FeatureTesting.framework in Embed Frameworks */,
055AF97A2CEC6E840060E408 /* PhotoGetherDomainTesting.framework in Embed Frameworks */,
60CB82A92CDB522900873DD6 /* EditPhotoRoomFeature.framework in Embed Frameworks */,
Expand All @@ -90,6 +93,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
050F6E112CEE19A2002F5473 /* PhotoGetherData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PhotoGetherData.framework; sourceTree = BUILT_PRODUCTS_DIR; };
053DC8522CE32AE900DC9F35 /* DesignSystem.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DesignSystem.framework; sourceTree = BUILT_PRODUCTS_DIR; };
055AF9722CEC6E7A0060E408 /* PhotoGetherDomain.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PhotoGetherDomain.framework; sourceTree = BUILT_PRODUCTS_DIR; };
055AF9752CEC6E7F0060E408 /* PhotoGetherDomainInterface.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PhotoGetherDomainInterface.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -162,6 +166,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
050F6E122CEE19A2002F5473 /* PhotoGetherData.framework in Frameworks */,
7B59510C2CDB598600B89C85 /* FeatureTesting.framework in Frameworks */,
055AF9792CEC6E840060E408 /* PhotoGetherDomainTesting.framework in Frameworks */,
60CB82A82CDB522900873DD6 /* EditPhotoRoomFeature.framework in Frameworks */,
Expand Down Expand Up @@ -197,6 +202,7 @@
60CB82A72CDB522900873DD6 /* Frameworks */ = {
isa = PBXGroup;
children = (
050F6E112CEE19A2002F5473 /* PhotoGetherData.framework */,
055AF9A62CEC739C0060E408 /* FeatureTesting.framework */,
055AF99E2CEC6F4A0060E408 /* PhotoGetherDomainInterface.framework */,
055AF98D2CEC6F0E0060E408 /* PhotoGetherDomainInterface.framework */,
Expand Down Expand Up @@ -522,6 +528,8 @@
};
60CB826A2CDB4FC500873DD6 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReferenceAnchor = 7B1746D22CE8E9D400E01D1A /* EditPhotoRoomFeatureDemo */;
baseConfigurationReferenceRelativePath = Resource/Secrets.xcconfig;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import UIKit
import Combine
import BaseFeature
import PhotoGetherDomainInterface

public class EditPhotoRoomHostViewController: BaseViewController, ViewControllerConfigure {
private let navigationView = UIView()
Expand Down Expand Up @@ -77,10 +78,12 @@ public class EditPhotoRoomHostViewController: BaseViewController, ViewController
public func bindOutput() {
let output = viewModel.transform(input: input.eraseToAnyPublisher())

output.sink { [weak self] in
output
.receive(on: RunLoop.main)
.sink { [weak self] in
switch $0 {
case .sticker(let data):
self?.renderSticker(data: data)
case .sticker(let entity):
self?.renderSticker(entity: entity)
}
}
.store(in: &cancellables)
Expand All @@ -94,15 +97,22 @@ public class EditPhotoRoomHostViewController: BaseViewController, ViewController
canvasScrollView.backgroundColor = .red
}

private func renderSticker(data: Data) {
let imageSize: CGFloat = 60
private func renderSticker(entity: StickerEntity) {
let imageSize: CGFloat = 64
let rect = calculateCenterPosition(imageSize: imageSize)
let stickerImageView = UIImageView(frame: rect)
let stickerImage = UIImage(data: data)

stickerImageView.image = stickerImage
guard let url = URL(string: entity.image) else { return }

canvasScrollView.imageView.addSubview(stickerImageView)
Task {
guard let (data, response) = try? await URLSession.shared.data(from: url)
else { return }

let stickerImage = UIImage(data: data)

stickerImageView.image = stickerImage

canvasScrollView.imageView.addSubview(stickerImageView)
}
}

private func calculateCenterPosition(imageSize: CGFloat) -> CGRect {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ public final class EditPhotoRoomHostViewModel {
}

enum Output {
case sticker(data: Data)
case sticker(entity: StickerEntity)
}

private let fetchStickerListUseCase: FetchStickerListUseCase
private var stickerList: [Data] = []
private var stickerList: [StickerEntity] = []

private var cancellables = Set<AnyCancellable>()
private var output = PassthroughSubject<Output, Never>()
Expand Down Expand Up @@ -42,13 +42,13 @@ public final class EditPhotoRoomHostViewModel {

private func fetchStickerList() {
fetchStickerListUseCase.execute()
.sink { [weak self] datas in
self?.stickerList = datas
.sink { [weak self] stickerEntities in
self?.stickerList = stickerEntities
}
.store(in: &cancellables)
}

private func addStickerToCanvas() {
output.send(.sticker(data: stickerList.randomElement()!))
output.send(.sticker(entity: stickerList.randomElement()!))
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import UIKit
import EditPhotoRoomFeature
import PhotoGetherData
import PhotoGetherDomainInterface
import PhotoGetherDomain
import PhotoGetherDomainTesting
Expand Down Expand Up @@ -28,8 +29,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
"sunglasses",
"tree",
]
let shapeRepositoryMock = ShapeRepositoryMock(imageNameList: imageNameList)
let fetchStickerListUseCase = FetchStickerListUseCaseImpl(shapeRepository: shapeRepositoryMock)

// TODO: 추후 Data 의존성 제거 및 Mock으로 전환
// let shapeRepositoryMock = ShapeRepositoryMock(imageNameList: imageNameList)
let localDataSource = LocalShapeDataSourceImpl()
let remoteDataSource = RemoteShapeDataSourceImpl()
let shapeRepositoryImpl = ShapeRepositoryImpl(localDataSource: localDataSource, remoteDataSource: remoteDataSource)
let fetchStickerListUseCase = FetchStickerListUseCaseImpl(shapeRepository: shapeRepositoryImpl)
let editPhotoRoomHostViewModel = EditPhotoRoomHostViewModel(fetchStickerListUseCase: fetchStickerListUseCase)
let editPhotoRoomHostViewController = EditPhotoRoomHostViewController(viewModel: editPhotoRoomHostViewModel)
window?.rootViewController = editPhotoRoomHostViewController
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@
</array>
</dict>
</dict>
<key>EMOJI_API_KEY</key>
<string>$(EMOJI_API_KEY)</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public final class FetchStickerListUseCaseMock: FetchStickerListUseCase {

public init() { }

public func execute() -> AnyPublisher<[Data], Never> {
public func execute() -> AnyPublisher<[StickerEntity], Never> {
return repository.fetchStickerList()
}
}
Loading