Skip to content

Commit

Permalink
feat/#187 프로필 이미지 서버 연동
Browse files Browse the repository at this point in the history
  • Loading branch information
hooni0918 committed Jul 16, 2024
1 parent 542f36f commit e20c7c6
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 9 deletions.
12 changes: 10 additions & 2 deletions KkuMulKum.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
789AD4B52C3C0147002E2688 /* ResissueResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */; };
789D73A72C46AF4900C7077D /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73A62C46AF4900C7077D /* KeychainService.swift */; };
789D73A92C46BBB000C7077D /* PagePromiseSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73A82C46BBB000C7077D /* PagePromiseSegmentedControl.swift */; };
789D73AB2C46BEFF00C7077D /* ProfileTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73AA2C46BEFF00C7077D /* ProfileTargetType.swift */; };
789D73AD2C46C19500C7077D /* ProfileModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73AC2C46C19500C7077D /* ProfileModel.swift */; };
78AED1342C3D951F000AD80A /* NicknameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1332C3D951F000AD80A /* NicknameViewController.swift */; };
78AED1372C3D98D1000AD80A /* NicknameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1362C3D98D1000AD80A /* NicknameView.swift */; };
78B9286C2C29402C006D9942 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B9286B2C29402C006D9942 /* AppDelegate.swift */; };
Expand Down Expand Up @@ -207,6 +209,8 @@
789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResissueResponseModel.swift; sourceTree = "<group>"; };
789D73A62C46AF4900C7077D /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = "<group>"; };
789D73A82C46BBB000C7077D /* PagePromiseSegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagePromiseSegmentedControl.swift; sourceTree = "<group>"; };
789D73AA2C46BEFF00C7077D /* ProfileTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTargetType.swift; sourceTree = "<group>"; };
789D73AC2C46C19500C7077D /* ProfileModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModel.swift; sourceTree = "<group>"; };
78AED1312C3D94F2000AD80A /* KkuMulKum.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KkuMulKum.entitlements; sourceTree = "<group>"; };
78AED1332C3D951F000AD80A /* NicknameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameViewController.swift; sourceTree = "<group>"; };
78AED1362C3D98D1000AD80A /* NicknameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -521,6 +525,7 @@
children = (
78BD61262C446A97005752FD /* LoginTargetType.swift */,
78BD61352C463B82005752FD /* NicknameTargetType.swift */,
789D73AA2C46BEFF00C7077D /* ProfileTargetType.swift */,
);
path = TargetType;
sourceTree = "<group>";
Expand Down Expand Up @@ -946,8 +951,8 @@
DD865B652C3920F600C351A2 /* Onboarding */ = {
isa = PBXGroup;
children = (
DDFA50782C4693BD000A62E2 /* Profile */,
782B40762C3E389F008B0CA7 /* Welcome */,
DDFA50782C4693BD000A62E2 /* Profile */,
78AED1322C3D9514000AD80A /* Nickname */,
DD865B662C39210E00C351A2 /* Login */,
);
Expand Down Expand Up @@ -1006,8 +1011,8 @@
DDFA50782C4693BD000A62E2 /* Profile */ = {
isa = PBXGroup;
children = (
DDFA50792C4693BD000A62E2 /* ViewModel */,
DDFA507B2C4693BD000A62E2 /* View */,
DDFA50792C4693BD000A62E2 /* ViewModel */,
DDFA507D2C4693BD000A62E2 /* VIewController */,
);
path = Profile;
Expand Down Expand Up @@ -1274,6 +1279,7 @@
789AD4B22C3C0093002E2688 /* SocialLoginResponseModel.swift */,
789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */,
78BD61372C463C8C005752FD /* NicknameModel.swift */,
789D73AC2C46C19500C7077D /* ProfileModel.swift */,
);
path = Auth;
sourceTree = "<group>";
Expand Down Expand Up @@ -1567,6 +1573,7 @@
78B9286E2C29402C006D9942 /* SceneDelegate.swift in Sources */,
DD39766F2C41B54400E2A4C4 /* InviteCodeService.swift in Sources */,
DE6D4D172C3F14D80005584B /* MeetingInfoViewModel.swift in Sources */,
789D73AD2C46C19500C7077D /* ProfileModel.swift in Sources */,
78AED1372C3D98D1000AD80A /* NicknameView.swift in Sources */,
A3FB185B2C3BF7DF001483E5 /* MeetingMembersResponseModel.swift in Sources */,
DD3072222C3C0DA300416D9F /* PromiseParticipantListResponseModel.swift in Sources */,
Expand All @@ -1587,6 +1594,7 @@
DD39766B2C41995A00E2A4C4 /* FinishCreateNavigationView.swift in Sources */,
DD3072202C3C0D4500416D9F /* MyReadyStatusResponseModel.swift in Sources */,
DD931B6E2C3DA27F00526452 /* ParticipantCollectionViewCell.swift in Sources */,
789D73AB2C46BEFF00C7077D /* ProfileTargetType.swift in Sources */,
DD3976882C41C2AD00E2A4C4 /* UpcomingEmptyView.swift in Sources */,
DD931B762C3DC16100526452 /* PromiseInfoView.swift in Sources */,
DD3072242C3C0EB200416D9F /* MyPromiseReadyInfoRequestModel.swift in Sources */,
Expand Down
12 changes: 12 additions & 0 deletions KkuMulKum/Network/DTO/Model/Auth/ProfileModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// ProfileModel.swift
// KkuMulKum
//
// Created by 이지훈 on 7/16/24.
//

import Foundation

struct ProfileImageResponseModel: ResponseModelType {
// 성공 시 data가 null
}
59 changes: 59 additions & 0 deletions KkuMulKum/Network/DTO/TargetType/ProfileTargetType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// ProfileTargetType.swift
// KkuMulKum
//
// Created by 이지훈 on 7/16/24.
//

import Foundation

import Moya

enum ProfileTargetType {
///jpg 고정이 아닌 jpg, png, webp 같이 받기위함
case updateProfileImage(image: Data, fileName: String, mimeType: String)
}

extension ProfileTargetType: TargetType {
var baseURL: URL {
guard let privacyInfo = Bundle.main.privacyInfo,
let urlString = privacyInfo["BASE_URL"] as? String,
let url = URL(string: urlString) else {
fatalError("Invalid BASE_URL in PrivacyInfo.plist")
}
return url
}

var path: String {
return "/api/v1/users/me/image"
}

var method: Moya.Method {
return .patch
}

var task: Task {
switch self {
case .updateProfileImage(let imageData, let fileName, let mimeType):
let formData: [MultipartFormData] = [
MultipartFormData(
provider: .data(imageData),
name: "image",
fileName: fileName,
mimeType: mimeType
)
]
return .uploadMultipart(formData)
}
}

var headers: [String : String]? {
guard let token = DefaultKeychainService.shared.accessToken else {
return ["Content-Type": "multipart/form-data"]
}
return [
"Authorization": "Bearer \(token)",
"Content-Type": "multipart/form-data"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class LoginViewController: BaseViewController {
print("Login State: Not logged in")
case .login:
print("Login State: Logged in with user info: ")
owner.navigateToMainScreen()
owner.navigateToOnboardingScreen()
case .needOnboarding:
print("Login State: Need onboarding")
owner.navigateToOnboardingScreen()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,18 @@ class ProfileSetupViewController: BaseViewController {
}

@objc private func confirmButtonTapped() {
let welcomeVC = WelcomeViewController(
viewModel: WelcomeViewModel(nickname: viewModel.nickname)
)
welcomeVC.modalPresentationStyle = .fullScreen
present(welcomeVC, animated: true, completion: nil)
}
viewModel.uploadProfileImage { [weak self] success in
if success {
DispatchQueue.main.async {
let welcomeVC = WelcomeViewController(
viewModel: WelcomeViewModel(nickname: self?.viewModel.nickname ?? "")
)
welcomeVC.modalPresentationStyle = .fullScreen
self?.present(welcomeVC, animated: true, completion: nil)
}
}
}
}

@objc private func skipButtonTapped() {
let welcomeVC = WelcomeViewController(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@

import UIKit

import Moya
import MobileCoreServices

class ProfileSetupViewModel {
let profileImage = ObservablePattern<UIImage?>(UIImage.imgProfile)
let isConfirmButtonEnabled = ObservablePattern<Bool>(false)
let nickname: String
let serverResponse = ObservablePattern<String?>(nil)

private let provider = MoyaProvider<ProfileTargetType>()

init(nickname: String) {
self.nickname = nickname
Expand All @@ -20,4 +26,67 @@ class ProfileSetupViewModel {
profileImage.value = image
isConfirmButtonEnabled.value = image != nil
}

func uploadProfileImage(completion: @escaping (Bool) -> Void) {
print("uploadProfileImage 함수 호출됨")
guard let image = profileImage.value,
let imageData = image.jpegData(compressionQuality: 0.8) else {
print("이미지 변환 실패")
serverResponse.value = "이미지 변환 중 오류가 발생했습니다."
completion(false)
return
}

print("이미지 데이터 크기: \(imageData.count) bytes")

let fileName = "profile_image.jpg"
let mimeType = "image/jpeg"

provider.request(.updateProfileImage(image: imageData, fileName: fileName, mimeType: mimeType)) { [weak self] result in
print("네트워크 요청 완료")
switch result {
case .success(let response):
print("서버 응답 상태 코드: \(response.statusCode)")
print("서버 응답 데이터: \(String(data: response.data, encoding: .utf8) ?? "디코딩 불가")")
do {
let decodedResponse = try JSONDecoder().decode(
ResponseBodyDTO<ProfileImageResponseModel>.self,
from: response.data
)
if decodedResponse.success {
self?.serverResponse.value = "프로필 이미지가 성공적으로 업로드되었습니다."
print("프로필 이미지 업로드 성공")
completion(true)
} else {
if let errorCode = decodedResponse.error?.code {
switch errorCode {
case 40080:
self?.serverResponse.value = "이미지 확장자는 jpg, png, webp만 가능합니다."
case 40081:
self?.serverResponse.value = "이미지 사이즈는 5MB를 넘을 수 없습니다."
case 40420:
self?.serverResponse.value = "유저를 찾을 수 없습니다."
default:
self?.serverResponse.value = decodedResponse.error?.message ??
"알 수 없는 오류가 발생했습니다."
}
} else {
self?.serverResponse.value = decodedResponse.error?.message ??
"알 수 없는 오류가 발생했습니다."
}
print("프로필 이미지 업로드 실패: \(self?.serverResponse.value ?? "")")
completion(false)
}
} catch {
self?.serverResponse.value = "데이터 디코딩 중 오류가 발생했습니다."
print("데이터 디코딩 오류: \(error)")
completion(false)
}
case .failure(let error):
self?.serverResponse.value = "네트워크 오류: \(error.localizedDescription)"
print("네트워크 오류: \(error.localizedDescription)")
completion(false)
}
}
}
}

0 comments on commit e20c7c6

Please sign in to comment.