From 9eb4f7ea3bfa9b96235525b8ef69fc7f5325ab28 Mon Sep 17 00:00:00 2001 From: Do-hyun-Kim Date: Thu, 11 Jan 2024 18:12:20 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20CameraViewController=20account=20?= =?UTF-8?q?=EB=B6=84=EA=B8=B0=20=EC=B2=98=EB=A6=AC=20=EA=B5=AC=EB=AC=B8=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20camera=20Image=20Data,=20Profi?= =?UTF-8?q?leImage=20String=20UserInfo=20=EC=A0=84=EB=8B=AC=20-=20AccountS?= =?UTF-8?q?ignUpViewController=20camerType=20profile=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20AccountProfileViewController=20userInfo=20image?= =?UTF-8?q?Data,=20ProfileImage=20String=20Action=20Binding=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20-=20CaemraType=20account=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reactor/AccountSignUpReactor.swift | 15 +++++++++++---- .../AccountProfileViewController.swift | 18 ++++++++++++++---- .../AccountSignUpViewController.swift | 2 +- .../Camera/CameraViewController.swift | 19 ++++++++++--------- .../Camera/Reactor/CameraViewReactor.swift | 9 +-------- .../CameraDisplayViewInterface.swift | 3 --- 6 files changed, 37 insertions(+), 29 deletions(-) diff --git a/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/Reactor/AccountSignUpReactor.swift b/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/Reactor/AccountSignUpReactor.swift index 1f6b949fe..f6c3fcd7f 100644 --- a/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/Reactor/AccountSignUpReactor.swift +++ b/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/Reactor/AccountSignUpReactor.swift @@ -32,7 +32,7 @@ public final class AccountSignUpReactor: Reactor { case profileImageTapped // Action Sheet출력 하는 이벤트 case didTapCompletehButton - case profilePresignedURL(String) + case profilePresignedURL(String, Data) } public enum Mutation { @@ -47,6 +47,7 @@ public final class AccountSignUpReactor: Reactor { case profileImageTapped case setprofilePresignedURL(String) + case setprofileImage(Data) case didTapCompletehButton(AccessTokenResponse?) } @@ -70,6 +71,7 @@ public final class AccountSignUpReactor: Reactor { var profilePresignedURL: String = "" var profileImageButtontapped: Bool = false + var profileImage: Data? = nil var didTapCompletehButtonFinish: AccessTokenResponse? = nil } @@ -109,8 +111,11 @@ extension AccountSignUpReactor { case .profileImageTapped: return Observable.just(Mutation.profileImageTapped) - case let .profilePresignedURL(presignedURL): - return Observable.just(Mutation.setprofilePresignedURL(presignedURL)) + case let .profilePresignedURL(presignedURL, originImage): + return .concat( + .just(.setprofilePresignedURL(presignedURL)), + .just(.setprofileImage(originImage)) + ) case let .didTapNickNameButton(nickName): let parameters: AccountNickNameEditParameter = AccountNickNameEditParameter(name: nickName) return accountRepository.executeNicknameUpdate(memberId: currentState.memberId, parameter: parameters) @@ -121,7 +126,7 @@ extension AccountSignUpReactor { case .didTapCompletehButton: let date = getDateToString(year: currentState.year!, month: currentState.month, day: currentState.day) - return accountRepository.signUp(name: currentState.nickname, date: date, photoURL: nil) + return accountRepository.signUp(name: currentState.nickname, date: date, photoURL: currentState.profilePresignedURL) .flatMap { tokenEntity -> Observable in return Observable.just(Mutation.didTapCompletehButton(tokenEntity)) } @@ -166,6 +171,8 @@ extension AccountSignUpReactor { newState.profilePresignedURL = url case .profileImageTapped: newState.profileImageButtontapped = true + case let .setprofileImage(profileImage): + newState.profileImage = profileImage case .didTapCompletehButton(let token): if let token = token { newState.didTapCompletehButtonFinish = token diff --git a/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/ViewControllers/AccountProfileViewController.swift b/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/ViewControllers/AccountProfileViewController.swift index d5506b8d7..50c3ac928 100644 --- a/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/ViewControllers/AccountProfileViewController.swift +++ b/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/ViewControllers/AccountProfileViewController.swift @@ -51,11 +51,15 @@ final class AccountProfileViewController: BaseViewController String? in - guard let userInfo = notification.userInfo else { return nil } - return userInfo["presignedURL"] as? String + .compactMap { notification -> (String, Data)? in + guard let userInfo = notification.userInfo, + let presignedURL = userInfo["presignedURL"] as? String, + let originImage = userInfo["originImage"] as? Data else { return nil + } + + return (presignedURL, originImage) } - .map { Reactor.Action.profilePresignedURL($0)} + .map { Reactor.Action.profilePresignedURL($0.0, $0.1)} .bind(to: reactor.action) .disposed(by: disposeBag) } @@ -67,6 +71,12 @@ final class AccountProfileViewController: BaseViewController { .bind(onNext: { $0.0.dismissCameraViewController() } ) .disposed(by: disposeBag) + Observable .zip( - reactor.state.map { $0.profileImageURLEntity }, - reactor.state.map { $0.cameraType } - ).filter { $0.1 == .account } - .map { $0.0 } + reactor.pulse(\.$profileMemberEntity), + reactor.state.map { $0.memberId }.distinctUntilChanged() + ).filter { $0.1.isEmpty } + .compactMap { $0.0 } + .compactMap { (try Data(contentsOf: $0.memberImage), $0.memberImage.absoluteString) } .withUnretained(self) - .subscribe(onNext: { owner, entity in - let userInfo: [AnyHashable: Any] = ["presignedURL": entity] + .subscribe { (owner, originEntity) in + let userInfo: [AnyHashable: Any] = ["presignedURL": originEntity.1, "originImage": originEntity.0] NotificationCenter.default.post(name: .AccountViewPresignURLDismissNotification, object: nil, userInfo: userInfo) owner.dismissCameraViewController() - }).disposed(by: disposeBag) - + }.disposed(by: disposeBag) shutterButton .rx.tap @@ -373,7 +374,7 @@ extension CameraViewController: AVCapturePhotoCaptureDelegate { public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { guard let photoData = photo.fileDataRepresentation(), let imageData = UIImage(data: photoData)?.jpegData(compressionQuality: 1.0) else { return } - if self.reactor?.cameraType == .profile || self.reactor?.cameraType == .account { + if self.reactor?.cameraType == .profile { output.photoOutputDidFinshProcessing(photo: imageData, error: error) } else { let cameraDisplayViewController = CameraDisplayDIContainer(displayData: imageData).makeViewController() 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 d9c490ca0..16ddcad61 100644 --- a/14th-team5-iOS/App/Sources/Presentation/Camera/Reactor/CameraViewReactor.swift +++ b/14th-team5-iOS/App/Sources/Presentation/Camera/Reactor/CameraViewReactor.swift @@ -88,14 +88,7 @@ public final class CameraViewReactor: Reactor { .flatMap { owner, entity -> Observable in // presignedURL에 image Upload 이것도 역시 병렬 큐 사용 guard let presingedURL = entity?.imageURL else { return .empty() } - - if self.currentState.cameraType == .account { - return .concat( - .just(.setProfileImageURLResponse(entity)), - .just(.setLoading(false)) - ) - } - + return owner.cameraUseCase.executeProfileUploadToS3(toURL: presingedURL, imageData: fileData) .subscribe(on: ConcurrentDispatchQueueScheduler.init(qos: .background)) .asObservable() diff --git a/14th-team5-iOS/Domain/Sources/Camera/Interfaces/CameraDisplayViewInterface.swift b/14th-team5-iOS/Domain/Sources/Camera/Interfaces/CameraDisplayViewInterface.swift index 5084932d2..7b057cab9 100644 --- a/14th-team5-iOS/Domain/Sources/Camera/Interfaces/CameraDisplayViewInterface.swift +++ b/14th-team5-iOS/Domain/Sources/Camera/Interfaces/CameraDisplayViewInterface.swift @@ -13,7 +13,6 @@ import RxSwift public enum UploadLocation { case feed case profile - case account public var location: String { switch self { @@ -21,8 +20,6 @@ public enum UploadLocation { return "images/feed/" case .profile: return "images/profile/" - case .account: - return "" } } } From 2b66de587dadd13ebac8e0cc5047ffd093f0e689 Mon Sep 17 00:00:00 2001 From: Do-hyun-Kim Date: Fri, 12 Jan 2024 01:38:16 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20AccountSignUpReactor=20didTapPHAsse?= =?UTF-8?q?tsImage=20Action=20=EC=B6=94=EA=B0=80=20-=20configureAccountOri?= =?UTF-8?q?ginalS3URL=20Method=20=EC=B6=94=EA=B0=80=20-=20AccountSignUpVie?= =?UTF-8?q?wController=20NotificationCenter=20PHPickerAssetsFinishPicking?= =?UTF-8?q?=20-=20CameraViewController=20Notification=20Post=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20CameraViewReactor=20member=20ID=20Empty?= =?UTF-8?q?=EC=9D=BC=20=EA=B2=BD=EC=9A=B0=20=EB=B6=84=EA=B8=B0=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EA=B5=AC=EB=AC=B8=20=EC=B6=94=EA=B0=80=20-=20Accou?= =?UTF-8?q?ntRepository=20Presigned=20API=20=ED=86=B5=EC=8B=A0=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20or=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reactor/AccountSignUpReactor.swift | 42 +++++++++++++++++++ .../AccountSignUpViewController.swift | 11 +++++ .../Camera/CameraViewController.swift | 17 ++++---- .../Camera/Reactor/CameraViewReactor.swift | 17 ++++++-- .../AccountRepository/AccountRepository.swift | 15 +++++++ 5 files changed, 89 insertions(+), 13 deletions(-) diff --git a/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/Reactor/AccountSignUpReactor.swift b/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/Reactor/AccountSignUpReactor.swift index f6c3fcd7f..de48346be 100644 --- a/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/Reactor/AccountSignUpReactor.swift +++ b/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/Reactor/AccountSignUpReactor.swift @@ -33,6 +33,7 @@ public final class AccountSignUpReactor: Reactor { case profileImageTapped // Action Sheet출력 하는 이벤트 case didTapCompletehButton case profilePresignedURL(String, Data) + case didTapPHAssetsImage(Data) } public enum Mutation { @@ -49,6 +50,7 @@ public final class AccountSignUpReactor: Reactor { case setprofilePresignedURL(String) case setprofileImage(Data) case didTapCompletehButton(AccessTokenResponse?) + case setPHAssetsImage(Data) } public struct State { @@ -130,6 +132,37 @@ extension AccountSignUpReactor { .flatMap { tokenEntity -> Observable in return Observable.just(Mutation.didTapCompletehButton(tokenEntity)) } + case let .didTapPHAssetsImage(profileImage): + let originalImage: String = "\(profileImage.hashValue).jpg" + let profileImageEditParameter: CameraDisplayImageParameters = CameraDisplayImageParameters(imageName: originalImage) + + return .concat( + accountRepository.executePresignedImageURLCreate(parameter: profileImageEditParameter) + .withUnretained(self) + .subscribe(on: ConcurrentDispatchQueueScheduler.init(qos: .background)) + .asObservable() + .flatMap { owner, entity -> Observable in + guard let accountPresignedURL = entity?.imageURL else { return .empty() } + return owner.accountRepository.executeProfileImageUpload(to: accountPresignedURL, data: profileImage) + .asObservable() + .flatMap { isSuccess -> Observable in + let originalPath = owner.configureAccountOriginalS3URL(url: accountPresignedURL) + let accountParameter = ProfileImageEditParameter(profileImageUrl: originalPath) + if isSuccess { + return .concat( + .just(.setprofilePresignedURL(accountPresignedURL)), + .just(.setprofileImage(profileImage)) + ) + } else { + return .empty() + } + + } + + } + + ) + } } @@ -179,6 +212,8 @@ extension AccountSignUpReactor { } case let .setEditNickName(entity): newState.profileNickNameEditEntity = entity + case let .setPHAssetsImage(profileImage): + newState.profileImage = profileImage } newState.isValidDateButton = newState.isValidYear && newState.isValidMonth && newState.isValidDay @@ -199,4 +234,11 @@ extension AccountSignUpReactor { let date = Calendar.current.date(from: components) ?? Date() return dateFormatter.string(from: date) } + + func configureAccountOriginalS3URL(url: String) -> String { + guard let range = url.range(of: #"[^&?]+"#, options: .regularExpression) else { return "" } + return String(url[range]) + } } + + diff --git a/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/ViewControllers/AccountSignUpViewController.swift b/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/ViewControllers/AccountSignUpViewController.swift index 1c424a54b..55300cfb8 100644 --- a/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/ViewControllers/AccountSignUpViewController.swift +++ b/14th-team5-iOS/App/Sources/Presentation/Account/AccountSignUp/ViewControllers/AccountSignUpViewController.swift @@ -57,6 +57,17 @@ public final class AccountSignUpViewController: BasePageViewController Data? in + guard let userInfo = notification.userInfo else { return nil } + return userInfo["selectImage"] as? Data + } + .map{ Reactor.Action.didTapPHAssetsImage($0)} + .bind(to: reactor.action) + .disposed(by: disposeBag) + } public override func setupUI() { diff --git a/14th-team5-iOS/App/Sources/Presentation/Camera/CameraViewController.swift b/14th-team5-iOS/App/Sources/Presentation/Camera/CameraViewController.swift index 7ddf6da29..0d1e65b24 100644 --- a/14th-team5-iOS/App/Sources/Presentation/Camera/CameraViewController.swift +++ b/14th-team5-iOS/App/Sources/Presentation/Camera/CameraViewController.swift @@ -163,19 +163,16 @@ public final class CameraViewController: BaseViewController { .disposed(by: disposeBag) - Observable - .zip( - reactor.pulse(\.$profileMemberEntity), - reactor.state.map { $0.memberId }.distinctUntilChanged() - ).filter { $0.1.isEmpty } - .compactMap { $0.0 } - .compactMap { (try Data(contentsOf: $0.memberImage), $0.memberImage.absoluteString) } + reactor.state + .map { ($0.accountImage, $0.profileImageURLEntity)} + .filter { $0.1 != nil } .withUnretained(self) - .subscribe { (owner, originEntity) in - let userInfo: [AnyHashable: Any] = ["presignedURL": originEntity.1, "originImage": originEntity.0] + .subscribe(onNext: { (owner, originEntity) in + let userInfo: [AnyHashable: Any] = ["presignedURL": originEntity.1?.imageURL, "originImage": originEntity.0] NotificationCenter.default.post(name: .AccountViewPresignURLDismissNotification, object: nil, userInfo: userInfo) owner.dismissCameraViewController() - }.disposed(by: disposeBag) + }).disposed(by: disposeBag) + shutterButton .rx.tap 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 16ddcad61..d478135dc 100644 --- a/14th-team5-iOS/App/Sources/Presentation/Camera/Reactor/CameraViewReactor.swift +++ b/14th-team5-iOS/App/Sources/Presentation/Camera/Reactor/CameraViewReactor.swift @@ -30,6 +30,7 @@ public final class CameraViewReactor: Reactor { case setPosition(Bool) case setFlashMode(Bool) case setProfileS3Edit(Bool) + case setAccountProfileData(Data) case setProfileImageURLResponse(CameraDisplayImageResponse?) case setProfileMemberResponse(ProfileMemberResponse?) } @@ -40,6 +41,7 @@ public final class CameraViewReactor: Reactor { @Pulse var isSwitchPosition: Bool @Pulse var profileImageURLEntity: CameraDisplayImageResponse? var cameraType: UploadLocation = .feed + var accountImage: Data? var memberId: String var isProfileEdit: Bool @Pulse var profileMemberEntity: ProfileMemberResponse? @@ -58,6 +60,7 @@ public final class CameraViewReactor: Reactor { isSwitchPosition: false, profileImageURLEntity: nil, cameraType: cameraType, + accountImage: nil, memberId: memberId, isProfileEdit: false, profileMemberEntity: nil @@ -94,6 +97,15 @@ public final class CameraViewReactor: Reactor { .asObservable() .flatMap { isSuccess -> Observable in guard let profilePresingedURL = entity?.imageURL else { return .empty() } + + if owner.memberId.isEmpty { + return .concat( + .just(.setProfileImageURLResponse(entity)), + .just(.setAccountProfileData(fileData)), + .just(.setLoading(false)) + ) + } + // 최종 Member Entity API 호출 작업 병렬 큐 사용 -> 사용 안하니 로딩 속도 느림 let originalURL = owner.configureProfileOriginalS3URL(url: profilePresingedURL, with: .profile) let profileImageEditParameter: ProfileImageEditParameter = ProfileImageEditParameter(profileImageUrl: originalURL) @@ -130,13 +142,12 @@ public final class CameraViewReactor: Reactor { newState.isFlashMode = isFlash case let .setProfileImageURLResponse(entity): newState.profileImageURLEntity = entity - print("newState profileimageURL: \(newState.profileImageURLEntity)") case let .setProfileS3Edit(isProfileEdit): newState.isProfileEdit = isProfileEdit - print("newState isProfileEdit: \(newState.isProfileEdit)") case let .setProfileMemberResponse(entity): newState.profileMemberEntity = entity - print("newState profileMemberEnity: \(newState.profileMemberEntity)") + case let .setAccountProfileData(accountImage): + newState.accountImage = accountImage } return newState diff --git a/14th-team5-iOS/Data/Sources/Account/AccountRepository/AccountRepository.swift b/14th-team5-iOS/Data/Sources/Account/AccountRepository/AccountRepository.swift index b43a3133a..6e5e10d11 100644 --- a/14th-team5-iOS/Data/Sources/Account/AccountRepository/AccountRepository.swift +++ b/14th-team5-iOS/Data/Sources/Account/AccountRepository/AccountRepository.swift @@ -26,6 +26,8 @@ public protocol AccountImpl: AnyObject { func appleLogin(with snsType: SNS, vc: UIViewController) -> Observable func executeNicknameUpdate(memberId: String, parameter: AccountNickNameEditParameter) -> Observable func signUp(name: String, date: String, photoURL: String?) -> Observable + func executePresignedImageURLCreate(parameter: CameraDisplayImageParameters) -> Observable + func executeProfileImageUpload(to url: String, data: Data) -> Observable } public final class AccountRepository: AccountImpl { @@ -35,6 +37,7 @@ public final class AccountRepository: AccountImpl { let signInHelper = AccountSignInHelper() private let apiWorker = AccountAPIWorker() + private let profileWorker = ProfileAPIWorker() private let meApiWorekr = MeAPIWorker() private let signInResult = PublishRelay() @@ -120,6 +123,18 @@ public final class AccountRepository: AccountImpl { .asObservable() } + public func executePresignedImageURLCreate(parameter: CameraDisplayImageParameters) -> Observable { + return profileWorker.createProfileImagePresingedURL(accessToken: accessToken, parameters: parameter) + .compactMap { $0?.toDomain() } + .asObservable() + } + + public func executeProfileImageUpload(to url: String, data: Data) -> Observable { + return profileWorker.uploadToProfilePresingedURL(accessToken: accessToken, toURL: url, with: data) + .asObservable() + } + + public init() { signInHelper.bind() }