From 4d33d517a22f4f9bb2f8b1296329a5db2462e984 Mon Sep 17 00:00:00 2001 From: loinsir Date: Fri, 8 Dec 2023 02:11:49 +0900 Subject: [PATCH 01/19] =?UTF-8?q?:recycle:=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=20=ED=8E=B8=EC=A7=91=20=EB=B7=B0=20=EC=82=AC=EC=9D=B4=ED=81=B4?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover.xcodeproj/project.pbxproj | 10 +-- .../xcshareddata/xcschemes/Layover.xcscheme | 90 +++++++++++++++++++ .../Network/DTOs/PresignedURLDTO.swift | 17 ++++ .../Factories/UserEndPointFactory.swift | 13 ++- .../Layover/Network/Provider/Provider.swift | 15 ++++ .../EditProfile/EditProfileInteractor.swift | 74 +++++++++++---- .../EditProfile/EditProfileModels.swift | 72 +++++++++------ .../EditProfile/EditProfilePresenter.swift | 25 +++--- .../EditProfile/EditProfileRouter.swift | 2 +- .../EditProfileViewController.swift | 89 ++++++++++-------- .../Scenes/Profile/ProfileRouter.swift | 2 +- .../Scenes/SignUpScene/SignUpPresenter.swift | 2 +- .../Workers/Mocks/MockUserWorker.swift | 9 +- iOS/Layover/Layover/Workers/UserWorker.swift | 29 +++++- 14 files changed, 342 insertions(+), 107 deletions(-) create mode 100644 iOS/Layover/Layover.xcodeproj/xcshareddata/xcschemes/Layover.xcscheme create mode 100644 iOS/Layover/Layover/Network/DTOs/PresignedURLDTO.swift diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index e3c8e5f..2b0eb08 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ 198167A32B20583D0032F563 /* SettingModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1981679E2B20583D0032F563 /* SettingModels.swift */; }; 198167A42B20583D0032F563 /* SettingInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1981679F2B20583D0032F563 /* SettingInteractor.swift */; }; 198167A62B20593B0032F563 /* SettingConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 198167A52B20593B0032F563 /* SettingConfigurator.swift */; }; + 198167A82B20DD670032F563 /* PresignedURLDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 198167A72B20DD670032F563 /* PresignedURLDTO.swift */; }; 19A169232B176C5F00DB34C0 /* TagPlayListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1691D2B176C5F00DB34C0 /* TagPlayListPresenter.swift */; }; 19A169242B176C5F00DB34C0 /* TagPlayListWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1691E2B176C5F00DB34C0 /* TagPlayListWorker.swift */; }; 19A169252B176C5F00DB34C0 /* TagPlayListRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1691F2B176C5F00DB34C0 /* TagPlayListRouter.swift */; }; @@ -268,6 +269,7 @@ 1981679E2B20583D0032F563 /* SettingModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingModels.swift; sourceTree = ""; }; 1981679F2B20583D0032F563 /* SettingInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingInteractor.swift; sourceTree = ""; }; 198167A52B20593B0032F563 /* SettingConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingConfigurator.swift; sourceTree = ""; }; + 198167A72B20DD670032F563 /* PresignedURLDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresignedURLDTO.swift; sourceTree = ""; }; 19A1691D2B176C5F00DB34C0 /* TagPlayListPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagPlayListPresenter.swift; sourceTree = ""; }; 19A1691E2B176C5F00DB34C0 /* TagPlayListWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagPlayListWorker.swift; sourceTree = ""; }; 19A1691F2B176C5F00DB34C0 /* TagPlayListRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagPlayListRouter.swift; sourceTree = ""; }; @@ -613,12 +615,11 @@ 19A169372B17BCA800DB34C0 /* PostDTO.swift */, 19A169392B17BCC400DB34C0 /* MemberDTO.swift */, 19A1693B2B17BD1C00DB34C0 /* BoardDTO.swift */, - 836C338C2B15D91F00ECAFB0 /* VideoDTO.swift */, - 836C338E2B160CC700ECAFB0 /* MemberDTO.swift */, 8321A2FE2B1E428C000A12AF /* ReportDTO.swift */, FC4084C52B1F1C5B00CE4727 /* UploadPostDTO.swift */, FC4084C92B1F291200CE4727 /* UploadVideoDTO.swift */, 1915D6E42B1FB82000CE1DD0 /* CheckSignUpDTO.swift */, + 198167A72B20DD670032F563 /* PresignedURLDTO.swift */, ); path = DTOs; sourceTree = ""; @@ -1223,7 +1224,6 @@ FCEE0FF22B036B6000195BBE /* LOButton.swift in Sources */, FC5BE1212B148D170036366D /* EditProfileInteractor.swift in Sources */, FC930E7C2B0CD80800AA48E3 /* ProfileConfigurator.swift in Sources */, - 836C33992B1843BE00ECAFB0 /* SettingScenePresenter.swift in Sources */, 8321A2F12B1E1026000A12AF /* ReportModels.swift in Sources */, FC2511A62B049020004717BC /* SignUpConfigurator.swift in Sources */, 194552392B05230E00299768 /* HomeCarouselCollectionViewCell.swift in Sources */, @@ -1287,7 +1287,6 @@ FC68E29D2B02326A001AABFF /* Responsable.swift in Sources */, FC2511A02B045C0A004717BC /* SignUpInteractor.swift in Sources */, FC767F862B1214C10088CF9B /* CheckUserNameDTO.swift in Sources */, - 836C338D2B15D91F00ECAFB0 /* VideoDTO.swift in Sources */, FC4084C42B1F14F600CE4727 /* UploadPostEndPointFactory.swift in Sources */, FC5BE1232B1490660036366D /* EditProfileConfigurator.swift in Sources */, 1945522A2B04883800299768 /* UIView+.swift in Sources */, @@ -1302,7 +1301,7 @@ 836C33912B17629400ECAFB0 /* MapRouter.swift in Sources */, 19C7AFCE2B02410F003B35F2 /* AuthManager.swift in Sources */, 836C339D2B1843BE00ECAFB0 /* SettingViewController.swift in Sources */, - 836C339D2B1843BE00ECAFB0 /* SettingSceneViewController.swift in Sources */, + 836C339D2B1843BE00ECAFB0 /* SettingViewController.swift in Sources */, 8321A2FF2B1E428C000A12AF /* ReportDTO.swift in Sources */, FC9BB82C2B094E5500EB72A9 /* UICollectionViewLayout+.swift in Sources */, 194552232B0478B400299768 /* HomeRouter.swift in Sources */, @@ -1346,6 +1345,7 @@ FC68E2A52B0233D3001AABFF /* Provider.swift in Sources */, FC930E7E2B0CDB2900AA48E3 /* ThumbnailCollectionViewCell.swift in Sources */, 194552112B03AF2B00299768 /* MainTabBarConfigurator.swift in Sources */, + 198167A82B20DD670032F563 /* PresignedURLDTO.swift in Sources */, 8321A2F72B1E14A1000A12AF /* LOPopUpView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/iOS/Layover/Layover.xcodeproj/xcshareddata/xcschemes/Layover.xcscheme b/iOS/Layover/Layover.xcodeproj/xcshareddata/xcschemes/Layover.xcscheme new file mode 100644 index 0000000..b985a9e --- /dev/null +++ b/iOS/Layover/Layover.xcodeproj/xcshareddata/xcschemes/Layover.xcscheme @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/Layover/Layover/Network/DTOs/PresignedURLDTO.swift b/iOS/Layover/Layover/Network/DTOs/PresignedURLDTO.swift new file mode 100644 index 0000000..28e7f43 --- /dev/null +++ b/iOS/Layover/Layover/Network/DTOs/PresignedURLDTO.swift @@ -0,0 +1,17 @@ +// +// PresignedURLDTO.swift +// Layover +// +// Created by 김인환 on 12/7/23. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import Foundation + +struct PresignedURLDTO: Decodable { + let preSignedURL: String + + enum CodingKeys: String, CodingKey { + case preSignedURL = "preSignedUrl" + } +} diff --git a/iOS/Layover/Layover/Network/EndPoint/Factories/UserEndPointFactory.swift b/iOS/Layover/Layover/Network/EndPoint/Factories/UserEndPointFactory.swift index 9d8e9ca..189f6cf 100644 --- a/iOS/Layover/Layover/Network/EndPoint/Factories/UserEndPointFactory.swift +++ b/iOS/Layover/Layover/Network/EndPoint/Factories/UserEndPointFactory.swift @@ -15,6 +15,7 @@ protocol UserEndPointFactory { func makeUserWithDrawEndPoint() -> EndPoint> func makeUserInformationEndPoint(with id: Int?) -> EndPoint> func makeUserPostsEndPoint(at page: Int, of id: Int?) -> EndPoint> + func makeFetchUserProfilePresignedURL(of fileType: String) -> EndPoint> } final class DefaultUserEndPointFactory: UserEndPointFactory { @@ -75,7 +76,6 @@ final class DefaultUserEndPointFactory: UserEndPointFactory { } func makeUserPostsEndPoint(at page: Int, of id: Int? = nil) -> EndPoint> { - var queryParameters = [String: String]() queryParameters.updateValue(String(page), forKey: "page") @@ -89,4 +89,15 @@ final class DefaultUserEndPointFactory: UserEndPointFactory { queryParameters: queryParameters ) } + + func makeFetchUserProfilePresignedURL(of fileType: String) -> EndPoint> { + var bodyParameters = [String: String]() + bodyParameters.updateValue(fileType, forKey: "fileType") + + return EndPoint( + path: "/member/profile-image/presigned-url", + method: .POST, + bodyParameters: bodyParameters + ) + } } diff --git a/iOS/Layover/Layover/Network/Provider/Provider.swift b/iOS/Layover/Layover/Network/Provider/Provider.swift index 3f01d1f..d933fdc 100644 --- a/iOS/Layover/Layover/Network/Provider/Provider.swift +++ b/iOS/Layover/Layover/Network/Provider/Provider.swift @@ -13,6 +13,7 @@ protocol ProviderType { func request(url: URL) async throws -> Data func request(url: String) async throws -> Data func upload(data: Data, to url: String, method: HTTPMethod) async throws -> Data + func upload(from fileURL: URL, to url: String, method: HTTPMethod) async throws -> Data func upload(fromFile: URL, to url: String, method: HTTPMethod, @@ -35,6 +36,10 @@ extension ProviderType { method: method) } + func upload(from fileURL: URL, to url: String, method: HTTPMethod = .PUT) async throws -> Data { + return try await upload(from: fileURL, to: url, method: method) + } + func upload(fromFile: URL, to url: String, method: HTTPMethod = .PUT, @@ -136,6 +141,16 @@ class Provider: ProviderType { return data } + func upload(from fileURL: URL, to url: String, method: HTTPMethod = .PUT) async throws -> Data { + guard let url = URL(string: url) else { throw NetworkError.components } + var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData) + request.httpMethod = method.rawValue + + let (data, response) = try await session.upload(for: request, fromFile: fileURL) + try self.checkStatusCode(of: response) + return data + } + // 동영상 업로드용 func upload(fromFile: URL, to url: String, diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift index b90f9af..b7e5636 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift @@ -10,10 +10,11 @@ import Foundation import OSLog protocol EditProfileBusinessLogic { - func fetchProfile() - func validateProfileInfo(with request: EditProfileModels.ValidateProfileInfo.Request) + func fetchProfile(with request: EditProfileModels.FetchProfile.Request) + func changeProfile(with request: EditProfileModels.ChangeProfile.Request) func checkDuplication(with request: EditProfileModels.CheckNicknameDuplication.Request) - func editProfile(with requeset: EditProfileModels.EditProfile.Request) + @discardableResult + func editProfile(with request: EditProfileModels.EditProfile.Request) -> Task } protocol EditProfileDataStore { @@ -31,6 +32,8 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor var userWorker: UserWorkerProtocol? var presenter: EditProfilePresentationLogic? + private var isChangedProfileImage = false + // MARK: - Data Store var nickname: String? @@ -39,20 +42,32 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor // MARK: - Use Case - func fetchProfile() { - presenter?.presentProfile(with: EditProfileModels.FetchProfile.Reponse(nickname: nickname, + func fetchProfile(with request: EditProfileModels.FetchProfile.Request) { + presenter?.presentProfile(with: EditProfileModels.FetchProfile.Response(nickname: nickname, introduce: introduce, profileImageData: profileImageData)) } - func validateProfileInfo(with request: Models.ValidateProfileInfo.Request) { - let nicknameChanged = nickname != request.nickname - let introduceChanged = introduce != request.introduce - let profileInfoChanged = nicknameChanged || request.profileImageChanged || introduceChanged - let profileInfoValiation = (userWorker?.validateNickname(request.nickname) == .valid) && validateIntroduce(request.introduce ?? "") - let response = EditProfileModels.ValidateProfileInfo.Response(isValid: profileInfoChanged && profileInfoValiation, - nicknameChanged: nicknameChanged) - presenter?.presentProfileInfoValidation(with: response) + func changeProfile(with request: Models.ChangeProfile.Request) { + let changedNicknameState = userWorker?.validateNickname(request.nickname ?? "") + let changedIntroduceState = introduceLengthState(of: request.introduce ?? "", by: request.validIntroduceLength) + + // 닉네임이 변경되었는지, 변경되었다면 변경된 닉네임이 유효한지 + let canCheckNicknameDuplication = nickname != request.nickname + && changedNicknameState == .valid + + // 이미지, 닉네임과 자기소개 중 하나라도 변경되었고, 변경된 닉네임과 자기소개가 유효한지 + let canEditProfile = changedNicknameState == .valid && changedIntroduceState == .valid + && (nickname != request.nickname || introduce != request.introduce || request.profileImageData != nil) + + + let response = Models.ChangeProfile.Response(newNicknameState: userWorker?.validateNickname(request.nickname ?? "") ?? .valid, + newIntroduceState: introduceLengthState(of: request.introduce ?? "", + by: request.validIntroduceLength), + canCheckNicknameDuplication: canCheckNicknameDuplication, + canEditProfile: canEditProfile) + + presenter?.presentProfileState(with: response) } func checkDuplication(with request: Models.CheckNicknameDuplication.Request) { @@ -67,12 +82,37 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor } } - func editProfile(with requeset: Models.EditProfile.Request) { + @discardableResult + func editProfile(with request: Models.EditProfile.Request) -> Task { + Task { + async let modifyNicknameResponse = userWorker?.modifyNickname(to: request.nickname) + async let modifyIntroduceResponse = userWorker?.modifyIntroduce(to: request.introduce ?? "") - } + guard await modifyNicknameResponse != nil, await modifyIntroduceResponse != nil else { + os_log(.error, log: .data, "Edit Profile Error") + return false + } + + // 프로필 이미지 바꾼 경우 + guard let modifiedProfileImageURL = request.profileImageURL, modifiedProfileImageURL.isFileURL, + let presignedUploadURL = await userWorker?.fetchImagePresignedURL(with: modifiedProfileImageURL.pathExtension), + let modifyProfileImageResponse = await userWorker?.modifyProfileImage(from: modifiedProfileImageURL, + to: presignedUploadURL), + modifyProfileImageResponse == true else { + os_log(.error, log: .data, "Edit ProfileImage Error") + return false + } - private func validateIntroduce(_ introduce: String) -> Bool { - return introduce.count <= 50 +// await MainActor.run { +// presenter?.presentProfile(with: Models.EditProfile.Response(nickname: request.nickname, +// introduce: request.introduce ?? "", +// profileImageURL: URL(string: presignedUploadURL))) +// } + return true + } } + private func introduceLengthState(of introduce: String, by length: Int) -> Models.ChangeProfile.IntroduceLengthState { + introduce.count <= length ? .valid : .overLength + } } diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift index 05195a6..4284cd2 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift @@ -12,49 +12,67 @@ enum EditProfileModels { enum FetchProfile { struct Request { - } - struct Reponse { - var nickname: String? - var introduce: String? - var profileImageData: Data? + struct Response { + let nickname: String? + let introduce: String? + let profileImageData: Data? } struct ViewModel { - var nickname: String? - var introduce: String? - var profileImageData: Data? + let nickname: String? + let introduce: String? + let profileImageData: Data? } } - enum ValidateProfileInfo { + enum ChangeProfile { + enum IntroduceLengthState: CustomStringConvertible { + case overLength + case valid + + var description: String { + switch self { + case .overLength: + return "자기소개는 30자 이내로 입력해주세요." + case .valid: + return "" + } + } + } + struct Request { - var nickname: String - var introduce: String? - var profileImageChanged: Bool + let nickname: String? + let introduce: String? + let profileImageData: Data? + let validIntroduceLength: Int = 50 // default value } + struct Response { - var isValid: Bool - var nicknameChanged: Bool + let newNicknameState: NicknameState + let newIntroduceState: IntroduceLengthState + let canCheckNicknameDuplication: Bool + let canEditProfile: Bool } + struct ViewModel { - var nicknameAlertDescription: String? - var introduceAlertDescription: String? - var canCheckDuplication: Bool - var canEditProfile: Bool + let nicknameAlertDescription: String + let introduceAlertDescription: String + let canCheckNicknameDuplication: Bool + let canEditProfile: Bool } } enum CheckNicknameDuplication { struct Request { - var nickname: String + let nickname: String } struct Response { - var isValid: Bool + let isValid: Bool } struct ViewModel { - var isValidNickname: Bool + let isValidNickname: Bool var alertDescription: String { isValidNickname ? "사용가능한 닉네임입니다." : "사용중인 닉네임입니다." } @@ -63,15 +81,15 @@ enum EditProfileModels { enum EditProfile { struct Request { - var nickname: String - var introduce: String? - var profileImage: Data? + let nickname: String + let introduce: String? + let profileImageURL: URL? } struct Response { - var nickname: String - var introduce: String? - var profileImgaeURL: URL? + let nickname: String + let introduce: String? + let profileImageURL: URL? } struct ViewModel { diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift index a3ead63..d9e5f0d 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift @@ -9,8 +9,9 @@ import UIKit protocol EditProfilePresentationLogic { - func presentProfile(with response: EditProfileModels.FetchProfile.Reponse) - func presentProfileInfoValidation(with response: EditProfileModels.ValidateProfileInfo.Response) + func presentProfile(with response: EditProfileModels.FetchProfile.Response) + func presentProfileState(with response: EditProfileModels.ChangeProfile.Response) + func presentNicknameDuplication(with response: EditProfileModels.CheckNicknameDuplication.Response) } @@ -21,22 +22,26 @@ final class EditProfilePresenter: EditProfilePresentationLogic { typealias Models = EditProfileModels weak var viewController: EditProfileDisplayLogic? - func presentProfile(with response: EditProfileModels.FetchProfile.Reponse) { + // MARK: - Methods + + func presentProfile(with response: Models.FetchProfile.Response) { let viewModel = Models.FetchProfile.ViewModel(nickname: response.nickname, introduce: response.introduce, profileImageData: response.profileImageData) - viewController?.displayProfile(viewModel: viewModel) + viewController?.displayProfile(with: viewModel) } - func presentProfileInfoValidation(with response: EditProfileModels.ValidateProfileInfo.Response) { - let viewModel = Models.ValidateProfileInfo.ViewModel(canCheckDuplication: response.nicknameChanged, - canEditProfile: response.isValid) - viewController?.displayProfileInfoValidation(viewModel: viewModel) + func presentProfileState(with response: Models.ChangeProfile.Response) { + let viewModel = Models.ChangeProfile.ViewModel(nicknameAlertDescription: response.newNicknameState.description, + introduceAlertDescription: response.newIntroduceState.description, + canCheckNicknameDuplication: response.canCheckNicknameDuplication, + canEditProfile: response.canEditProfile) + + viewController?.displayChangedProfileState(with: viewModel) } func presentNicknameDuplication(with response: EditProfileModels.CheckNicknameDuplication.Response) { - let viewModel = Models.CheckNicknameDuplication.ViewModel(isValidNickname: response.isValid) - viewController?.displayNicknameDuplication(viewModel: viewModel) + return } } diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileRouter.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileRouter.swift index 1901cfa..661f778 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileRouter.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileRouter.swift @@ -16,7 +16,7 @@ protocol EditProfileDataPassing { var dataStore: EditProfileDataStore? { get } } -final class EditProfileRouter: NSObject, EditProfileRoutingLogic, EditProfileDataPassing { +final class EditProfileRouter: EditProfileRoutingLogic, EditProfileDataPassing { // MARK: - Properties diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift index 130217f..27793e7 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift @@ -6,13 +6,14 @@ // Copyright © 2023 CodeBomber. All rights reserved. // -import PhotosUI + import UIKit +import PhotosUI +import OSLog protocol EditProfileDisplayLogic: AnyObject { - func displayProfile(viewModel: EditProfileModels.FetchProfile.ViewModel) - func displayProfileInfoValidation(viewModel: EditProfileModels.ValidateProfileInfo.ViewModel) - func displayNicknameDuplication(viewModel: EditProfileModels.CheckNicknameDuplication.ViewModel) + func displayProfile(with viewModel: EditProfileModels.FetchProfile.ViewModel) + func displayChangedProfileState(with viewModel: EditProfileModels.ChangeProfile.ViewModel) } final class EditProfileViewController: BaseViewController { @@ -33,6 +34,7 @@ final class EditProfileViewController: BaseViewController { imageView.image = UIImage.profile imageView.layer.cornerRadius = 36 imageView.clipsToBounds = true + imageView.contentMode = .scaleAspectFill return imageView }() @@ -85,14 +87,31 @@ final class EditProfileViewController: BaseViewController { return button }() + private lazy var editProfileImageController: UIAlertController = { + let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + let defaultAction = UIAlertAction(title: "기본 이미지로 변경", style: .default) { [weak self] _ in + guard let self = self else { return } + self.changedProfileImageData = nil + self.profileImageView.image = UIImage.profile + self.profileInfoChanged() + } + let albumAction = UIAlertAction(title: "앨범에서 선택", style: .default) { [weak self] _ in + guard let self else { return } + self.present(self.phPickerViewController, animated: true) + } + let cancelAction = UIAlertAction(title: "취소", style: .cancel) + [defaultAction, albumAction, cancelAction].forEach { alertController.addAction($0) } + return alertController + }() + // MARK: - Properties typealias Models = EditProfileModels - var router: (NSObjectProtocol & EditProfileRoutingLogic & EditProfileDataPassing)? + var router: (EditProfileRoutingLogic & EditProfileDataPassing)? var interactor: EditProfileBusinessLogic? - private var changedProfileImage: UIImage? - private var nicknameIsValid: Bool = true + private var changedProfileImageData: Data? + private var changedProfileImageExtension: String? // MARK: - Object Lifecycle @@ -106,18 +125,18 @@ final class EditProfileViewController: BaseViewController { setup() } - // MARK: - View Lifecycle + // MARK: - Setup - override func viewDidLoad() { - super.viewDidLoad() - interactor?.fetchProfile() + private func setup() { + EditProfileConfigurator.shared.configure(self) } - // MARK: - Methods + // MARK: - UI Layout override func setUI() { super.setUI() - self.title = "프로필 수정" + title = "프로필 수정" + interactor?.fetchProfile(with: Models.FetchProfile.Request()) } override func setConstraints() { @@ -168,18 +187,12 @@ final class EditProfileViewController: BaseViewController { } - private func setup() { - EditProfileConfigurator.shared.configure(self) - } + // MARK: - Actions @objc private func profileInfoChanged() { - guard let nickname = nicknameTextfield.text, - let introduce = introduceTextfield.text else { return } - let profileImageChanged = changedProfileImage != nil - let profileInfoRequest = EditProfileModels.ValidateProfileInfo.Request(nickname: nickname, - introduce: introduce, - profileImageChanged: profileImageChanged) - interactor?.validateProfileInfo(with: profileInfoRequest) + interactor?.changeProfile(with: Models.ChangeProfile.Request(nickname: nicknameTextfield.text, + introduce: introduceTextfield.text, + profileImageData: changedProfileImageData)) } @objc private func checkDuplicateNicknameButtonDidTap() { @@ -193,7 +206,6 @@ final class EditProfileViewController: BaseViewController { @objc private func editProfileImageButtonDidTap() { self.present(phPickerViewController, animated: true) } - } extension EditProfileViewController: PHPickerViewControllerDelegate { @@ -203,21 +215,24 @@ extension EditProfileViewController: PHPickerViewControllerDelegate { if let item = item, item.canLoadObject(ofClass: UIImage.self) { item.loadObject(ofClass: UIImage.self) { [weak self] (image, _) in guard let self else { return } - DispatchQueue.main.async { - self.profileImageView.image = image as? UIImage - self.changedProfileImage = image as? UIImage - self.profileInfoChanged() + Task { + await MainActor.run { + self.profileImageView.image = image as? UIImage + self.changedProfileImageData = (image as? UIImage)?.jpegData(compressionQuality: 1.0) + self.profileInfoChanged() + } } } } } } +// MARK: - Display Logic + extension EditProfileViewController: EditProfileDisplayLogic { - func displayProfile(viewModel: Models.FetchProfile.ViewModel) { + func displayProfile(with viewModel: Models.FetchProfile.ViewModel) { nicknameTextfield.text = viewModel.nickname - if let introduce = viewModel.introduce { introduceTextfield.text = introduce } @@ -229,18 +244,14 @@ extension EditProfileViewController: EditProfileDisplayLogic { } } - func displayProfileInfoValidation(viewModel: EditProfileModels.ValidateProfileInfo.ViewModel) { - nicknameAlertLabel.isHidden = viewModel.canCheckDuplication + func displayChangedProfileState(with viewModel: EditProfileModels.ChangeProfile.ViewModel) { nicknameAlertLabel.text = viewModel.nicknameAlertDescription - nicknameAlertLabel.textColor = .error - - checkDuplicateNicknameButton.isEnabled = viewModel.canCheckDuplication - + nicknameAlertLabel.isHidden = viewModel.nicknameAlertDescription.count == 0 introduceAlertLabel.text = viewModel.introduceAlertDescription - introduceAlertLabel.textColor = .error + introduceAlertLabel.isHidden = viewModel.introduceAlertDescription.count == 0 - confirmButton.isEnabled = viewModel.canEditProfile && !viewModel.canCheckDuplication - checkDuplicateNicknameButton.isEnabled = viewModel.canCheckDuplication + checkDuplicateNicknameButton.isEnabled = viewModel.canCheckNicknameDuplication + confirmButton.isEnabled = viewModel.canEditProfile } func displayNicknameDuplication(viewModel: Models.CheckNicknameDuplication.ViewModel) { diff --git a/iOS/Layover/Layover/Scenes/Profile/ProfileRouter.swift b/iOS/Layover/Layover/Scenes/Profile/ProfileRouter.swift index 96cf9a5..ac295d6 100644 --- a/iOS/Layover/Layover/Scenes/Profile/ProfileRouter.swift +++ b/iOS/Layover/Layover/Scenes/Profile/ProfileRouter.swift @@ -40,7 +40,7 @@ final class ProfileRouter: ProfileRoutingLogic, ProfileDataPassing { } func routeToSetting() { - let settingViewController: SettingViewController = SettingViewController() + let settingViewController = SettingViewController() viewController?.navigationController?.pushViewController(settingViewController, animated: true) } diff --git a/iOS/Layover/Layover/Scenes/SignUpScene/SignUpPresenter.swift b/iOS/Layover/Layover/Scenes/SignUpScene/SignUpPresenter.swift index 57fcfb2..64a0c61 100644 --- a/iOS/Layover/Layover/Scenes/SignUpScene/SignUpPresenter.swift +++ b/iOS/Layover/Layover/Scenes/SignUpScene/SignUpPresenter.swift @@ -25,7 +25,7 @@ final class SignUpPresenter: SignUpPresentationLogic { func presentNicknameValidation(with response: Models.ValidateNickname.Response) { let viewModel = Models.ValidateNickname.ViewModel(canCheckDuplication: response.nicknameState == .valid, - alertDescription: response.nicknameState.alertDescription) + alertDescription: response.nicknameState.description) viewController?.displayNicknameValidation(response: viewModel) } diff --git a/iOS/Layover/Layover/Workers/Mocks/MockUserWorker.swift b/iOS/Layover/Layover/Workers/Mocks/MockUserWorker.swift index cad599d..ac79aa5 100644 --- a/iOS/Layover/Layover/Workers/Mocks/MockUserWorker.swift +++ b/iOS/Layover/Layover/Workers/Mocks/MockUserWorker.swift @@ -10,7 +10,6 @@ import Foundation import OSLog final class MockUserWorker: UserWorkerProtocol { - // MARK: - Properties @@ -195,4 +194,12 @@ final class MockUserWorker: UserWorkerProtocol { } } + func modifyProfileImage(from fileURL: URL, to url: String) async -> Bool { + return true + } + + func fetchImagePresignedURL(with fileType: String) async -> String? { + return nil + } + } diff --git a/iOS/Layover/Layover/Workers/UserWorker.swift b/iOS/Layover/Layover/Workers/UserWorker.swift index af403bb..bcab7e4 100644 --- a/iOS/Layover/Layover/Workers/UserWorker.swift +++ b/iOS/Layover/Layover/Workers/UserWorker.swift @@ -9,15 +9,15 @@ import Foundation import OSLog -enum NicknameState { +enum NicknameState: CustomStringConvertible { case valid case lessThan2GreaterThan8 case invalidCharacter - var alertDescription: String? { + var description: String { switch self { case .valid: - return nil + return "" case .lessThan2GreaterThan8: return "2자 이상 8자 이하로 입력해주세요." case .invalidCharacter: @@ -30,7 +30,7 @@ protocol UserWorkerProtocol { func validateNickname(_ nickname: String) -> NicknameState func modifyNickname(to nickname: String) async -> String? func checkNotDuplication(for userName: String) async -> Bool? - // func modifyProfileImage() async throws -> URL + func modifyProfileImage(data: Data, to url: String) async -> Bool func modifyIntroduce(to introduce: String) async -> String? func withdraw() async -> String? func logout() @@ -38,6 +38,7 @@ protocol UserWorkerProtocol { func fetchProfile(by id: Int?) async -> Member? func fetchPosts(at page: Int, of id: Int?) async -> [Post]? func fetchImageData(with url: URL) async -> Data? + func fetchImagePresignedURL(with fileType: String) async -> String? } final class UserWorker: UserWorkerProtocol { @@ -95,6 +96,16 @@ final class UserWorker: UserWorkerProtocol { } } + func modifyProfileImage(data: Data, to url: String) async -> Bool { + do { + let responseData = try await provider.upload(data: data, to: url, method: .PUT) + return true + } catch { + os_log(.error, log: .default, "Failed to modify profile image with error: %@", error.localizedDescription) + return false + } + } + func modifyIntroduce(to introduce: String) async -> String? { let endPoint = userEndPointFactory.makeIntroduceModifyEndPoint(introduce: introduce) do { @@ -161,4 +172,14 @@ final class UserWorker: UserWorkerProtocol { return nil } } + + func fetchImagePresignedURL(with fileType: String) async -> String? { + do { + let endPoint = userEndPointFactory.makeFetchUserProfilePresignedURL(of: fileType) + return try await provider.request(with: endPoint).data?.preSignedURL + } catch { + os_log(.error, log: .data, "Error: %s", error.localizedDescription) + return nil + } + } } From 05ebd071d7d258036b7eb0d8c9e938fda5b8f4e9 Mon Sep 17 00:00:00 2001 From: loinsir Date: Fri, 8 Dec 2023 05:14:50 +0900 Subject: [PATCH 02/19] =?UTF-8?q?:sparkles:=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=20=ED=8E=B8=EC=A7=91=20validation=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EditProfile/EditProfileInteractor.swift | 80 ++++++++++++------- .../EditProfile/EditProfileModels.swift | 37 +++++---- .../EditProfile/EditProfilePresenter.swift | 14 +++- .../EditProfileViewController.swift | 53 ++++++++---- .../Workers/Mocks/MockUserWorker.swift | 5 +- 5 files changed, 122 insertions(+), 67 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift index b7e5636..8beb8ea 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift @@ -32,7 +32,9 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor var userWorker: UserWorkerProtocol? var presenter: EditProfilePresentationLogic? - private var isChangedProfileImage = false + private var didCheckedNicknameDuplicate = true + private var nicknameState: NicknameState = .valid + private var introduceState: Models.ChangeProfile.IntroduceLengthState = .valid // MARK: - Data Store @@ -44,28 +46,46 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor func fetchProfile(with request: EditProfileModels.FetchProfile.Request) { presenter?.presentProfile(with: EditProfileModels.FetchProfile.Response(nickname: nickname, - introduce: introduce, - profileImageData: profileImageData)) + introduce: introduce, + profileImageData: profileImageData)) } func changeProfile(with request: Models.ChangeProfile.Request) { - let changedNicknameState = userWorker?.validateNickname(request.nickname ?? "") - let changedIntroduceState = introduceLengthState(of: request.introduce ?? "", by: request.validIntroduceLength) - - // 닉네임이 변경되었는지, 변경되었다면 변경된 닉네임이 유효한지 - let canCheckNicknameDuplication = nickname != request.nickname - && changedNicknameState == .valid - - // 이미지, 닉네임과 자기소개 중 하나라도 변경되었고, 변경된 닉네임과 자기소개가 유효한지 - let canEditProfile = changedNicknameState == .valid && changedIntroduceState == .valid - && (nickname != request.nickname || introduce != request.introduce || request.profileImageData != nil) - + let response: Models.ChangeProfile.Response + + switch request.changedProfileComponent { + case .nickname(let changedNickname): + didCheckedNicknameDuplicate = changedNickname == nickname + nicknameState = userWorker?.validateNickname(changedNickname ?? "") ?? .invalidCharacter + + response = Models.ChangeProfile.Response(nicknameAlertDescription: nicknameState != .valid ? nicknameState.description : nil, + introduceAlertDescription: nil, + canCheckNicknameDuplication: (nicknameState == .valid && changedNickname != nickname), + canEditProfile: false) + + case .introduce(let changedIntroduce): + introduceState = introduceLengthState(of: changedIntroduce ?? "", by: request.validIntroduceLength) + let canEditProfile = changedIntroduce != introduce + && introduceState == .valid + && nicknameState == .valid + && didCheckedNicknameDuplicate + + let introduceAlertDescription = introduce == changedIntroduce || introduceState == .valid ? nil : introduceState.description + + response = Models.ChangeProfile.Response(nicknameAlertDescription: nil, + introduceAlertDescription: introduceAlertDescription, + canCheckNicknameDuplication: nil, + canEditProfile: canEditProfile) - let response = Models.ChangeProfile.Response(newNicknameState: userWorker?.validateNickname(request.nickname ?? "") ?? .valid, - newIntroduceState: introduceLengthState(of: request.introduce ?? "", - by: request.validIntroduceLength), - canCheckNicknameDuplication: canCheckNicknameDuplication, + case .profileImage(_): + let canEditProfile = didCheckedNicknameDuplicate + && nicknameState == .valid + && introduceState == .valid + response = Models.ChangeProfile.Response(nicknameAlertDescription: nil, + introduceAlertDescription: nil, + canCheckNicknameDuplication: nil, canEditProfile: canEditProfile) + } presenter?.presentProfileState(with: response) } @@ -76,8 +96,10 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor os_log(.error, log: .data, "checkDuplication Server Error") return } + didCheckedNicknameDuplicate = response + let canEditProfile = didCheckedNicknameDuplicate && nicknameState == .valid && introduceState == .valid await MainActor.run { - presenter?.presentNicknameDuplication(with: Models.CheckNicknameDuplication.Response(isValid: response)) + presenter?.presentNicknameDuplication(with: Models.CheckNicknameDuplication.Response(isValid: response, canEditProfile: canEditProfile)) } } } @@ -94,20 +116,20 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor } // 프로필 이미지 바꾼 경우 - guard let modifiedProfileImageURL = request.profileImageURL, modifiedProfileImageURL.isFileURL, - let presignedUploadURL = await userWorker?.fetchImagePresignedURL(with: modifiedProfileImageURL.pathExtension), - let modifyProfileImageResponse = await userWorker?.modifyProfileImage(from: modifiedProfileImageURL, - to: presignedUploadURL), - modifyProfileImageResponse == true else { + guard let modifiedProfileImageData = request.profileImageData, + let modifiedProfileImageExtension = request.profileImageExtension, + let presignedUploadURL = await userWorker?.fetchImagePresignedURL(with: modifiedProfileImageExtension), + let modifyProfileImageResponse = await userWorker?.modifyProfileImage(data: modifiedProfileImageData, + to: presignedUploadURL), + modifyProfileImageResponse == true + else { os_log(.error, log: .data, "Edit ProfileImage Error") return false } -// await MainActor.run { -// presenter?.presentProfile(with: Models.EditProfile.Response(nickname: request.nickname, -// introduce: request.introduce ?? "", -// profileImageURL: URL(string: presignedUploadURL))) -// } + await MainActor.run { + presenter?.presentProfile(with: Models.EditProfile.Response()) + } return true } } diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift index 4284cd2..0ec2045 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift @@ -28,6 +28,8 @@ enum EditProfileModels { } enum ChangeProfile { + static let introduceLengthLimit = 30 + enum IntroduceLengthState: CustomStringConvertible { case overLength case valid @@ -35,31 +37,35 @@ enum EditProfileModels { var description: String { switch self { case .overLength: - return "자기소개는 30자 이내로 입력해주세요." + return "자기소개는 \(introduceLengthLimit)자 이내로 입력해주세요." case .valid: return "" } } } + enum ChangedProfileComponent { + case nickname(String?) + case introduce(String?) + case profileImage(Data?) + } + struct Request { - let nickname: String? - let introduce: String? - let profileImageData: Data? - let validIntroduceLength: Int = 50 // default value + let changedProfileComponent: ChangedProfileComponent + let validIntroduceLength: Int = introduceLengthLimit } struct Response { - let newNicknameState: NicknameState - let newIntroduceState: IntroduceLengthState - let canCheckNicknameDuplication: Bool + let nicknameAlertDescription: String? + let introduceAlertDescription: String? + let canCheckNicknameDuplication: Bool? let canEditProfile: Bool } struct ViewModel { - let nicknameAlertDescription: String - let introduceAlertDescription: String - let canCheckNicknameDuplication: Bool + let nicknameAlertDescription: String? + let introduceAlertDescription: String? + let canCheckNicknameDuplication: Bool? let canEditProfile: Bool } } @@ -70,9 +76,11 @@ enum EditProfileModels { } struct Response { let isValid: Bool + let canEditProfile: Bool } struct ViewModel { let isValidNickname: Bool + let canEditProfile: Bool var alertDescription: String { isValidNickname ? "사용가능한 닉네임입니다." : "사용중인 닉네임입니다." } @@ -83,17 +91,14 @@ enum EditProfileModels { struct Request { let nickname: String let introduce: String? - let profileImageURL: URL? + let profileImageData: Data? + let profileImageExtension: String? } struct Response { - let nickname: String - let introduce: String? - let profileImageURL: URL? } struct ViewModel { - } } diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift index d9e5f0d..b41d964 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift @@ -10,8 +10,8 @@ import UIKit protocol EditProfilePresentationLogic { func presentProfile(with response: EditProfileModels.FetchProfile.Response) + func presentProfile(with response: EditProfileModels.EditProfile.Response) func presentProfileState(with response: EditProfileModels.ChangeProfile.Response) - func presentNicknameDuplication(with response: EditProfileModels.CheckNicknameDuplication.Response) } @@ -31,9 +31,13 @@ final class EditProfilePresenter: EditProfilePresentationLogic { viewController?.displayProfile(with: viewModel) } + func presentProfile(with response: EditProfileModels.EditProfile.Response) { + viewController?.displayProfileEditCompleted(with: Models.EditProfile.ViewModel()) + } + func presentProfileState(with response: Models.ChangeProfile.Response) { - let viewModel = Models.ChangeProfile.ViewModel(nicknameAlertDescription: response.newNicknameState.description, - introduceAlertDescription: response.newIntroduceState.description, + let viewModel = Models.ChangeProfile.ViewModel(nicknameAlertDescription: response.nicknameAlertDescription, + introduceAlertDescription: response.introduceAlertDescription, canCheckNicknameDuplication: response.canCheckNicknameDuplication, canEditProfile: response.canEditProfile) @@ -41,7 +45,9 @@ final class EditProfilePresenter: EditProfilePresentationLogic { } func presentNicknameDuplication(with response: EditProfileModels.CheckNicknameDuplication.Response) { - return + let viewModel = Models.CheckNicknameDuplication.ViewModel(isValidNickname: response.isValid, + canEditProfile: response.canEditProfile) + viewController?.displayNicknameDuplication(with: viewModel) } } diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift index 27793e7..2a13b88 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift @@ -13,7 +13,9 @@ import OSLog protocol EditProfileDisplayLogic: AnyObject { func displayProfile(with viewModel: EditProfileModels.FetchProfile.ViewModel) + func displayProfileEditCompleted(with viewModel: EditProfileModels.EditProfile.ViewModel) func displayChangedProfileState(with viewModel: EditProfileModels.ChangeProfile.ViewModel) + func displayNicknameDuplication(with viewModel: EditProfileModels.CheckNicknameDuplication.ViewModel) } final class EditProfileViewController: BaseViewController { @@ -47,7 +49,7 @@ final class EditProfileViewController: BaseViewController { private lazy var nicknameTextfield: LOTextField = { let textField = LOTextField() textField.placeholder = "닉네임을 입력해주세요." - textField.addTarget(self, action: #selector(profileInfoChanged), for: .editingChanged) + textField.addTarget(self, action: #selector(nicknameChanged(_:)), for: .editingChanged) return textField }() @@ -61,7 +63,7 @@ final class EditProfileViewController: BaseViewController { private lazy var introduceTextfield: LOTextField = { let textField = LOTextField() textField.placeholder = "소개를 입력해주세요." - textField.addTarget(self, action: #selector(profileInfoChanged), for: .editingChanged) + textField.addTarget(self, action: #selector(introduceChanged(_:)), for: .editingChanged) return textField }() @@ -93,7 +95,7 @@ final class EditProfileViewController: BaseViewController { guard let self = self else { return } self.changedProfileImageData = nil self.profileImageView.image = UIImage.profile - self.profileInfoChanged() + self.profileImageDataChanged() } let albumAction = UIAlertAction(title: "앨범에서 선택", style: .default) { [weak self] _ in guard let self else { return } @@ -110,6 +112,7 @@ final class EditProfileViewController: BaseViewController { var router: (EditProfileRoutingLogic & EditProfileDataPassing)? var interactor: EditProfileBusinessLogic? + private var isValidNickname = true private var changedProfileImageData: Data? private var changedProfileImageExtension: String? @@ -184,15 +187,20 @@ final class EditProfileViewController: BaseViewController { confirmButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), confirmButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16) ]) + } + private func profileImageDataChanged() { + interactor?.changeProfile(with: Models.ChangeProfile.Request(changedProfileComponent: .profileImage(changedProfileImageData))) } // MARK: - Actions - @objc private func profileInfoChanged() { - interactor?.changeProfile(with: Models.ChangeProfile.Request(nickname: nicknameTextfield.text, - introduce: introduceTextfield.text, - profileImageData: changedProfileImageData)) + @objc private func nicknameChanged(_ sender: Any?) { + interactor?.changeProfile(with: Models.ChangeProfile.Request(changedProfileComponent: .nickname(nicknameTextfield.text))) + } + + @objc private func introduceChanged(_ sender: Any?) { + interactor?.changeProfile(with: Models.ChangeProfile.Request(changedProfileComponent: .introduce(introduceTextfield.text))) } @objc private func checkDuplicateNicknameButtonDidTap() { @@ -206,6 +214,18 @@ final class EditProfileViewController: BaseViewController { @objc private func editProfileImageButtonDidTap() { self.present(phPickerViewController, animated: true) } + + @objc private func completeButtonDidTap() { + guard let nickname = nicknameTextfield.text, + let introduce = introduceTextfield.text + else { return } + + let request = Models.EditProfile.Request(nickname: nickname, + introduce: introduce, + profileImageData: changedProfileImageData, + profileImageExtension: changedProfileImageExtension) + interactor?.editProfile(with: request) + } } extension EditProfileViewController: PHPickerViewControllerDelegate { @@ -219,7 +239,7 @@ extension EditProfileViewController: PHPickerViewControllerDelegate { await MainActor.run { self.profileImageView.image = image as? UIImage self.changedProfileImageData = (image as? UIImage)?.jpegData(compressionQuality: 1.0) - self.profileInfoChanged() + self.profileImageDataChanged() } } } @@ -230,7 +250,6 @@ extension EditProfileViewController: PHPickerViewControllerDelegate { // MARK: - Display Logic extension EditProfileViewController: EditProfileDisplayLogic { - func displayProfile(with viewModel: Models.FetchProfile.ViewModel) { nicknameTextfield.text = viewModel.nickname if let introduce = viewModel.introduce { @@ -244,23 +263,27 @@ extension EditProfileViewController: EditProfileDisplayLogic { } } + func displayProfileEditCompleted(with viewModel: EditProfileModels.EditProfile.ViewModel) { + Toast.shared.showToast(message: "프로필 변경이 반영되었습니다.") + } + func displayChangedProfileState(with viewModel: EditProfileModels.ChangeProfile.ViewModel) { nicknameAlertLabel.text = viewModel.nicknameAlertDescription - nicknameAlertLabel.isHidden = viewModel.nicknameAlertDescription.count == 0 + nicknameAlertLabel.isHidden = viewModel.nicknameAlertDescription == nil introduceAlertLabel.text = viewModel.introduceAlertDescription - introduceAlertLabel.isHidden = viewModel.introduceAlertDescription.count == 0 + introduceAlertLabel.isHidden = viewModel.introduceAlertDescription == nil - checkDuplicateNicknameButton.isEnabled = viewModel.canCheckNicknameDuplication + checkDuplicateNicknameButton.isEnabled = viewModel.canCheckNicknameDuplication == true confirmButton.isEnabled = viewModel.canEditProfile } - func displayNicknameDuplication(viewModel: Models.CheckNicknameDuplication.ViewModel) { + func displayNicknameDuplication(with viewModel: Models.CheckNicknameDuplication.ViewModel) { nicknameAlertLabel.isHidden = false nicknameAlertLabel.text = viewModel.alertDescription nicknameAlertLabel.textColor = viewModel.isValidNickname ? .correct : .error - confirmButton.isEnabled = viewModel.isValidNickname + confirmButton.isEnabled = viewModel.canEditProfile + isValidNickname = viewModel.isValidNickname } - } #Preview { diff --git a/iOS/Layover/Layover/Workers/Mocks/MockUserWorker.swift b/iOS/Layover/Layover/Workers/Mocks/MockUserWorker.swift index ac79aa5..5d40cbc 100644 --- a/iOS/Layover/Layover/Workers/Mocks/MockUserWorker.swift +++ b/iOS/Layover/Layover/Workers/Mocks/MockUserWorker.swift @@ -194,12 +194,11 @@ final class MockUserWorker: UserWorkerProtocol { } } - func modifyProfileImage(from fileURL: URL, to url: String) async -> Bool { - return true + func modifyProfileImage(data: Data, to url: String) async -> Bool { + true } func fetchImagePresignedURL(with fileType: String) async -> String? { return nil } - } From 09720d32893c910e2b533456ab59bd61c5cc78d8 Mon Sep 17 00:00:00 2001 From: loinsir Date: Fri, 8 Dec 2023 05:18:34 +0900 Subject: [PATCH 03/19] =?UTF-8?q?:bug:=20=EC=99=84=EB=A3=8C=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layover/Scenes/EditProfile/EditProfileConfigurator.swift | 2 +- .../Layover/Scenes/EditProfile/EditProfileViewController.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileConfigurator.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileConfigurator.swift index 41676d7..d9871f7 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileConfigurator.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileConfigurator.swift @@ -20,7 +20,7 @@ final class EditProfileConfigurator: Configurator { let viewController = viewController let interactor = EditProfileInteractor() let presenter = EditProfilePresenter() - let worker = MockUserWorker() + let worker = UserWorker() let router = EditProfileRouter() router.viewController = viewController diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift index 2a13b88..ae24394 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift @@ -86,6 +86,7 @@ final class EditProfileViewController: BaseViewController { let button = LOButton(style: .basic) button.isEnabled = false button.setTitle("완료", for: .normal) + button.addTarget(self, action: #selector(confirmButtonDidTap), for: .touchUpInside) return button }() @@ -215,7 +216,7 @@ final class EditProfileViewController: BaseViewController { self.present(phPickerViewController, animated: true) } - @objc private func completeButtonDidTap() { + @objc private func confirmButtonDidTap() { guard let nickname = nicknameTextfield.text, let introduce = introduceTextfield.text else { return } From 33bf5e9312d1aeab35af25a1c121cbc026676a25 Mon Sep 17 00:00:00 2001 From: loinsir Date: Fri, 8 Dec 2023 05:22:30 +0900 Subject: [PATCH 04/19] =?UTF-8?q?:bug:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20validation=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layover/Scenes/EditProfile/EditProfileViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift index ae24394..7abee48 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift @@ -274,7 +274,7 @@ extension EditProfileViewController: EditProfileDisplayLogic { introduceAlertLabel.text = viewModel.introduceAlertDescription introduceAlertLabel.isHidden = viewModel.introduceAlertDescription == nil - checkDuplicateNicknameButton.isEnabled = viewModel.canCheckNicknameDuplication == true + checkDuplicateNicknameButton.isEnabled = viewModel.canCheckNicknameDuplication == true ? true : checkDuplicateNicknameButton.isEnabled confirmButton.isEnabled = viewModel.canEditProfile } From e6b4d78b99b50493dd9ef8b455fd151e95febaa5 Mon Sep 17 00:00:00 2001 From: loinsir Date: Fri, 8 Dec 2023 05:25:16 +0900 Subject: [PATCH 05/19] =?UTF-8?q?:sparkles:=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=8E=B8=EC=A7=91=20=EC=95=A1?= =?UTF-8?q?=EC=85=98=EC=8B=9C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layover/Scenes/EditProfile/EditProfileViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift index 7abee48..f8b7234 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift @@ -213,7 +213,7 @@ final class EditProfileViewController: BaseViewController { } @objc private func editProfileImageButtonDidTap() { - self.present(phPickerViewController, animated: true) + self.present(editProfileImageController, animated: true) } @objc private func confirmButtonDidTap() { From 63a02a140a15b37105c144f11e1ce8eacf6e4c32 Mon Sep 17 00:00:00 2001 From: loinsir Date: Fri, 8 Dec 2023 23:53:50 +0900 Subject: [PATCH 06/19] =?UTF-8?q?:bug:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=EC=B2=B4=ED=81=AC=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94=20=EC=A1=B0=EA=B1=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EditProfile/EditProfileInteractor.swift | 26 ++++++++++--------- .../EditProfileViewController.swift | 9 +++---- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift index 8beb8ea..e6f8a6d 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift @@ -58,10 +58,12 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor didCheckedNicknameDuplicate = changedNickname == nickname nicknameState = userWorker?.validateNickname(changedNickname ?? "") ?? .invalidCharacter + let canCheckNicknameDuplication = changedNickname == nickname ? false : nicknameState == .valid + response = Models.ChangeProfile.Response(nicknameAlertDescription: nicknameState != .valid ? nicknameState.description : nil, - introduceAlertDescription: nil, - canCheckNicknameDuplication: (nicknameState == .valid && changedNickname != nickname), - canEditProfile: false) + introduceAlertDescription: nil, + canCheckNicknameDuplication: canCheckNicknameDuplication, + canEditProfile: false) case .introduce(let changedIntroduce): introduceState = introduceLengthState(of: changedIntroduce ?? "", by: request.validIntroduceLength) @@ -116,15 +118,15 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor } // 프로필 이미지 바꾼 경우 - guard let modifiedProfileImageData = request.profileImageData, - let modifiedProfileImageExtension = request.profileImageExtension, - let presignedUploadURL = await userWorker?.fetchImagePresignedURL(with: modifiedProfileImageExtension), - let modifyProfileImageResponse = await userWorker?.modifyProfileImage(data: modifiedProfileImageData, - to: presignedUploadURL), - modifyProfileImageResponse == true - else { - os_log(.error, log: .data, "Edit ProfileImage Error") - return false + if let modifiedProfileImageData = request.profileImageData, + let modifiedProfileImageExtension = request.profileImageExtension, + let presignedUploadURL = await userWorker?.fetchImagePresignedURL(with: modifiedProfileImageExtension) { + let modifyProfileImageResponse = await userWorker?.modifyProfileImage(data: modifiedProfileImageData, + to: presignedUploadURL) + guard await modifyProfileImageResponse != nil else { + os_log(.error, log: .data, "Edit ProfileImage Error") + return false + } } await MainActor.run { diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift index f8b7234..8985e7a 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift @@ -273,8 +273,9 @@ extension EditProfileViewController: EditProfileDisplayLogic { nicknameAlertLabel.isHidden = viewModel.nicknameAlertDescription == nil introduceAlertLabel.text = viewModel.introduceAlertDescription introduceAlertLabel.isHidden = viewModel.introduceAlertDescription == nil - - checkDuplicateNicknameButton.isEnabled = viewModel.canCheckNicknameDuplication == true ? true : checkDuplicateNicknameButton.isEnabled + if let canCheckNicknameDuplication = viewModel.canCheckNicknameDuplication { + checkDuplicateNicknameButton.isEnabled = canCheckNicknameDuplication + } confirmButton.isEnabled = viewModel.canEditProfile } @@ -286,7 +287,3 @@ extension EditProfileViewController: EditProfileDisplayLogic { isValidNickname = viewModel.isValidNickname } } - -#Preview { - EditProfileViewController() -} From 383780f1aaed83d6bc2b766d6333068b04a657d3 Mon Sep 17 00:00:00 2001 From: loinsir Date: Sat, 9 Dec 2023 00:24:44 +0900 Subject: [PATCH 07/19] =?UTF-8?q?:bug:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=99=84=EB=A3=8C=20=ED=9B=84=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20=EB=B2=84=ED=8A=BC=20status=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layover/Scenes/EditProfile/EditProfileViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift index 8985e7a..2bd6d03 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift @@ -266,6 +266,7 @@ extension EditProfileViewController: EditProfileDisplayLogic { func displayProfileEditCompleted(with viewModel: EditProfileModels.EditProfile.ViewModel) { Toast.shared.showToast(message: "프로필 변경이 반영되었습니다.") + confirmButton.isEnabled = false } func displayChangedProfileState(with viewModel: EditProfileModels.ChangeProfile.ViewModel) { From 248aa3a79a96e2d54a307588243ff52722d55120 Mon Sep 17 00:00:00 2001 From: loinsir Date: Sat, 9 Dec 2023 00:28:30 +0900 Subject: [PATCH 08/19] =?UTF-8?q?:bug:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=ED=9B=84=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=ED=83=AD=20=EB=8F=8C=EC=95=84=EC=98=AC=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=ED=91=9C=EC=8B=9C=20=EC=95=88?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover/Scenes/Profile/ProfileInteractor.swift | 4 +++- .../Layover/Scenes/Profile/ProfileViewController.swift | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/iOS/Layover/Layover/Scenes/Profile/ProfileInteractor.swift b/iOS/Layover/Layover/Scenes/Profile/ProfileInteractor.swift index 401848a..1789ddb 100644 --- a/iOS/Layover/Layover/Scenes/Profile/ProfileInteractor.swift +++ b/iOS/Layover/Layover/Scenes/Profile/ProfileInteractor.swift @@ -54,7 +54,9 @@ final class ProfileInteractor: ProfileBusinessLogic, ProfileDataStore { @discardableResult func fetchProfile(with request: ProfileModels.FetchProfile.Request) -> Task { - Task { + fetchPostsPage = 1 + canFetchMorePosts = true + return Task { guard let userProfile = await userWorker?.fetchProfile(by: profileId) else { return false } diff --git a/iOS/Layover/Layover/Scenes/Profile/ProfileViewController.swift b/iOS/Layover/Layover/Scenes/Profile/ProfileViewController.swift index a2af250..9d9ad62 100644 --- a/iOS/Layover/Layover/Scenes/Profile/ProfileViewController.swift +++ b/iOS/Layover/Layover/Scenes/Profile/ProfileViewController.swift @@ -72,6 +72,10 @@ final class ProfileViewController: BaseViewController { super.viewDidLoad() setNavigationBar() setDataSource() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) fetchProfile() } From e31e68fbe86c6dd2739da7140592a452ac2267e3 Mon Sep 17 00:00:00 2001 From: loinsir Date: Sat, 9 Dec 2023 00:50:41 +0900 Subject: [PATCH 09/19] =?UTF-8?q?:wrench:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=84=B1=EA=B3=B5=20=EC=97=AC=EB=B6=80=20?= =?UTF-8?q?=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EditProfile/EditProfileInteractor.swift | 42 +++++++++++++++---- .../EditProfile/EditProfileModels.swift | 2 + .../EditProfile/EditProfilePresenter.swift | 3 +- .../EditProfileViewController.swift | 2 +- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift index e6f8a6d..be04af7 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift @@ -105,15 +105,17 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor } } } - + @discardableResult func editProfile(with request: Models.EditProfile.Request) -> Task { Task { - async let modifyNicknameResponse = userWorker?.modifyNickname(to: request.nickname) - async let modifyIntroduceResponse = userWorker?.modifyIntroduce(to: request.introduce ?? "") + async let isSuccessNicknameEdit = nicknameEditRequest(into: request.nickname) + async let isSuccessIntroduceEdit = introduceEditRequest(into: request.introduce) - guard await modifyNicknameResponse != nil, await modifyIntroduceResponse != nil else { - os_log(.error, log: .data, "Edit Profile Error") + guard await isSuccessNicknameEdit, await isSuccessIntroduceEdit else { + await MainActor.run { + presenter?.presentProfile(with: Models.EditProfile.Response(isSuccess: false)) + } return false } @@ -121,21 +123,43 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor if let modifiedProfileImageData = request.profileImageData, let modifiedProfileImageExtension = request.profileImageExtension, let presignedUploadURL = await userWorker?.fetchImagePresignedURL(with: modifiedProfileImageExtension) { - let modifyProfileImageResponse = await userWorker?.modifyProfileImage(data: modifiedProfileImageData, - to: presignedUploadURL) - guard await modifyProfileImageResponse != nil else { + guard let modifyProfileImageResponse = await userWorker?.modifyProfileImage(data: modifiedProfileImageData, + to: presignedUploadURL) else { os_log(.error, log: .data, "Edit ProfileImage Error") + await MainActor.run { + presenter?.presentProfile(with: Models.EditProfile.Response(isSuccess: false)) + } return false } } await MainActor.run { - presenter?.presentProfile(with: Models.EditProfile.Response()) + presenter?.presentProfile(with: Models.EditProfile.Response(isSuccess: true)) } return true } } + private func nicknameEditRequest(into nickname: String) async -> Bool { + if self.nickname != nickname { + guard let response = await userWorker?.modifyNickname(to: nickname) else { + os_log(.error, log: .data, "Edit Profile Error") + return false + } + self.nickname = nickname + } + return true + } + + private func introduceEditRequest(into introduce: String?) async -> Bool { + guard let modifyIntroduceResponse = await userWorker?.modifyIntroduce(to: introduce ?? "") else { + os_log(.error, log: .data, "Edit Profile Error") + return false + } + self.introduce = introduce + return true + } + private func introduceLengthState(of introduce: String, by length: Int) -> Models.ChangeProfile.IntroduceLengthState { introduce.count <= length ? .valid : .overLength } diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift index 0ec2045..975232b 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift @@ -96,9 +96,11 @@ enum EditProfileModels { } struct Response { + let isSuccess: Bool } struct ViewModel { + let toastMessage: String } } diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift index b41d964..24430ac 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift @@ -32,7 +32,8 @@ final class EditProfilePresenter: EditProfilePresentationLogic { } func presentProfile(with response: EditProfileModels.EditProfile.Response) { - viewController?.displayProfileEditCompleted(with: Models.EditProfile.ViewModel()) + let toastMessage = response.isSuccess ? "프로필이 수정되었습니다." : "프로필 수정에 실패했습니다." + viewController?.displayProfileEditCompleted(with: Models.EditProfile.ViewModel(toastMessage: toastMessage)) } func presentProfileState(with response: Models.ChangeProfile.Response) { diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift index 2bd6d03..ae5cf31 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift @@ -265,7 +265,7 @@ extension EditProfileViewController: EditProfileDisplayLogic { } func displayProfileEditCompleted(with viewModel: EditProfileModels.EditProfile.ViewModel) { - Toast.shared.showToast(message: "프로필 변경이 반영되었습니다.") + Toast.shared.showToast(message: viewModel.toastMessage) confirmButton.isEnabled = false } From eb7afdb30ca5abdff71e243b2eaa77eb85c4a29c Mon Sep 17 00:00:00 2001 From: loinsir Date: Sat, 9 Dec 2023 01:50:05 +0900 Subject: [PATCH 10/19] =?UTF-8?q?:wrench:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=B3=80=EA=B2=BD=20=EC=8B=9C?= =?UTF-8?q?=EB=8F=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EditProfile/EditProfileInteractor.swift | 42 ++++++++++++------- .../EditProfileViewController.swift | 41 +++++++++++++----- 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift index be04af7..4dfe0da 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift @@ -68,9 +68,9 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor case .introduce(let changedIntroduce): introduceState = introduceLengthState(of: changedIntroduce ?? "", by: request.validIntroduceLength) let canEditProfile = changedIntroduce != introduce - && introduceState == .valid - && nicknameState == .valid - && didCheckedNicknameDuplicate + && introduceState == .valid + && nicknameState == .valid + && didCheckedNicknameDuplicate let introduceAlertDescription = introduce == changedIntroduce || introduceState == .valid ? nil : introduceState.description @@ -81,8 +81,8 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor case .profileImage(_): let canEditProfile = didCheckedNicknameDuplicate - && nicknameState == .valid - && introduceState == .valid + && nicknameState == .valid + && introduceState == .valid response = Models.ChangeProfile.Response(nicknameAlertDescription: nil, introduceAlertDescription: nil, canCheckNicknameDuplication: nil, @@ -105,11 +105,27 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor } } } - + + private func profileImageEditRequest(into imageData: Data, fileExtension: String) async -> Bool { + guard let presignedUploadURL = await userWorker?.fetchImagePresignedURL(with: fileExtension), + await userWorker?.modifyProfileImage(data: imageData, to: presignedUploadURL) != nil else { + os_log(.error, log: .data, "Edit ProfileImage Error") + await MainActor.run { + presenter?.presentProfile(with: Models.EditProfile.Response(isSuccess: false)) + } + return false + } + + await MainActor.run { + presenter?.presentProfile(with: Models.EditProfile.Response(isSuccess: true)) + } + return true + } + @discardableResult func editProfile(with request: Models.EditProfile.Request) -> Task { Task { - async let isSuccessNicknameEdit = nicknameEditRequest(into: request.nickname) + async let isSuccessNicknameEdit = nicknameEditRequest(into: request.nickname) async let isSuccessIntroduceEdit = introduceEditRequest(into: request.introduce) guard await isSuccessNicknameEdit, await isSuccessIntroduceEdit else { @@ -119,18 +135,16 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor return false } - // 프로필 이미지 바꾼 경우 - if let modifiedProfileImageData = request.profileImageData, - let modifiedProfileImageExtension = request.profileImageExtension, - let presignedUploadURL = await userWorker?.fetchImagePresignedURL(with: modifiedProfileImageExtension) { - guard let modifyProfileImageResponse = await userWorker?.modifyProfileImage(data: modifiedProfileImageData, - to: presignedUploadURL) else { - os_log(.error, log: .data, "Edit ProfileImage Error") + // 이미지 변경 시도 + if let profileImageData = request.profileImageData, let profileImageExtension = request.profileImageExtension { + guard await profileImageEditRequest(into: profileImageData, fileExtension: profileImageExtension) else { await MainActor.run { presenter?.presentProfile(with: Models.EditProfile.Response(isSuccess: false)) } return false } + } else { // 이미지 변경이 없는 경우 이미지 삭제 시도 + _ = await userWorker?.fetchImagePresignedURL(with: "jpeg") } await MainActor.run { diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift index ae5cf31..9042bfd 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift @@ -95,6 +95,7 @@ final class EditProfileViewController: BaseViewController { let defaultAction = UIAlertAction(title: "기본 이미지로 변경", style: .default) { [weak self] _ in guard let self = self else { return } self.changedProfileImageData = nil + self.changedProfileImageExtension = nil self.profileImageView.image = UIImage.profile self.profileImageDataChanged() } @@ -231,18 +232,36 @@ final class EditProfileViewController: BaseViewController { extension EditProfileViewController: PHPickerViewControllerDelegate { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { - picker.dismiss(animated: true) - let item = results.first?.itemProvider - if let item = item, item.canLoadObject(ofClass: UIImage.self) { - item.loadObject(ofClass: UIImage.self) { [weak self] (image, _) in - guard let self else { return } - Task { - await MainActor.run { - self.profileImageView.image = image as? UIImage - self.changedProfileImageData = (image as? UIImage)?.jpegData(compressionQuality: 1.0) - self.profileImageDataChanged() + picker.dismiss(animated: true) { + let item = results.first?.itemProvider + if let item = item, item.canLoadObject(ofClass: UIImage.self) { + item.loadFileRepresentation(for: .image) { url, Bool, error in + if let error { + os_log(.error, log: .ui, "%@", error.localizedDescription) } - } + + if let url { // item.loadFileRepresentation에서 주는 url은 클로저가 끝나면 없어지는 temporary file url이므로, 복사해서 사용한다. + let fileData = FileManager.default.contents(atPath: url.path()) + let pathExtension = url.pathExtension + let temporaryCopyFileName = UUID().uuidString + ".\(pathExtension)" + let temporaryCopyFileURL = FileManager.default.temporaryDirectory.appendingPathComponent(temporaryCopyFileName) + do { + try FileManager.default.copyItem(atPath: url.path(), + toPath: temporaryCopyFileURL.path()) + Task { + await MainActor.run { + self.profileImageView.image = UIImage(contentsOfFile: temporaryCopyFileURL.path()) + self.changedProfileImageExtension = pathExtension + self.changedProfileImageData = try? Data(contentsOf: temporaryCopyFileURL) + self.profileImageDataChanged() + } + try FileManager.default.removeItem(at: temporaryCopyFileURL) + } + } catch { + os_log(.error, log: .data, "%@", error.localizedDescription) + } + } + }.resume() } } } From 54593b54ec58196ffcf8b7c532e70f16f84e5645 Mon Sep 17 00:00:00 2001 From: loinsir Date: Sat, 9 Dec 2023 01:53:46 +0900 Subject: [PATCH 11/19] =?UTF-8?q?:wrench:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=82=AD=EC=A0=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scenes/EditProfile/EditProfileInteractor.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift index 4dfe0da..ab26db1 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift @@ -113,6 +113,7 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor await MainActor.run { presenter?.presentProfile(with: Models.EditProfile.Response(isSuccess: false)) } + profileImageData = imageData return false } @@ -144,7 +145,13 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor return false } } else { // 이미지 변경이 없는 경우 이미지 삭제 시도 - _ = await userWorker?.fetchImagePresignedURL(with: "jpeg") + guard await userWorker?.fetchImagePresignedURL(with: "jpeg") != nil else { + await MainActor.run { + presenter?.presentProfile(with: Models.EditProfile.Response(isSuccess: false)) + } + return false + } + profileImageData = nil } await MainActor.run { From 3345df523e2c9b72e736b8175a3acb7666456c81 Mon Sep 17 00:00:00 2001 From: loinsir Date: Sat, 9 Dec 2023 13:42:22 +0900 Subject: [PATCH 12/19] =?UTF-8?q?:bug:=20presignedURL=20body=20parameter?= =?UTF-8?q?=20key=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Network/EndPoint/Factories/UserEndPointFactory.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Layover/Layover/Network/EndPoint/Factories/UserEndPointFactory.swift b/iOS/Layover/Layover/Network/EndPoint/Factories/UserEndPointFactory.swift index 189f6cf..32d2a2a 100644 --- a/iOS/Layover/Layover/Network/EndPoint/Factories/UserEndPointFactory.swift +++ b/iOS/Layover/Layover/Network/EndPoint/Factories/UserEndPointFactory.swift @@ -92,7 +92,7 @@ final class DefaultUserEndPointFactory: UserEndPointFactory { func makeFetchUserProfilePresignedURL(of fileType: String) -> EndPoint> { var bodyParameters = [String: String]() - bodyParameters.updateValue(fileType, forKey: "fileType") + bodyParameters.updateValue(fileType, forKey: "filetype") return EndPoint( path: "/member/profile-image/presigned-url", From 3ca6269974ccbf14f08205940a74040ae7210e3b Mon Sep 17 00:00:00 2001 From: loinsir Date: Sat, 9 Dec 2023 14:02:34 +0900 Subject: [PATCH 13/19] =?UTF-8?q?:bug:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover/Network/Provider/Provider.swift | 13 +++++++------ .../Scenes/EditProfile/EditProfileInteractor.swift | 3 ++- iOS/Layover/Layover/Workers/UserWorker.swift | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/iOS/Layover/Layover/Network/Provider/Provider.swift b/iOS/Layover/Layover/Network/Provider/Provider.swift index d933fdc..382aa9d 100644 --- a/iOS/Layover/Layover/Network/Provider/Provider.swift +++ b/iOS/Layover/Layover/Network/Provider/Provider.swift @@ -12,8 +12,7 @@ protocol ProviderType { func request(with endPoint: E, authenticationIfNeeded: Bool, retryCount: Int) async throws -> R where E.Response == R func request(url: URL) async throws -> Data func request(url: String) async throws -> Data - func upload(data: Data, to url: String, method: HTTPMethod) async throws -> Data - func upload(from fileURL: URL, to url: String, method: HTTPMethod) async throws -> Data + func upload(data: Data, to presignedURL: String, method: HTTPMethod) async throws -> Data func upload(fromFile: URL, to url: String, method: HTTPMethod, @@ -30,9 +29,9 @@ extension ProviderType { retryCount: retryCount) } - func upload(data: Data, to url: String, method: HTTPMethod = .PUT) async throws -> Data { + func upload(data: Data, to presignedURL: String, method: HTTPMethod = .PUT) async throws -> Data { return try await upload(data: data, - to: url, + to: presignedURL, method: method) } @@ -131,10 +130,12 @@ class Provider: ProviderType { } // 이미지 업로드용 - func upload(data: Data, to url: String, method: HTTPMethod = .PUT) async throws -> Data { - guard let url = URL(string: url) else { throw NetworkError.components } + func upload(data: Data, to presignedURL: String, method: HTTPMethod = .PUT) async throws -> Data { + guard let contentType = URLComponents(string: presignedURL)?.queryItems?.first(where: { $0.name == "Content-Type"} )?.value, + let url = URL(string: presignedURL) else { throw NetworkError.components } var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData) request.httpMethod = method.rawValue + request.setValue(contentType, forHTTPHeaderField: "Content-Type") let (data, response) = try await session.upload(for: request, from: data) try self.checkStatusCode(of: response) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift index ab26db1..05ac269 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift @@ -138,7 +138,8 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor // 이미지 변경 시도 if let profileImageData = request.profileImageData, let profileImageExtension = request.profileImageExtension { - guard await profileImageEditRequest(into: profileImageData, fileExtension: profileImageExtension) else { + let imageExtension = profileImageExtension == "jpg" ? "jpeg" : profileImageExtension // 서버에서는 jpeg로 받음 + guard await profileImageEditRequest(into: profileImageData, fileExtension: imageExtension) else { await MainActor.run { presenter?.presentProfile(with: Models.EditProfile.Response(isSuccess: false)) } diff --git a/iOS/Layover/Layover/Workers/UserWorker.swift b/iOS/Layover/Layover/Workers/UserWorker.swift index bcab7e4..e9c3bfb 100644 --- a/iOS/Layover/Layover/Workers/UserWorker.swift +++ b/iOS/Layover/Layover/Workers/UserWorker.swift @@ -98,7 +98,7 @@ final class UserWorker: UserWorkerProtocol { func modifyProfileImage(data: Data, to url: String) async -> Bool { do { - let responseData = try await provider.upload(data: data, to: url, method: .PUT) + let responseData = try await provider.upload(data: data, to: url) return true } catch { os_log(.error, log: .default, "Failed to modify profile image with error: %@", error.localizedDescription) From 50bceff9bb028d49abc03712baf524aba74d47a7 Mon Sep 17 00:00:00 2001 From: loinsir Date: Sat, 9 Dec 2023 20:13:38 +0900 Subject: [PATCH 14/19] =?UTF-8?q?:sparkles:=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=9C=20=EA=B8=B0=EB=8A=A5=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover/Network/DTOs/MemberDTO.swift | 9 +++++++-- .../EndPoint/Factories/UserEndPointFactory.swift | 8 ++++++++ .../EditProfile/EditProfileInteractor.swift | 2 +- .../Layover/Workers/Mocks/MockUserWorker.swift | 4 ++++ iOS/Layover/Layover/Workers/UserWorker.swift | 15 ++++++++++++++- 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/iOS/Layover/Layover/Network/DTOs/MemberDTO.swift b/iOS/Layover/Layover/Network/DTOs/MemberDTO.swift index e58a073..5c2e331 100644 --- a/iOS/Layover/Layover/Network/DTOs/MemberDTO.swift +++ b/iOS/Layover/Layover/Network/DTOs/MemberDTO.swift @@ -11,7 +11,7 @@ import Foundation struct MemberDTO: Decodable { let id: Int let username, introduce: String - let profileImageURL: String + let profileImageURL: String? enum CodingKeys: String, CodingKey { case id, username, introduce @@ -21,11 +21,16 @@ struct MemberDTO: Decodable { extension MemberDTO { func toDomain() -> Member { + var imageURL: URL? = nil + if let profileImageURL { + imageURL = URL(string: profileImageURL) + } + return Member( identifier: id, username: username, introduce: introduce, - profileImageURL: URL(string: profileImageURL) + profileImageURL: imageURL ) } } diff --git a/iOS/Layover/Layover/Network/EndPoint/Factories/UserEndPointFactory.swift b/iOS/Layover/Layover/Network/EndPoint/Factories/UserEndPointFactory.swift index 32d2a2a..8c09757 100644 --- a/iOS/Layover/Layover/Network/EndPoint/Factories/UserEndPointFactory.swift +++ b/iOS/Layover/Layover/Network/EndPoint/Factories/UserEndPointFactory.swift @@ -15,6 +15,7 @@ protocol UserEndPointFactory { func makeUserWithDrawEndPoint() -> EndPoint> func makeUserInformationEndPoint(with id: Int?) -> EndPoint> func makeUserPostsEndPoint(at page: Int, of id: Int?) -> EndPoint> + func makeUserProfileImageDefaultEndPoint() -> EndPoint> func makeFetchUserProfilePresignedURL(of fileType: String) -> EndPoint> } @@ -90,6 +91,13 @@ final class DefaultUserEndPointFactory: UserEndPointFactory { ) } + func makeUserProfileImageDefaultEndPoint() -> EndPoint> { + return EndPoint( + path: "/member/profile-image/default", + method: .POST + ) + } + func makeFetchUserProfilePresignedURL(of fileType: String) -> EndPoint> { var bodyParameters = [String: String]() bodyParameters.updateValue(fileType, forKey: "filetype") diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift index 05ac269..c39e996 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift @@ -146,7 +146,7 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor return false } } else { // 이미지 변경이 없는 경우 이미지 삭제 시도 - guard await userWorker?.fetchImagePresignedURL(with: "jpeg") != nil else { + guard await userWorker?.setProfileImageDefault() == true else { await MainActor.run { presenter?.presentProfile(with: Models.EditProfile.Response(isSuccess: false)) } diff --git a/iOS/Layover/Layover/Workers/Mocks/MockUserWorker.swift b/iOS/Layover/Layover/Workers/Mocks/MockUserWorker.swift index 5d40cbc..b33a182 100644 --- a/iOS/Layover/Layover/Workers/Mocks/MockUserWorker.swift +++ b/iOS/Layover/Layover/Workers/Mocks/MockUserWorker.swift @@ -194,6 +194,10 @@ final class MockUserWorker: UserWorkerProtocol { } } + func setProfileImageDefault() async -> Bool { + true + } + func modifyProfileImage(data: Data, to url: String) async -> Bool { true } diff --git a/iOS/Layover/Layover/Workers/UserWorker.swift b/iOS/Layover/Layover/Workers/UserWorker.swift index e9c3bfb..436191d 100644 --- a/iOS/Layover/Layover/Workers/UserWorker.swift +++ b/iOS/Layover/Layover/Workers/UserWorker.swift @@ -38,6 +38,7 @@ protocol UserWorkerProtocol { func fetchProfile(by id: Int?) async -> Member? func fetchPosts(at page: Int, of id: Int?) async -> [Post]? func fetchImageData(with url: URL) async -> Data? + func setProfileImageDefault() async -> Bool func fetchImagePresignedURL(with fileType: String) async -> String? } @@ -139,7 +140,7 @@ final class UserWorker: UserWorkerProtocol { func logout() { authManager.logout() } - + func fetchProfile(by id: Int?) async -> Member? { let endPoint = userEndPointFactory.makeUserInformationEndPoint(with: id) @@ -173,6 +174,18 @@ final class UserWorker: UserWorkerProtocol { } } + func setProfileImageDefault() async -> Bool { + let endPoint = userEndPointFactory.makeUserProfileImageDefaultEndPoint() + + do { + let response = try await provider.request(with: endPoint) + return true + } catch { + os_log(.error, log: .data, "Error: %s", error.localizedDescription) + return false + } + } + func fetchImagePresignedURL(with fileType: String) async -> String? { do { let endPoint = userEndPointFactory.makeFetchUserProfilePresignedURL(of: fileType) From 4954c48011f36f5b8477a17c151056beeda3e080 Mon Sep 17 00:00:00 2001 From: loinsir Date: Sat, 9 Dec 2023 20:18:38 +0900 Subject: [PATCH 15/19] =?UTF-8?q?:wrench:=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover/Network/DTOs/UploadVideoDTO.swift | 8 -------- .../EndPoint/Factories/UploadPostEndPointFactory.swift | 4 ++-- iOS/Layover/Layover/Network/Provider/Provider.swift | 8 ++++---- .../Scenes/EditProfile/EditProfileViewController.swift | 5 ++--- iOS/Layover/Layover/Workers/UserWorker.swift | 2 +- 5 files changed, 9 insertions(+), 18 deletions(-) diff --git a/iOS/Layover/Layover/Network/DTOs/UploadVideoDTO.swift b/iOS/Layover/Layover/Network/DTOs/UploadVideoDTO.swift index 0d8160a..e6d25c3 100644 --- a/iOS/Layover/Layover/Network/DTOs/UploadVideoDTO.swift +++ b/iOS/Layover/Layover/Network/DTOs/UploadVideoDTO.swift @@ -8,14 +8,6 @@ import Foundation -struct UploadVideoDTO: Decodable { - let preSignedURL: String - - enum CodingKeys: String, CodingKey { - case preSignedURL = "preSignedUrl" - } -} - struct UploadVideoRequestDTO: Encodable { let boardID: Int let filetype: String diff --git a/iOS/Layover/Layover/Network/EndPoint/Factories/UploadPostEndPointFactory.swift b/iOS/Layover/Layover/Network/EndPoint/Factories/UploadPostEndPointFactory.swift index 8345709..41cc75e 100644 --- a/iOS/Layover/Layover/Network/EndPoint/Factories/UploadPostEndPointFactory.swift +++ b/iOS/Layover/Layover/Network/EndPoint/Factories/UploadPostEndPointFactory.swift @@ -14,7 +14,7 @@ protocol UploadPostEndPointFactory { latitude: Double, longitude: Double, tag: [String]) -> EndPoint> - func makeUploadVideoEndPoint(boardID: Int, fileType: String) -> EndPoint> + func makeUploadVideoEndPoint(boardID: Int, fileType: String) -> EndPoint> } final class DefaultUploadPostEndPointFactory: UploadPostEndPointFactory { @@ -33,7 +33,7 @@ final class DefaultUploadPostEndPointFactory: UploadPostEndPointFactory { tag: tag)) } - func makeUploadVideoEndPoint(boardID: Int, fileType: String) -> EndPoint> { + func makeUploadVideoEndPoint(boardID: Int, fileType: String) -> EndPoint> { return EndPoint(path: "/board/presigned-url", method: .POST, bodyParameters: UploadVideoRequestDTO(boardID: boardID, diff --git a/iOS/Layover/Layover/Network/Provider/Provider.swift b/iOS/Layover/Layover/Network/Provider/Provider.swift index 382aa9d..89bf2d1 100644 --- a/iOS/Layover/Layover/Network/Provider/Provider.swift +++ b/iOS/Layover/Layover/Network/Provider/Provider.swift @@ -14,10 +14,10 @@ protocol ProviderType { func request(url: String) async throws -> Data func upload(data: Data, to presignedURL: String, method: HTTPMethod) async throws -> Data func upload(fromFile: URL, - to url: String, - method: HTTPMethod, - sessionTaskDelegate: URLSessionTaskDelegate?, - delegateQueue: OperationQueue?) async throws -> Data + to url: String, + method: HTTPMethod, + sessionTaskDelegate: URLSessionTaskDelegate?, + delegateQueue: OperationQueue?) async throws -> Data } extension ProviderType { diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift index 9042bfd..10e02b4 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift @@ -93,7 +93,7 @@ final class EditProfileViewController: BaseViewController { private lazy var editProfileImageController: UIAlertController = { let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) let defaultAction = UIAlertAction(title: "기본 이미지로 변경", style: .default) { [weak self] _ in - guard let self = self else { return } + guard let self else { return } self.changedProfileImageData = nil self.changedProfileImageExtension = nil self.profileImageView.image = UIImage.profile @@ -235,13 +235,12 @@ extension EditProfileViewController: PHPickerViewControllerDelegate { picker.dismiss(animated: true) { let item = results.first?.itemProvider if let item = item, item.canLoadObject(ofClass: UIImage.self) { - item.loadFileRepresentation(for: .image) { url, Bool, error in + item.loadFileRepresentation(for: .image) { url, _, error in if let error { os_log(.error, log: .ui, "%@", error.localizedDescription) } if let url { // item.loadFileRepresentation에서 주는 url은 클로저가 끝나면 없어지는 temporary file url이므로, 복사해서 사용한다. - let fileData = FileManager.default.contents(atPath: url.path()) let pathExtension = url.pathExtension let temporaryCopyFileName = UUID().uuidString + ".\(pathExtension)" let temporaryCopyFileURL = FileManager.default.temporaryDirectory.appendingPathComponent(temporaryCopyFileName) diff --git a/iOS/Layover/Layover/Workers/UserWorker.swift b/iOS/Layover/Layover/Workers/UserWorker.swift index 436191d..35405cd 100644 --- a/iOS/Layover/Layover/Workers/UserWorker.swift +++ b/iOS/Layover/Layover/Workers/UserWorker.swift @@ -178,7 +178,7 @@ final class UserWorker: UserWorkerProtocol { let endPoint = userEndPointFactory.makeUserProfileImageDefaultEndPoint() do { - let response = try await provider.request(with: endPoint) + let _ = try await provider.request(with: endPoint) return true } catch { os_log(.error, log: .data, "Error: %s", error.localizedDescription) From 777c6661239d3e1923ce2f77a9e6f068c00b7654 Mon Sep 17 00:00:00 2001 From: loinsir Date: Sat, 9 Dec 2023 20:22:36 +0900 Subject: [PATCH 16/19] =?UTF-8?q?:wrench:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EA=B2=B0=EA=B3=BC=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=EC=95=88=EB=82=B4=20=EB=AC=B8=EA=B5=AC=20=EC=83=89?= =?UTF-8?q?=EC=83=81=20=ED=91=9C=EC=8B=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scenes/EditProfile/EditProfileInteractor.swift | 9 ++++++--- .../Layover/Scenes/EditProfile/EditProfileModels.swift | 2 ++ .../Scenes/EditProfile/EditProfilePresenter.swift | 3 ++- .../Scenes/EditProfile/EditProfileViewController.swift | 1 + 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift index c39e996..cabf984 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift @@ -60,7 +60,8 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor let canCheckNicknameDuplication = changedNickname == nickname ? false : nicknameState == .valid - response = Models.ChangeProfile.Response(nicknameAlertDescription: nicknameState != .valid ? nicknameState.description : nil, + response = Models.ChangeProfile.Response(nicknameState: nicknameState, + nicknameAlertDescription: nicknameState != .valid ? nicknameState.description : nil, introduceAlertDescription: nil, canCheckNicknameDuplication: canCheckNicknameDuplication, canEditProfile: false) @@ -74,7 +75,8 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor let introduceAlertDescription = introduce == changedIntroduce || introduceState == .valid ? nil : introduceState.description - response = Models.ChangeProfile.Response(nicknameAlertDescription: nil, + response = Models.ChangeProfile.Response(nicknameState: nicknameState, + nicknameAlertDescription: nil, introduceAlertDescription: introduceAlertDescription, canCheckNicknameDuplication: nil, canEditProfile: canEditProfile) @@ -83,7 +85,8 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor let canEditProfile = didCheckedNicknameDuplicate && nicknameState == .valid && introduceState == .valid - response = Models.ChangeProfile.Response(nicknameAlertDescription: nil, + response = Models.ChangeProfile.Response(nicknameState: nicknameState, + nicknameAlertDescription: nil, introduceAlertDescription: nil, canCheckNicknameDuplication: nil, canEditProfile: canEditProfile) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift index 975232b..1852f1b 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift @@ -56,6 +56,7 @@ enum EditProfileModels { } struct Response { + let nicknameState: NicknameState let nicknameAlertDescription: String? let introduceAlertDescription: String? let canCheckNicknameDuplication: Bool? @@ -63,6 +64,7 @@ enum EditProfileModels { } struct ViewModel { + let nicknameState: NicknameState let nicknameAlertDescription: String? let introduceAlertDescription: String? let canCheckNicknameDuplication: Bool? diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift index 24430ac..13f72d6 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift @@ -37,7 +37,8 @@ final class EditProfilePresenter: EditProfilePresentationLogic { } func presentProfileState(with response: Models.ChangeProfile.Response) { - let viewModel = Models.ChangeProfile.ViewModel(nicknameAlertDescription: response.nicknameAlertDescription, + let viewModel = Models.ChangeProfile.ViewModel(nicknameState: response.nicknameState, + nicknameAlertDescription: response.nicknameAlertDescription, introduceAlertDescription: response.introduceAlertDescription, canCheckNicknameDuplication: response.canCheckNicknameDuplication, canEditProfile: response.canEditProfile) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift index 10e02b4..30feeaa 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift @@ -290,6 +290,7 @@ extension EditProfileViewController: EditProfileDisplayLogic { func displayChangedProfileState(with viewModel: EditProfileModels.ChangeProfile.ViewModel) { nicknameAlertLabel.text = viewModel.nicknameAlertDescription nicknameAlertLabel.isHidden = viewModel.nicknameAlertDescription == nil + nicknameAlertLabel.textColor = viewModel.nicknameState == .valid ? .correct : .error introduceAlertLabel.text = viewModel.introduceAlertDescription introduceAlertLabel.isHidden = viewModel.introduceAlertDescription == nil if let canCheckNicknameDuplication = viewModel.canCheckNicknameDuplication { From 90ec979fcd078ef1b713c95fdd0286523c9d4c6c Mon Sep 17 00:00:00 2001 From: loinsir Date: Sat, 9 Dec 2023 21:00:02 +0900 Subject: [PATCH 17/19] =?UTF-8?q?:wrench:=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover/Network/DTOs/MemberDTO.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Layover/Layover/Network/DTOs/MemberDTO.swift b/iOS/Layover/Layover/Network/DTOs/MemberDTO.swift index 5c2e331..f31ba19 100644 --- a/iOS/Layover/Layover/Network/DTOs/MemberDTO.swift +++ b/iOS/Layover/Layover/Network/DTOs/MemberDTO.swift @@ -21,7 +21,7 @@ struct MemberDTO: Decodable { extension MemberDTO { func toDomain() -> Member { - var imageURL: URL? = nil + var imageURL: URL? if let profileImageURL { imageURL = URL(string: profileImageURL) } From e3da5df12194ef8f94e3555bdae91284005931b1 Mon Sep 17 00:00:00 2001 From: loinsir Date: Sat, 9 Dec 2023 21:12:46 +0900 Subject: [PATCH 18/19] =?UTF-8?q?:wrench:=20=EB=A6=B0=ED=8A=B8=EA=B2=BD?= =?UTF-8?q?=EA=B3=A0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover.xcodeproj/project.pbxproj | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index fbe1b64..64b7a74 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -1042,15 +1042,14 @@ isa = PBXNativeTarget; buildConfigurationList = FC7E45602AFEB62C004F155A /* Build configuration list for PBXNativeTarget "Layover" */; buildPhases = ( + FC182BD22B1D90C700051CBC /* SwiftLint Script */, FC7E45322AFEB623004F155A /* Sources */, FC7E45332AFEB623004F155A /* Frameworks */, FC7E45342AFEB623004F155A /* Resources */, - FC182BD22B1D90C700051CBC /* SwiftLint Script */, ); buildRules = ( ); dependencies = ( - 194552192B03DD7A00299768 /* PBXTargetDependency */, ); name = Layover; packageProductDependencies = ( @@ -1377,10 +1376,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 194552192B03DD7A00299768 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - productRef = 194552182B03DD7A00299768 /* SwiftLintPlugin */; - }; FC7E454E2AFEB62C004F155A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = FC7E45352AFEB623004F155A /* Layover */; @@ -1668,14 +1663,6 @@ kind = branch; }; }; - FC7E456D2AFF6E44004F155A /* XCRemoteSwiftPackageReference "SwiftLint" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/realm/SwiftLint"; - requirement = { - branch = main; - kind = branch; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1684,11 +1671,6 @@ package = 194551FB2B03863B00299768 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; productName = KakaoSDK; }; - 194552182B03DD7A00299768 /* SwiftLintPlugin */ = { - isa = XCSwiftPackageProductDependency; - package = FC7E456D2AFF6E44004F155A /* XCRemoteSwiftPackageReference "SwiftLint" */; - productName = "plugin:SwiftLintPlugin"; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = FC7E452E2AFEB623004F155A /* Project object */; From abda7b7b1a0b9aa7d8281caf49303c0d981698cb Mon Sep 17 00:00:00 2001 From: loinsir Date: Sat, 9 Dec 2023 21:46:06 +0900 Subject: [PATCH 19/19] =?UTF-8?q?:wrench:=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layover/Scenes/EditProfile/EditProfileInteractor.swift | 6 +++--- .../Layover/Scenes/EditProfile/EditProfileModels.swift | 2 +- .../Layover/Scenes/EditProfile/EditProfilePresenter.swift | 6 +++--- .../Scenes/EditProfile/EditProfileViewController.swift | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift index cabf984..0e4c507 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileInteractor.swift @@ -10,7 +10,7 @@ import Foundation import OSLog protocol EditProfileBusinessLogic { - func fetchProfile(with request: EditProfileModels.FetchProfile.Request) + func setProfile(with request: EditProfileModels.SetProfile.Request) func changeProfile(with request: EditProfileModels.ChangeProfile.Request) func checkDuplication(with request: EditProfileModels.CheckNicknameDuplication.Request) @discardableResult @@ -44,8 +44,8 @@ final class EditProfileInteractor: EditProfileBusinessLogic, EditProfileDataStor // MARK: - Use Case - func fetchProfile(with request: EditProfileModels.FetchProfile.Request) { - presenter?.presentProfile(with: EditProfileModels.FetchProfile.Response(nickname: nickname, + func setProfile(with request: EditProfileModels.SetProfile.Request) { + presenter?.presentProfile(with: EditProfileModels.SetProfile.Response(nickname: nickname, introduce: introduce, profileImageData: profileImageData)) } diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift index 1852f1b..2ed4719 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileModels.swift @@ -10,7 +10,7 @@ import Foundation enum EditProfileModels { - enum FetchProfile { + enum SetProfile { struct Request { } diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift index 13f72d6..39aa93c 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfilePresenter.swift @@ -9,7 +9,7 @@ import UIKit protocol EditProfilePresentationLogic { - func presentProfile(with response: EditProfileModels.FetchProfile.Response) + func presentProfile(with response: EditProfileModels.SetProfile.Response) func presentProfile(with response: EditProfileModels.EditProfile.Response) func presentProfileState(with response: EditProfileModels.ChangeProfile.Response) func presentNicknameDuplication(with response: EditProfileModels.CheckNicknameDuplication.Response) @@ -24,8 +24,8 @@ final class EditProfilePresenter: EditProfilePresentationLogic { // MARK: - Methods - func presentProfile(with response: Models.FetchProfile.Response) { - let viewModel = Models.FetchProfile.ViewModel(nickname: response.nickname, + func presentProfile(with response: Models.SetProfile.Response) { + let viewModel = Models.SetProfile.ViewModel(nickname: response.nickname, introduce: response.introduce, profileImageData: response.profileImageData) viewController?.displayProfile(with: viewModel) diff --git a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift index 30feeaa..c72417d 100644 --- a/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift +++ b/iOS/Layover/Layover/Scenes/EditProfile/EditProfileViewController.swift @@ -12,7 +12,7 @@ import PhotosUI import OSLog protocol EditProfileDisplayLogic: AnyObject { - func displayProfile(with viewModel: EditProfileModels.FetchProfile.ViewModel) + func displayProfile(with viewModel: EditProfileModels.SetProfile.ViewModel) func displayProfileEditCompleted(with viewModel: EditProfileModels.EditProfile.ViewModel) func displayChangedProfileState(with viewModel: EditProfileModels.ChangeProfile.ViewModel) func displayNicknameDuplication(with viewModel: EditProfileModels.CheckNicknameDuplication.ViewModel) @@ -141,7 +141,7 @@ final class EditProfileViewController: BaseViewController { override func setUI() { super.setUI() title = "프로필 수정" - interactor?.fetchProfile(with: Models.FetchProfile.Request()) + interactor?.setProfile(with: Models.SetProfile.Request()) } override func setConstraints() { @@ -269,7 +269,7 @@ extension EditProfileViewController: PHPickerViewControllerDelegate { // MARK: - Display Logic extension EditProfileViewController: EditProfileDisplayLogic { - func displayProfile(with viewModel: Models.FetchProfile.ViewModel) { + func displayProfile(with viewModel: Models.SetProfile.ViewModel) { nicknameTextfield.text = viewModel.nickname if let introduce = viewModel.introduce { introduceTextfield.text = introduce