diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index 49bf08c4..2a50094e 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -53,6 +53,10 @@ 789AD4B32C3C0093002E2688 /* SocialLoginResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789AD4B22C3C0093002E2688 /* SocialLoginResponseModel.swift */; }; 789AD4B52C3C0147002E2688 /* ResissueResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */; }; 789D73AF2C46D99B00C7077D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 789D73AE2C46D99B00C7077D /* GoogleService-Info.plist */; }; + 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 */; }; @@ -63,8 +67,9 @@ 78BD61272C446A97005752FD /* LoginTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61262C446A97005752FD /* LoginTargetType.swift */; }; 78BD612B2C4550A6005752FD /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD612A2C4550A6005752FD /* Bundle.swift */; }; 78BD612F2C4561B9005752FD /* KeychainAccessible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD612E2C4561B9005752FD /* KeychainAccessible.swift */; }; - 78BD61312C456799005752FD /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61302C456799005752FD /* KeychainService.swift */; }; 78BD61342C45B4A7005752FD /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61332C45B4A7005752FD /* AuthService.swift */; }; + 78BD61362C463B82005752FD /* NicknameTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61352C463B82005752FD /* NicknameTargetType.swift */; }; + 78BD61382C463C8C005752FD /* NicknameModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61372C463C8C005752FD /* NicknameModel.swift */; }; A3DD9C3D2C41BAD000E58A13 /* MeetingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3DD9C322C41BAD000E58A13 /* MeetingTableViewCell.swift */; }; A3DD9C3F2C41BAD000E58A13 /* MeetingListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3DD9C362C41BAD000E58A13 /* MeetingListView.swift */; }; A3DD9C402C41BAD000E58A13 /* MeetingListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3DD9C382C41BAD000E58A13 /* MeetingListViewController.swift */; }; @@ -205,6 +210,10 @@ 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResissueResponseModel.swift; sourceTree = ""; }; 789D73AE2C46D99B00C7077D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 789D73B02C46DACD00C7077D /* KkuMulKum.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KkuMulKum.entitlements; sourceTree = ""; }; + 789D73A62C46AF4900C7077D /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = ""; }; + 789D73A82C46BBB000C7077D /* PagePromiseSegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagePromiseSegmentedControl.swift; sourceTree = ""; }; + 789D73AA2C46BEFF00C7077D /* ProfileTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTargetType.swift; sourceTree = ""; }; + 789D73AC2C46C19500C7077D /* ProfileModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModel.swift; sourceTree = ""; }; 78AED1332C3D951F000AD80A /* NicknameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameViewController.swift; sourceTree = ""; }; 78AED1362C3D98D1000AD80A /* NicknameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameView.swift; sourceTree = ""; }; 78B928682C29402C006D9942 /* KkuMulKum.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KkuMulKum.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -216,8 +225,9 @@ 78BD61262C446A97005752FD /* LoginTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginTargetType.swift; sourceTree = ""; }; 78BD612A2C4550A6005752FD /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; 78BD612E2C4561B9005752FD /* KeychainAccessible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainAccessible.swift; sourceTree = ""; }; - 78BD61302C456799005752FD /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = ""; }; 78BD61332C45B4A7005752FD /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; + 78BD61352C463B82005752FD /* NicknameTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameTargetType.swift; sourceTree = ""; }; + 78BD61372C463C8C005752FD /* NicknameModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameModel.swift; sourceTree = ""; }; A3DD9C322C41BAD000E58A13 /* MeetingTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeetingTableViewCell.swift; sourceTree = ""; }; A3DD9C362C41BAD000E58A13 /* MeetingListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeetingListView.swift; sourceTree = ""; }; A3DD9C382C41BAD000E58A13 /* MeetingListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeetingListViewController.swift; sourceTree = ""; }; @@ -282,7 +292,6 @@ DD931B752C3DC16100526452 /* PromiseInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromiseInfoView.swift; sourceTree = ""; }; DDA2EE722C385EB9007C6059 /* MainTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = ""; }; DDAF1C832C3D5D19008A37D3 /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; }; - DDAF1C892C3D6E3D008A37D3 /* PagePromiseSegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagePromiseSegmentedControl.swift; sourceTree = ""; }; DDAF1C8A2C3D6E3D008A37D3 /* PagePromiseViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagePromiseViewModel.swift; sourceTree = ""; }; DDAF1C8B2C3D6E3D008A37D3 /* TardyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TardyViewController.swift; sourceTree = ""; }; DDAF1C8C2C3D6E3D008A37D3 /* PromiseInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromiseInfoViewController.swift; sourceTree = ""; }; @@ -517,6 +526,8 @@ isa = PBXGroup; children = ( 78BD61262C446A97005752FD /* LoginTargetType.swift */, + 78BD61352C463B82005752FD /* NicknameTargetType.swift */, + 789D73AA2C46BEFF00C7077D /* ProfileTargetType.swift */, ); path = TargetType; sourceTree = ""; @@ -533,19 +544,11 @@ isa = PBXGroup; children = ( 78BD612E2C4561B9005752FD /* KeychainAccessible.swift */, - 78BD61302C456799005752FD /* KeychainService.swift */, + 789D73A62C46AF4900C7077D /* KeychainService.swift */, ); path = KeyChain; sourceTree = ""; }; - 78BD61322C45B4A7005752FD /* Service */ = { - isa = PBXGroup; - children = ( - 78BD61332C45B4A7005752FD /* AuthService.swift */, - ); - path = Service; - sourceTree = ""; - }; A3DD9C372C41BAD000E58A13 /* View */ = { isa = PBXGroup; children = ( @@ -584,6 +587,7 @@ A3DD9C642C45B30600E58A13 /* Service */ = { isa = PBXGroup; children = ( + 78BD61332C45B4A7005752FD /* AuthService.swift */, A3DD9C612C455E3300E58A13 /* MeetingListService.swift */, ); path = Service; @@ -705,7 +709,7 @@ DD3976902C41CACF00E2A4C4 /* View */ = { isa = PBXGroup; children = ( - DDAF1C892C3D6E3D008A37D3 /* PagePromiseSegmentedControl.swift */, + 789D73A82C46BBB000C7077D /* PagePromiseSegmentedControl.swift */, ); path = View; sourceTree = ""; @@ -949,8 +953,8 @@ DD865B652C3920F600C351A2 /* Onboarding */ = { isa = PBXGroup; children = ( - DDFA50782C4693BD000A62E2 /* Profile */, 782B40762C3E389F008B0CA7 /* Welcome */, + DDFA50782C4693BD000A62E2 /* Profile */, 78AED1322C3D9514000AD80A /* Nickname */, DD865B662C39210E00C351A2 /* Login */, ); @@ -960,7 +964,6 @@ DD865B662C39210E00C351A2 /* Login */ = { isa = PBXGroup; children = ( - 78BD61322C45B4A7005752FD /* Service */, 789873372C3D1B4800435E96 /* ViewController */, 789873362C3D1B3900435E96 /* VIewModel */, 789873352C3D1B3000435E96 /* View */, @@ -1010,8 +1013,8 @@ DDFA50782C4693BD000A62E2 /* Profile */ = { isa = PBXGroup; children = ( - DDFA50792C4693BD000A62E2 /* ViewModel */, DDFA507B2C4693BD000A62E2 /* View */, + DDFA50792C4693BD000A62E2 /* ViewModel */, DDFA507D2C4693BD000A62E2 /* VIewController */, ); path = Profile; @@ -1278,6 +1281,8 @@ DE9E18912C3BCC9D00DB76B4 /* SocialLoginRequestModel.swift */, 789AD4B22C3C0093002E2688 /* SocialLoginResponseModel.swift */, 789AD4B42C3C0147002E2688 /* ResissueResponseModel.swift */, + 78BD61372C463C8C005752FD /* NicknameModel.swift */, + 789D73AC2C46C19500C7077D /* ProfileModel.swift */, ); path = Auth; sourceTree = ""; @@ -1487,6 +1492,7 @@ DED5DBEC2C345210006ECE7E /* BaseViewController.swift in Sources */, DE6D4D142C3F14D80005584B /* MeetingPromiseCell.swift in Sources */, DD3976892C41C2AD00E2A4C4 /* TodayEmptyView.swift in Sources */, + 789D73A72C46AF4900C7077D /* KeychainService.swift in Sources */, DD43937C2C412F4500EC1799 /* InviteCodeViewController.swift in Sources */, DD4393782C412F4500EC1799 /* CheckInviteCodeView.swift in Sources */, DD8626622C4606A300E4F980 /* ReadyStatusViewModel.swift in Sources */, @@ -1510,6 +1516,7 @@ DEBA03312C3C2972002ED8F2 /* ViewController.swift in Sources */, DD43937D2C412F4500EC1799 /* CheckInviteCodeViewController.swift in Sources */, DD4393772C412F4500EC1799 /* CreateMeetingView.swift in Sources */, + 78BD61362C463B82005752FD /* NicknameTargetType.swift in Sources */, 789873342C3D1A7B00435E96 /* LoginView.swift in Sources */, 782B40822C3E4925008B0CA7 /* NicknameViewModel.swift in Sources */, A3FB18592C3BF77D001483E5 /* MeetingInfoResponseModel.swift in Sources */, @@ -1528,9 +1535,11 @@ DDAF1C922C3D6E3D008A37D3 /* PromiseInfoViewController.swift in Sources */, 78AED1342C3D951F000AD80A /* NicknameViewController.swift in Sources */, DE254AB42C31199B00A4015E /* UITextField+.swift in Sources */, + 78BD61382C463C8C005752FD /* NicknameModel.swift in Sources */, DDAF1C912C3D6E3D008A37D3 /* TardyViewController.swift in Sources */, 782B407D2C3E3984008B0CA7 /* WelcomeViewController.swift in Sources */, DE8248002C36E857000601BC /* ObservablePattern.swift in Sources */, + 789D73A92C46BBB000C7077D /* PagePromiseSegmentedControl.swift in Sources */, DDAF1C902C3D6E3D008A37D3 /* PagePromiseViewModel.swift in Sources */, DE254AAA2C31190E00A4015E /* UIStackView+.swift in Sources */, DE159D362C406E1600425101 /* MyPageViewController.swift in Sources */, @@ -1569,6 +1578,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 */, @@ -1589,9 +1599,9 @@ 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 */, - 78BD61312C456799005752FD /* KeychainService.swift in Sources */, DD3072242C3C0EB200416D9F /* MyPromiseReadyInfoRequestModel.swift in Sources */, DD3976872C41C2AD00E2A4C4 /* TodayPromiseView.swift in Sources */, 789873332C3D1A7B00435E96 /* LoginViewModel.swift in Sources */, @@ -1756,7 +1766,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = KkuMulKum/KkuMulKum.entitlements; + CODE_SIGN_ENTITLEMENTS = KkuMulKum/KkuMulKumDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; diff --git a/KkuMulKum/KkuMulKumDebug.entitlements b/KkuMulKum/KkuMulKumDebug.entitlements new file mode 100644 index 00000000..0ccc911f --- /dev/null +++ b/KkuMulKum/KkuMulKumDebug.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.developer.applesignin + + Default + + keychain-access-groups + + $(AppIdentifierPrefix)KkuMulKum.yizihn + + + diff --git a/KkuMulKum/Network/DTO/Model/Auth/NicknameModel.swift b/KkuMulKum/Network/DTO/Model/Auth/NicknameModel.swift new file mode 100644 index 00000000..bf1c1788 --- /dev/null +++ b/KkuMulKum/Network/DTO/Model/Auth/NicknameModel.swift @@ -0,0 +1,12 @@ +// +// NicknameModel.swift +// KkuMulKum +// +// Created by 이지훈 on 7/16/24. +// + +import Foundation + +struct NameResponseModel: ResponseModelType { + let name: String +} diff --git a/KkuMulKum/Network/DTO/Model/Auth/ProfileModel.swift b/KkuMulKum/Network/DTO/Model/Auth/ProfileModel.swift new file mode 100644 index 00000000..ea9e7ab1 --- /dev/null +++ b/KkuMulKum/Network/DTO/Model/Auth/ProfileModel.swift @@ -0,0 +1,12 @@ +// +// ProfileModel.swift +// KkuMulKum +// +// Created by 이지훈 on 7/16/24. +// + +import Foundation + +struct ProfileImageResponseModel: ResponseModelType { + // 성공 시 data가 null +} diff --git a/KkuMulKum/Network/DTO/TargetType/NicknameTargetType.swift b/KkuMulKum/Network/DTO/TargetType/NicknameTargetType.swift new file mode 100644 index 00000000..f00f1453 --- /dev/null +++ b/KkuMulKum/Network/DTO/TargetType/NicknameTargetType.swift @@ -0,0 +1,54 @@ +// +// LoginTatgetType.swift +// KkuMulKum +// +// Created by 이지훈 on 7/16/24. +// + +import Foundation + +import Moya + +enum NicknameTargetType { + case updateName(name: String) +} + +extension NicknameTargetType: 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 { + switch self { + case .updateName: + return "/api/v1/users/me/name" + } + } + + var method: Moya.Method { + switch self { + case .updateName: + return .patch + } + } + + var task: Task { + switch self { + case .updateName(let name): + return .requestParameters(parameters: ["name": name], encoding: JSONEncoding.default) + } + } + + var headers: [String : String]? { + guard let token = DefaultKeychainService.shared.accessToken else { + fatalError("No access token available") + } + return ["Content-Type": "application/json", "Authorization": "Bearer \(token)"] + } +} diff --git a/KkuMulKum/Network/DTO/TargetType/ProfileTargetType.swift b/KkuMulKum/Network/DTO/TargetType/ProfileTargetType.swift new file mode 100644 index 00000000..19f1609b --- /dev/null +++ b/KkuMulKum/Network/DTO/TargetType/ProfileTargetType.swift @@ -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" + ] + } +} diff --git a/KkuMulKum/Resource/Info.plist b/KkuMulKum/Resource/Info.plist index a2c106b0..aa26eb10 100644 --- a/KkuMulKum/Resource/Info.plist +++ b/KkuMulKum/Resource/Info.plist @@ -2,55 +2,64 @@ -<<<<<<< HEAD FirebaseAppDelegateProxyEnabled No -======= - NSPhotoLibraryAddUsageDescription - 정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진! - NSCameraUsageDescription - 정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진!정혜진! ->>>>>>> suyeon - UIUserInterfaceStyle - Light - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLSchemes - - - - - UIAppFonts - - Pretendard-Black.otf - Pretendard-Bold.otf - Pretendard-ExtraBold.otf - Pretendard-ExtraLight.otf - Pretendard-Light.otf - Pretendard-Medium.otf - Pretendard-Regular.otf - Pretendard-SemiBold.otf - Pretendard-Thin.otf - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneConfigurationName - Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - - - - + LSApplicationQueriesSchemes + + kakaotalk + kakaoplus + kakaolink + kakaokompassauth + + NSPhotoLibraryAddUsageDescription + 사진 라이브러리 접근 권한이 필요합니다. + NSCameraUsageDescription + 카메라 사용 권한이 필요합니다. + UIUserInterfaceStyle + Light + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + kakao69aeef4a49d5b6772d62efdf1686994c + + + + KeychainAccessGroups + + $(AppIdentifierPrefix)KkuMulKum.yizihn + + UIAppFonts + + Pretendard-Black.otf + Pretendard-Bold.otf + Pretendard-ExtraBold.otf + Pretendard-ExtraLight.otf + Pretendard-Light.otf + Pretendard-Medium.otf + Pretendard-Regular.otf + Pretendard-SemiBold.otf + Pretendard-Thin.otf + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + diff --git a/KkuMulKum/Resource/KeyChain/KeychainAccessible.swift b/KkuMulKum/Resource/KeyChain/KeychainAccessible.swift index 52c8ab3c..16320e87 100644 --- a/KkuMulKum/Resource/KeyChain/KeychainAccessible.swift +++ b/KkuMulKum/Resource/KeyChain/KeychainAccessible.swift @@ -3,44 +3,47 @@ // KkuMulKum // // Created by 이지훈 on 7/15/24. -// import Foundation import SwiftKeychainWrapper protocol KeychainAccessible { - func saveToken(_ key: String, _ value: String) - func saveExpiresIn(_ key: String, _ value: Int) + func saveToken(_ key: String, _ value: String) -> Bool + func saveExpiresIn(_ key: String, _ value: Int) -> Bool func getToken(_ key: String) -> String? func getExpiresIn(_ Key: String) -> Int? - func remove(_ key: String) - func removeAll() + func remove(_ key: String) -> Bool + func removeAll() -> Bool } class DefaultKeychainAccessible: KeychainAccessible { - private let keychain = KeychainWrapper.standard + private var keychain = KeychainWrapper.standard - func saveToken(_ key: String, _ value: String) { - keychain.set(value, forKey: key) + func saveToken(_ key: String, _ value: String) -> Bool { + let result = keychain.set(value, forKey: key, withAccessibility: .afterFirstUnlock) + return result } - func saveExpiresIn(_ key: String, _ value: Int) { - keychain.set(value, forKey: key) + func getToken(_ key: String) -> String? { + let token = keychain.string(forKey: key) + return token } - func getToken(_ key: String) -> String? { - keychain.string(forKey: key) + func saveExpiresIn(_ key: String, _ value: Int) -> Bool { + return keychain.set(value, forKey: key, withAccessibility: .afterFirstUnlockThisDeviceOnly) } + + func getExpiresIn(_ Key: String) -> Int? { - keychain.integer(forKey: Key) + return keychain.integer(forKey: Key) } - func remove(_ key: String) { - keychain.removeObject(forKey: key) + func remove(_ key: String) -> Bool { + return keychain.removeObject(forKey: key) } - func removeAll() { - keychain.removeAllKeys() + func removeAll() -> Bool { + return keychain.removeAllKeys() } } diff --git a/KkuMulKum/Resource/KeyChain/KeychainService.swift b/KkuMulKum/Resource/KeyChain/KeychainService.swift index ddbf57c1..7b7576de 100644 --- a/KkuMulKum/Resource/KeyChain/KeychainService.swift +++ b/KkuMulKum/Resource/KeyChain/KeychainService.swift @@ -1,110 +1,79 @@ // -// KeychainService.swift +// Keychain.swift // KkuMulKum // -// Created by 이지훈 on 7/15/24. +// Created by 이지훈 on 7/16/24. // import Foundation +import Security protocol KeychainService { var accessToken: String? { get set } var refreshToken: String? { get set } - var accessTokenExpiresIn: Int? { get set } } class DefaultKeychainService: KeychainService { static let shared = DefaultKeychainService() - private init() {} - private let keychainAccess = DefaultKeychainAccessible() + private init() {} - struct Key { - static let tempAccessToken = "tempAccessToken" - static let tempRefreshToken = "tempRefreshToken" - static let tempAccessTokenExpiresIn = "tempAccessTokenExpiresIn" + private func save(_ data: Data, forKey key: String) { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecValueData as String: data + ] - static let accessToken = "accessToken" - static let refreshToken = "refreshToken" - static let accessTokenExpiresIn = "accessTokenExpiresIn" - } - - var tempAccessToken: String? { - get { - keychainAccess.getToken(Key.tempAccessToken) - } - set { - if newValue != nil { - keychainAccess.saveToken(Key.tempAccessToken, newValue ?? "") - } else { - keychainAccess.remove(Key.tempAccessToken) - } - } - } - - var tempRefreshToken: String? { - get { - keychainAccess.getToken(Key.tempRefreshToken) - } - set { - if newValue != nil { - keychainAccess.saveToken(Key.tempRefreshToken, newValue ?? "") - } else { - keychainAccess.remove(Key.tempRefreshToken) - } - } + SecItemDelete(query as CFDictionary) + SecItemAdd(query as CFDictionary, nil) } - var tempAccessTokenExpiresIn: Int? { - get { - keychainAccess.getExpiresIn(Key.tempAccessTokenExpiresIn) - } - set { - if newValue != nil { - keychainAccess.saveExpiresIn(Key.tempAccessTokenExpiresIn, newValue ?? 0) - } else { - keychainAccess.remove(Key.tempAccessTokenExpiresIn) - } - } + private func load(forKey key: String) -> Data? { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecReturnData as String: true + ] + + var result: AnyObject? + SecItemCopyMatching(query as CFDictionary, &result) + return result as? Data } - var accessToken: String? { get { - keychainAccess.getToken(Key.accessToken) + guard let data = load(forKey: "accessToken") else { return nil } + return String(data: data, encoding: .utf8) } set { - if newValue != nil { - keychainAccess.saveToken(Key.accessToken, newValue ?? "") - } else { - keychainAccess.remove(Key.accessToken) + guard let newValue = newValue else { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: "accessToken" + ] + SecItemDelete(query as CFDictionary) + return } + save(Data(newValue.utf8), forKey: "accessToken") } } var refreshToken: String? { get { - keychainAccess.getToken(Key.refreshToken) - } - set { - if newValue != nil { - keychainAccess.saveToken(Key.refreshToken, newValue ?? "") - } else { - keychainAccess.remove(Key.refreshToken) - } - } - } - - var accessTokenExpiresIn: Int? { - get { - keychainAccess.getExpiresIn(Key.accessTokenExpiresIn) + guard let data = load(forKey: "refreshToken") else { return nil } + return String(data: data, encoding: .utf8) } set { - if newValue != nil { - keychainAccess.saveExpiresIn(Key.accessTokenExpiresIn, newValue ?? 0) - } else { - keychainAccess.remove(Key.accessTokenExpiresIn) + guard let newValue = newValue else { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: "refreshToken" + ] + SecItemDelete(query as CFDictionary) + return } + save(Data(newValue.utf8), forKey: "refreshToken") } } } diff --git a/KkuMulKum/Source/Onboarding/Login/Service/AuthService.swift b/KkuMulKum/Resource/Service/AuthService.swift similarity index 57% rename from KkuMulKum/Source/Onboarding/Login/Service/AuthService.swift rename to KkuMulKum/Resource/Service/AuthService.swift index faea5b93..273c6a55 100644 --- a/KkuMulKum/Source/Onboarding/Login/Service/AuthService.swift +++ b/KkuMulKum/Resource/Service/AuthService.swift @@ -8,11 +8,11 @@ import Foundation protocol AuthServiceType { - func saveAccessToken(_ token: String) - func saveRefreshToken(_ token: String) + func saveAccessToken(_ token: String) -> Bool + func saveRefreshToken(_ token: String) -> Bool func getAccessToken() -> String? func getRefreshToken() -> String? - func clearTokens() + func clearTokens() -> Bool } class AuthService: AuthServiceType { @@ -22,12 +22,14 @@ class AuthService: AuthServiceType { self.keychainService = keychainService } - func saveAccessToken(_ token: String) { + func saveAccessToken(_ token: String) -> Bool { keychainService.accessToken = token + return keychainService.accessToken == token } - func saveRefreshToken(_ token: String) { + func saveRefreshToken(_ token: String) -> Bool { keychainService.refreshToken = token + return keychainService.refreshToken == token } func getAccessToken() -> String? { @@ -38,9 +40,9 @@ class AuthService: AuthServiceType { return keychainService.refreshToken } - ///앱잼내 구현 X - func clearTokens() { -// keychainService.accessToken = nil -// keychainService.refreshToken = nil + func clearTokens() -> Bool { + keychainService.accessToken = nil + keychainService.refreshToken = nil + return keychainService.accessToken == nil && keychainService.refreshToken == nil } } diff --git a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift index 658639ef..d64a9068 100644 --- a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift @@ -23,16 +23,16 @@ class LoginViewModel: NSObject { var error: ObservablePattern = ObservablePattern("") private let provider: MoyaProvider - private var keychainService: KeychainService + private var authService: AuthServiceType init( provider: MoyaProvider = MoyaProvider( plugins: [NetworkLoggerPlugin(configuration: .init(logOptions: .verbose))] ), - keychainService: KeychainService = DefaultKeychainService.shared + authService: AuthServiceType = AuthService() ) { self.provider = provider - self.keychainService = keychainService + self.authService = authService super.init() } @@ -83,9 +83,15 @@ class LoginViewModel: NSObject { case .success(let response): print("Received response from server: \(response)") do { - let loginResponse = try response.map(ResponseBodyDTO.self) - print("Successfully mapped response: \(loginResponse)") - self?.handleLoginResponse(loginResponse) + let loginResponse = try response.map( + ResponseBodyDTO.self + ) + print( + "Successfully mapped response: \(loginResponse)" + ) + self?.handleLoginResponse( + loginResponse + ) } catch { print("Failed to decode response: \(error)") self?.error.value = "Failed to decode response: \(error.localizedDescription)" @@ -110,7 +116,10 @@ class LoginViewModel: NSObject { loginState.value = .needOnboarding } - saveTokens(accessToken: data.jwtTokenDTO.accessToken, refreshToken: data.jwtTokenDTO.refreshToken) + saveTokens( + accessToken: data.jwtTokenDTO.accessToken, + refreshToken: data.jwtTokenDTO.refreshToken + ) } else { print("Warning: No data received in response") error.value = "No data received" @@ -125,67 +134,57 @@ class LoginViewModel: NSObject { } } } + + private func saveTokens(accessToken: String, refreshToken: String) { + print("Attempting to save tokens") + let accessTokenSaved = authService.saveAccessToken(accessToken) + let refreshTokenSaved = authService.saveRefreshToken(refreshToken) - private func saveTokens(accessToken: String, refreshToken: String) { - keychainService.accessToken = accessToken - keychainService.refreshToken = refreshToken - print("Tokens saved to keychain") - } + print("Access token saved: \(accessTokenSaved), Refresh token saved: \(refreshTokenSaved)") - func refreshToken() { - guard let refreshToken = keychainService.refreshToken else { - error.value = "No refresh token available" - return - } - - provider.request(.refreshToken(refreshToken: refreshToken)) { [weak self] result in - switch result { - case .success(let response): - do { - let refreshResponse = try response.map(ResponseBodyDTO.self) - if refreshResponse.success, let data = refreshResponse.data { - self?.saveTokens(accessToken: data.accessToken, refreshToken: data.refreshToken) - self?.loginState.value = .login - } else if let error = refreshResponse.error { - self?.error.value = error.message - } - } catch { - self?.error.value = "Failed to decode refresh token response" - } - case .failure(let error): - self?.error.value = "Token refresh failed: \(error.localizedDescription)" - } - } + if accessTokenSaved && refreshTokenSaved { + print("Tokens successfully saved") + } else { + print("Failed to save tokens") } } +} - -extension LoginViewModel: ASAuthorizationControllerDelegate, - ASAuthorizationControllerPresentationContextProviding { +extension LoginViewModel: ASAuthorizationControllerDelegate, + ASAuthorizationControllerPresentationContextProviding { func authorizationController( controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization ) { - print("Apple authorization completed") + print( + "Apple authorization completed" + ) guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential, let identityToken = appleIDCredential.identityToken, - let tokenString = String(data: identityToken, encoding: .utf8) else { - print("Failed to get Apple ID Credential or identity token") + let tokenString = String( + data: identityToken, + encoding: .utf8 + ) else { + print( + "Failed to get Apple ID Credential or identity token" + ) return } - + print("Apple Login Successful, identity token: \(tokenString)") loginToServer(with: .appleLogin(identityToken: tokenString, fcmToken: "dummy_fcm_token")) } - + func authorizationController( controller: ASAuthorizationController, didCompleteWithError error: Error ) { - print("Apple authorization error: \(error.localizedDescription)") + print( + "Apple authorization error: \(error.localizedDescription)" + ) self.error.value = error.localizedDescription } - + func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { print("Providing presentation anchor for Apple Login") let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene diff --git a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift index f445f116..b1cfd275 100644 --- a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift +++ b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift @@ -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() diff --git a/KkuMulKum/Source/Onboarding/Nickname/ViewController/NicknameViewController.swift b/KkuMulKum/Source/Onboarding/Nickname/ViewController/NicknameViewController.swift index 5f9be66d..349ddd4f 100644 --- a/KkuMulKum/Source/Onboarding/Nickname/ViewController/NicknameViewController.swift +++ b/KkuMulKum/Source/Onboarding/Nickname/ViewController/NicknameViewController.swift @@ -4,6 +4,7 @@ // // Created by 이지훈 on 7/10/24. // +// NicknameViewController.swift import UIKit @@ -22,6 +23,15 @@ class NicknameViewController: BaseViewController { setupTextField() setupTapGesture() setupNavigationBarTitle(with: "닉네임 설정") + + print("---") + let keychainService = DefaultKeychainService.shared + + if let accessToken = keychainService.accessToken { + print("Access token is available in NicknameViewController: \(accessToken)") + } else { + print("No access token available in NicknameViewController. User may need to log in.") + } } override func setupAction() { @@ -64,6 +74,12 @@ class NicknameViewController: BaseViewController { viewModel.characterCount.bind { [weak self] count in self?.nicknameView.characterCountLabel.text = count } + + viewModel.serverResponse.bind { response in + if let response = response { + print("서버 응답: \(response)") + } + } } private func setupTextField() { @@ -81,16 +97,19 @@ class NicknameViewController: BaseViewController { } @objc private func nextButtonTapped() { - let profileSetupVC = ProfileSetupViewController( - viewModel: ProfileSetupViewModel( - nickname: viewModel.nickname.value - ) - ) -// profileSetupVC.modalPresentationStyle = .fullScreen -// present(profileSetupVC, animated: true, completion: nil) - // TODO: 온보딩 플로우 네비게이션으로 실행 - navigationController?.pushViewController(profileSetupVC, animated: true) - + viewModel.updateNickname { [weak self] success in + if success { + print("닉네임이 성공적으로 서버에 등록되었습니다.") + let profileSetupVC = ProfileSetupViewController( + viewModel: ProfileSetupViewModel( + nickname: self?.viewModel.nickname.value ?? "" + ) + ) + self?.navigationController?.pushViewController(profileSetupVC, animated: true) + } else { + print("닉네임 등록에 실패했습니다.") + } + } } @objc private func dismissKeyboard() { diff --git a/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift b/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift index ef6d3b2d..d1b90e05 100644 --- a/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift @@ -7,6 +7,8 @@ import Foundation +import Moya + enum NicknameState { case empty case valid @@ -21,6 +23,14 @@ class NicknameViewModel { let characterCount = ObservablePattern("0/5") private let nicknameRegex = "^[가-힣a-zA-Z0-9]{1,5}$" + private let provider = MoyaProvider() + let serverResponse = ObservablePattern(nil) + + private let authService: AuthServiceType + + init(authService: AuthServiceType = AuthService()) { + self.authService = authService + } func validateNickname(_ name: String) { nickname.value = name @@ -40,4 +50,64 @@ class NicknameViewModel { isNextButtonEnabled.value = false } } + + func updateNickname(completion: @escaping (Bool) -> Void) { + guard nicknameState.value == .valid else { + completion(false) + return + } + + guard let accessToken = authService.getAccessToken() else { + print("No access token available") + completion(false) + return + } + + print("닉네임 업데이트 요청 시작: \(nickname.value)") + + let headers = [ + "Content-Type": "application/json", + "Authorization": "Bearer \(accessToken)" + ] + + print("요청 헤더: \(headers)") + print("요청 바디: \(["name": nickname.value])") + + provider.request(.updateName(name: nickname.value)) { [weak self] result in + switch result { + case .success(let response): + print("서버 응답: \(String(data: response.data, encoding: .utf8) ?? "Unable to decode response")") + do { + let decodedResponse = try JSONDecoder().decode(ResponseBodyDTO.self, from: response.data) + if decodedResponse.success { + self?.serverResponse.value = "닉네임이 성공적으로 업데이트되었습니다." + print("닉네임 업데이트 성공: \(self?.nickname.value ?? "")") + completion(true) + } else { + if let errorCode = decodedResponse.error?.code { + switch errorCode { + 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) + } + } + } + } diff --git a/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileSetupViewController.swift b/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileSetupViewController.swift index 2a0b3ded..d4debc42 100644 --- a/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileSetupViewController.swift +++ b/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileSetupViewController.swift @@ -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( diff --git a/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift b/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift index 4a2f9cc2..fda10f79 100644 --- a/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift @@ -7,10 +7,16 @@ import UIKit +import Moya +import MobileCoreServices + class ProfileSetupViewModel { let profileImage = ObservablePattern(UIImage.imgProfile) let isConfirmButtonEnabled = ObservablePattern(false) let nickname: String + let serverResponse = ObservablePattern(nil) + + private let provider = MoyaProvider() init(nickname: String) { self.nickname = nickname @@ -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.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) + } + } + } } diff --git a/KkuMulKum/Source/Promise/PagePromise/ViewModel/PagePromiseViewModel.swift b/KkuMulKum/Source/Promise/PagePromise/ViewModel/PagePromiseViewModel.swift index 3be9e9b9..f7d90a04 100644 --- a/KkuMulKum/Source/Promise/PagePromise/ViewModel/PagePromiseViewModel.swift +++ b/KkuMulKum/Source/Promise/PagePromise/ViewModel/PagePromiseViewModel.swift @@ -19,7 +19,7 @@ class PagePromiseViewModel { // MARK: - Extension -private extension PagePromiseViewModel { +extension PagePromiseViewModel { func didSegmentIndexChanged(index: Int) { currentPage.value = index } diff --git a/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift b/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift index 633f9a63..91548f91 100644 --- a/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift +++ b/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift @@ -115,7 +115,7 @@ extension ReadyStatusViewController: UICollectionViewDataSource { color: .gray8 ) - if let imageURL = readyStatusViewModel.participantInfos.value[indexPath.row].profileImg { + if let imageURL = readyStatusViewModel.participantInfos.value[indexPath.row].profileImageURL { cell.profileImageView.kf.setImage(with: URL(string: imageURL)) } else { cell.profileImageView.image = .imgProfile