diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index 2e4fc29b..d42b069d 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -27,6 +27,9 @@ 784824F72C6E1C9900FE07A0 /* AuthServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784824F62C6E1C9900FE07A0 /* AuthServiceProtocol.swift */; }; 784824FC2C75BF7900FE07A0 /* MyPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784824FB2C75BF7900FE07A0 /* MyPageViewModel.swift */; }; 784824FE2C75F25900FE07A0 /* MyPageEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784824FD2C75F25900FE07A0 /* MyPageEditViewController.swift */; }; + 784825092C77623C00FE07A0 /* MypageTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784825082C77623C00FE07A0 /* MypageTargetType.swift */; }; + 7848250B2C77627200FE07A0 /* UserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7848250A2C77627200FE07A0 /* UserService.swift */; }; + 784825112C77666500FE07A0 /* MyPageUserServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784825102C77666500FE07A0 /* MyPageUserServiceType.swift */; }; 784825012C77016400FE07A0 /* MyPageEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784825002C77016400FE07A0 /* MyPageEditView.swift */; }; 784825032C77016F00FE07A0 /* MyPageEditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784825022C77016F00FE07A0 /* MyPageEditViewModel.swift */; }; 784825052C7716E900FE07A0 /* MyPageAskViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784825042C7716E900FE07A0 /* MyPageAskViewController.swift */; }; @@ -262,6 +265,9 @@ 784824F62C6E1C9900FE07A0 /* AuthServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthServiceProtocol.swift; sourceTree = ""; }; 784824FB2C75BF7900FE07A0 /* MyPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageViewModel.swift; sourceTree = ""; }; 784824FD2C75F25900FE07A0 /* MyPageEditViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageEditViewController.swift; sourceTree = ""; }; + 784825082C77623C00FE07A0 /* MypageTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MypageTargetType.swift; sourceTree = ""; }; + 7848250A2C77627200FE07A0 /* UserService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserService.swift; sourceTree = ""; }; + 784825102C77666500FE07A0 /* MyPageUserServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageUserServiceType.swift; sourceTree = ""; }; 784825002C77016400FE07A0 /* MyPageEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageEditView.swift; sourceTree = ""; }; 784825022C77016F00FE07A0 /* MyPageEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageEditViewModel.swift; sourceTree = ""; }; 784825042C7716E900FE07A0 /* MyPageAskViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageAskViewController.swift; sourceTree = ""; }; @@ -573,6 +579,14 @@ path = ViewModel; sourceTree = ""; }; + 7848250F2C77664400FE07A0 /* MyPageServiceProtocol */ = { + isa = PBXGroup; + children = ( + 784825102C77666500FE07A0 /* MyPageUserServiceType.swift */, + ); + path = MyPageServiceProtocol; + sourceTree = ""; + }; 789196392C49697F00FF8CDF /* Auth */ = { isa = PBXGroup; children = ( @@ -737,6 +751,7 @@ A3DD9C672C45C78300E58A13 /* HomeService.swift */, DD3F9DC52C484DEB008E1FF7 /* PromiseService.swift */, DD3F9DD12C485753008E1FF7 /* UtilService.swift */, + 7848250A2C77627200FE07A0 /* UserService.swift */, ); path = Service; sourceTree = ""; @@ -1174,6 +1189,7 @@ DDE7D2C92C47EE81005A921F /* PromiseTargetType.swift */, A39F2B1C2C47F3D0008DA5F5 /* HomeTargetType.swift */, DD3F9DD32C4858A3008E1FF7 /* UtilTargetType.swift */, + 784825082C77623C00FE07A0 /* MypageTargetType.swift */, ); path = TargetType; sourceTree = ""; @@ -1246,6 +1262,7 @@ DE159D312C406E1600425101 /* MyPage */ = { isa = PBXGroup; children = ( + 7848250F2C77664400FE07A0 /* MyPageServiceProtocol */, 784824FA2C75BF6800FE07A0 /* ViewModel */, DE159D2E2C406E1600425101 /* View */, DE159D302C406E1600425101 /* ViewController */, @@ -1877,6 +1894,7 @@ DE6D4D0F2C3F14D80005584B /* MeetingInfoService.swift in Sources */, DD3F9DD62C4988E2008E1FF7 /* RegisterMeetingsResponseModel.swift in Sources */, DD3F9DCC2C485614008E1FF7 /* HomeServiceType.swift in Sources */, + 7848250B2C77627200FE07A0 /* UserService.swift in Sources */, DD39768A2C41C2AD00E2A4C4 /* HomeViewController.swift in Sources */, DED5DBF42C34539A006ECE7E /* BaseTableViewCell.swift in Sources */, DDE7D2CA2C47EE81005A921F /* PromiseTargetType.swift in Sources */, @@ -1898,7 +1916,9 @@ DE32D1D22C3BF703006848DF /* LoginUserResponseModel.swift in Sources */, DD8626692C4606A300E4F980 /* ReadyStatusProgressView.swift in Sources */, DE9E18892C3BC91000DB76B4 /* ResponseBodyDTO.swift in Sources */, + 784825092C77623C00FE07A0 /* MypageTargetType.swift in Sources */, DD8626642C4606A300E4F980 /* ReadyPlanInfoView.swift in Sources */, + 784825112C77666500FE07A0 /* MyPageUserServiceType.swift in Sources */, DD3976682C41769400E2A4C4 /* CreateMeetingViewModel.swift in Sources */, DECB84582C43FBEB0022A003 /* AddPromiseViewController.swift in Sources */, DDE7D2C62C47D2BB005A921F /* MeetingTargetType.swift in Sources */, diff --git a/KkuMulKum/Network/Service/AuthService.swift b/KkuMulKum/Network/Service/AuthService.swift index a22d2218..15dd0ddd 100644 --- a/KkuMulKum/Network/Service/AuthService.swift +++ b/KkuMulKum/Network/Service/AuthService.swift @@ -23,7 +23,7 @@ enum NetworkErrorMapper { } } -class AuthService: AuthServiceType { +class AuthService: AuthServiceProtocol { private var keychainService: KeychainService private var provider = MoyaProvider() diff --git a/KkuMulKum/Network/Service/UserService.swift b/KkuMulKum/Network/Service/UserService.swift new file mode 100644 index 00000000..1a36a334 --- /dev/null +++ b/KkuMulKum/Network/Service/UserService.swift @@ -0,0 +1,44 @@ +// +// UserService.swift +// KkuMulKum +// +// Created by 이지훈 on 8/22/24. +// + +import Foundation + +import Moya + +final class MyPageUserService: MyPageUserServiceType { + private var provider = MoyaProvider() + + init(provider: MoyaProvider = MoyaProvider(plugins: [MoyaLoggingPlugin()])) { + self.provider = provider + } + + func getUserInfo() async throws -> LoginUserModel { + return try await performRequest(.getUserInfo) + } + + func performRequest(_ target: UserTargetType) async throws -> T { + return try await withCheckedThrowingContinuation { continuation in + provider.request(target) { result in + switch result { + case .success(let response): + do { + let decodedResponse = try JSONDecoder().decode(ResponseBodyDTO.self, from: response.data) + guard decodedResponse.success, let data = decodedResponse.data else { + throw decodedResponse.error.map(NetworkErrorMapper.mapErrorResponse) ?? + NetworkError.unknownError("Unknown error occurred") + } + continuation.resume(returning: data) + } catch { + continuation.resume(throwing: error is NetworkError ? error : NetworkError.decodingError) + } + case .failure(let error): + continuation.resume(throwing: NetworkError.networkError(error)) + } + } + } + } +} diff --git a/KkuMulKum/Network/TargetType/MypageTargetType.swift b/KkuMulKum/Network/TargetType/MypageTargetType.swift new file mode 100644 index 00000000..ce63f138 --- /dev/null +++ b/KkuMulKum/Network/TargetType/MypageTargetType.swift @@ -0,0 +1,56 @@ +// +// MypageTargetType.swift +// KkuMulKum +// +// Created by 이지훈 on 8/22/24. +// + +import Foundation + +import Moya + +enum UserTargetType { + case getUserInfo +} + +extension UserTargetType: 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 .getUserInfo: + return "/api/v1/users/me" + } + } + + var method: Moya.Method { + switch self { + case .getUserInfo: + return .get + } + } + + var task: Task { + switch self { + case .getUserInfo: + return .requestPlain + } + } + + 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/Source/Core/Auth/AuthInterceptor.swift b/KkuMulKum/Source/Core/Auth/AuthInterceptor.swift index 7f9422e5..0b6f205e 100644 --- a/KkuMulKum/Source/Core/Auth/AuthInterceptor.swift +++ b/KkuMulKum/Source/Core/Auth/AuthInterceptor.swift @@ -15,10 +15,10 @@ enum AuthError: Error { } class AuthInterceptor: RequestInterceptor { - let authService: AuthServiceType + let authService: AuthServiceProtocol let provider: MoyaProvider - init(authService: AuthServiceType, provider: MoyaProvider) { + init(authService: AuthServiceProtocol, provider: MoyaProvider) { self.authService = authService self.provider = provider } diff --git a/KkuMulKum/Source/MyPage/MyPageServiceProtocol/MyPageUserServiceType.swift b/KkuMulKum/Source/MyPage/MyPageServiceProtocol/MyPageUserServiceType.swift new file mode 100644 index 00000000..7281e049 --- /dev/null +++ b/KkuMulKum/Source/MyPage/MyPageServiceProtocol/MyPageUserServiceType.swift @@ -0,0 +1,13 @@ +// +// MyPageUserServiceType.swift +// KkuMulKum +// +// Created by 이지훈 on 8/22/24. +// + +import Foundation + +protocol MyPageUserServiceType { + func getUserInfo() async throws -> LoginUserModel + func performRequest(_ target: UserTargetType) async throws -> T +} diff --git a/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift b/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift index 7fe96b7a..f8c4cb1e 100644 --- a/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift +++ b/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift @@ -10,7 +10,6 @@ import UIKit import RxSwift import RxCocoa - class MyPageViewController: BaseViewController, CustomActionSheetDelegate { private let rootView = MyPageView() private let viewModel = MyPageViewModel() @@ -25,6 +24,7 @@ class MyPageViewController: BaseViewController, CustomActionSheetDelegate { view.backgroundColor = .green1 bindViewModel() + viewModel.fetchUserInfo() } override func setupView() { @@ -33,6 +33,7 @@ class MyPageViewController: BaseViewController, CustomActionSheetDelegate { } private func bindViewModel() { + // Inputs rootView.contentView.editButton.rx.tap .bind(to: viewModel.editButtonTapped) .disposed(by: disposeBag) @@ -44,7 +45,7 @@ class MyPageViewController: BaseViewController, CustomActionSheetDelegate { bindRowTapGesture(for: rootView.etcSettingView.unsubscribeRow) .bind(to: viewModel.unsubscribeButtonTapped) .disposed(by: disposeBag) - + // Other rows bindRowTapGesture(for: rootView.etcSettingView.versionInfoRow) .subscribe(onNext: { print("버전정보 탭됨") }) .disposed(by: disposeBag) @@ -63,7 +64,8 @@ class MyPageViewController: BaseViewController, CustomActionSheetDelegate { viewModel.pushEditProfileVC .emit(onNext: { [weak self] in self?.pushEditProfileViewController() - }) .disposed(by: disposeBag) + }) + .disposed(by: disposeBag) viewModel.showActionSheet .emit(onNext: { [weak self] kind in @@ -82,6 +84,31 @@ class MyPageViewController: BaseViewController, CustomActionSheetDelegate { self?.viewModel.unsubscribe() }) .disposed(by: disposeBag) + + viewModel.userInfo + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak self] userInfo in + self?.updateUI(with: userInfo) + }) + .disposed(by: disposeBag) + } + + private func updateUI(with userInfo: LoginUserModel?) { + guard let userInfo = userInfo else { return } + + rootView.contentView.nameLabel.text = userInfo.name ?? "꾸물리안 님" + rootView.contentView.levelLabel.setText("Lv. \(userInfo.level) 지각대장 꾸물이", style: .body05, color: .white) + rootView.contentView.levelLabel.setHighlightText("Lv. \(userInfo.level)", style: .body05, color: .lightGreen) + + if let profileImageURL = userInfo.profileImageURL { + loadImage(from: profileImageURL, into: rootView.contentView.profileImageView) + } else { + rootView.contentView.profileImageView.image = UIImage.imgProfile + } + } + + private func loadImage(from urlString: String, into imageView: UIImageView) { + } private func bindRowTapGesture(for view: UIView) -> Observable { @@ -90,6 +117,7 @@ class MyPageViewController: BaseViewController, CustomActionSheetDelegate { .first? .rx.event .map { _ in } + ?? Observable.empty() } diff --git a/KkuMulKum/Source/MyPage/ViewModel/MyPageEditViewModel.swift b/KkuMulKum/Source/MyPage/ViewModel/MyPageEditViewModel.swift index f7ab73ae..8fcd0785 100644 --- a/KkuMulKum/Source/MyPage/ViewModel/MyPageEditViewModel.swift +++ b/KkuMulKum/Source/MyPage/ViewModel/MyPageEditViewModel.swift @@ -14,7 +14,7 @@ import RxCocoa import Moya class MyPageEditViewModel: ViewModelType { - private let authService: AuthServiceType + private let authService: AuthServiceProtocol struct Input { let profileImageTap: Observable @@ -28,7 +28,7 @@ class MyPageEditViewModel: ViewModelType { let serverResponse: Driver } - init(authService: AuthServiceType) { + init(authService: AuthServiceProtocol) { self.authService = authService } diff --git a/KkuMulKum/Source/MyPage/ViewModel/MyPageViewModel.swift b/KkuMulKum/Source/MyPage/ViewModel/MyPageViewModel.swift index 3065ab65..4e8eceee 100644 --- a/KkuMulKum/Source/MyPage/ViewModel/MyPageViewModel.swift +++ b/KkuMulKum/Source/MyPage/ViewModel/MyPageViewModel.swift @@ -11,6 +11,8 @@ import RxSwift import RxCocoa class MyPageViewModel { + private let userService: MyPageUserServiceType + private let disposeBag = DisposeBag() let editButtonTapped = PublishRelay() let logoutButtonTapped = PublishRelay() @@ -21,10 +23,12 @@ class MyPageViewModel { let showActionSheet: Signal let performLogout: Signal let performUnsubscribe: Signal - private let disposeBag = DisposeBag() - + let userInfo: BehaviorRelay - init() { + init(userService: MyPageUserServiceType = MyPageUserService()) { + self.userService = userService + self.userInfo = BehaviorRelay(value: nil) + pushEditProfileVC = editButtonTapped.asSignal() showActionSheet = Observable.merge( @@ -45,6 +49,17 @@ class MyPageViewModel { .asSignal(onErrorJustReturn: ()) } + func fetchUserInfo() { + Task { + do { + let info = try await userService.getUserInfo() + userInfo.accept(info) + } catch { + print("Failed to fetch user info: \(error)") + } + } + } + func logout() { print("로그아웃 눌름 ㅂㅂ") } diff --git a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift index 2080af3c..daff4861 100644 --- a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift @@ -25,7 +25,7 @@ class LoginViewModel: NSObject { var userName: ObservablePattern = ObservablePattern(nil) private let provider: MoyaProvider - private var authService: AuthServiceType + private var authService: AuthServiceProtocol private let authInterceptor: AuthInterceptor private let keychainAccessible: KeychainAccessible @@ -33,7 +33,7 @@ class LoginViewModel: NSObject { provider: MoyaProvider = MoyaProvider( plugins: [NetworkLoggerPlugin(configuration: .init(logOptions: .verbose))] ), - authService: AuthServiceType = AuthService(), + authService: AuthServiceProtocol = AuthService(), keychainAccessible: KeychainAccessible = DefaultKeychainAccessible() ) { self.provider = provider diff --git a/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift b/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift index ce646700..acab9749 100644 --- a/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Nickname/ViewModel/NicknameViewModel.swift @@ -32,10 +32,10 @@ class NicknameViewModel { private let disposeBag = DisposeBag() private let nicknameRegex = "^[가-힣a-zA-Z]{1,5}$" private let provider: MoyaProvider - private let authService: AuthServiceType + private let authService: AuthServiceProtocol init(provider: MoyaProvider = MoyaProvider(), - authService: AuthServiceType = AuthService()) { + authService: AuthServiceProtocol = AuthService()) { self.provider = provider self.authService = authService diff --git a/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift b/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift index c3ea45bc..0ab93193 100644 --- a/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift @@ -14,11 +14,11 @@ class ProfileSetupViewModel { let nickname: String let serverResponse = ObservablePattern(nil) - private let authService: AuthServiceType + private let authService: AuthServiceProtocol private var imageData: Data? private let maxImageSizeBytes = 4 * 1024 * 1024 - init(nickname: String, authService: AuthServiceType = AuthService()) { + init(nickname: String, authService: AuthServiceProtocol = AuthService()) { self.nickname = nickname self.authService = authService } diff --git a/KkuMulKum/Source/Onboarding/ServiceProtocol/AuthServiceProtocol.swift b/KkuMulKum/Source/Onboarding/ServiceProtocol/AuthServiceProtocol.swift index 5bb64773..1cbde734 100644 --- a/KkuMulKum/Source/Onboarding/ServiceProtocol/AuthServiceProtocol.swift +++ b/KkuMulKum/Source/Onboarding/ServiceProtocol/AuthServiceProtocol.swift @@ -7,7 +7,7 @@ import Foundation -protocol AuthServiceType { +protocol AuthServiceProtocol { func saveAccessToken(_ token: String) -> Bool func saveRefreshToken(_ token: String) -> Bool func getAccessToken() -> String?