diff --git a/14th-team5-iOS/App/Sources/Application/Navigator/CameraDisplayNavigator.swift b/14th-team5-iOS/App/Sources/Application/Navigator/CameraDisplayNavigator.swift index 292603cbc..32cebdeea 100644 --- a/14th-team5-iOS/App/Sources/Application/Navigator/CameraDisplayNavigator.swift +++ b/14th-team5-iOS/App/Sources/Application/Navigator/CameraDisplayNavigator.swift @@ -11,6 +11,8 @@ import UIKit protocol CameraDisplayNavigatorProtocol: BaseNavigator { func toHome() + func toCamera() + func showErrorAlert() } final class CameraDisplayNavigator: CameraDisplayNavigatorProtocol { @@ -24,9 +26,31 @@ final class CameraDisplayNavigator: CameraDisplayNavigatorProtocol { self.navigationController = navigationController } + func showErrorAlert() { + let confirmHandler: BBAlertActionHandler = { [weak self] alert in + self?.toCamera() + alert?.close() + } + let cancelHandler: BBAlertActionHandler = { [weak self] alert in + self?.toHome() + alert?.close() + } + + BBAlert.style( + .uploadFailed, + primaryAction: confirmHandler, + secondaryAction: cancelHandler + ).show() + } + + func toCamera() { + navigationController.popViewController(animated: true) + } + + //MARK: - Configure func toHome() { let vc = MainViewControllerWrapper().viewController - navigationController.setViewControllers([vc], animated: true) + navigationController.setViewControllers([vc], animated: false) } } diff --git a/14th-team5-iOS/App/Sources/Application/Navigator/CameraNavigator.swift b/14th-team5-iOS/App/Sources/Application/Navigator/CameraNavigator.swift index f79bb785c..da34040b7 100644 --- a/14th-team5-iOS/App/Sources/Application/Navigator/CameraNavigator.swift +++ b/14th-team5-iOS/App/Sources/Application/Navigator/CameraNavigator.swift @@ -9,20 +9,49 @@ import UIKit import DesignSystem import Core +import Domain protocol CameraNavigatorProtocol: BaseNavigator { + func showErrorAlert(_ type: UploadLocation) func showErrorToast(_ description: String) + func toCamera(_ type: UploadLocation) + func toHome() } -final class CameraNavigator: CameraNavigatorProtocol { +final class CameraNavigator: CameraNavigatorProtocol { var navigationController: UINavigationController init(navigationController: UINavigationController) { self.navigationController = navigationController } + + func showErrorAlert(_ type: UploadLocation) { + let confirmHandler: BBAlertActionHandler = makeConfirmHandler(for: type) + let cancelHandler: BBAlertActionHandler = { [weak self] alert in + self?.toHome() + alert?.close() + } + + BBAlert.style( + .uploadFailed, + primaryAction: confirmHandler, + secondaryAction: cancelHandler + ).show() + } + + func toCamera(_ type: UploadLocation) { + let vc = CameraViewControllerWrapper(cameraType: type).viewController + navigationController.pushViewController(vc, animated: true) + } + + func toHome() { + let vc = MainViewControllerWrapper().viewController + navigationController.setViewControllers([vc], animated: false) + } + func showErrorToast(_ descrption: String) { let config = BBToastConfiguration(direction: .top(yOffset: 75)) let viewConfig = BBToastViewConfiguration(minWidth: 100) @@ -36,3 +65,21 @@ final class CameraNavigator: CameraNavigatorProtocol { } +private extension CameraNavigator { + func makeConfirmHandler(for type: UploadLocation) -> BBAlertActionHandler { + switch type { + case .survival, .mission: + return { [weak self] alert in + self?.toCamera(type) + alert?.close() + } + case .profile, .realEmoji: + return { alert in + alert?.close() + } + default: + return nil + } + } +} + diff --git a/14th-team5-iOS/App/Sources/Presentation/Camera/Reactor/CameraDisplayViewReactor.swift b/14th-team5-iOS/App/Sources/Presentation/Camera/Reactor/CameraDisplayViewReactor.swift index 4a701a582..e67e4d733 100644 --- a/14th-team5-iOS/App/Sources/Presentation/Camera/Reactor/CameraDisplayViewReactor.swift +++ b/14th-team5-iOS/App/Sources/Presentation/Camera/Reactor/CameraDisplayViewReactor.swift @@ -90,8 +90,9 @@ public final class CameraDisplayViewReactor: Reactor { .just(.setError(false)), .just(.setLoading(true)) ) - }.catch { _ in - return .just(.setError(true)) + }.catchError(with: self) { owner, _ in + owner.cameraDisplayNavigator.showErrorAlert() + return .empty() } case let .fetchDisplayImage(description): @@ -149,6 +150,9 @@ public final class CameraDisplayViewReactor: Reactor { .flatMap { _ in Observable.empty() } ) } + }.catchError(with: self) { owner, _ in + owner.cameraDisplayNavigator.showErrorAlert() + return .empty() } case .hideDisplayEditCell: return .concat( diff --git a/14th-team5-iOS/App/Sources/Presentation/Camera/Reactor/CameraViewReactor.swift b/14th-team5-iOS/App/Sources/Presentation/Camera/Reactor/CameraViewReactor.swift index 585da4ded..0bb9812bf 100644 --- a/14th-team5-iOS/App/Sources/Presentation/Camera/Reactor/CameraViewReactor.swift +++ b/14th-team5-iOS/App/Sources/Presentation/Camera/Reactor/CameraViewReactor.swift @@ -54,13 +54,11 @@ public final class CameraViewReactor: Reactor { case setProfilePresignedResponse(CreateMemberPresignedEntity?) case setProfileMemberResponse(MembersProfileEntity?) case setRealEmojiImageURLResponse(CameraRealEmojiPreSignedEntity?) - case setRealEmojiImageCreateResponse(CameraCreateRealEmojiEntity?) case setRealEmojiItems([CameraRealEmojiImageItemEntity?]) case setRealEmojiSection([EmojiSectionItem]) case setErrorAlert(Bool) case setRealEmojiType(Emojis) case setImageData(Data) - case setUpdateEmojiImage(URL) } public struct State { @@ -70,13 +68,11 @@ public final class CameraViewReactor: Reactor { @Pulse var missionEntity: MissonTodayContentEntity? @Pulse var memberPresignedEntity: CreateMemberPresignedEntity? @Pulse var realEmojiURLEntity: CameraRealEmojiPreSignedEntity? - @Pulse var realEmojiCreateEntity: CameraCreateRealEmojiEntity? @Pulse var realEmojiEntity: [CameraRealEmojiImageItemEntity?] @Pulse var realEmojiSection: [EmojiSectionModel] @Pulse var zoomScale: CGFloat @Pulse var pinchZoomScale: CGFloat @Pulse var imageData: Data? - var updateEmojiImage: URL? var emojiType: Emojis = .emoji(forIndex: 1) @Pulse var cameraType: UploadLocation = .survival var accountImage: Data? @@ -154,8 +150,6 @@ public final class CameraViewReactor: Reactor { newState.profileMemberEntity = entity case let .setRealEmojiImageURLResponse(entity): newState.realEmojiURLEntity = entity - case let .setRealEmojiImageCreateResponse(entity): - newState.realEmojiCreateEntity = entity case let .setRealEmojiItems(items): newState.realEmojiEntity = items case let .setRealEmojiSection(section): @@ -165,8 +159,6 @@ public final class CameraViewReactor: Reactor { newState.isError = isError case let .setRealEmojiType(emojiType): newState.emojiType = emojiType - case let .setUpdateEmojiImage(realEmoji): - newState.updateEmojiImage = realEmoji case let .setZoomScale(zoomScale): newState.zoomScale = zoomScale case let .setPinchZoomScale(pinchZoomScale): @@ -304,12 +296,14 @@ extension CameraViewReactor { .just(.setProfileMemberResponse(entity)), .just(.setLoading(true)) ) - }.catch { [weak self] error in - self?.cameraNavigator.showErrorToast(error.localizedDescription) + }.catchError(with: self) { owner, _ in + let type = owner.currentState.cameraType + owner.cameraNavigator.showErrorAlert(type) return .empty() } - }.catch { [weak self] error in - self?.cameraNavigator.showErrorToast(error.localizedDescription) + }.catchError(with: self) { owner, _ in + let type = owner.currentState.cameraType + owner.cameraNavigator.showErrorAlert(type) return .empty() } case .realEmoji: @@ -320,84 +314,52 @@ extension CameraViewReactor { let realEmojiImage = "\(imageData.hashValue).jpg" let body = CreatePresignedURLRequest(imageName: realEmojiImage) if currentState.realEmojiEntity[currentState.emojiType.rawValue - 1] == nil { - return .concat( - .just(.setLoading(false)), - fetchRealEmojiPreSignedUseCase.execute(memberId: memberId, body: body) - .withUnretained(self) - .subscribe(on: ConcurrentDispatchQueueScheduler.init(qos: .background)) - .flatMap { owner, entity -> Observable in - guard let remoteURL = entity?.imageURL else { return .just(.setErrorAlert(true))} - - return owner.uploadImageUseCase.execute(remoteURL, image: imageData) - .subscribe(on: ConcurrentDispatchQueueScheduler.init(qos: .background)) - .flatMap { isSuccess -> Observable in - let originalURL = owner.configureProfileOriginalS3URL(url: remoteURL, with: .realEmoji) - let body = CreateEmojiImageRequest(type: owner.currentState.emojiType.emojiString, imageUrl: originalURL) - if isSuccess { - return owner.fetchRealEmojiCreateUseCase.execute(memberId: memberId, body: body) - .subscribe(on: ConcurrentDispatchQueueScheduler.init(qos: .background)) - .flatMap { realEmojiEntity -> Observable in - guard let createRealEmojiEntity = realEmojiEntity else { return .just(.setErrorAlert(true))} - owner.provider.realEmojiGlobalState.createRealEmojiImage(indexPath: owner.currentState.emojiType.rawValue - 1, image: createRealEmojiEntity.realEmojiImageURL, emojiType: createRealEmojiEntity.realEmojiType) - return owner.fetchRealEmojiListUseCase.execute(memberId: memberId) - .asObservable() - .flatMap { reloadEntity -> Observable in - return .concat( - .just(.setRealEmojiImageURLResponse(entity)), - .just(.setRealEmojiItems(reloadEntity)), - .just(.setRealEmojiImageCreateResponse(realEmojiEntity)), - .just(.setErrorAlert(false)), - .just(.setLoading(true)) - ) - } - - } - } else { - return .just(.setErrorAlert(true)) - } - + return fetchRealEmojiPreSignedUseCase.execute(memberId: memberId, body: body, imageData: imageData) + .withUnretained(self) + .flatMap { owner, remoteURL -> Observable in + let originalURL = owner.configureProfileOriginalS3URL(url: remoteURL.imageURL, with: .realEmoji) + let body = CreateEmojiImageRequest(type: owner.currentState.emojiType.emojiString, imageUrl: originalURL) + return owner.fetchRealEmojiCreateUseCase.execute(memberId: memberId, body: body) + .flatMap { entity -> Observable in + guard let entity else { + return .empty() } - - } - - ) + owner.provider.realEmojiGlobalState.createRealEmojiImage(indexPath: owner.currentState.emojiType.rawValue - 1, image: entity.realEmojiImageURL, emojiType: entity.realEmojiType) + return .empty() + } + }.catchError(with: self) { owner, _ in + let type = owner.currentState.cameraType + owner.cameraNavigator.showErrorAlert(type) + return .empty() + } } else { let realEmojiImage = "\(imageData.hashValue).jpg" let body = CreatePresignedURLRequest(imageName: realEmojiImage) - return .concat( - .just(.setLoading(false)), - fetchRealEmojiPreSignedUseCase.execute(memberId: memberId, body: body) - .withUnretained(self) - .subscribe(on: ConcurrentDispatchQueueScheduler.init(qos: .background)) - .flatMap { owner, entity -> Observable in - guard let remoteURL = entity?.imageURL else { return .just(.setErrorAlert(true))} - let originalURL = owner.configureProfileOriginalS3URL(url: remoteURL, with: .realEmoji) - - return owner.uploadImageUseCase.execute(remoteURL, image: imageData) - .subscribe(on: ConcurrentDispatchQueueScheduler.init(qos: .background)) - .asObservable() - .flatMap { isSuccess -> Observable in - if isSuccess { - let body = UpdateRealEmojiImageRequest(imageUrl: originalURL) - return owner.fetchRealEmojiUpdateUseCase.execute(memberId: memberId, realEmojiId: owner.currentState.realEmojiEntity[owner.currentState.emojiType.rawValue - 1]?.realEmojiId ?? "", body: body) - .flatMap { updateRealEmojiEntity -> Observable in - guard let updateEntity = updateRealEmojiEntity else { return .just(.setErrorAlert(true))} - owner.provider.realEmojiGlobalState.updateRealEmojiImage(indexPath: owner.currentState.emojiType.rawValue - 1, image: updateEntity.realEmojiImageURL) - return .concat( - .just(.setUpdateEmojiImage(updateEntity.realEmojiImageURL)), - .just(.setLoading(true)), - .just(.setErrorAlert(false)) - ) - } - } else { - return .empty() - } - } - - } - - ) + return fetchRealEmojiPreSignedUseCase.execute(memberId: memberId, body: body, imageData: imageData) + .withUnretained(self) + .flatMap { owner, remoteURL -> Observable in + let originalURL = owner.configureProfileOriginalS3URL(url: remoteURL.imageURL, with: .realEmoji) + let body = UpdateRealEmojiImageRequest(imageUrl: originalURL) + let realEmojiId = owner.currentState.realEmojiEntity[owner.currentState.emojiType.rawValue - 1]?.realEmojiId ?? "" + return owner.fetchRealEmojiUpdateUseCase.execute(memberId: memberId, realEmojiId: realEmojiId, body: body) + .flatMap { entity -> Observable in + guard let entity else { + return .empty() + } + owner.provider.realEmojiGlobalState.updateRealEmojiImage(indexPath: owner.currentState.emojiType.rawValue - 1, image: entity.realEmojiImageURL) + + return .empty() + }.catchError(with: self) { owner, _ in + let type = owner.currentState.cameraType + owner.cameraNavigator.showErrorAlert(type) + return .empty() + } + }.catchError(with: self) { owner, _ in + let type = owner.currentState.cameraType + owner.cameraNavigator.showErrorAlert(type) + return .empty() + } } } } diff --git a/14th-team5-iOS/Core/Sources/Bibbi/BBCommons/BBAlert/BBAlert.swift b/14th-team5-iOS/Core/Sources/Bibbi/BBCommons/BBAlert/BBAlert.swift index 4991b3f2f..9d8b5e045 100644 --- a/14th-team5-iOS/Core/Sources/Bibbi/BBCommons/BBAlert/BBAlert.swift +++ b/14th-team5-iOS/Core/Sources/Bibbi/BBCommons/BBAlert/BBAlert.swift @@ -154,14 +154,15 @@ public class BBAlert { /// - Returns: BBAlert public static func style( _ style: BBAlertStyle, - primaryAction action: BBAlertActionHandler = nil, + primaryAction primaryHandler: BBAlertActionHandler = nil, + secondaryAction secondaryHandler: BBAlertActionHandler = nil, config: BBAlertConfiguration = BBAlertConfiguration() ) -> BBAlert { switch style { case .logout: let actions = [ BBAlertAction(title: "취소", style: .cancel), - BBAlertAction(title: "확인", handler: action) + BBAlertAction(title: "확인", handler: primaryHandler) ] let viewConfig = BBAlertViewConfiguration( minHeight: 145, @@ -180,7 +181,7 @@ public class BBAlert { case .makeNewFamily: let actions = [ BBAlertAction(title: "취소", style: .cancel), - BBAlertAction(title: "확인", handler: action) + BBAlertAction(title: "확인", handler: primaryHandler) ] let viewConfig = BBAlertViewConfiguration( minHeight: 181, @@ -199,7 +200,7 @@ public class BBAlert { case .resetFamilyName: let actions = [ BBAlertAction(title: "취소", style: .cancel), - BBAlertAction(title: "확인", handler: action) + BBAlertAction(title: "확인", handler: primaryHandler) ] let viewConfig = BBAlertViewConfiguration( minHeight: 181, @@ -217,7 +218,7 @@ public class BBAlert { case .widget: let actions = [ - BBAlertAction(title: "확인하기", handler: action), + BBAlertAction(title: "확인하기", handler: primaryHandler), BBAlertAction(title: "닫기", style: .cancel) ] let viewConfig = BBAlertViewConfiguration( @@ -237,7 +238,7 @@ public class BBAlert { case .mission: let actions = [ - BBAlertAction(title: "미션 사진 찍기", handler: action), + BBAlertAction(title: "미션 사진 찍기", handler: primaryHandler), BBAlertAction(title: "닫기", style: .cancel) ] let viewConfig = BBAlertViewConfiguration( @@ -257,7 +258,7 @@ public class BBAlert { case let .picking(name): let actions = [ - BBAlertAction(title: "지금 하기", handler: action), + BBAlertAction(title: "지금 하기", handler: primaryHandler), BBAlertAction(title: "다음에 하기", style: .cancel) ] let viewConfig = BBAlertViewConfiguration( @@ -277,7 +278,7 @@ public class BBAlert { case .takePhoto: let actions = [ - BBAlertAction(title: "생존 신고 먼저하기", handler: action), + BBAlertAction(title: "생존 신고 먼저하기", handler: primaryHandler), BBAlertAction(title: "다음에 하기", style: .cancel) ] let viewConfig = BBAlertViewConfiguration( @@ -295,6 +296,34 @@ public class BBAlert { ) return BBAlert(view: view, actions: actions, config: config) + case .uploadFailed: + + let actions = [ + BBAlertAction(title: "다시 촬영하기", handler: primaryHandler), + BBAlertAction( + title: "홈으로 이동", + style: .custom( + titleFontStyle: .body1Bold, + titleColor: .gray400, + backgroundColor: .gray700 + ), + handler: secondaryHandler + ) + ] + let viewConfig = BBAlertViewConfiguration( + minHeight: 384, + buttonAxis: .vertical + ) + let view = DefaultAlertView( + child: ImageAlertView( + image: DesignSystemAsset.uploadFailed.image, + title: "업로드에 실패했어요", + subtitle: "같은 문제가 반복된다면 앱을 껐다 켜거나\n 삭제하고 다시 설치해주세요.", + viewConfig: viewConfig + ), + viewConfig: viewConfig + ) + return BBAlert(view: view, actions: actions, config: config) } } diff --git a/14th-team5-iOS/Core/Sources/Bibbi/BBCommons/BBAlert/BBAlertStyle.swift b/14th-team5-iOS/Core/Sources/Bibbi/BBCommons/BBAlert/BBAlertStyle.swift index 400e12484..ef58397ec 100644 --- a/14th-team5-iOS/Core/Sources/Bibbi/BBCommons/BBAlert/BBAlertStyle.swift +++ b/14th-team5-iOS/Core/Sources/Bibbi/BBCommons/BBAlert/BBAlertStyle.swift @@ -24,6 +24,7 @@ extension BBAlert { case mission case picking(name: String) case takePhoto + case uploadFailed } } diff --git a/14th-team5-iOS/Data/Sources/APIs/Camera/CameraAPIWorker.swift b/14th-team5-iOS/Data/Sources/APIs/Camera/CameraAPIWorker.swift index c78da3430..bec4df922 100644 --- a/14th-team5-iOS/Data/Sources/APIs/Camera/CameraAPIWorker.swift +++ b/14th-team5-iOS/Data/Sources/APIs/Camera/CameraAPIWorker.swift @@ -23,7 +23,7 @@ extension CameraAPIWorker { /// - MemberId (사용자 멤버 ID) /// - body: CreatePresignedURLReqeustDTO (업로드 Image Name) /// - Returns : CameraRealEmojiPreSignedResponseDTO - public func createRealEmojiPresignedURL(memberId: String, body: CreatePresignedURLReqeustDTO) -> Observable { + public func createRealEmojiPresignedURL(memberId: String, body: CreatePresignedURLReqeustDTO) -> Observable { let spec = CameraAPIs.createRealEmojiPresignedURL(memberID: memberId, body: body).spec return request(spec) diff --git a/14th-team5-iOS/Data/Sources/APIs/Camera/Repository/CameraRepository.swift b/14th-team5-iOS/Data/Sources/APIs/Camera/Repository/CameraRepository.swift index 4a6193f67..4646f9c94 100644 --- a/14th-team5-iOS/Data/Sources/APIs/Camera/Repository/CameraRepository.swift +++ b/14th-team5-iOS/Data/Sources/APIs/Camera/Repository/CameraRepository.swift @@ -25,10 +25,10 @@ public final class CameraRepository { extension CameraRepository: CameraRepositoryProtocol { - public func createEmojiImagePresignedURL(memberID: String, body: CreatePresignedURLRequest) -> Observable { + public func createEmojiImagePresignedURL(memberID: String, body: CreatePresignedURLRequest) -> Observable { let body = CreatePresignedURLReqeustDTO(imageName: body.imageName) return cameraAPIWorker.createRealEmojiPresignedURL(memberId: memberID, body: body) - .map { $0?.toDomain() } + .map { $0.toDomain() } } public func createEmojiImage(memberId: String, body: CreateEmojiImageRequest) -> Observable { @@ -49,4 +49,7 @@ extension CameraRepository: CameraRepositoryProtocol { .map { $0?.toDomain() } } + public func uploadEmojiImageToS3Bucket(_ presignedURL: String, image: Data) -> Observable { + return cameraAPIWorker.upload(presignedURL, with: image) + } } diff --git a/14th-team5-iOS/DesignSystem/Resources/Assets.xcassets/Character/UploadFailed.imageset/Contents.json b/14th-team5-iOS/DesignSystem/Resources/Assets.xcassets/Character/UploadFailed.imageset/Contents.json new file mode 100644 index 000000000..e92a0c7f2 --- /dev/null +++ b/14th-team5-iOS/DesignSystem/Resources/Assets.xcassets/Character/UploadFailed.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "UploadFailed.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/14th-team5-iOS/DesignSystem/Resources/Assets.xcassets/Character/UploadFailed.imageset/UploadFailed.svg b/14th-team5-iOS/DesignSystem/Resources/Assets.xcassets/Character/UploadFailed.imageset/UploadFailed.svg new file mode 100644 index 000000000..8f5a5af63 --- /dev/null +++ b/14th-team5-iOS/DesignSystem/Resources/Assets.xcassets/Character/UploadFailed.imageset/UploadFailed.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/14th-team5-iOS/Domain/Sources/Repositories/CameraRepository.swift b/14th-team5-iOS/Domain/Sources/Repositories/CameraRepository.swift index 6ad157c65..51bdbce4a 100644 --- a/14th-team5-iOS/Domain/Sources/Repositories/CameraRepository.swift +++ b/14th-team5-iOS/Domain/Sources/Repositories/CameraRepository.swift @@ -69,10 +69,12 @@ public enum UploadLocation { public protocol CameraRepositoryProtocol { var disposeBag: DisposeBag { get } - func createEmojiImagePresignedURL(memberID: String, body: CreatePresignedURLRequest) -> Observable + func createEmojiImagePresignedURL(memberID: String, body: CreatePresignedURLRequest) -> Observable func createEmojiImage(memberId: String, body: CreateEmojiImageRequest) -> Observable /// FETCH 메서드 func fetchEmojiList(memberId: String) -> Observable<[CameraRealEmojiImageItemEntity?]> func updateEmojiImage(memberId: String, realEmojiId: String, body: UpdateRealEmojiImageRequest) -> Observable + + func uploadEmojiImageToS3Bucket(_ presignedURL: String, image: Data) -> Observable } diff --git a/14th-team5-iOS/Domain/Sources/Trash/Camera/UseCases/RealEmoji/FetchCameraRealEmojiUseCase.swift b/14th-team5-iOS/Domain/Sources/Trash/Camera/UseCases/RealEmoji/FetchCameraRealEmojiUseCase.swift index ede13a632..77e56c4e6 100644 --- a/14th-team5-iOS/Domain/Sources/Trash/Camera/UseCases/RealEmoji/FetchCameraRealEmojiUseCase.swift +++ b/14th-team5-iOS/Domain/Sources/Trash/Camera/UseCases/RealEmoji/FetchCameraRealEmojiUseCase.swift @@ -6,13 +6,14 @@ // import Foundation +import Core import RxSwift import RxCocoa public protocol FetchCameraRealEmojiUseCaseProtocol { - func execute(memberId: String, body: CreatePresignedURLRequest) -> Observable + func execute(memberId: String, body: CreatePresignedURLRequest, imageData: Data) -> Observable } public final class FetchCameraRealEmojiUseCase: FetchCameraRealEmojiUseCaseProtocol { @@ -23,7 +24,18 @@ public final class FetchCameraRealEmojiUseCase: FetchCameraRealEmojiUseCaseProto self.cameraRepository = cameraRepository } - public func execute(memberId: String, body: CreatePresignedURLRequest) -> Observable { + public func execute(memberId: String, body: CreatePresignedURLRequest, imageData: Data) -> Observable { + return cameraRepository.createEmojiImagePresignedURL(memberID: memberId, body: body) + .flatMap { [unowned self] presignedURL -> Observable in + + return self.cameraRepository.uploadEmojiImageToS3Bucket(presignedURL.imageURL, image: imageData) + .flatMap { isSuccess -> Observable in + if isSuccess { + return .just(presignedURL) + } + return .error(BBUploadError.uploadFailed) + } + } } }