From 01f07fb49508be197e5eb04b5a2e896b046123fb Mon Sep 17 00:00:00 2001 From: hooni Date: Thu, 22 Aug 2024 16:06:38 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat/#321=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20+=20=EB=AC=B8=EC=9D=98=ED=95=98=EA=B8=B0?= =?UTF-8?q?=20=EC=9B=B9=EB=B7=B0=20=EC=94=8C=EC=9A=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KkuMulKum.xcodeproj/project.pbxproj | 12 ++ .../Source/MyPage/View/MyPageEditView.swift | 76 ++++++++++ .../MyPageAskViewController.swift | 41 +++++ .../MyPageEditViewController.swift | 123 +++++++++++++-- .../ViewController/MyPageViewController.swift | 140 +++++++++--------- .../ViewModel/MyPageEditViewModel.swift | 94 ++++++++++++ 6 files changed, 408 insertions(+), 78 deletions(-) create mode 100644 KkuMulKum/Source/MyPage/View/MyPageEditView.swift create mode 100644 KkuMulKum/Source/MyPage/ViewController/MyPageAskViewController.swift create mode 100644 KkuMulKum/Source/MyPage/ViewModel/MyPageEditViewModel.swift diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index db606bd0..a798542c 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 */; }; + 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 */; }; 784E4D942C3B1C7F00BC943C /* KakaoSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 784E4D932C3B1C7F00BC943C /* KakaoSDK */; }; 784E4D962C3B1C7F00BC943C /* KakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 784E4D952C3B1C7F00BC943C /* KakaoSDKAuth */; }; 784E4D992C3B95A900BC943C /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 784E4D982C3B95A900BC943C /* KeychainAccess */; }; @@ -258,6 +261,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 = ""; }; + 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 = ""; }; 789196332C486F6B00FF8CDF /* KeychainAccessible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainAccessible.swift; sourceTree = ""; }; 789196352C492F8600FF8CDF /* AuthTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTargetType.swift; sourceTree = ""; }; 789196372C49697B00FF8CDF /* AuthError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthError.swift; sourceTree = ""; }; @@ -560,6 +566,7 @@ isa = PBXGroup; children = ( 784824FB2C75BF7900FE07A0 /* MyPageViewModel.swift */, + 784825022C77016F00FE07A0 /* MyPageEditViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -1218,6 +1225,7 @@ DE0137D22C43C5E50088C777 /* MyPageView.swift */, DE159D2B2C406E1600425101 /* MyPageContentView.swift */, DE159D2C2C406E1600425101 /* MyPageEtcSettingView.swift */, + 784825002C77016400FE07A0 /* MyPageEditView.swift */, ); path = View; sourceTree = ""; @@ -1227,6 +1235,7 @@ children = ( DE159D2F2C406E1600425101 /* MyPageViewController.swift */, 784824FD2C75F25900FE07A0 /* MyPageEditViewController.swift */, + 784825042C7716E900FE07A0 /* MyPageAskViewController.swift */, ); path = ViewController; sourceTree = ""; @@ -1769,6 +1778,7 @@ DECB84622C44472F0022A003 /* FindPlaceViewModel.swift in Sources */, 789D73A72C46AF4900C7077D /* KeychainService.swift in Sources */, DD43937C2C412F4500EC1799 /* InviteCodeViewController.swift in Sources */, + 784825012C77016400FE07A0 /* MyPageEditView.swift in Sources */, DD4393782C412F4500EC1799 /* CheckInviteCodeView.swift in Sources */, DE6D4D102C3F14D80005584B /* InvitationCodePopUpView.swift in Sources */, 789196382C49697B00FF8CDF /* AuthError.swift in Sources */, @@ -1805,6 +1815,7 @@ A3FB184F2C3BF4BC001483E5 /* MakeMeetingsResponseModel.swift in Sources */, DEF725DB2C3F3BBF008C87C7 /* Toast.swift in Sources */, DD43937A2C412F4500EC1799 /* CreateSuccessViewController.swift in Sources */, + 784825032C77016F00FE07A0 /* MyPageEditViewModel.swift in Sources */, DE254AAC2C31192400A4015E /* UILabel+.swift in Sources */, DE254AB72C3119D000A4015E /* ReuseIdentifiable.swift in Sources */, A3DD9C412C41BAD000E58A13 /* MeetingListViewModel.swift in Sources */, @@ -1846,6 +1857,7 @@ DD3072142C3BF87A00416D9F /* NearestPromiseResponseModel.swift in Sources */, 782B407F2C3E44B7008B0CA7 /* WelcomeViewModel.swift in Sources */, DD41BEFA2C41D4160095A068 /* TardyView.swift in Sources */, + 784825052C7716E900FE07A0 /* MyPageAskViewController.swift in Sources */, DD3072162C3BFE4E00416D9F /* UpcomingPromiseListResponseModel.swift in Sources */, A3FB18572C3BF704001483E5 /* MeetingListResponseModel.swift in Sources */, DE254AB22C31197B00A4015E /* UIButton+.swift in Sources */, diff --git a/KkuMulKum/Source/MyPage/View/MyPageEditView.swift b/KkuMulKum/Source/MyPage/View/MyPageEditView.swift new file mode 100644 index 00000000..3cbcc69b --- /dev/null +++ b/KkuMulKum/Source/MyPage/View/MyPageEditView.swift @@ -0,0 +1,76 @@ +// +// MyPageEditView.swift +// KkuMulKum +// +// Created by 이지훈 on 8/22/24. +// + +import UIKit + +import SnapKit +import Then + +class MyPageEditView: BaseView { + let titleLabel = UILabel().then { + $0.setText("프로필을 설정해 주세요", style: .head01, color: .gray8) + $0.textAlignment = .left + } + + let profileImageView = UIImageView().then { + $0.image = .imgProfile + $0.contentMode = .scaleAspectFill + $0.clipsToBounds = true + } + + let cameraButton = UIButton().then { + $0.setImage(.iconCamera, for: .normal) + $0.tintColor = .white + $0.setLayer(borderWidth: 0, borderColor: .clear, cornerRadius: 15) + } + + let skipButton = UIButton().then { + $0.setTitle("기본 프로필로 설정", style: .body05, color: .gray5) + $0.addUnderline() + } + + let confirmButton = UIButton().then { + $0.setTitle("확인", style: .body03, color: .white) + $0.backgroundColor = .maincolor + $0.setLayer(borderWidth: 0, borderColor: .clear, cornerRadius: 8) + } + + override func setupView() { + backgroundColor = .white + + addSubviews(titleLabel, profileImageView, cameraButton, skipButton, confirmButton) + } + + override func setupAutoLayout() { + titleLabel.snp.makeConstraints { + $0.top.equalTo(safeAreaLayoutGuide).offset(24) + $0.leading.trailing.equalToSuperview().inset(20) + } + + profileImageView.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(120) + $0.centerX.equalToSuperview() + $0.size.equalTo(Screen.width(150)) + } + + cameraButton.snp.makeConstraints { + $0.bottom.trailing.equalTo(profileImageView) + $0.size.equalTo(Screen.width(42)) + } + + skipButton.snp.makeConstraints { + $0.bottom.equalTo(confirmButton.snp.top).offset(-20) + $0.centerX.equalToSuperview() + } + + confirmButton.snp.makeConstraints { + $0.bottom.equalTo(safeAreaLayoutGuide).offset(-20) + $0.leading.trailing.equalToSuperview().inset(20) + $0.height.equalTo(Screen.height(50)) + } + } +} diff --git a/KkuMulKum/Source/MyPage/ViewController/MyPageAskViewController.swift b/KkuMulKum/Source/MyPage/ViewController/MyPageAskViewController.swift new file mode 100644 index 00000000..d123927c --- /dev/null +++ b/KkuMulKum/Source/MyPage/ViewController/MyPageAskViewController.swift @@ -0,0 +1,41 @@ +// +// MyPageAskViewController.swift +// KkuMulKum +// +// Created by 이지훈 on 8/22/24. +// + +import UIKit +import WebKit + +class MyPageAskViewController: UIViewController, WKUIDelegate { + + private let viewModel : MyPageViewModel + private var webView: WKWebView! + + + init(viewModel: MyPageViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + let webConfiguration = WKWebViewConfiguration() + webView = WKWebView(frame: .zero, configuration: webConfiguration) + webView.uiDelegate = self + view = webView + } + + + override func viewDidLoad() { + super.viewDidLoad() + + let myURL = URL(string:"https://www.apple.com") + let myRequest = URLRequest(url: myURL!) + webView.load(myRequest) + } +} diff --git a/KkuMulKum/Source/MyPage/ViewController/MyPageEditViewController.swift b/KkuMulKum/Source/MyPage/ViewController/MyPageEditViewController.swift index b1fcb812..2c21c8e2 100644 --- a/KkuMulKum/Source/MyPage/ViewController/MyPageEditViewController.swift +++ b/KkuMulKum/Source/MyPage/ViewController/MyPageEditViewController.swift @@ -7,23 +7,122 @@ import UIKit -class MyPageEditViewController: BaseViewController { +import RxSwift +import RxCocoa +class MyPageEditViewController: BaseViewController { + private let rootView = MyPageEditView() + private let viewModel: MyPageEditViewModel + private let disposeBag = DisposeBag() + + init(viewModel: MyPageEditViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = rootView + } + override func viewDidLoad() { super.viewDidLoad() - - view.backgroundColor = .white + + setupNavigationBarTitle(with: "프로필 설정") + setupNavigationBarBackButton() + setupBindings() } - - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. + private func setupBindings() { + let input = MyPageEditViewModel.Input( + profileImageTap: rootView.cameraButton.rx.tap.asObservable(), + confirmButtonTap: rootView.confirmButton.rx.tap.asObservable(), + newProfileImage: Observable.never() // This will be updated in imagePickerController + ) + + let output = viewModel.transform(input: input, disposeBag: DisposeBag()) + output.profileImage + .drive(rootView.profileImageView.rx.image) + .disposed(by: disposeBag) + + output.isConfirmButtonEnabled + .drive(rootView.confirmButton.rx.isEnabled) + .disposed(by: disposeBag) + + output.isConfirmButtonEnabled + .map { $0 ? 1.0 : 0.5 } + .drive(rootView.confirmButton.rx.alpha) + .disposed(by: disposeBag) + + output.serverResponse + .drive(onNext: { [weak self] response in + if let response = response { + self?.showAlert(message: response) + } + }) + .disposed(by: disposeBag) + + rootView.cameraButton.rx.tap + .subscribe(onNext: { [weak self] in + self?.showImagePicker() + }) + .disposed(by: disposeBag) + + navigationItem.leftBarButtonItem?.rx.tap + .subscribe(onNext: { [weak self] in + self?.navigateToMyPageViewController() + }) + .disposed(by: disposeBag) + } + private func showImagePicker() { + let imagePicker = UIImagePickerController() + imagePicker.delegate = self + imagePicker.sourceType = .photoLibrary + imagePicker.allowsEditing = true + present(imagePicker, animated: true) + } + + private func showAlert(message: String) { + let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .default)) + present(alert, animated: true) + } + + private func navigateToMyPageViewController() { + navigationController?.popViewController(animated: true) } - */ +} +extension MyPageEditViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { + func imagePickerController( + _ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any] + ) { + if let editedImage = info[.editedImage] as? UIImage ?? info[.originalImage] as? UIImage { + let croppedImage = cropToCircle(image: editedImage) + let input = MyPageEditViewModel.Input( + profileImageTap: Observable.never(), + confirmButtonTap: Observable.never(), + newProfileImage: Observable.just(croppedImage) + ) + _ = viewModel.transform(input: input, disposeBag: DisposeBag()) + } + dismiss(animated: true) + } + + private func cropToCircle(image: UIImage) -> UIImage { + let shorterSide = min(image.size.width, image.size.height) + let imageBounds = CGRect(x: 0, y: 0, width: shorterSide, height: shorterSide) + UIGraphicsBeginImageContextWithOptions(imageBounds.size, false, UIScreen.main.scale) + let context = UIGraphicsGetCurrentContext()! + context.addEllipse(in: imageBounds) + context.clip() + image.draw(in: imageBounds) + let circleImage = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + return circleImage + } } diff --git a/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift b/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift index 04683a49..800a2272 100644 --- a/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift +++ b/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift @@ -23,7 +23,7 @@ class MyPageViewController: BaseViewController, CustomActionSheetDelegate { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .green1 - + bindViewModel() } @@ -33,79 +33,87 @@ class MyPageViewController: BaseViewController, CustomActionSheetDelegate { } private func bindViewModel() { - // Inputs - rootView.contentView.editButton.rx.tap - .bind(to: viewModel.editButtonTapped) - .disposed(by: disposeBag) - - bindRowTapGesture(for: rootView.etcSettingView.logoutRow) - .bind(to: viewModel.logoutButtonTapped) - .disposed(by: disposeBag) - - 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) - - bindRowTapGesture(for: rootView.etcSettingView.termsOfServiceRow) - .subscribe(onNext: { print("이용약관 탭됨") }) - .disposed(by: disposeBag) - - bindRowTapGesture(for: rootView.etcSettingView.inquiryRow) - .subscribe(onNext: { print("문의하기 탭됨") }) - .disposed(by: disposeBag) - - // Outputs - viewModel.pushEditProfileVC - .emit(onNext: { [weak self] in - self?.pushEditProfileViewController() - }) - .disposed(by: disposeBag) - - viewModel.showActionSheet - .emit(onNext: { [weak self] kind in - self?.showActionSheet(for: kind) - }) - .disposed(by: disposeBag) - - viewModel.performLogout - .emit(onNext: { [weak self] in - self?.viewModel.logout() - }) - .disposed(by: disposeBag) - - viewModel.performUnsubscribe - .emit(onNext: { [weak self] in - self?.viewModel.unsubscribe() - }) - .disposed(by: disposeBag) - } + rootView.contentView.editButton.rx.tap + .bind(to: viewModel.editButtonTapped) + .disposed(by: disposeBag) + + bindRowTapGesture(for: rootView.etcSettingView.logoutRow) + .bind(to: viewModel.logoutButtonTapped) + .disposed(by: disposeBag) + + bindRowTapGesture(for: rootView.etcSettingView.unsubscribeRow) + .bind(to: viewModel.unsubscribeButtonTapped) + .disposed(by: disposeBag) + + bindRowTapGesture(for: rootView.etcSettingView.versionInfoRow) + .subscribe(onNext: { print("버전정보 탭됨") }) + .disposed(by: disposeBag) + + bindRowTapGesture(for: rootView.etcSettingView.termsOfServiceRow) + .subscribe(onNext: { print("이용약관 탭됨") }) + .disposed(by: disposeBag) + + bindRowTapGesture(for: rootView.etcSettingView.inquiryRow) + .subscribe(onNext: { [weak self] in + self?.pushAskViewController() }) + .disposed(by: disposeBag) + + // Outputs + viewModel.pushEditProfileVC + .emit(onNext: { [weak self] in + self?.pushEditProfileViewController() + }) + .disposed(by: disposeBag) + + viewModel.showActionSheet + .emit(onNext: { [weak self] kind in + self?.showActionSheet(for: kind) + }) + .disposed(by: disposeBag) + + viewModel.performLogout + .emit(onNext: { [weak self] in + self?.viewModel.logout() + }) + .disposed(by: disposeBag) + + viewModel.performUnsubscribe + .emit(onNext: { [weak self] in + self?.viewModel.unsubscribe() + }) + .disposed(by: disposeBag) + } + private func bindRowTapGesture(for view: UIView) -> Observable { - return view.gestureRecognizers? - .compactMap { $0 as? UITapGestureRecognizer } - .first? - .rx.event - .map { _ in } - ?? Observable.empty() - } + return view.gestureRecognizers? + .compactMap { $0 as? UITapGestureRecognizer } + .first? + .rx.event + .map { _ in } + ?? Observable.empty() + } private func pushEditProfileViewController() { - let editProfileViewController = MyPageEditViewController() + let authService = AuthService() + let editProfileViewModel = MyPageEditViewModel(authService: authService) + let editProfileViewController = MyPageEditViewController(viewModel: editProfileViewModel) + navigationController?.pushViewController(editProfileViewController, animated: true) } + private func pushAskViewController() { + let askViewController = MyPageAskViewController(viewModel: self.viewModel) + navigationController?.pushViewController(askViewController, animated: true) + } + func actionButtonDidTap(for kind: ActionSheetKind) { - viewModel.actionSheetButtonTapped.accept(kind) - } + viewModel.actionSheetButtonTapped.accept(kind) + } private func showActionSheet(for kind: ActionSheetKind) { - let actionSheet = CustomActionSheetController(kind: kind) - actionSheet.delegate = self - present(actionSheet, animated: true, completion: nil) - } + let actionSheet = CustomActionSheetController(kind: kind) + actionSheet.delegate = self + present(actionSheet, animated: true, completion: nil) + } } diff --git a/KkuMulKum/Source/MyPage/ViewModel/MyPageEditViewModel.swift b/KkuMulKum/Source/MyPage/ViewModel/MyPageEditViewModel.swift new file mode 100644 index 00000000..0f07c352 --- /dev/null +++ b/KkuMulKum/Source/MyPage/ViewModel/MyPageEditViewModel.swift @@ -0,0 +1,94 @@ +// +// MyPageEditViewModel.swift +// KkuMulKum +// +// Created by 이지훈 on 8/22/24. +// + +import Foundation + +import UIKit +import Kingfisher +import RxSwift +import RxCocoa +import Moya + +class MyPageEditViewModel: ViewModelType { + private let authService: AuthServiceType + + struct Input { + let profileImageTap: Observable + let confirmButtonTap: Observable + let newProfileImage: Observable + } + + struct Output { + let profileImage: Driver + let isConfirmButtonEnabled: Driver + let serverResponse: Driver + } + + init(authService: AuthServiceType) { + self.authService = authService + } + + func transform(input: Input, disposeBag: DisposeBag) -> Output { + let imageDataRelay = BehaviorRelay(value: nil) + let serverResponseRelay = PublishRelay() + + + input.newProfileImage + .compactMap { $0?.jpegData(compressionQuality: 1.0) } + .bind(to: imageDataRelay) + .disposed(by: DisposeBag()) + + let profileImage = imageDataRelay + .map { data -> UIImage? in + guard let data = data else { return UIImage.imgProfile } + return UIImage(data: data) + } + .asDriver(onErrorJustReturn: UIImage.imgProfile) + + let isConfirmButtonEnabled = imageDataRelay + .map { $0 != nil } + .asDriver(onErrorJustReturn: false) + +// input.confirmButtonTap +// .withLatestFrom(imageDataRelay) +// .flatMapLatest { [weak self] imageData -> Observable in +// guard let self = self, let imageData = imageData else { +// return .just("이미지 데이터가 없습니다.") +// } +// return self.authService.rx.request(.updateProfileImage(image: imageData, fileName: "profile_image.jpg", mimeType: "image/jpeg")) +// .filterSuccessfulStatusCodes() +// .map { _ in "프로필 이미지가 성공적으로 업로드되었습니다." } +// .catchError { error in +// let networkError = error as? NetworkError ?? .unknownError("알 수 없는 오류가 발생했습니다.") +// return .just(self.handleError(networkError)) +// } +// } +// .bind(to: serverResponseRelay) +// .disposed(by: disposeBag) + + return Output( + profileImage: profileImage, + isConfirmButtonEnabled: isConfirmButtonEnabled, + serverResponse: serverResponseRelay.asDriver(onErrorJustReturn: nil) + ) + } + + private func handleError(_ error: NetworkError) -> String { + switch error { + case .apiError(let code, let message): + return code == 413 ? "이미지 크기가 너무 큽니다. 더 작은 이미지를 선택해주세요." : "업로드 실패: \(message)" + case .networkError(let error): + return "네트워크 오류: \(error.localizedDescription)" + case .decodingError: + return "데이터 처리 중 오류가 발생했습니다." + default: + return "알 수 없는 오류가 발생했습니다." + } + } +} + + From baca2a1577c034eb6cc3020a7a7432b11bb00265 Mon Sep 17 00:00:00 2001 From: hooni Date: Thu, 22 Aug 2024 16:38:18 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat/#321=20=EC=9D=B4=EC=9A=A9=EC=95=BD?= =?UTF-8?q?=EA=B4=80=20=EC=97=B0=EA=B2=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KkuMulKum.xcodeproj/project.pbxproj | 4 ++ .../MyPageAskViewController.swift | 2 - .../MyPageTermsViewController.swift | 39 +++++++++++++++++++ .../ViewController/MyPageViewController.swift | 13 +++++-- 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 KkuMulKum/Source/MyPage/ViewController/MyPageTermsViewController.swift diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index a798542c..2e4fc29b 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 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 */; }; + 784825072C77215B00FE07A0 /* MyPageTermsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 784825062C77215B00FE07A0 /* MyPageTermsViewController.swift */; }; 784E4D942C3B1C7F00BC943C /* KakaoSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 784E4D932C3B1C7F00BC943C /* KakaoSDK */; }; 784E4D962C3B1C7F00BC943C /* KakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 784E4D952C3B1C7F00BC943C /* KakaoSDKAuth */; }; 784E4D992C3B95A900BC943C /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 784E4D982C3B95A900BC943C /* KeychainAccess */; }; @@ -264,6 +265,7 @@ 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 = ""; }; + 784825062C77215B00FE07A0 /* MyPageTermsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageTermsViewController.swift; sourceTree = ""; }; 789196332C486F6B00FF8CDF /* KeychainAccessible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainAccessible.swift; sourceTree = ""; }; 789196352C492F8600FF8CDF /* AuthTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTargetType.swift; sourceTree = ""; }; 789196372C49697B00FF8CDF /* AuthError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthError.swift; sourceTree = ""; }; @@ -1236,6 +1238,7 @@ DE159D2F2C406E1600425101 /* MyPageViewController.swift */, 784824FD2C75F25900FE07A0 /* MyPageEditViewController.swift */, 784825042C7716E900FE07A0 /* MyPageAskViewController.swift */, + 784825062C77215B00FE07A0 /* MyPageTermsViewController.swift */, ); path = ViewController; sourceTree = ""; @@ -1839,6 +1842,7 @@ A39F2B212C499CE5008DA5F5 /* SetReadyStatusInfoServiceType.swift in Sources */, DE6D4D132C3F14D80005584B /* MeetingMemberCell.swift in Sources */, DD3F9DD82C49C25D008E1FF7 /* PromiseServiceProtocol.swift in Sources */, + 784825072C77215B00FE07A0 /* MyPageTermsViewController.swift in Sources */, DDAF1C932C3D6E3D008A37D3 /* PromiseViewController.swift in Sources */, 789D73BE2C47FE0F00C7077D /* AuthInterceptor.swift in Sources */, 789D73B32C47CC6D00C7077D /* LocalNotificationManager.swift in Sources */, diff --git a/KkuMulKum/Source/MyPage/ViewController/MyPageAskViewController.swift b/KkuMulKum/Source/MyPage/ViewController/MyPageAskViewController.swift index d123927c..7e4bd01f 100644 --- a/KkuMulKum/Source/MyPage/ViewController/MyPageAskViewController.swift +++ b/KkuMulKum/Source/MyPage/ViewController/MyPageAskViewController.swift @@ -13,7 +13,6 @@ class MyPageAskViewController: UIViewController, WKUIDelegate { private let viewModel : MyPageViewModel private var webView: WKWebView! - init(viewModel: MyPageViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) @@ -30,7 +29,6 @@ class MyPageAskViewController: UIViewController, WKUIDelegate { view = webView } - override func viewDidLoad() { super.viewDidLoad() diff --git a/KkuMulKum/Source/MyPage/ViewController/MyPageTermsViewController.swift b/KkuMulKum/Source/MyPage/ViewController/MyPageTermsViewController.swift new file mode 100644 index 00000000..746b62a8 --- /dev/null +++ b/KkuMulKum/Source/MyPage/ViewController/MyPageTermsViewController.swift @@ -0,0 +1,39 @@ +// +// MyPageTermsViewController.swift +// KkuMulKum +// +// Created by 이지훈 on 8/22/24. +// + +import UIKit +import WebKit + +class MyPageTermsViewController: UIViewController, WKUIDelegate { + + private let viewModel : MyPageViewModel + private var webView: WKWebView! + + init(viewModel: MyPageViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + let webConfiguration = WKWebViewConfiguration() + webView = WKWebView(frame: .zero, configuration: webConfiguration) + webView.uiDelegate = self + view = webView + } + + override func viewDidLoad() { + super.viewDidLoad() + + let myURL = URL(string:"https://www.apple.com") + let myRequest = URLRequest(url: myURL!) + webView.load(myRequest) + } +} diff --git a/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift b/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift index 800a2272..7fe96b7a 100644 --- a/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift +++ b/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift @@ -50,7 +50,8 @@ class MyPageViewController: BaseViewController, CustomActionSheetDelegate { .disposed(by: disposeBag) bindRowTapGesture(for: rootView.etcSettingView.termsOfServiceRow) - .subscribe(onNext: { print("이용약관 탭됨") }) + .subscribe(onNext: { [weak self] in + self?.pushTermsViewController() }) .disposed(by: disposeBag) bindRowTapGesture(for: rootView.etcSettingView.inquiryRow) @@ -62,8 +63,7 @@ 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 @@ -105,7 +105,12 @@ class MyPageViewController: BaseViewController, CustomActionSheetDelegate { let askViewController = MyPageAskViewController(viewModel: self.viewModel) navigationController?.pushViewController(askViewController, animated: true) } - + + private func pushTermsViewController() { + let askViewController = MyPageTermsViewController(viewModel: self.viewModel) + navigationController?.pushViewController(askViewController, animated: true) + } + func actionButtonDidTap(for kind: ActionSheetKind) { viewModel.actionSheetButtonTapped.accept(kind) } From e242262b69560b0dbca0be3c62e2873320087c63 Mon Sep 17 00:00:00 2001 From: hooni Date: Thu, 22 Aug 2024 17:03:09 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat/#321=20=EB=A7=81=ED=81=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20baseVC=20=EC=83=81=EC=86=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/MyPageAskViewController.swift | 14 +++++++++++--- .../ViewController/MyPageTermsViewController.swift | 14 +++++++++++--- .../MyPage/ViewModel/MyPageEditViewModel.swift | 2 ++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/KkuMulKum/Source/MyPage/ViewController/MyPageAskViewController.swift b/KkuMulKum/Source/MyPage/ViewController/MyPageAskViewController.swift index 7e4bd01f..b7091180 100644 --- a/KkuMulKum/Source/MyPage/ViewController/MyPageAskViewController.swift +++ b/KkuMulKum/Source/MyPage/ViewController/MyPageAskViewController.swift @@ -8,9 +8,9 @@ import UIKit import WebKit -class MyPageAskViewController: UIViewController, WKUIDelegate { +class MyPageAskViewController: BaseViewController { - private let viewModel : MyPageViewModel + private let viewModel: MyPageViewModel private var webView: WKWebView! init(viewModel: MyPageViewModel) { @@ -32,8 +32,16 @@ class MyPageAskViewController: UIViewController, WKUIDelegate { override func viewDidLoad() { super.viewDidLoad() - let myURL = URL(string:"https://www.apple.com") + let myURL = URL(string: "https://docs.google.com/forms/d/e/1FAIpQLSdRR65ARe2M7JxQEAx8vpFz-I8tyEYwlpLwtSnJjniGOapPVQ/viewform") let myRequest = URLRequest(url: myURL!) webView.load(myRequest) } + + override func setupView() { + super.setupView() + setupNavigationBarTitle(with: "문의하기") + setupNavigationBarBackButton() + } } + +extension MyPageAskViewController: WKUIDelegate {} diff --git a/KkuMulKum/Source/MyPage/ViewController/MyPageTermsViewController.swift b/KkuMulKum/Source/MyPage/ViewController/MyPageTermsViewController.swift index 746b62a8..49c2ad0c 100644 --- a/KkuMulKum/Source/MyPage/ViewController/MyPageTermsViewController.swift +++ b/KkuMulKum/Source/MyPage/ViewController/MyPageTermsViewController.swift @@ -8,9 +8,9 @@ import UIKit import WebKit -class MyPageTermsViewController: UIViewController, WKUIDelegate { +class MyPageTermsViewController: BaseViewController { - private let viewModel : MyPageViewModel + private let viewModel: MyPageViewModel private var webView: WKWebView! init(viewModel: MyPageViewModel) { @@ -32,8 +32,16 @@ class MyPageTermsViewController: UIViewController, WKUIDelegate { override func viewDidLoad() { super.viewDidLoad() - let myURL = URL(string:"https://www.apple.com") + let myURL = URL(string: "https://arrow-frog-4b9.notion.site/a66033a3ff4a40bfaa6eff0a5bee737d") let myRequest = URLRequest(url: myURL!) webView.load(myRequest) } + + override func setupView() { + super.setupView() + setupNavigationBarTitle(with: "이용약관") + setupNavigationBarBackButton() + } } + +extension MyPageTermsViewController: WKUIDelegate {} diff --git a/KkuMulKum/Source/MyPage/ViewModel/MyPageEditViewModel.swift b/KkuMulKum/Source/MyPage/ViewModel/MyPageEditViewModel.swift index 0f07c352..f7ab73ae 100644 --- a/KkuMulKum/Source/MyPage/ViewModel/MyPageEditViewModel.swift +++ b/KkuMulKum/Source/MyPage/ViewModel/MyPageEditViewModel.swift @@ -53,6 +53,8 @@ class MyPageEditViewModel: ViewModelType { .map { $0 != nil } .asDriver(onErrorJustReturn: false) + + // TODO: api 연결시 다시 연결예정 // input.confirmButtonTap // .withLatestFrom(imageDataRelay) // .flatMapLatest { [weak self] imageData -> Observable in