From 432076e3ca5b9f9fcc9863a57f8da4ba366f5db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=98=88=EC=84=B1?= Date: Thu, 21 Nov 2024 18:26:59 +0900 Subject: [PATCH 01/19] =?UTF-8?q?[feat]=20MyPage=20=ED=91=B8=EC=8B=9C=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=84=A4=EC=A0=95=20=EB=B7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cells/NotificationOptionCell.swift | 52 ++++++++++++++++ .../Settings/NotificationSettingsVC.swift | 62 ++++++++++++++++++- 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 DropDrug/Sources/Cells/NotificationOptionCell.swift diff --git a/DropDrug/Sources/Cells/NotificationOptionCell.swift b/DropDrug/Sources/Cells/NotificationOptionCell.swift new file mode 100644 index 00000000..ef25c9f8 --- /dev/null +++ b/DropDrug/Sources/Cells/NotificationOptionCell.swift @@ -0,0 +1,52 @@ +// Copyright © 2024 RT4. All rights reserved + +import UIKit +import SnapKit + +class NotificationOptionCell: UITableViewCell { + private let titleLabel = UILabel() + private let toggleSwitch = UISwitch() + private var switchAction: ((Bool) -> Void)? + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupViews() { + // 타이틀 라벨 + titleLabel.font = .systemFont(ofSize: 16) + titleLabel.textColor = .darkGray + contentView.addSubview(titleLabel) + contentView.addSubview(toggleSwitch) + + // 레이아웃 설정 + titleLabel.translatesAutoresizingMaskIntoConstraints = false + toggleSwitch.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + + toggleSwitch.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), + toggleSwitch.centerYAnchor.constraint(equalTo: contentView.centerYAnchor) + ]) + + // 스위치 값 변경 시 동작 설정 + toggleSwitch.addTarget(self, action: #selector(didToggleSwitch), for: .valueChanged) + } + + func configure(title: String, isSwitchOn: Bool, action: @escaping (Bool) -> Void) { + titleLabel.text = title + toggleSwitch.isOn = isSwitchOn + self.switchAction = action + } + + @objc private func didToggleSwitch() { + switchAction?(toggleSwitch.isOn) + } +} diff --git a/DropDrug/Sources/ViewControllers/MyPage/Settings/NotificationSettingsVC.swift b/DropDrug/Sources/ViewControllers/MyPage/Settings/NotificationSettingsVC.swift index 7103e2aa..05ce0ac5 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/Settings/NotificationSettingsVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/Settings/NotificationSettingsVC.swift @@ -1,23 +1,83 @@ // Copyright © 2024 RT4. All rights reserved import UIKit +import SnapKit class NotificationSettingsVC: UIViewController { + // MARK: - UI Components private lazy var backButton: CustomBackButton = { let button = CustomBackButton(title: " 푸시 알림 설정") button.addTarget(self, action: #selector(didTapBackButton), for: .touchUpInside) return button }() + private let tableView = UITableView() + private let notificationOptions = [ + "리워드 적립", + "공지사항", + "폐기 안내", + "푸시 알림" + ] + + // 알림 설정 상태 (기본값은 false) + private var notificationStates = Array(repeating: false, count: 4) + + // MARK: - Lifecycle Methods override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton) + + setupTableView() + } + + // MARK: - Setup Methods + private func setupTableView() { + tableView.dataSource = self + tableView.delegate = self + tableView.rowHeight = 60 + tableView.separatorStyle = .none + tableView.register(NotificationOptionCell.self, forCellReuseIdentifier: "NotificationOptionCell") + view.addSubview(tableView) + + tableView.snp.makeConstraints { make in + make.edges.equalTo(view.safeAreaLayoutGuide) + } } // MARK: - Actions @objc private func didTapBackButton() { - navigationController?.popViewController(animated: false) + navigationController?.popViewController(animated: true) + } +} + +// MARK: - UITableViewDataSource +extension NotificationSettingsVC: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return notificationOptions.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "NotificationOptionCell", for: indexPath) as! NotificationOptionCell + + let option = notificationOptions[indexPath.row] + let isEnabled = notificationStates[indexPath.row] + + // 셀 구성 + cell.configure(title: option, isSwitchOn: isEnabled) { [weak self] isOn in + self?.notificationStates[indexPath.row] = isOn + print("\(option) 설정 변경됨: \(isOn)") + } + cell.selectionStyle = .none + + return cell + } +} + +// MARK: - UITableViewDelegate +extension NotificationSettingsVC: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + // 추가 함수 } } From 316a006e6adf8b8dbc85b269946db8a09838e9f9 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Fri, 22 Nov 2024 03:39:07 +0900 Subject: [PATCH 02/19] =?UTF-8?q?=EC=95=84=EC=A7=81=EB=8F=84=20=EC=95=A0?= =?UTF-8?q?=ED=94=8C=EB=A1=9C=EA=B7=B8=EC=9D=B8..;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DropDrug/Sources/AppDelegate.swift | 1 + .../EnrollDetailViewController.swift | 24 ++++++++++ .../Register/SelectLoginTypeVC.swift | 45 +++++++++++++------ 3 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 DropDrug/Sources/ViewControllers/EnrollDetailViewController.swift diff --git a/DropDrug/Sources/AppDelegate.swift b/DropDrug/Sources/AppDelegate.swift index 6e6b2745..9f60f498 100644 --- a/DropDrug/Sources/AppDelegate.swift +++ b/DropDrug/Sources/AppDelegate.swift @@ -119,6 +119,7 @@ extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { print("Firebase registration token: \(String(describing: fcmToken))") SelectLoginTypeVC.keychain.set(fcmToken!, forKey: "FCMToken") +// print(SelectLoginTypeVC.keychain.get("appleUserFullName") ?? "저장된 이름 없음") let dataDict: [String: String] = ["token": fcmToken ?? ""] NotificationCenter.default.post( diff --git a/DropDrug/Sources/ViewControllers/EnrollDetailViewController.swift b/DropDrug/Sources/ViewControllers/EnrollDetailViewController.swift new file mode 100644 index 00000000..b422d845 --- /dev/null +++ b/DropDrug/Sources/ViewControllers/EnrollDetailViewController.swift @@ -0,0 +1,24 @@ +// Copyright © 2024 RT4. All rights reserved + +import UIKit + +class EnrollDetailViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + + /* + // 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. + } + */ + +} diff --git a/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift b/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift index d85748df..1ea285ad 100644 --- a/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift +++ b/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift @@ -326,32 +326,51 @@ extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { print("Error: Nonce is missing.") return } + guard let appleIDToken = appleIDCredential.identityToken else { print("Error: Unable to fetch identity token.") return } + guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else { print("Error: Unable to serialize token string.") return } + SelectLoginTypeVC.keychain.set(idTokenString, forKey: "appleUserIDToken") + + var fullNameToUse: PersonNameComponents? = nil - guard let fullName = appleIDCredential.fullName else { - print("Unable to fetch full name") - return + if let fullName = appleIDCredential.fullName { + fullNameToUse = fullName + if let givenName = fullName.givenName, let familyName = fullName.familyName { + let completeName = "\(givenName) \(familyName)" + SelectLoginTypeVC.keychain.set(completeName, forKey: "appleUserFullName") + print("Saved fullName: \(completeName)") + } } - SelectLoginTypeVC.keychain.set(appleIDToken, forKey: "appleUserIDToken") - if let givenName = fullName.givenName, let familyName = fullName.familyName { - let completeName = "\(givenName) \(familyName)" - SelectLoginTypeVC.keychain.set(completeName, forKey: "appleUserfullName") - print("Saved fullName: \(completeName)") - } else { - print("No full name provided.") + if let savedFullName = SelectLoginTypeVC.keychain.get("appleUserFullName") { + print("무조건 진입아님?\(SelectLoginTypeVC.keychain.get("appleUserFullName"))") + + var nameComponents = PersonNameComponents() + let nameParts = savedFullName.split(separator: " ") + if nameParts.count > 1 { + nameComponents.givenName = String(nameParts[0]) + nameComponents.familyName = String(nameParts[1]) + } else { + nameComponents.givenName = savedFullName + } + fullNameToUse = nameComponents + print("Fetched saved fullName from Keychain: \(savedFullName)") } - print("idTokenString : \(idTokenString)") - - let credential = OAuthProvider.appleCredential(withIDToken: idTokenString, rawNonce: nonce, fullName: fullName) + print("idTokenString") + print(idTokenString) + print("nonce") + print(nonce) + print(fullNameToUse ?? "nil") + + let credential = OAuthProvider.appleCredential(withIDToken: idTokenString, rawNonce: nonce, fullName: fullNameToUse!) Auth.auth().signIn(with: credential) { authResult, error in if let error = error { From 031dff9b20a98fd791d78db50c60f89f6b65c315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=98=88=EC=84=B1?= Date: Fri, 22 Nov 2024 11:18:05 +0900 Subject: [PATCH 03/19] =?UTF-8?q?[feat]=20=EC=B2=98=EB=B0=A9=EC=95=BD=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Cells/PrescriptionDrugCell.swift | 47 +++++ .../{Register => Auth}/KakaoAuthVM.swift | 0 .../{Register => Auth}/RegisterManager.swift | 0 .../ValidationUtility.swift | 0 .../{Register => Auth}/LoginVC.swift | 0 .../{Register => Auth}/OnboardingVC.swift | 0 .../SelectLoginTypeVC.swift | 0 .../{Register => Auth}/SignUpVC.swift | 0 .../{Register => Auth}/SplashVC.swift | 0 .../Disposal/SelectDropTypeVC.swift | 15 +- .../MainTabBarController.swift | 2 +- .../MyPage/Settings/AccountSettingsVC.swift | 8 +- .../Register/DiscardPrescriptionDrugVC.swift | 136 +++++++++++++++ .../Register/PrescriptionDrugVC.swift | 160 ++++++++++++++++++ .../{Register => Auth}/CheckBoxButton.swift | 0 .../CustomLabelTextFieldView.swift | 0 .../{Register => Auth}/PaddedTextField.swift | 0 .../Sources/Views/Disposal/AddDrugView.swift | 63 +++++++ .../Sources/Views/MyPage/RewardView.swift | 1 - 19 files changed, 414 insertions(+), 18 deletions(-) create mode 100644 DropDrug/Sources/Cells/PrescriptionDrugCell.swift rename DropDrug/Sources/Models/{Register => Auth}/KakaoAuthVM.swift (100%) rename DropDrug/Sources/Models/{Register => Auth}/RegisterManager.swift (100%) rename DropDrug/Sources/Models/{Register => Auth}/ValidationUtility.swift (100%) rename DropDrug/Sources/ViewControllers/{Register => Auth}/LoginVC.swift (100%) rename DropDrug/Sources/ViewControllers/{Register => Auth}/OnboardingVC.swift (100%) rename DropDrug/Sources/ViewControllers/{Register => Auth}/SelectLoginTypeVC.swift (100%) rename DropDrug/Sources/ViewControllers/{Register => Auth}/SignUpVC.swift (100%) rename DropDrug/Sources/ViewControllers/{Register => Auth}/SplashVC.swift (100%) create mode 100644 DropDrug/Sources/ViewControllers/Register/DiscardPrescriptionDrugVC.swift create mode 100644 DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift rename DropDrug/Sources/Views/{Register => Auth}/CheckBoxButton.swift (100%) rename DropDrug/Sources/Views/{Register => Auth}/CustomLabelTextFieldView.swift (100%) rename DropDrug/Sources/Views/{Register => Auth}/PaddedTextField.swift (100%) create mode 100644 DropDrug/Sources/Views/Disposal/AddDrugView.swift diff --git a/DropDrug/Sources/Cells/PrescriptionDrugCell.swift b/DropDrug/Sources/Cells/PrescriptionDrugCell.swift new file mode 100644 index 00000000..b4f8a44c --- /dev/null +++ b/DropDrug/Sources/Cells/PrescriptionDrugCell.swift @@ -0,0 +1,47 @@ +// Copyright © 2024 RT4. All rights reserved + +import Foundation +import UIKit + +class PrescriptionDrugCell: UITableViewCell { + static let identifier = "PrescriptionDrugCell" + + private let dateLabel: UILabel = { + let label = UILabel() + label.font = UIFont.systemFont(ofSize: 14) + label.textColor = .darkGray + return label + }() + + private let durationLabel: UILabel = { + let label = UILabel() + label.font = UIFont.systemFont(ofSize: 14) + label.textColor = .black + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + let stackView = UIStackView(arrangedSubviews: [dateLabel, durationLabel]) + stackView.axis = .horizontal + stackView.distribution = .fill + stackView.alignment = .center + stackView.spacing = 10 + + contentView.addSubview(stackView) + stackView.snp.makeConstraints { make in + make.edges.equalToSuperview().inset(20) +// make.leading.trailing.equalToSuperview() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configure(date: String, duration: String) { + dateLabel.text = date + durationLabel.text = duration + } +} diff --git a/DropDrug/Sources/Models/Register/KakaoAuthVM.swift b/DropDrug/Sources/Models/Auth/KakaoAuthVM.swift similarity index 100% rename from DropDrug/Sources/Models/Register/KakaoAuthVM.swift rename to DropDrug/Sources/Models/Auth/KakaoAuthVM.swift diff --git a/DropDrug/Sources/Models/Register/RegisterManager.swift b/DropDrug/Sources/Models/Auth/RegisterManager.swift similarity index 100% rename from DropDrug/Sources/Models/Register/RegisterManager.swift rename to DropDrug/Sources/Models/Auth/RegisterManager.swift diff --git a/DropDrug/Sources/Models/Register/ValidationUtility.swift b/DropDrug/Sources/Models/Auth/ValidationUtility.swift similarity index 100% rename from DropDrug/Sources/Models/Register/ValidationUtility.swift rename to DropDrug/Sources/Models/Auth/ValidationUtility.swift diff --git a/DropDrug/Sources/ViewControllers/Register/LoginVC.swift b/DropDrug/Sources/ViewControllers/Auth/LoginVC.swift similarity index 100% rename from DropDrug/Sources/ViewControllers/Register/LoginVC.swift rename to DropDrug/Sources/ViewControllers/Auth/LoginVC.swift diff --git a/DropDrug/Sources/ViewControllers/Register/OnboardingVC.swift b/DropDrug/Sources/ViewControllers/Auth/OnboardingVC.swift similarity index 100% rename from DropDrug/Sources/ViewControllers/Register/OnboardingVC.swift rename to DropDrug/Sources/ViewControllers/Auth/OnboardingVC.swift diff --git a/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift b/DropDrug/Sources/ViewControllers/Auth/SelectLoginTypeVC.swift similarity index 100% rename from DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift rename to DropDrug/Sources/ViewControllers/Auth/SelectLoginTypeVC.swift diff --git a/DropDrug/Sources/ViewControllers/Register/SignUpVC.swift b/DropDrug/Sources/ViewControllers/Auth/SignUpVC.swift similarity index 100% rename from DropDrug/Sources/ViewControllers/Register/SignUpVC.swift rename to DropDrug/Sources/ViewControllers/Auth/SignUpVC.swift diff --git a/DropDrug/Sources/ViewControllers/Register/SplashVC.swift b/DropDrug/Sources/ViewControllers/Auth/SplashVC.swift similarity index 100% rename from DropDrug/Sources/ViewControllers/Register/SplashVC.swift rename to DropDrug/Sources/ViewControllers/Auth/SplashVC.swift diff --git a/DropDrug/Sources/ViewControllers/Disposal/SelectDropTypeVC.swift b/DropDrug/Sources/ViewControllers/Disposal/SelectDropTypeVC.swift index 38fa30c3..4ea1d380 100644 --- a/DropDrug/Sources/ViewControllers/Disposal/SelectDropTypeVC.swift +++ b/DropDrug/Sources/ViewControllers/Disposal/SelectDropTypeVC.swift @@ -7,7 +7,7 @@ import SnapKit class SelectDropTypeVC : UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate { private var collectionView: UICollectionView! - // TODO : 이미지 에셋 추가 + // TODO: 이미지 에셋 추가 let categories = [ ("일반 의약품", "OB1"), ("병원 처방약", "OB1") @@ -87,16 +87,13 @@ class SelectDropTypeVC : UIViewController, UICollectionViewDataSource, UICollect } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - // 선택된 카테고리에 따라 분기 처리 switch indexPath.item { - case 0: // 첫 번째 아이템 - let SelectDrugTypeVC = SelectDrugTypeVC() // 일반 의약품 화면 + case 0: + let SelectDrugTypeVC = SelectDrugTypeVC() navigationController?.pushViewController(SelectDrugTypeVC, animated: true) - - case 1: // 두 번째 아이템 - let prescriptionMedicineVC = TestVC() // 병원 처방약 화면 - navigationController?.pushViewController(prescriptionMedicineVC, animated: true) - + case 1: + let prescriptionDrugVC = PrescriptionDrugVC() + navigationController?.pushViewController(prescriptionDrugVC, animated: true) default: print("알 수 없는 카테고리 선택됨") } diff --git a/DropDrug/Sources/ViewControllers/MainTabBarController.swift b/DropDrug/Sources/ViewControllers/MainTabBarController.swift index 811d54a3..7272afa9 100644 --- a/DropDrug/Sources/ViewControllers/MainTabBarController.swift +++ b/DropDrug/Sources/ViewControllers/MainTabBarController.swift @@ -5,7 +5,7 @@ import UIKit class MainTabBarController: UITabBarController { private let HomeVC = UINavigationController(rootViewController: HomeViewController()) - private let SearchVC = UINavigationController(rootViewController: TestVC()) + private let SearchVC = UINavigationController(rootViewController: PrescriptionDrugVC()) private let LocationVC = UINavigationController(rootViewController: MapViewController()) private let InfoVC = UINavigationController(rootViewController: InfoMainViewController()) private let MyVC = UINavigationController(rootViewController: MyPageVC()) diff --git a/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift b/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift index 4debbf48..0f0fc586 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift @@ -144,7 +144,7 @@ class AccountSettingsVC: UIViewController, UITableViewDataSource, UITableViewDel SelectLoginTypeVC.keychain.delete("serverAccessToken") SelectLoginTypeVC.keychain.delete("serverRefreshToken") print("로그아웃 처리") - self.navigateToSplashScreen() + self.showSplashScreen() case .failure(let error): print("로그아웃 요청 실패: \(error.localizedDescription)") } @@ -171,12 +171,6 @@ class AccountSettingsVC: UIViewController, UITableViewDataSource, UITableViewDel } } - private func navigateToSplashScreen() { - let SplashVC = SplashVC() - SplashVC.modalPresentationStyle = .fullScreen - present(SplashVC, animated: true) - } - private func showSplashScreen() { let splashViewController = SplashVC() diff --git a/DropDrug/Sources/ViewControllers/Register/DiscardPrescriptionDrugVC.swift b/DropDrug/Sources/ViewControllers/Register/DiscardPrescriptionDrugVC.swift new file mode 100644 index 00000000..d6311676 --- /dev/null +++ b/DropDrug/Sources/ViewControllers/Register/DiscardPrescriptionDrugVC.swift @@ -0,0 +1,136 @@ +// Copyright © 2024 RT4. All rights reserved + +import UIKit +import SnapKit + +class DiscardPrescriptionDrugVC: UIViewController { + + // MARK: - UI Elements + private lazy var backButton: CustomBackButton = { + let button = CustomBackButton(title: " 의약품 삭제하기") + button.addTarget(self, action: #selector(didTapBackButton), for: .touchUpInside) + return button + }() + + private lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.register(PrescriptionDrugCell.self, forCellReuseIdentifier: PrescriptionDrugCell.identifier) + tableView.dataSource = self + tableView.delegate = self + tableView.separatorStyle = .none + return tableView + }() + + private lazy var completeButton: UIButton = { + let button = UIButton() + button.setTitle("삭제하기", for: .normal) + button.backgroundColor = Constants.Colors.skyblue + button.setTitleColor(.white, for: .normal) + button.layer.cornerRadius = 8 + button.addTarget(self, action: #selector(didTapCompleteButton), for: .touchUpInside) + return button + }() + + // MARK: - Data + private var drugList = [ + (date: "23/04/05", duration: "n일치"), + (date: "23/04/06", duration: "n일치"), + (date: "23/04/07", duration: "n일치"), + (date: "23/04/08", duration: "n일치") + ] + private var selectedIndex: Int? + + // MARK: - Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + + navigationController?.navigationBar.isHidden = false + navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton) + + setupView() + setConstraints() + } + + // MARK: - Setup Methods + func setupView() { + [tableView, completeButton].forEach { + view.addSubview($0) + } + } + + func setConstraints() { + tableView.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(16) + make.left.right.equalToSuperview().inset(16) + make.bottom.equalTo(completeButton.snp.top).offset(-16) + } + + completeButton.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(16) + make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-16) + make.height.equalTo(50) + } + } + + // MARK: - Actions + @objc private func didTapBackButton() { + navigationController?.popViewController(animated: true) + navigationController?.navigationBar.isHidden = true + } + + @objc private func didTapCompleteButton() { + guard let selectedIndex = selectedIndex else { + showAlert(title: "오류", message: "삭제할 항목을 선택해주세요.") + return + } + + let selectedDrug = drugList[selectedIndex] + print("삭제 완료: \(selectedDrug)") + + // 삭제 후 데이터 갱신 + drugList.remove(at: selectedIndex) + self.selectedIndex = nil + tableView.reloadData() + } + + private func showAlert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "확인", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } +} + +// MARK: - UITableViewDataSource, UITableViewDelegate +extension DiscardPrescriptionDrugVC: UITableViewDataSource, UITableViewDelegate { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return drugList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: PrescriptionDrugCell.identifier, for: indexPath) as? PrescriptionDrugCell else { + return UITableViewCell() + } + + let drug = drugList[indexPath.row] + cell.configure(date: drug.date, duration: drug.duration) + + // 선택 상태에 따라 체크마크 또는 빈 동그라미 설정 + if selectedIndex == indexPath.row { + let checkmarkIcon = UIImageView(image: UIImage(systemName: "checkmark.circle.fill")) + checkmarkIcon.tintColor = Constants.Colors.skyblue + cell.accessoryView = checkmarkIcon + } else { + let emptyCircleIcon = UIImageView(image: UIImage(systemName: "circle")) + emptyCircleIcon.tintColor = .lightGray + cell.accessoryView = emptyCircleIcon + } + + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + selectedIndex = indexPath.row + tableView.reloadData() + } +} diff --git a/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift b/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift new file mode 100644 index 00000000..1e63aa47 --- /dev/null +++ b/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift @@ -0,0 +1,160 @@ +// Copyright © 2024 RT4. All rights reserved + +import UIKit +import SnapKit + +class PrescriptionDrugVC: UIViewController { + //TODO: api 연결 시 따로 빼기 + struct PrescriptionDrug { + let date: String + let duration: String + } + + var drugs: [PrescriptionDrug] = [] + + // MARK: - UI Elements + private lazy var logoLabelView: UILabel = { + let label = UILabel() + label.text = "DropDrug" + label.textAlignment = .center + label.numberOfLines = 0 + label.font = UIFont.roRegularFont(ofSize: 26) + label.textColor = Constants.Colors.skyblue + return label + }() + + private var addDrugView = AddDrugView() + + public lazy var registeredDrugLabel: UILabel = { + let label = UILabel() + label.text = "등록된 의약품" + label.textColor = Constants.Colors.gray900 + label.textAlignment = .left + label.font = UIFont.ptdSemiBoldFont(ofSize: 17) + return label + }() + + private lazy var discardButton: UIButton = { + let button = UIButton() + button.setImage(UIImage(systemName: "trash.fill")?.withTintColor(Constants.Colors.gray500 ?? .systemGray , renderingMode: .alwaysOriginal), for: .normal) + button.addTarget(self, action: #selector(discardButtonTapped), for: .touchUpInside) + return button + }() + + private lazy var drugsTableView: UITableView = { + let tableView = UITableView() + tableView.register(PrescriptionDrugCell.self, forCellReuseIdentifier: PrescriptionDrugCell.identifier) + tableView.dataSource = self + tableView.delegate = self + tableView.separatorStyle = .none + return tableView + }() + + // MARK: - Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + + if navigationController == nil { + let navigationController = UINavigationController(rootViewController: self) + navigationController.modalPresentationStyle = .fullScreen + + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let keyWindow = windowScene.windows.first(where: { $0.isKeyWindow }) { + keyWindow.rootViewController?.present(navigationController, animated: true) + } + } + + self.navigationController?.isNavigationBarHidden = true + + drugs = [ + PrescriptionDrug(date: "23/04/05", duration: "5일치"), + PrescriptionDrug(date: "23/05/01", duration: "3일치"), + PrescriptionDrug(date: "23/04/05", duration: "5일치"), + PrescriptionDrug(date: "23/04/05", duration: "5일치"), + PrescriptionDrug(date: "23/04/05", duration: "5일치"), + PrescriptionDrug(date: "23/04/05", duration: "5일치"), + PrescriptionDrug(date: "23/04/05", duration: "5일치"), + PrescriptionDrug(date: "23/04/05", duration: "5일치"), + PrescriptionDrug(date: "23/06/15", duration: "7일치") + ] + drugsTableView.reloadData() + setupView() + setConstraints() + setupGestures() + } + + // MARK: - Setup Methods + + func setupView() { + [logoLabelView, addDrugView, registeredDrugLabel, discardButton, drugsTableView].forEach { + view.addSubview($0) + } + } + + func setConstraints() { + logoLabelView.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(20) + make.leading.equalToSuperview().inset(20) + } + addDrugView.snp.makeConstraints { make in + make.top.equalTo(logoLabelView.snp.bottom).offset(20) + make.leading.trailing.equalToSuperview() + make.height.equalTo(superViewHeight * 0.25) + } + registeredDrugLabel.snp.makeConstraints { make in + make.top.equalTo(addDrugView.snp.bottom).offset(20) + make.leading.equalToSuperview().inset(20) + } + discardButton.snp.makeConstraints { make in + make.centerY.equalTo(registeredDrugLabel) + make.width.height.equalTo(50) + make.trailing.equalTo(view.safeAreaLayoutGuide) + } + drugsTableView.snp.makeConstraints { make in + make.top.equalTo(registeredDrugLabel.snp.bottom).offset(20) + make.bottom.leading.trailing.equalToSuperview() + } + } + + func setupGestures() { + let addDrugTapGesture = UITapGestureRecognizer(target: self, action: #selector(addDrugViewTapped)) + addDrugView.addGestureRecognizer(addDrugTapGesture) + addDrugView.isUserInteractionEnabled = true + } + + // MARK: - Actions + + @objc func addDrugViewTapped() { + //TODO: 약 등록하기 VC로 연결 + self.navigationController?.isNavigationBarHidden = false + let testVC = TestVC() + navigationController?.pushViewController(testVC, animated: false) + } + + @objc func discardButtonTapped(){ + self.navigationController?.isNavigationBarHidden = false + let DiscardPrescriptionDrugVC = DiscardPrescriptionDrugVC() + navigationController?.pushViewController(DiscardPrescriptionDrugVC, animated: false) + } +} + + + +// MARK: - UITableViewDataSource & UITableViewDelegate + +extension PrescriptionDrugVC: UITableViewDataSource, UITableViewDelegate { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return drugs.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: PrescriptionDrugCell.identifier, for: indexPath) as? PrescriptionDrugCell else { + return UITableViewCell() + } + let drug = drugs[indexPath.row] + cell.configure(date: drug.date, duration: drug.duration) + cell.selectionStyle = .none + return cell + } +} diff --git a/DropDrug/Sources/Views/Register/CheckBoxButton.swift b/DropDrug/Sources/Views/Auth/CheckBoxButton.swift similarity index 100% rename from DropDrug/Sources/Views/Register/CheckBoxButton.swift rename to DropDrug/Sources/Views/Auth/CheckBoxButton.swift diff --git a/DropDrug/Sources/Views/Register/CustomLabelTextFieldView.swift b/DropDrug/Sources/Views/Auth/CustomLabelTextFieldView.swift similarity index 100% rename from DropDrug/Sources/Views/Register/CustomLabelTextFieldView.swift rename to DropDrug/Sources/Views/Auth/CustomLabelTextFieldView.swift diff --git a/DropDrug/Sources/Views/Register/PaddedTextField.swift b/DropDrug/Sources/Views/Auth/PaddedTextField.swift similarity index 100% rename from DropDrug/Sources/Views/Register/PaddedTextField.swift rename to DropDrug/Sources/Views/Auth/PaddedTextField.swift diff --git a/DropDrug/Sources/Views/Disposal/AddDrugView.swift b/DropDrug/Sources/Views/Disposal/AddDrugView.swift new file mode 100644 index 00000000..7ec9f3ee --- /dev/null +++ b/DropDrug/Sources/Views/Disposal/AddDrugView.swift @@ -0,0 +1,63 @@ +// Copyright © 2024 RT4. All rights reserved + +import UIKit +import SnapKit + +class AddDrugView: UIView { + + private lazy var headerImageView: UIImageView = { + let imageView = UIImageView() + imageView.image = UIImage(named: "OB1") // TODO: 이미지 에셋 변경 + imageView.contentMode = .scaleAspectFill + imageView.clipsToBounds = true + return imageView + }() + + public lazy var addDrugLabel: UILabel = { + let label = UILabel() + label.text = "의약품 등록하기" + label.textColor = .white + label.textAlignment = .left + label.font = UIFont.ptdSemiBoldFont(ofSize: 17) + return label + }() + + private let chevronImageView: UIImageView = { + let imageView = UIImageView() + imageView.image = UIImage(systemName: "chevron.right") + imageView.tintColor = .white + return imageView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + setConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupView() { + [headerImageView, addDrugLabel, chevronImageView].forEach { + addSubview($0) + } + } + + private func setConstraints() { + headerImageView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + addDrugLabel.snp.makeConstraints { make in + make.leading.equalToSuperview().offset(20) + make.bottom.equalToSuperview().offset(-20) + } + + chevronImageView.snp.makeConstraints { make in + make.centerY.equalTo(addDrugLabel) + make.leading.equalTo(addDrugLabel.snp.trailing).offset(10) + } + } +} diff --git a/DropDrug/Sources/Views/MyPage/RewardView.swift b/DropDrug/Sources/Views/MyPage/RewardView.swift index cfd676eb..6e556876 100644 --- a/DropDrug/Sources/Views/MyPage/RewardView.swift +++ b/DropDrug/Sources/Views/MyPage/RewardView.swift @@ -33,7 +33,6 @@ class RewardView: UIView { private let gradientLayer = CAGradientLayer() - /// Property to control chevron visibility var isChevronHidden: Bool = false { didSet { chevronImageView.isHidden = isChevronHidden From 0408e5f73a5715d3ab2c1cd411f9a05cdc8275a3 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:17:19 +0900 Subject: [PATCH 04/19] =?UTF-8?q?=EC=95=A0=ED=94=8C=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Register/SelectLoginTypeVC.swift | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift b/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift index 1ea285ad..111e1565 100644 --- a/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift +++ b/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift @@ -350,7 +350,6 @@ extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { } if let savedFullName = SelectLoginTypeVC.keychain.get("appleUserFullName") { - print("무조건 진입아님?\(SelectLoginTypeVC.keychain.get("appleUserFullName"))") var nameComponents = PersonNameComponents() let nameParts = savedFullName.split(separator: " ") @@ -361,16 +360,20 @@ extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { nameComponents.givenName = savedFullName } fullNameToUse = nameComponents - print("Fetched saved fullName from Keychain: \(savedFullName)") +// print("Fetched saved fullName from Keychain: \(savedFullName)") } - print("idTokenString") - print(idTokenString) - print("nonce") - print(nonce) - print(fullNameToUse ?? "nil") + print("idTokenString: \(idTokenString)") + print("nonce: \(nonce)") + if let fullName = fullNameToUse { + print("fullNameToUse - givenName: \(fullName.givenName ?? "nil"), familyName: \(fullName.familyName ?? "nil")") + } else { + print("fullNameToUse is nil") + } - let credential = OAuthProvider.appleCredential(withIDToken: idTokenString, rawNonce: nonce, fullName: fullNameToUse!) + let credential = OAuthProvider.appleCredential(withIDToken: idTokenString, + rawNonce: nonce, + fullName: fullNameToUse) Auth.auth().signIn(with: credential) { authResult, error in if let error = error { @@ -390,7 +393,6 @@ extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { } } } - default: print("Error: Unsupported credential type.") break @@ -399,7 +401,6 @@ extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { } func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { - // Handle error. print("Sign in with Apple errored: \(error)") } From 25b5ccae8a28ad291a723f28329d1c7ebc79af76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=98=88=EC=84=B1?= Date: Fri, 22 Nov 2024 13:18:13 +0900 Subject: [PATCH 05/19] [chore] ys --- .../APIServices/BearerToken+Plugin.swift | 2 +- .../APIServices/MyPage/MemberAPI.swift | 10 +---- .../ViewControllers/Auth/SplashVC.swift | 18 +------- .../Main/HomeViewController.swift | 7 +++- .../MyPage/Settings/AccountSettingsVC.swift | 42 +++++++++++++++---- .../Register/DiscardPrescriptionDrugVC.swift | 5 +-- .../Sources/Views/MyPage/ProfileView.swift | 2 +- .../Sources/Views/MyPage/RewardView.swift | 2 +- 8 files changed, 48 insertions(+), 40 deletions(-) diff --git a/DropDrug/Sources/APIServices/BearerToken+Plugin.swift b/DropDrug/Sources/APIServices/BearerToken+Plugin.swift index 0da96415..4f517308 100644 --- a/DropDrug/Sources/APIServices/BearerToken+Plugin.swift +++ b/DropDrug/Sources/APIServices/BearerToken+Plugin.swift @@ -5,7 +5,7 @@ import Moya final class BearerTokenPlugin: PluginType { private var accessToken: String? { - return SelectLoginTypeVC.keychain.get("serverRefreshToken") + return SelectLoginTypeVC.keychain.get("serverAccessToken") } func prepare(_ request: URLRequest, target: TargetType) -> URLRequest { diff --git a/DropDrug/Sources/APIServices/MyPage/MemberAPI.swift b/DropDrug/Sources/APIServices/MyPage/MemberAPI.swift index a67010cb..a45f3ea3 100644 --- a/DropDrug/Sources/APIServices/MyPage/MemberAPI.swift +++ b/DropDrug/Sources/APIServices/MyPage/MemberAPI.swift @@ -64,14 +64,6 @@ extension MemberAPI: TargetType { } var headers: [String: String]? { - let keychain = KeychainSwift() - if let accessToken = keychain.get("serverRefreshToken") { - return [ - "Authorization": "Bearer \(accessToken)", - "Accept": "*/*", - "Content-Type": "application/json" - ] - } - return ["Accept": "*/*"] + return ["Content-Type": "application/json"] } } diff --git a/DropDrug/Sources/ViewControllers/Auth/SplashVC.swift b/DropDrug/Sources/ViewControllers/Auth/SplashVC.swift index bde56c6c..a777b550 100644 --- a/DropDrug/Sources/ViewControllers/Auth/SplashVC.swift +++ b/DropDrug/Sources/ViewControllers/Auth/SplashVC.swift @@ -23,27 +23,14 @@ class SplashVC : UIViewController { setConstraints() DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { self.checkAuthenticationStatus() -// self.navigateToMainScreen() } } private func checkAuthenticationStatus() { - let keychain = KeychainSwift() - - if let accessToken = keychain.get("serverAccessToken"), - let refreshToken = keychain.get("serverRefreshToken") { + if let accessToken = SelectLoginTypeVC.keychain.get("serverAccessToken") + { print("Access Token 존재: \(accessToken)") navigateToMainScreen() -// } else if let refreshToken = keychain.get("serverRefreshToken") { -// print("Access Token 없음. Refresh Token 존재.") -// refreshAccessToken(refreshToken: refreshToken) { success in -// if success { -// self?.navigateToMainScreen() -// } else { -// print("Refresh Token 갱신 실패.") -// self?.navigateToOnBoaringScreen() -// } -// } } else { print("토큰 없음. 로그인 화면으로 이동.") navigateToOnBoaringScreen() @@ -53,7 +40,6 @@ class SplashVC : UIViewController { func setupViews() { view.backgroundColor = Constants.Colors.skyblue view.addSubview(titleLabel) - } func navigateToMainScreen() { diff --git a/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift b/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift index a2a43f2e..7a2121b2 100644 --- a/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift +++ b/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift @@ -21,7 +21,10 @@ class HomeViewController: UIViewController, CLLocationManagerDelegate, MKMapView configureMapView() navigationController?.navigationBar.isHidden = true - + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) getHomeInfo { [weak self] isSuccess in if isSuccess { DispatchQueue.main.async { @@ -32,8 +35,8 @@ class HomeViewController: UIViewController, CLLocationManagerDelegate, MKMapView print("GET 호출 실패") } } + } - private let homeView: HomeView = { let hv = HomeView() hv.resetBtn.addTarget(self, action: #selector(resetBtnTapped), for: .touchUpInside) diff --git a/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift b/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift index 0f0fc586..b35bcf4d 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift @@ -7,7 +7,7 @@ import KeychainSwift class AccountSettingsVC: UIViewController, UITableViewDataSource, UITableViewDelegate { - let provider = MoyaProvider(plugins: [NetworkLoggerPlugin()]) + let provider = MoyaProvider(plugins: [BearerTokenPlugin(), NetworkLoggerPlugin()]) let Authprovider = MoyaProvider(plugins: [NetworkLoggerPlugin(), BearerTokenPlugin()]) private lazy var backButton: CustomBackButton = { @@ -99,10 +99,17 @@ class AccountSettingsVC: UIViewController, UITableViewDataSource, UITableViewDel textField.text = currentValue } alert.addAction(UIAlertAction(title: "취소", style: .cancel, handler: nil)) - alert.addAction(UIAlertAction(title: "저장", style: .default, handler: { _ in - // TODO: 닉네임 변경 api - if let text = alert.textFields?.first?.text, !text.isEmpty { - completion(text) + alert.addAction(UIAlertAction(title: "저장", style: .default, handler: {_ in + if let newNickname = alert.textFields?.first?.text, !newNickname.isEmpty { + self.updateNickname(newNickname: newNickname) { isSuccess in + if isSuccess { + print("닉네임 변경 성공") + self.nickname = newNickname + self.tableView.reloadData() + } else { + print("닉네임 변경 실패") + } + } } })) present(alert, animated: true, completion: nil) @@ -120,8 +127,6 @@ class AccountSettingsVC: UIViewController, UITableViewDataSource, UITableViewDel print("계정 삭제 실패 - 다시 시도해주세요") } } - - })) present(alert, animated: true, completion: nil) } @@ -171,6 +176,29 @@ class AccountSettingsVC: UIViewController, UITableViewDataSource, UITableViewDel } } + private func updateNickname(newNickname: String, completion: @escaping (Bool) -> Void) { + guard let accessToken = SelectLoginTypeVC.keychain.get("serverAccessToken") else { + print("Access Token 없음") + completion(false) + return + } + + provider.request(.updateNickname(newNickname: newNickname)) { result in + switch result { + case .success(let response): + if response.statusCode == 200 { + completion(true) // 성공 + } else { + print("서버 응답 오류: \(response.statusCode)") + completion(false) + } + case .failure(let error): + print("네트워크 오류: \(error.localizedDescription)") + completion(false) + } + } + } + private func showSplashScreen() { let splashViewController = SplashVC() diff --git a/DropDrug/Sources/ViewControllers/Register/DiscardPrescriptionDrugVC.swift b/DropDrug/Sources/ViewControllers/Register/DiscardPrescriptionDrugVC.swift index d6311676..c50c7d25 100644 --- a/DropDrug/Sources/ViewControllers/Register/DiscardPrescriptionDrugVC.swift +++ b/DropDrug/Sources/ViewControllers/Register/DiscardPrescriptionDrugVC.swift @@ -4,7 +4,6 @@ import UIKit import SnapKit class DiscardPrescriptionDrugVC: UIViewController { - // MARK: - UI Elements private lazy var backButton: CustomBackButton = { let button = CustomBackButton(title: " 의약품 삭제하기") @@ -26,7 +25,8 @@ class DiscardPrescriptionDrugVC: UIViewController { button.setTitle("삭제하기", for: .normal) button.backgroundColor = Constants.Colors.skyblue button.setTitleColor(.white, for: .normal) - button.layer.cornerRadius = 8 + button.layer.cornerRadius = 25 + button.titleLabel?.font = UIFont.ptdSemiBoldFont(ofSize: 16) button.addTarget(self, action: #selector(didTapCompleteButton), for: .touchUpInside) return button }() @@ -65,7 +65,6 @@ class DiscardPrescriptionDrugVC: UIViewController { make.left.right.equalToSuperview().inset(16) make.bottom.equalTo(completeButton.snp.top).offset(-16) } - completeButton.snp.makeConstraints { make in make.left.right.equalToSuperview().inset(16) make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-16) diff --git a/DropDrug/Sources/Views/MyPage/ProfileView.swift b/DropDrug/Sources/Views/MyPage/ProfileView.swift index 4524a00b..217a5937 100644 --- a/DropDrug/Sources/Views/MyPage/ProfileView.swift +++ b/DropDrug/Sources/Views/MyPage/ProfileView.swift @@ -6,7 +6,7 @@ import Moya class ProfileView: UIView { - let provider = MoyaProvider(plugins: [NetworkLoggerPlugin()]) + let provider = MoyaProvider(plugins: [BearerTokenPlugin(), NetworkLoggerPlugin()]) // MARK: - UI Components let profileImageView: UIImageView = { diff --git a/DropDrug/Sources/Views/MyPage/RewardView.swift b/DropDrug/Sources/Views/MyPage/RewardView.swift index 6e556876..18cd9614 100644 --- a/DropDrug/Sources/Views/MyPage/RewardView.swift +++ b/DropDrug/Sources/Views/MyPage/RewardView.swift @@ -6,7 +6,7 @@ import Moya class RewardView: UIView { - let provider = MoyaProvider(plugins: [NetworkLoggerPlugin()]) + let provider = MoyaProvider(plugins: [BearerTokenPlugin(), NetworkLoggerPlugin()]) private let rewardLabel: UILabel = { let label = UILabel() From d044c8220bfd7c921d263f2dbd75be194b4d575e Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:08:52 +0900 Subject: [PATCH 06/19] =?UTF-8?q?=EC=95=A0=ED=94=8C=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=95=88=EB=90=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DropDrug/Sources/AppDelegate.swift | 8 +++++++- .../ViewControllers/Register/SelectLoginTypeVC.swift | 11 ++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/DropDrug/Sources/AppDelegate.swift b/DropDrug/Sources/AppDelegate.swift index 9f60f498..e98e6c57 100644 --- a/DropDrug/Sources/AppDelegate.swift +++ b/DropDrug/Sources/AppDelegate.swift @@ -30,8 +30,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { if let kakaoAPIkey = Bundle.main.object(forInfoDictionaryKey: "KAKAO_NATIVE_APP_KEY") as? String { KakaoSDK.initSDK(appKey: "\(kakaoAPIkey)") } - FirebaseApp.configure() + if FirebaseApp.app() == nil { + print("FirebaseApp is not initialized. Configuring now...") + FirebaseApp.configure() + } else { + print("FirebaseApp is initialized successfully.") + } + UNUserNotificationCenter.current().delegate = self let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] // 필요한 알림 권한을 설정 UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { granted, error in diff --git a/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift b/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift index 111e1565..4761c22f 100644 --- a/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift +++ b/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift @@ -322,6 +322,7 @@ extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { switch authorization.credential { case let appleIDCredential as ASAuthorizationAppleIDCredential: + // nonce 검사 guard let nonce = currentNonce else { print("Error: Nonce is missing.") return @@ -332,6 +333,7 @@ extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { return } + // 애플 id 토큰 검사 guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else { print("Error: Unable to serialize token string.") return @@ -359,12 +361,15 @@ extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { } else { nameComponents.givenName = savedFullName } - fullNameToUse = nameComponents + if nameComponents.familyName != nil && nameComponents.givenName != nil { + fullNameToUse = nameComponents + } // print("Fetched saved fullName from Keychain: \(savedFullName)") } +// +// print("idTokenString: \(idTokenString)") +// print("nonce: \(nonce)") - print("idTokenString: \(idTokenString)") - print("nonce: \(nonce)") if let fullName = fullNameToUse { print("fullNameToUse - givenName: \(fullName.givenName ?? "nil"), familyName: \(fullName.familyName ?? "nil")") } else { From 76bf7725e8158799028ccd8b12abf7a62ab933f0 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:35:24 +0900 Subject: [PATCH 07/19] =?UTF-8?q?=EC=95=A0=ED=94=8C=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EB=8B=A8=EC=88=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../APIServices/Auth/LoginService.swift | 4 + .../Models/Register/RegisterManager.swift | 24 ++ .../Request/OAuthSocialLoginRequest.swift | 5 + .../Register/SelectLoginTypeVC.swift | 266 ++++++------------ 4 files changed, 113 insertions(+), 186 deletions(-) diff --git a/DropDrug/Sources/APIServices/Auth/LoginService.swift b/DropDrug/Sources/APIServices/Auth/LoginService.swift index 89c0ef7d..27088f75 100644 --- a/DropDrug/Sources/APIServices/Auth/LoginService.swift +++ b/DropDrug/Sources/APIServices/Auth/LoginService.swift @@ -12,6 +12,7 @@ enum LoginService { // SNS 로그인 case postGoogleLogin(param: OAuthSocialLoginRequest) case postKakaoLogin(param: OAuthSocialLoginRequest) + case postAppleLogin(param: OAuthAppleLoginRequest) // 기타 case postLogOut(accessToken: String) @@ -32,6 +33,7 @@ extension LoginService: TargetType { case .postRegister: return "auth/signup/pw" case .postKakaoLogin: return "auth/login/kakao" case .postGoogleLogin: return "auth/login/google" + case .postAppleLogin: return "auth/login/apple" case .postLogOut: return "auth/logout" case .postQuit: return "auth/quit" } @@ -51,6 +53,8 @@ extension LoginService: TargetType { return .requestJSONEncodable(param) case .postKakaoLogin(let param) : return .requestJSONEncodable(param) + case .postAppleLogin(let param) : + return .requestJSONEncodable(param) case .postLogOut(let accessToken) : return .requestParameters(parameters: ["accessToken": accessToken], encoding: JSONEncoding.default) case .postQuit(let accessToken) : diff --git a/DropDrug/Sources/Models/Register/RegisterManager.swift b/DropDrug/Sources/Models/Register/RegisterManager.swift index 1ffdf2a0..a3293ad3 100644 --- a/DropDrug/Sources/Models/Register/RegisterManager.swift +++ b/DropDrug/Sources/Models/Register/RegisterManager.swift @@ -91,4 +91,28 @@ extension SelectLoginTypeVC { } } } + + func setupAppleDTO(_ idToken: String) -> OAuthAppleLoginRequest? { + guard let fcmToken = SelectLoginTypeVC.keychain.get("FCMToken") else { return nil } + return OAuthAppleLoginRequest(fcmToken: fcmToken, idToken: idToken) + } + func callAppleLoginAPI(param : OAuthAppleLoginRequest, completion: @escaping (Bool) -> Void) { + provider.request(.postAppleLogin(param: param)) { result in + switch result { + case .success(let response): + do { + let data = try response.map(TokenDto.self) + SelectLoginTypeVC.keychain.set(data.refreshToken, forKey: "serverRefreshToken") + SelectLoginTypeVC.keychain.set(data.accessToken, forKey: "serverAccessToken") + completion(true) + } catch { + print("Failed to map data : \(error)") + completion(false) + } + case .failure(let error) : + print("Request failed: \(error.localizedDescription)") + completion(false) + } + } + } } diff --git a/DropDrug/Sources/Models/Request/OAuthSocialLoginRequest.swift b/DropDrug/Sources/Models/Request/OAuthSocialLoginRequest.swift index 7fd9a33b..eaccab1e 100644 --- a/DropDrug/Sources/Models/Request/OAuthSocialLoginRequest.swift +++ b/DropDrug/Sources/Models/Request/OAuthSocialLoginRequest.swift @@ -7,3 +7,8 @@ struct OAuthSocialLoginRequest : Codable{ let fcmToken : String let idToken : String } + +struct OAuthAppleLoginRequest : Codable{ + let fcmToken : String + let idToken : String +} diff --git a/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift b/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift index 4761c22f..fdc84c90 100644 --- a/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift +++ b/DropDrug/Sources/ViewControllers/Register/SelectLoginTypeVC.swift @@ -40,7 +40,7 @@ class SelectLoginTypeVC : UIViewController { button.addTarget(self, action: #selector(kakaoButtonTapped), for: .touchUpInside) return button }() -// let appleLoginButton = ASAuthorizationAppleIDButton() + // let appleLoginButton = ASAuthorizationAppleIDButton() lazy var signUpButton: UIButton = { let button = UIButton(type: .system) @@ -81,10 +81,10 @@ class SelectLoginTypeVC : UIViewController { } } } - + private func setupGradientBackground() { let gradientLayer = CAGradientLayer() - + gradientLayer.colors = [ (Constants.Colors.coralpink?.withAlphaComponent(0.7) ?? UIColor.systemPink.withAlphaComponent(0.7)).cgColor, (Constants.Colors.skyblue ?? UIColor.systemTeal).cgColor @@ -102,7 +102,7 @@ class SelectLoginTypeVC : UIViewController { view.layer.insertSublayer(gradientLayer, at: 0) } - + private func setupUI() { let subviews = [mainLabel, kakaoLoginButton, signUpButton, loginButton] subviews.forEach { view.addSubview($0) } @@ -146,34 +146,34 @@ class SelectLoginTypeVC : UIViewController { } } -// @objc func kakaoButtonTapped() { -// Task { -// if await kakaoAuthVM.KakaoLogin() { -// UserApi.shared.me() { [weak self] (user, error) in -// guard let self = self else { return } -// -// if let error = error { -// print("에러 발생: \(error.localizedDescription)") -// return -// } -// -// guard let kakaoAccount = user?.kakaoAccount else { -// print("사용자 정보 없음") -// return -// } -// -// let userId = user?.id ?? 123 -// -// print("유저: \(userId)") -// let idToken = SelectLoginTypeVC.keychain.get("idToken") -// // TODO: 서버에 카카오 사용자 정보 전달 및 로그인 처리 -// self.handleKakaoLoginSuccess() -// } -// } else { -// print("카카오 로그인 실패") -// } -// } -// } + // @objc func kakaoButtonTapped() { + // Task { + // if await kakaoAuthVM.KakaoLogin() { + // UserApi.shared.me() { [weak self] (user, error) in + // guard let self = self else { return } + // + // if let error = error { + // print("에러 발생: \(error.localizedDescription)") + // return + // } + // + // guard let kakaoAccount = user?.kakaoAccount else { + // print("사용자 정보 없음") + // return + // } + // + // let userId = user?.id ?? 123 + // + // print("유저: \(userId)") + // let idToken = SelectLoginTypeVC.keychain.get("idToken") + // // TODO: 서버에 카카오 사용자 정보 전달 및 로그인 처리 + // self.handleKakaoLoginSuccess() + // } + // } else { + // print("카카오 로그인 실패") + // } + // } + // } @objc func kakaoButtonTapped() { DispatchQueue.global(qos: .userInitiated).async { [weak self] in @@ -211,35 +211,35 @@ class SelectLoginTypeVC : UIViewController { } } } - + private func handleKakaoLoginSuccess() { let mainVC = MainTabBarController() mainVC.modalPresentationStyle = .fullScreen present(mainVC, animated: true, completion: nil) } -// @objc private func googleButtonTapped() { -// // Google login setup -// guard let clientID = FirebaseApp.app()?.options.clientID else { return } -// let config = GIDConfiguration(clientID: clientID) -// -// GIDSignIn.sharedInstance.signIn(withPresenting: self) {signInResult, error in -// guard error == nil else { return } -// guard let result = signInResult, -// let token = result.user.idToken?.tokenString else { return } -// -// let user = result.user -// let fullName = user.profile?.name -// let accesstoken = result.user.accessToken.tokenString -// let refreshtoken = result.user.refreshToken.tokenString -// -// print(user) -// print(fullName as Any) -// print("accesstoken : \(accesstoken)") -// print("refreshtoken: \(refreshtoken)") -// } -// -// } + // @objc private func googleButtonTapped() { + // // Google login setup + // guard let clientID = FirebaseApp.app()?.options.clientID else { return } + // let config = GIDConfiguration(clientID: clientID) + // + // GIDSignIn.sharedInstance.signIn(withPresenting: self) {signInResult, error in + // guard error == nil else { return } + // guard let result = signInResult, + // let token = result.user.idToken?.tokenString else { return } + // + // let user = result.user + // let fullName = user.profile?.name + // let accesstoken = result.user.accessToken.tokenString + // let refreshtoken = result.user.refreshToken.tokenString + // + // print(user) + // print(fullName as Any) + // print("accesstoken : \(accesstoken)") + // print("refreshtoken: \(refreshtoken)") + // } + // + // } @objc func startTapped() { let SignUpVC = SignUpVC() @@ -254,36 +254,9 @@ class SelectLoginTypeVC : UIViewController { } @objc func handleAppleLogin() { - startSignInWithAppleFlow() - } - - func fetchFirebaseIDToken(completion: @escaping (String?) -> Void) { - guard let currentUser = Auth.auth().currentUser else { - print("User is not logged in") - completion(nil) - return - } - - currentUser.getIDToken { token, error in - if let error = error { - print("Error fetching ID token: \(error.localizedDescription)") - completion(nil) - return - } - - completion(token) - } - } -} - -extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { - func startSignInWithAppleFlow() { - let nonce = randomNonceString() - currentNonce = nonce let appleIDProvider = ASAuthorizationAppleIDProvider() let request = appleIDProvider.createRequest() request.requestedScopes = [.fullName, .email] - request.nonce = sha256(nonce) let authorizationController = ASAuthorizationController(authorizationRequests: [request]) authorizationController.delegate = self @@ -291,124 +264,45 @@ extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { authorizationController.performRequests() } - func randomNonceString(length: Int = 32) -> String { - precondition(length > 0) - var randomBytes = [UInt8](repeating: 0, count: length) - let errorCode = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes) - if errorCode != errSecSuccess { - fatalError( - "Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)" - ) - } - let charset: [Character] = - Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._") - let nonce = randomBytes.map { byte in - // Pick a random character from the set, wrapping around if needed. - charset[Int(byte) % charset.count] - } - return String(nonce) - } - - func sha256(_ input: String) -> String { - let inputData = Data(input.utf8) - let hashedData = SHA256.hash(data: inputData) - let hashString = hashedData.compactMap { - String(format: "%02x", $0) - }.joined() - - return hashString - } - +} + +extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { switch authorization.credential { case let appleIDCredential as ASAuthorizationAppleIDCredential: - // nonce 검사 - guard let nonce = currentNonce else { - print("Error: Nonce is missing.") - return - } - - guard let appleIDToken = appleIDCredential.identityToken else { - print("Error: Unable to fetch identity token.") - return - } + let userIdentifier = appleIDCredential.user + let fullName = appleIDCredential.fullName - // 애플 id 토큰 검사 - guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else { - print("Error: Unable to serialize token string.") - return - } - SelectLoginTypeVC.keychain.set(idTokenString, forKey: "appleUserIDToken") - - var fullNameToUse: PersonNameComponents? = nil - - if let fullName = appleIDCredential.fullName { - fullNameToUse = fullName - if let givenName = fullName.givenName, let familyName = fullName.familyName { - let completeName = "\(givenName) \(familyName)" - SelectLoginTypeVC.keychain.set(completeName, forKey: "appleUserFullName") - print("Saved fullName: \(completeName)") + if let identityToken = appleIDCredential.identityToken, + let identityTokenString = String(data: identityToken, encoding: .utf8) { + SelectLoginTypeVC.keychain.set(identityTokenString, forKey: "AppleIDToken") + callAppleLoginAPI(param: setupAppleDTO(identityTokenString)!) { isSuccess in + if isSuccess { + self.handleKakaoLoginSuccess() + } else { + print("애플 로그인(바로 로그인) 실패") + } } } - - if let savedFullName = SelectLoginTypeVC.keychain.get("appleUserFullName") { - - var nameComponents = PersonNameComponents() - let nameParts = savedFullName.split(separator: " ") - if nameParts.count > 1 { - nameComponents.givenName = String(nameParts[0]) - nameComponents.familyName = String(nameParts[1]) + case let passwordCredential as ASPasswordCredential: + let username = passwordCredential.user + let password = passwordCredential.password + guard let identityTokenString = SelectLoginTypeVC.keychain.get("AppleIDToken") else { return } + callAppleLoginAPI(param: setupAppleDTO(identityTokenString)!) { isSuccess in + if isSuccess { + self.handleKakaoLoginSuccess() } else { - nameComponents.givenName = savedFullName + print("애플 로그인 (패스워드 시도) 실패") } - if nameComponents.familyName != nil && nameComponents.givenName != nil { - fullNameToUse = nameComponents - } -// print("Fetched saved fullName from Keychain: \(savedFullName)") } -// -// print("idTokenString: \(idTokenString)") -// print("nonce: \(nonce)") - if let fullName = fullNameToUse { - print("fullNameToUse - givenName: \(fullName.givenName ?? "nil"), familyName: \(fullName.familyName ?? "nil")") - } else { - print("fullNameToUse is nil") - } - - let credential = OAuthProvider.appleCredential(withIDToken: idTokenString, - rawNonce: nonce, - fullName: fullNameToUse) - - Auth.auth().signIn(with: credential) { authResult, error in - if let error = error { - print("Error Apple sign in: \(error.localizedDescription)") - return - } - - print("애플 로그인 성공") - - self.fetchFirebaseIDToken { idToken in - if let idToken = idToken { - print("Firebase ID Token: \(idToken)") - } - DispatchQueue.main.async { - let mainTabBarVC = MainTabBarController() - self.navigationController?.pushViewController(mainTabBarVC, animated: true) - } - } - } default: - print("Error: Unsupported credential type.") break + + } - - } - - func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { - print("Sign in with Apple errored: \(error)") + } - } extension SelectLoginTypeVC: ASAuthorizationControllerPresentationContextProviding { From 431763360702b0fbe16333fa84c7a00fffcbe125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=98=88=EC=84=B1?= Date: Fri, 22 Nov 2024 16:37:49 +0900 Subject: [PATCH 08/19] [feat] mypage --- .../APIServices/MyPage/MemberAPI.swift | 26 ++- DropDrug/Sources/APIServices/PointAPI.swift | 60 ++++++ .../Cells/NotificationOptionCell.swift | 57 ++---- .../Sources/Cells/PrescriptionDrugCell.swift | 1 - .../Cells/SeoulCollectionViewCell.swift | 3 + .../Sources/Models/Auth/RegisterManager.swift | 7 +- DropDrug/Sources/Models/Constants.swift | 5 + .../Models/MyPage/MemberInfoManager.swift | 52 +++--- .../Sources/Models/MyPage/PointManager.swift | 25 +++ .../Sources/Models/Response/MemberInfo.swift | 4 +- .../Models/Response/PointResponse.swift | 7 + .../Auth/SelectLoginTypeVC.swift | 2 +- .../Main/HomeViewController.swift | 2 +- .../ViewControllers/MyPage/MyPageVC.swift | 14 +- .../ViewControllers/MyPage/RewardVC.swift | 13 +- .../MyPage/Settings/AccountSettingsVC.swift | 14 +- .../MyPage/Settings/CharacterSettingsVC.swift | 173 ++++++++++++++++++ .../Settings/NotificationSettingsVC.swift | 83 +++++++-- .../Sources/Views/MyPage/ProfileView.swift | 3 - .../Sources/Views/MyPage/RewardView.swift | 3 - 20 files changed, 444 insertions(+), 110 deletions(-) create mode 100644 DropDrug/Sources/APIServices/PointAPI.swift create mode 100644 DropDrug/Sources/Models/MyPage/PointManager.swift create mode 100644 DropDrug/Sources/Models/Response/PointResponse.swift diff --git a/DropDrug/Sources/APIServices/MyPage/MemberAPI.swift b/DropDrug/Sources/APIServices/MyPage/MemberAPI.swift index a45f3ea3..5548f5bb 100644 --- a/DropDrug/Sources/APIServices/MyPage/MemberAPI.swift +++ b/DropDrug/Sources/APIServices/MyPage/MemberAPI.swift @@ -9,26 +9,29 @@ enum MemberAPI { case purchaseCharacter(characterId: Int) // 캐릭터 구매 case updateCharacter(characterId: Int) // 캐릭터 변경 case updateNickname(newNickname: String) // 닉네임 변경 - case updateNotificationSettings(disposal: Bool, noticeboard: Bool, reward: Bool) // 알림 설정 변경 + case updateNotificationSettings(param: NotificationSetting) // 알림 설정 변경 } extension MemberAPI: TargetType { var baseURL: URL { - return URL(string: "http://54.180.45.153:8080")! + guard let url = URL(string: Constants.NetworkManager.baseURL) else { + fatalError("fatal error - invalid url") + } + return url } var path: String { switch self { case .fetchMemberInfo: - return "/members" + return "members" case .purchaseCharacter(let characterId): - return "/members/character/\(characterId)" + return "members/character/\(characterId)" case .updateCharacter(let characterId): - return "/members/character/\(characterId)" + return "members/character/\(characterId)" case .updateNickname(let newNickname): - return "/members/nickname/\(newNickname)" + return "members/nickname/\(newNickname)" case .updateNotificationSettings: - return "/members/notification" + return "members/notification" } } @@ -53,13 +56,8 @@ extension MemberAPI: TargetType { return .requestPlain case .updateNickname: return .requestPlain - case .updateNotificationSettings(let disposal, let noticeboard, let reward): - let parameters: [String: Any] = [ - "disposal": disposal, - "noticeboard": noticeboard, - "reward": reward - ] - return .requestParameters(parameters: parameters, encoding: JSONEncoding.default) + case .updateNotificationSettings(let param) : + return .requestJSONEncodable(param) } } diff --git a/DropDrug/Sources/APIServices/PointAPI.swift b/DropDrug/Sources/APIServices/PointAPI.swift new file mode 100644 index 00000000..719ff65a --- /dev/null +++ b/DropDrug/Sources/APIServices/PointAPI.swift @@ -0,0 +1,60 @@ +// Copyright © 2024 RT4. All rights reserved + +import Foundation +import Moya +import KeychainSwift + +enum PointAPI { + case getPoint + case postPoint + case getPointHistory + case getMonthlyStats +} + +extension PointAPI: TargetType { + var baseURL: URL { + guard let url = URL(string: Constants.NetworkManager.baseURL) else { + fatalError("fatal error - invalid url") + } + return url + } + + var path: String { + switch self { + case .getPoint: + return "points" + case .postPoint: + return "members/notification" + case .getPointHistory: + return "points/history" + case .getMonthlyStats: + return "points/monthly" + } + } + + var method: Moya.Method { + switch self { + case .getPoint, .getPointHistory, .getMonthlyStats: + return .get + case .postPoint: + return .post + } + } + + var task: Task { + switch self { + case .getPoint: + return .requestPlain + case .postPoint: + return .requestPlain + case .getPointHistory: + return .requestPlain + case .getMonthlyStats: + return .requestPlain + } + } + + var headers: [String: String]? { + return ["Content-Type": "application/json"] + } +} diff --git a/DropDrug/Sources/Cells/NotificationOptionCell.swift b/DropDrug/Sources/Cells/NotificationOptionCell.swift index ef25c9f8..8af40370 100644 --- a/DropDrug/Sources/Cells/NotificationOptionCell.swift +++ b/DropDrug/Sources/Cells/NotificationOptionCell.swift @@ -6,47 +6,30 @@ import SnapKit class NotificationOptionCell: UITableViewCell { private let titleLabel = UILabel() private let toggleSwitch = UISwitch() - private var switchAction: ((Bool) -> Void)? + private var toggleAction: ((Bool) -> Void)? - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupViews() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setupViews() { - // 타이틀 라벨 - titleLabel.font = .systemFont(ofSize: 16) - titleLabel.textColor = .darkGray - contentView.addSubview(titleLabel) - contentView.addSubview(toggleSwitch) - - // 레이아웃 설정 - titleLabel.translatesAutoresizingMaskIntoConstraints = false - toggleSwitch.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), - titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - - toggleSwitch.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), - toggleSwitch.centerYAnchor.constraint(equalTo: contentView.centerYAnchor) - ]) - - // 스위치 값 변경 시 동작 설정 - toggleSwitch.addTarget(self, action: #selector(didToggleSwitch), for: .valueChanged) - } - - func configure(title: String, isSwitchOn: Bool, action: @escaping (Bool) -> Void) { + func configure(title: String, isSwitchOn: Bool, isSwitchEnabled: Bool, onToggle: @escaping (Bool) -> Void) { titleLabel.text = title toggleSwitch.isOn = isSwitchOn - self.switchAction = action + toggleSwitch.isEnabled = isSwitchEnabled + toggleAction = onToggle + + toggleSwitch.removeTarget(self, action: #selector(handleToggle(_:)), for: .valueChanged) + toggleSwitch.addTarget(self, action: #selector(handleToggle(_:)), for: .valueChanged) + + contentView.addSubview(titleLabel) + contentView.addSubview(toggleSwitch) + titleLabel.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.leading.equalToSuperview().offset(16) + } + toggleSwitch.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.trailing.equalToSuperview().offset(-16) + } } - @objc private func didToggleSwitch() { - switchAction?(toggleSwitch.isOn) + @objc private func handleToggle(_ sender: UISwitch) { + toggleAction?(sender.isOn) } } diff --git a/DropDrug/Sources/Cells/PrescriptionDrugCell.swift b/DropDrug/Sources/Cells/PrescriptionDrugCell.swift index b4f8a44c..ec5d260e 100644 --- a/DropDrug/Sources/Cells/PrescriptionDrugCell.swift +++ b/DropDrug/Sources/Cells/PrescriptionDrugCell.swift @@ -32,7 +32,6 @@ class PrescriptionDrugCell: UITableViewCell { contentView.addSubview(stackView) stackView.snp.makeConstraints { make in make.edges.equalToSuperview().inset(20) -// make.leading.trailing.equalToSuperview() } } diff --git a/DropDrug/Sources/Cells/SeoulCollectionViewCell.swift b/DropDrug/Sources/Cells/SeoulCollectionViewCell.swift index 3b8e815d..522706c0 100644 --- a/DropDrug/Sources/Cells/SeoulCollectionViewCell.swift +++ b/DropDrug/Sources/Cells/SeoulCollectionViewCell.swift @@ -73,4 +73,7 @@ class SeoulCollectionViewCell: UICollectionViewCell { make.centerX.equalToSuperview() } } + func configure(showNameLabel: Bool) { + name.isHidden = !showNameLabel + } } diff --git a/DropDrug/Sources/Models/Auth/RegisterManager.swift b/DropDrug/Sources/Models/Auth/RegisterManager.swift index 1ffdf2a0..fe6cedfd 100644 --- a/DropDrug/Sources/Models/Auth/RegisterManager.swift +++ b/DropDrug/Sources/Models/Auth/RegisterManager.swift @@ -50,7 +50,7 @@ extension LoginVC { completion(true) } catch { print("Failed to map data : \(error)") - completion(false) + } case .failure(let error) : print("Error: \(error.localizedDescription)") @@ -64,11 +64,10 @@ extension LoginVC { } extension SelectLoginTypeVC { - func setupSocialLoginDTO() -> OAuthSocialLoginRequest? { + func setupKakaoLoginDTO() -> OAuthSocialLoginRequest? { guard let fcmToken = SelectLoginTypeVC.keychain.get("FCMToken") else { return nil } - guard let accessToken = SelectLoginTypeVC.keychain.get("KakaoAccessToken") else { return nil } - guard let idToken = SelectLoginTypeVC.keychain.get("FCMToken") else { return nil } + guard let idToken = SelectLoginTypeVC.keychain.get("KakaoIdToken") else { return nil } return OAuthSocialLoginRequest(accessToken: accessToken, fcmToken: fcmToken, idToken: idToken) } diff --git a/DropDrug/Sources/Models/Constants.swift b/DropDrug/Sources/Models/Constants.swift index e018bf5b..736ac391 100644 --- a/DropDrug/Sources/Models/Constants.swift +++ b/DropDrug/Sources/Models/Constants.swift @@ -7,6 +7,10 @@ struct Constants { static let baseURL = "http://54.180.45.153:8080/" } + struct AllCharacter { + static var allCharCount = 12 + } + static let seoulDistrictsList: [DistrictsDataModel] = [ DistrictsDataModel(name: "강남구", image: "강남구", url: "https://www.gangnam.go.kr/board/waste/list.do?mid=ID02_011109#collapse21"), DistrictsDataModel(name: "강동구", image: "강동구", url: "https://www.gangdong.go.kr/web/newportal/contents/gdp_005_004_010_001"), @@ -65,4 +69,5 @@ struct Constants { static let red = UIColor(named: "red") static let skyblue = UIColor(named: "skyblue") } + } diff --git a/DropDrug/Sources/Models/MyPage/MemberInfoManager.swift b/DropDrug/Sources/Models/MyPage/MemberInfoManager.swift index 4b5ac617..3af5f1a8 100644 --- a/DropDrug/Sources/Models/MyPage/MemberInfoManager.swift +++ b/DropDrug/Sources/Models/MyPage/MemberInfoManager.swift @@ -1,10 +1,10 @@ // Copyright © 2024 RT4. All rights reserved -import UIKit +import Foundation import Moya extension AccountSettingsVC { - func fetchMemberInfo() { + func fetchMemberInfo(completion: @escaping (Bool) -> Void) { provider.request(.fetchMemberInfo) { result in switch result { case .success(let response): @@ -12,55 +12,61 @@ extension AccountSettingsVC { let data = try response.map(MemberInfo.self) self.nickname = data.nickname self.userId = data.email - DispatchQueue.main.async { - self.tableView.reloadData() - } + completion(true) } catch { - print("JSON 파싱 에러: \(error)") + completion(false) } case .failure(let error): - print("네트워크 에러: \(error.localizedDescription)") + print("Error: \(error.localizedDescription)") + if let response = error.response { + print("Response Body: \(String(data: response.data, encoding: .utf8) ?? "")") + } + completion(false) } } } } -extension ProfileView { - // TODO: 프로필 캐릭터 parsing 후 적용 - func fetchMemberInfo() { - provider.request(.fetchMemberInfo) { result in +extension MyPageVC { + func fetchMemberInfo(completion: @escaping (Bool) -> Void) { + MemberProvider.request(.fetchMemberInfo) { result in switch result { case .success(let response): do { let data = try response.map(MemberInfo.self) - self.nameLabel.text = data.nickname - self.emailLabel.text = data.email + DispatchQueue.main.async { + self.myPageProfileView.nameLabel.text = data.nickname + self.myPageProfileView.emailLabel.text = data.email + self.rewardView.pointsLabel.text = "\(data.point) P" + } + completion(true) } catch { - print("JSON 파싱 에러: \(error)") + completion(false) } case .failure(let error): - print("네트워크 에러: \(error.localizedDescription)") + completion(false) } } } } -extension RewardView { - // TODO: point Controller 로 변경 - func fetchMemberInfo() { - provider.request(.fetchMemberInfo) { result in +extension CharacterSettingsVC { + func fetchMemberInfo(completion: @escaping (Bool) -> Void) { + MemberProvider.request(.fetchMemberInfo) { result in switch result { case .success(let response): do { let data = try response.map(MemberInfo.self) - self.pointsLabel.text = "\(data.point) P" + DispatchQueue.main.async { + self.ownedCharCount = data.ownedChars.count + } + completion(true) } catch { - print("JSON 파싱 에러: \(error)") + completion(false) } case .failure(let error): - print("네트워크 에러: \(error.localizedDescription)") + completion(false) } } } } - diff --git a/DropDrug/Sources/Models/MyPage/PointManager.swift b/DropDrug/Sources/Models/MyPage/PointManager.swift new file mode 100644 index 00000000..ce2fb3f5 --- /dev/null +++ b/DropDrug/Sources/Models/MyPage/PointManager.swift @@ -0,0 +1,25 @@ +// Copyright © 2024 RT4. All rights reserved + +import UIKit +import Moya + +extension RewardVC { + func fetchPoint(completion: @escaping (Bool) -> Void) { + PointProvider.request(.getPoint) { result in + switch result { + case .success(let response): + do { + // JSON 디코딩 + let data = try response.map(PointResponse.self) + DispatchQueue.main.async { + self.rewardView.pointsLabel.text = "\(data.point) P" + } + } catch { + print("JSON 파싱 에러: \(error)") + } + case .failure(let error): + print("네트워크 에러: \(error.localizedDescription)") + } + } + } +} diff --git a/DropDrug/Sources/Models/Response/MemberInfo.swift b/DropDrug/Sources/Models/Response/MemberInfo.swift index 9260b12d..d9a02bcb 100644 --- a/DropDrug/Sources/Models/Response/MemberInfo.swift +++ b/DropDrug/Sources/Models/Response/MemberInfo.swift @@ -2,7 +2,7 @@ import Foundation -struct MemberInfo: Decodable { +struct MemberInfo: Codable { let email: String let nickname: String let notificationSetting: NotificationSetting @@ -11,7 +11,7 @@ struct MemberInfo: Decodable { let selectedChar: Int } -struct NotificationSetting: Decodable { +struct NotificationSetting: Codable { let disposal: Bool let noticeboard: Bool let reward: Bool diff --git a/DropDrug/Sources/Models/Response/PointResponse.swift b/DropDrug/Sources/Models/Response/PointResponse.swift new file mode 100644 index 00000000..6d5ffdc1 --- /dev/null +++ b/DropDrug/Sources/Models/Response/PointResponse.swift @@ -0,0 +1,7 @@ +// Copyright © 2024 RT4. All rights reserved + +import Foundation + +struct PointResponse: Codable { + let point: Int +} diff --git a/DropDrug/Sources/ViewControllers/Auth/SelectLoginTypeVC.swift b/DropDrug/Sources/ViewControllers/Auth/SelectLoginTypeVC.swift index d85748df..2d72837d 100644 --- a/DropDrug/Sources/ViewControllers/Auth/SelectLoginTypeVC.swift +++ b/DropDrug/Sources/ViewControllers/Auth/SelectLoginTypeVC.swift @@ -193,7 +193,7 @@ class SelectLoginTypeVC : UIViewController { print("사용자 정보 없음") return } - if let loginRequest = self.setupSocialLoginDTO() { + if let loginRequest = self.setupKakaoLoginDTO() { self.callKakaoLoginAPI(loginRequest) { isSuccess in if isSuccess { self.handleKakaoLoginSuccess() diff --git a/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift b/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift index 7a2121b2..334b7ae1 100644 --- a/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift +++ b/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift @@ -7,7 +7,7 @@ import MapKit class HomeViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate { - let provider = MoyaProvider(plugins: [ BearerTokenPlugin() ]) + let provider = MoyaProvider(plugins: [ BearerTokenPlugin(), NetworkLoggerPlugin() ]) var selectedCharacterNum: Int = 0 diff --git a/DropDrug/Sources/ViewControllers/MyPage/MyPageVC.swift b/DropDrug/Sources/ViewControllers/MyPage/MyPageVC.swift index 371b8060..3f2a3420 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/MyPageVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/MyPageVC.swift @@ -2,9 +2,12 @@ import UIKit import SnapKit +import Moya class MyPageVC : UIViewController { + let MemberProvider = MoyaProvider(plugins: [BearerTokenPlugin(), NetworkLoggerPlugin()]) + private lazy var titleLabel: UILabel = { let label = UILabel() label.text = "마이페이지" @@ -21,8 +24,8 @@ class MyPageVC : UIViewController { return button }() - private let myPageProfileView = ProfileView() - private let rewardView = RewardView() + let myPageProfileView = ProfileView() + let rewardView = RewardView() private let dropCardLabel = SubLabelView() private let disposalStateLabel = SubLabelView() @@ -56,6 +59,13 @@ class MyPageVC : UIViewController { [titleLabel, settingButton, myPageProfileView, rewardView, dropCardLabel, disposalStateLabel].forEach { view.addSubview($0) } + fetchMemberInfo { success in + if success { + print("Profile updated successfully") + } else { + print("Failed to update profile") + } + } } func setConstraints() { diff --git a/DropDrug/Sources/ViewControllers/MyPage/RewardVC.swift b/DropDrug/Sources/ViewControllers/MyPage/RewardVC.swift index dab745f4..820dceda 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/RewardVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/RewardVC.swift @@ -2,15 +2,19 @@ import UIKit import SnapKit +import Moya class RewardVC : UIViewController { + + let PointProvider = MoyaProvider(plugins: [BearerTokenPlugin(), NetworkLoggerPlugin()]) + private lazy var backButton: CustomBackButton = { let button = CustomBackButton(title: " 리워드 내역") button.addTarget(self, action: #selector(didTapBackButton), for: .touchUpInside) return button }() - private let rewardView: RewardView = { + let rewardView: RewardView = { let view = RewardView() view.isChevronHidden = true return view @@ -28,6 +32,13 @@ class RewardVC : UIViewController { [rewardView].forEach { view.addSubview($0) } + fetchPoint { success in + if success { + print("Profile updated successfully") + } else { + print("Failed to update profile") + } + } } func setConstraints() { diff --git a/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift b/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift index b35bcf4d..e630a050 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift @@ -17,7 +17,7 @@ class AccountSettingsVC: UIViewController, UITableViewDataSource, UITableViewDel }() let tableView = UITableView() - private let accountOptions = ["닉네임", "아이디", "비밀번호 변경", "캐릭터 변경", "로그아웃", "계정 삭제"] + private let accountOptions = ["닉네임", "아이디", /*"비밀번호 변경",*/ "캐릭터 변경", "로그아웃", "계정 삭제"] // 가상 데이터 var nickname: String = "김드롭" @@ -27,7 +27,13 @@ class AccountSettingsVC: UIViewController, UITableViewDataSource, UITableViewDel super.viewDidLoad() self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton) setupView() - fetchMemberInfo() + fetchMemberInfo { isSucess in + if isSucess { + self.tableView.reloadData() + } else { + // 토스트 메세지 띄워서 에러 알려주기 + } + } } private func setupView() { @@ -78,8 +84,8 @@ class AccountSettingsVC: UIViewController, UITableViewDataSource, UITableViewDel func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.section == 0 { switch accountOptions[indexPath.row] { - case "비밀번호 변경": - print("비밀번호 변경 화면 이동") +// case "비밀번호 변경": +// print("비밀번호 변경 화면 이동") case "캐릭터 변경": navigationController?.pushViewController(CharacterSettingsVC(), animated: true) case "로그아웃": diff --git a/DropDrug/Sources/ViewControllers/MyPage/Settings/CharacterSettingsVC.swift b/DropDrug/Sources/ViewControllers/MyPage/Settings/CharacterSettingsVC.swift index f923d92b..1b73d89b 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/Settings/CharacterSettingsVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/Settings/CharacterSettingsVC.swift @@ -1,8 +1,14 @@ // Copyright © 2024 RT4. All rights reserved import UIKit +import SnapKit +import Moya class CharacterSettingsVC: UIViewController { + + let MemberProvider = MoyaProvider(plugins: [BearerTokenPlugin(), NetworkLoggerPlugin()]) + + var ownedCharCount : Int = 0 private lazy var backButton: CustomBackButton = { let button = CustomBackButton(title: " 캐릭터 설정") @@ -10,10 +16,125 @@ class CharacterSettingsVC: UIViewController { return button }() + public lazy var scrollView: UIScrollView = { + let s = UIScrollView() + s.showsVerticalScrollIndicator = false + s.showsHorizontalScrollIndicator = false + return s + }() + + public lazy var contentView: UIView = { + let v = UIView() + return v + }() + private let ownedCharLabel = SubLabelView() + private let allCharLabel = SubLabelView() + + private lazy var ownedCharCollectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.itemSize = CGSize(width: 80, height: 80) + layout.minimumInteritemSpacing = 12 + layout.minimumLineSpacing = 16 + layout.sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) + layout.scrollDirection = .horizontal + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.dataSource = self + collectionView.delegate = self + collectionView.backgroundColor = .white + collectionView.register(SeoulCollectionViewCell.self, forCellWithReuseIdentifier: "SeoulCollectionViewCell") + return collectionView + }() + + private lazy var allCharCollectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + let screenWidth = UIScreen.main.bounds.width + let itemsPerRow: CGFloat = 4 + let totalSpacing = (screenWidth / (itemsPerRow * 2 - 1)) * 0.6 + let itemWidth = (screenWidth - totalSpacing * 3) / 4 + + layout.itemSize = CGSize(width: itemWidth, height: itemWidth) + layout.minimumInteritemSpacing = totalSpacing * 0.5 + layout.minimumLineSpacing = totalSpacing * 0.3 + layout.sectionInset = UIEdgeInsets(top: 0, left: totalSpacing * 0.3, bottom: 0, right: totalSpacing * 0.5) + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.dataSource = self + collectionView.delegate = self + collectionView.backgroundColor = .white + collectionView.register(SeoulCollectionViewCell.self, forCellWithReuseIdentifier: "SeoulCollectionViewCell") + return collectionView + }() + override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton) + + setupUI() + setConstraints() + + fetchMemberInfo { success in + if success { + print("Profile updated successfully") + } else { + print("Failed to update profile") + } + } + + } + + func setupUI() { + ownedCharLabel.text = "보유 캐릭터" + allCharLabel.text = "전체 캐릭터" + [scrollView].forEach { + view.addSubview($0) + } + scrollView.addSubview(contentView) + [ownedCharLabel, ownedCharCollectionView, allCharLabel, allCharCollectionView].forEach { + contentView.addSubview($0) + } + } + + func setConstraints() { + scrollView.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide.snp.top) + make.horizontalEdges.bottom.equalTo(view.safeAreaLayoutGuide) + } + + contentView.snp.makeConstraints { make in + make.edges.equalTo(scrollView) // 스크롤뷰의 모든 가장자리에 맞춰 배치 + make.width.equalTo(scrollView) // 가로 스크롤을 방지, 스크롤뷰와 같은 너비로 설정 + } + + ownedCharLabel.snp.makeConstraints { make in + make.top.equalToSuperview().offset(20) + make.leading.equalToSuperview().inset(20) + make.height.equalTo(20) + } + + ownedCharCollectionView.snp.makeConstraints { make in + make.top.equalTo(ownedCharLabel.snp.bottom) + make.horizontalEdges.equalToSuperview() + make.height.equalTo(80) + } + + allCharLabel.snp.makeConstraints { make in + make.top.equalTo(ownedCharCollectionView.snp.bottom).offset(20) + make.leading.equalToSuperview().inset(20) + make.height.equalTo(20) + } + + allCharCollectionView.snp.makeConstraints { make in + make.top.equalTo(allCharLabel.snp.bottom) + make.horizontalEdges.equalToSuperview() + make.height.equalTo(850) + make.bottom.equalToSuperview().offset(-15) + } + +// contentView.snp.makeConstraints { make in +// make.bottom.equalTo(seoulPageCollectionView.snp.bottom).offset(15) +// } } // MARK: - Actions @@ -21,3 +142,55 @@ class CharacterSettingsVC: UIViewController { navigationController?.popViewController(animated: false) } } + + +extension CharacterSettingsVC: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if collectionView.tag == 0 { + //TODO: 캐릭터 변경 + } + else if collectionView.tag == 1 { + //TODO: 캐릭터 구매 + } + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + if collectionView.tag == 0 { + return ownedCharCount + } + else if collectionView.tag == 1 { + return Constants.AllCharacter.allCharCount + } + return 0 + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SeoulCollectionViewCell", for: indexPath) as? SeoulCollectionViewCell else { + return UICollectionViewCell() + } + + if collectionView == ownedCharCollectionView { + cell.configure(showNameLabel: false) + } else if collectionView == allCharCollectionView { + cell.configure(showNameLabel: false) + } + + return cell + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + if collectionView.tag == 0 { + return CGSize(width: 80, height: 80) // 아이템 크기 설정 + } + else if collectionView.tag == 1 { + let screenWidth = UIScreen.main.bounds.width + let itemsPerRow: CGFloat = 4 + let totalSpacing = (screenWidth / (itemsPerRow * 2 - 1))*0.6 + let itemWidth = (screenWidth - totalSpacing * 3)/4 + return CGSize(width: itemWidth, height: itemWidth * 1.25) // 원하는 높이 비율로 설정 + } + return CGSize() + } + +} diff --git a/DropDrug/Sources/ViewControllers/MyPage/Settings/NotificationSettingsVC.swift b/DropDrug/Sources/ViewControllers/MyPage/Settings/NotificationSettingsVC.swift index 05ce0ac5..6757669d 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/Settings/NotificationSettingsVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/Settings/NotificationSettingsVC.swift @@ -2,9 +2,13 @@ import UIKit import SnapKit - +import Moya +//TODO: 노티 옵션 api 추가 후 셀 정리 class NotificationSettingsVC: UIViewController { + // MARK: - Moya Provider + let provider = MoyaProvider(plugins: [BearerTokenPlugin(), NetworkLoggerPlugin()]) + // MARK: - UI Components private lazy var backButton: CustomBackButton = { let button = CustomBackButton(title: " 푸시 알림 설정") @@ -14,25 +18,24 @@ class NotificationSettingsVC: UIViewController { private let tableView = UITableView() private let notificationOptions = [ + "푸시 알림", // 마스터 토글 "리워드 적립", "공지사항", - "폐기 안내", - "푸시 알림" + "폐기 안내" ] - // 알림 설정 상태 (기본값은 false) private var notificationStates = Array(repeating: false, count: 4) - // MARK: - Lifecycle Methods override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton) setupTableView() + fetchNotificationSettings() } - // MARK: - Setup Methods + // MARK: - UI 설정 private func setupTableView() { tableView.dataSource = self tableView.delegate = self @@ -46,7 +49,50 @@ class NotificationSettingsVC: UIViewController { } } - // MARK: - Actions + // MARK: - API 호출 + private func fetchNotificationSettings() { + provider.request(.fetchMemberInfo) { [weak self] result in + switch result { + case .success(let response): + do { + let memberInfo = try JSONDecoder().decode(MemberInfo.self, from: response.data) + + self?.notificationStates = [ + memberInfo.notificationSetting.reward || + memberInfo.notificationSetting.noticeboard || + memberInfo.notificationSetting.disposal, + memberInfo.notificationSetting.reward, + memberInfo.notificationSetting.noticeboard, + memberInfo.notificationSetting.disposal + ] + self?.tableView.reloadData() + } catch { + print("디코딩 실패: \(error)") + } + case .failure(let error): + print("API 호출 실패: \(error)") + } + } + } + + private func updateNotificationSetting() { + let updatedSettings = NotificationSetting( + disposal: notificationStates[3], + noticeboard: notificationStates[2], + reward: notificationStates[1] + ) + + provider.request(.updateNotificationSettings(param: updatedSettings)) { result in + switch result { + case .success: + print("알림 설정 업데이트 성공") + case .failure(let error): + print("알림 설정 업데이트 실패: \(error)") + } + } + } + + // MARK: - 액션 @objc private func didTapBackButton() { navigationController?.popViewController(animated: true) } @@ -60,17 +106,27 @@ extension NotificationSettingsVC: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "NotificationOptionCell", for: indexPath) as! NotificationOptionCell - + let option = notificationOptions[indexPath.row] let isEnabled = notificationStates[indexPath.row] - - // 셀 구성 - cell.configure(title: option, isSwitchOn: isEnabled) { [weak self] isOn in - self?.notificationStates[indexPath.row] = isOn + let isPushEnabled = notificationStates[0] + + cell.configure(title: option, isSwitchOn: isEnabled, isSwitchEnabled: indexPath.row == 0 || isPushEnabled) { [weak self] isOn in + guard let self = self else { return } + + if indexPath.row == 0 { + self.notificationStates = self.notificationStates.map { _ in isOn } + self.tableView.reloadData() + } else { + self.notificationStates[indexPath.row] = isOn + self.notificationStates[0] = self.notificationStates[1...3].contains(true) + } + print("\(option) 설정 변경됨: \(isOn)") + self.updateNotificationSetting() } cell.selectionStyle = .none - + return cell } } @@ -78,6 +134,5 @@ extension NotificationSettingsVC: UITableViewDataSource { // MARK: - UITableViewDelegate extension NotificationSettingsVC: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - // 추가 함수 } } diff --git a/DropDrug/Sources/Views/MyPage/ProfileView.swift b/DropDrug/Sources/Views/MyPage/ProfileView.swift index 217a5937..726b11a4 100644 --- a/DropDrug/Sources/Views/MyPage/ProfileView.swift +++ b/DropDrug/Sources/Views/MyPage/ProfileView.swift @@ -5,8 +5,6 @@ import SnapKit import Moya class ProfileView: UIView { - - let provider = MoyaProvider(plugins: [BearerTokenPlugin(), NetworkLoggerPlugin()]) // MARK: - UI Components let profileImageView: UIImageView = { @@ -39,7 +37,6 @@ class ProfileView: UIView { override init(frame: CGRect) { super.init(frame: frame) setupView() - fetchMemberInfo() } required init?(coder: NSCoder) { diff --git a/DropDrug/Sources/Views/MyPage/RewardView.swift b/DropDrug/Sources/Views/MyPage/RewardView.swift index 18cd9614..4bd1d030 100644 --- a/DropDrug/Sources/Views/MyPage/RewardView.swift +++ b/DropDrug/Sources/Views/MyPage/RewardView.swift @@ -6,8 +6,6 @@ import Moya class RewardView: UIView { - let provider = MoyaProvider(plugins: [BearerTokenPlugin(), NetworkLoggerPlugin()]) - private let rewardLabel: UILabel = { let label = UILabel() label.text = "리워드" @@ -42,7 +40,6 @@ class RewardView: UIView { override init(frame: CGRect) { super.init(frame: frame) setupView() - fetchMemberInfo() setupGradientBackground() } From 88fdc03415caada3cd0393c592c14262dcf3466b Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:26:50 +0900 Subject: [PATCH 09/19] =?UTF-8?q?#24=20=EB=93=B1=EB=A1=9D=20=EB=94=94?= =?UTF-8?q?=ED=85=8C=EC=9D=BC=20=EB=B7=B0=EC=BB=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EnrollDetailViewController.swift | 24 ------ .../Register/EnrollDetailViewController.swift | 82 +++++++++++++++++++ .../Register/PrescriptionDrugVC.swift | 4 +- Project.swift | 2 + Tuist/Package.resolved | 20 ++++- Tuist/Package.swift | 6 +- 6 files changed, 110 insertions(+), 28 deletions(-) delete mode 100644 DropDrug/Sources/ViewControllers/EnrollDetailViewController.swift create mode 100644 DropDrug/Sources/ViewControllers/Register/EnrollDetailViewController.swift diff --git a/DropDrug/Sources/ViewControllers/EnrollDetailViewController.swift b/DropDrug/Sources/ViewControllers/EnrollDetailViewController.swift deleted file mode 100644 index b422d845..00000000 --- a/DropDrug/Sources/ViewControllers/EnrollDetailViewController.swift +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright © 2024 RT4. All rights reserved - -import UIKit - -class EnrollDetailViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - - // Do any additional setup after loading the view. - } - - - /* - // 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. - } - */ - -} diff --git a/DropDrug/Sources/ViewControllers/Register/EnrollDetailViewController.swift b/DropDrug/Sources/ViewControllers/Register/EnrollDetailViewController.swift new file mode 100644 index 00000000..4cb2752e --- /dev/null +++ b/DropDrug/Sources/ViewControllers/Register/EnrollDetailViewController.swift @@ -0,0 +1,82 @@ +// Copyright © 2024 RT4. All rights reserved + +import UIKit + +class EnrollDetailViewController: UIViewController { + + private lazy var backButton: CustomBackButton = { + let button = CustomBackButton(title: " 의약품 삭제하기") + button.addTarget(self, action: #selector(didTapBackButton), for: .touchUpInside) + return button + }() + + public lazy var selectStartDateLabel: UILabel = { + let label = UILabel() + label.text = "복용 시작 날짜 선택" + label.textColor = Constants.Colors.gray900 + label.textAlignment = .left + label.font = UIFont.ptdSemiBoldFont(ofSize: 17) + return label + }() + + private lazy var datePicker: UIDatePicker = { + let picker = UIDatePicker() + picker.datePickerMode = .date // 날짜 선택 모드 + picker.preferredDatePickerStyle = .inline + picker.locale = Locale(identifier: "ko_KR") // 한국어로 표시 + picker.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged) + return picker + }() + + public lazy var selectDateCountLabel: UILabel = { + let label = UILabel() + label.text = "복용 일수 선택" + label.textColor = Constants.Colors.gray900 + label.textAlignment = .left + label.font = UIFont.ptdSemiBoldFont(ofSize: 17) + return label + }() + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton) + } + + func setupLayout() { + let views = [selectStartDateLabel, selectDateCountLabel, datePicker] + views.forEach { view.addSubview($0) } + + selectStartDateLabel.snp.makeConstraints { l in + l.top.equalToSuperview().offset(12) + l.leading.equalToSuperview().offset(20) + l.height.equalTo(48) + } + + datePicker.snp.makeConstraints { make in + make.top.equalTo(selectStartDateLabel.snp.bottom).offset(10) + make.leading.equalToSuperview().offset(30) + make.trailing.equalToSuperview().offset(-30) + make.height.equalTo(datePicker.snp.width) + } + + selectDateCountLabel.snp.makeConstraints { make in + make.top.equalTo(datePicker.snp.bottom).offset(16) + make.leading.equalTo(selectStartDateLabel.snp.leading) + make.height.equalTo(48) + } + } + + // MARK: - Actions + @objc private func didTapBackButton() { + navigationController?.popViewController(animated: false) + } + + @objc private func dateChanged(_ sender: UIDatePicker) { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.locale = Locale(identifier: "ko_KR") +// selectedDateLabel.text = "선택된 날짜: \(formatter.string(from: sender.date))" + } + +} diff --git a/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift b/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift index 1e63aa47..f2680983 100644 --- a/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift +++ b/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift @@ -128,8 +128,8 @@ class PrescriptionDrugVC: UIViewController { @objc func addDrugViewTapped() { //TODO: 약 등록하기 VC로 연결 self.navigationController?.isNavigationBarHidden = false - let testVC = TestVC() - navigationController?.pushViewController(testVC, animated: false) + let detailVC = EnrollDetailViewController() + navigationController?.pushViewController(detailVC, animated: false) } @objc func discardButtonTapped(){ diff --git a/Project.swift b/Project.swift index 1c25bc5f..68b8af3b 100644 --- a/Project.swift +++ b/Project.swift @@ -84,6 +84,8 @@ let project = Project( // .external(name: "FirebaseAnalytics"), .external(name: "NaverMapSDK"), + .external(name: "SDWebImage"), + .external(name: "Toast") // .external(name: "GoogleSignIn"), // .external(name: "NMapsGeometry"), // .external(name: "NMapsMap") diff --git a/Tuist/Package.resolved b/Tuist/Package.resolved index c86a68c6..96faef6b 100644 --- a/Tuist/Package.resolved +++ b/Tuist/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "a9835bfeadab6fd8ffe9413aeeaebfe62a8c6e97d150bc2588394d03b2787418", + "originHash" : "4915c3f1fbbee0cd346a03a9c751be644eaf01b58bc4cd5c10c66a327a2130b8", "pins" : [ { "identity" : "abseil-cpp-binary", @@ -190,6 +190,15 @@ "version" : "6.8.0" } }, + { + "identity" : "sdwebimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImage.git", + "state" : { + "revision" : "10d06f6a33bafae8c164fbfd1f03391f6d4692b3", + "version" : "5.20.0" + } + }, { "identity" : "snapkit", "kind" : "remoteSourceControl", @@ -207,6 +216,15 @@ "revision" : "ebc7251dd5b37f627c93698e4374084d98409633", "version" : "1.28.2" } + }, + { + "identity" : "toast-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scalessec/Toast-Swift.git", + "state" : { + "revision" : "ddccd20d6fb718448d9b179c454c561653fda7d5", + "version" : "5.1.1" + } } ], "version" : 3 diff --git a/Tuist/Package.swift b/Tuist/Package.swift index ed70f5f7..23f8b07f 100644 --- a/Tuist/Package.swift +++ b/Tuist/Package.swift @@ -24,6 +24,8 @@ "NaverMapSDK" : .framework, // "NMapsMap" : .staticLibrary, // "NMapsGeometry" : .staticLibrary + "SDWebImage" : .framework, + "Toast" : .framework ] ) #endif @@ -39,6 +41,8 @@ let package = Package( .package(url: "https://github.com/kakao/kakao-ios-sdk", from: "2.23.0"), // .package(url: "https://github.com/google/GoogleSignIn-iOS", from: "8.0.0"), .package(url: "https://github.com/firebase/firebase-ios-sdk", from: "11.4.0"), - .package(url: "https://github.com/slr-09/Naver-Map-iOS-SPM.git", from: "0.1.0") + .package(url: "https://github.com/slr-09/Naver-Map-iOS-SPM.git", from: "0.1.0"), + .package(url: "https://github.com/SDWebImage/SDWebImage.git", from: "5.19.7"), + .package(url: "https://github.com/scalessec/Toast-Swift.git", from: "5.1.1") ] ) From 298025847e2fd316318264cc725fd57534042275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=98=88=EC=84=B1?= Date: Fri, 22 Nov 2024 17:34:58 +0900 Subject: [PATCH 10/19] =?UTF-8?q?[feat]=20=EC=BA=90=EB=A6=AD=ED=84=B0=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B7=B0=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../APIServices/Auth/LoginService.swift | 6 + .../Cells/NotificationOptionCell.swift | 4 + .../MyPage/Settings/AccountSettingsVC.swift | 1 + .../MyPage/Settings/CharacterSettingsVC.swift | 112 +++++++++++------- .../Settings/NotificationSettingsVC.swift | 1 + .../ViewControllers/MyPage/SettingsVC.swift | 1 + 6 files changed, 79 insertions(+), 46 deletions(-) diff --git a/DropDrug/Sources/APIServices/Auth/LoginService.swift b/DropDrug/Sources/APIServices/Auth/LoginService.swift index 89c0ef7d..163cd323 100644 --- a/DropDrug/Sources/APIServices/Auth/LoginService.swift +++ b/DropDrug/Sources/APIServices/Auth/LoginService.swift @@ -16,6 +16,9 @@ enum LoginService { // 기타 case postLogOut(accessToken: String) case postQuit(token: String) + + //리프레시 + case refreshAccessToken(token: String) } extension LoginService: TargetType { @@ -34,6 +37,7 @@ extension LoginService: TargetType { case .postGoogleLogin: return "auth/login/google" case .postLogOut: return "auth/logout" case .postQuit: return "auth/quit" + case .refreshAccessToken: return "auth/refresh" } } @@ -55,6 +59,8 @@ extension LoginService: TargetType { return .requestParameters(parameters: ["accessToken": accessToken], encoding: JSONEncoding.default) case .postQuit(let accessToken) : return .requestParameters(parameters: ["token": accessToken], encoding: JSONEncoding.default) + case .refreshAccessToken(let accessToken) : + return .requestParameters(parameters: ["token": accessToken], encoding: JSONEncoding.default) } } diff --git a/DropDrug/Sources/Cells/NotificationOptionCell.swift b/DropDrug/Sources/Cells/NotificationOptionCell.swift index 8af40370..2620ed71 100644 --- a/DropDrug/Sources/Cells/NotificationOptionCell.swift +++ b/DropDrug/Sources/Cells/NotificationOptionCell.swift @@ -12,6 +12,10 @@ class NotificationOptionCell: UITableViewCell { titleLabel.text = title toggleSwitch.isOn = isSwitchOn toggleSwitch.isEnabled = isSwitchEnabled + + toggleSwitch.onTintColor = Constants.Colors.skyblue // 켜졌을 때 배경색 + toggleSwitch.thumbTintColor = UIColor.white + toggleAction = onToggle toggleSwitch.removeTarget(self, action: #selector(handleToggle(_:)), for: .valueChanged) diff --git a/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift b/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift index e630a050..d3396247 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/Settings/AccountSettingsVC.swift @@ -46,6 +46,7 @@ class AccountSettingsVC: UIViewController, UITableViewDataSource, UITableViewDel tableView.register(AccountOptionCell.self, forCellReuseIdentifier: "AccountOptionCell") tableView.separatorStyle = .none tableView.backgroundColor = .white + tableView.isScrollEnabled = false view.addSubview(tableView) tableView.snp.makeConstraints { make in make.edges.equalTo(view.safeAreaLayoutGuide) diff --git a/DropDrug/Sources/ViewControllers/MyPage/Settings/CharacterSettingsVC.swift b/DropDrug/Sources/ViewControllers/MyPage/Settings/CharacterSettingsVC.swift index 1b73d89b..7dd21c30 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/Settings/CharacterSettingsVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/Settings/CharacterSettingsVC.swift @@ -5,7 +5,7 @@ import SnapKit import Moya class CharacterSettingsVC: UIViewController { - + //TODO: 캐릭터 불러오기 api 연결 let MemberProvider = MoyaProvider(plugins: [BearerTokenPlugin(), NetworkLoggerPlugin()]) var ownedCharCount : Int = 0 @@ -31,57 +31,78 @@ class CharacterSettingsVC: UIViewController { private let allCharLabel = SubLabelView() private lazy var ownedCharCollectionView: UICollectionView = { - let layout = UICollectionViewFlowLayout() - layout.itemSize = CGSize(width: 80, height: 80) - layout.minimumInteritemSpacing = 12 - layout.minimumLineSpacing = 16 - layout.sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) + let layout = UICollectionViewFlowLayout() + layout.itemSize = CGSize(width: 100, height: 100) + layout.minimumInteritemSpacing = 12 + layout.minimumLineSpacing = 16 + layout.sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) layout.scrollDirection = .horizontal - - let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) - collectionView.dataSource = self - collectionView.delegate = self - collectionView.backgroundColor = .white - collectionView.register(SeoulCollectionViewCell.self, forCellWithReuseIdentifier: "SeoulCollectionViewCell") - return collectionView - }() + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.dataSource = self + collectionView.delegate = self + collectionView.backgroundColor = .white + collectionView.register(SeoulCollectionViewCell.self, forCellWithReuseIdentifier: "SeoulCollectionViewCell") + collectionView.showsHorizontalScrollIndicator = false + collectionView.tag = 0 + return collectionView + }() + + private let emptyStateLabel: UILabel = { + let label = UILabel() + label.text = "보유한 캐릭터가 없습니다." + label.textColor = .gray + label.font = UIFont.systemFont(ofSize: 16) + label.textAlignment = .center + label.isHidden = true + return label + }() private lazy var allCharCollectionView: UICollectionView = { - let layout = UICollectionViewFlowLayout() - let screenWidth = UIScreen.main.bounds.width - let itemsPerRow: CGFloat = 4 - let totalSpacing = (screenWidth / (itemsPerRow * 2 - 1)) * 0.6 - let itemWidth = (screenWidth - totalSpacing * 3) / 4 - - layout.itemSize = CGSize(width: itemWidth, height: itemWidth) - layout.minimumInteritemSpacing = totalSpacing * 0.5 - layout.minimumLineSpacing = totalSpacing * 0.3 - layout.sectionInset = UIEdgeInsets(top: 0, left: totalSpacing * 0.3, bottom: 0, right: totalSpacing * 0.5) - - let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) - collectionView.dataSource = self - collectionView.delegate = self - collectionView.backgroundColor = .white - collectionView.register(SeoulCollectionViewCell.self, forCellWithReuseIdentifier: "SeoulCollectionViewCell") - return collectionView - }() + let layout = UICollectionViewFlowLayout() + let screenWidth = UIScreen.main.bounds.width + let itemsPerRow: CGFloat = 4 + let totalSpacing = (screenWidth / (itemsPerRow * 2 - 1)) * 0.6 + let itemWidth = (screenWidth - totalSpacing * 3) / 4 + + layout.itemSize = CGSize(width: itemWidth, height: itemWidth) + layout.minimumInteritemSpacing = totalSpacing * 0.5 + layout.minimumLineSpacing = totalSpacing * 0.3 + layout.sectionInset = UIEdgeInsets(top: 0, left: totalSpacing * 0.3, bottom: 0, right: totalSpacing * 0.5) + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.dataSource = self + collectionView.delegate = self + collectionView.backgroundColor = .white + collectionView.register(SeoulCollectionViewCell.self, forCellWithReuseIdentifier: "SeoulCollectionViewCell") + collectionView.tag = 1 + return collectionView + }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton) + navigationController?.navigationBar.isTranslucent = false + + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() // 불투명한 배경 설정 + appearance.backgroundColor = .white // 원하는 배경색 설정 + appearance.shadowImage = UIImage() // 구분선 이미지 제거 + appearance.shadowColor = nil + + navigationController?.navigationBar.scrollEdgeAppearance = appearance setupUI() setConstraints() fetchMemberInfo { success in if success { - print("Profile updated successfully") + self.ownedCharCollectionView.reloadData() } else { print("Failed to update profile") } } - } func setupUI() { @@ -91,7 +112,7 @@ class CharacterSettingsVC: UIViewController { view.addSubview($0) } scrollView.addSubview(contentView) - [ownedCharLabel, ownedCharCollectionView, allCharLabel, allCharCollectionView].forEach { + [ownedCharLabel, ownedCharCollectionView, emptyStateLabel, allCharLabel, allCharCollectionView].forEach { contentView.addSubview($0) } } @@ -112,29 +133,25 @@ class CharacterSettingsVC: UIViewController { make.leading.equalToSuperview().inset(20) make.height.equalTo(20) } - ownedCharCollectionView.snp.makeConstraints { make in make.top.equalTo(ownedCharLabel.snp.bottom) make.horizontalEdges.equalToSuperview() make.height.equalTo(80) } - + emptyStateLabel.snp.makeConstraints { make in + make.center.equalTo(ownedCharCollectionView) + } allCharLabel.snp.makeConstraints { make in make.top.equalTo(ownedCharCollectionView.snp.bottom).offset(20) make.leading.equalToSuperview().inset(20) make.height.equalTo(20) } - allCharCollectionView.snp.makeConstraints { make in make.top.equalTo(allCharLabel.snp.bottom) make.horizontalEdges.equalToSuperview() make.height.equalTo(850) make.bottom.equalToSuperview().offset(-15) } - -// contentView.snp.makeConstraints { make in -// make.bottom.equalTo(seoulPageCollectionView.snp.bottom).offset(15) -// } } // MARK: - Actions @@ -149,20 +166,23 @@ extension CharacterSettingsVC: UICollectionViewDataSource, UICollectionViewDeleg func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if collectionView.tag == 0 { //TODO: 캐릭터 변경 + print("캐릭터 변경") } else if collectionView.tag == 1 { //TODO: 캐릭터 구매 + print("캐릭터 구매") } } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { if collectionView.tag == 0 { + emptyStateLabel.isHidden = ownedCharCount > 0 return ownedCharCount - } - else if collectionView.tag == 1 { + } else if collectionView.tag == 1 { return Constants.AllCharacter.allCharCount + } else { + return 0 } - return 0 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { @@ -181,7 +201,7 @@ extension CharacterSettingsVC: UICollectionViewDataSource, UICollectionViewDeleg func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { if collectionView.tag == 0 { - return CGSize(width: 80, height: 80) // 아이템 크기 설정 + return CGSize(width: 100, height: 100) // 아이템 크기 설정 } else if collectionView.tag == 1 { let screenWidth = UIScreen.main.bounds.width diff --git a/DropDrug/Sources/ViewControllers/MyPage/Settings/NotificationSettingsVC.swift b/DropDrug/Sources/ViewControllers/MyPage/Settings/NotificationSettingsVC.swift index 6757669d..a34e84fc 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/Settings/NotificationSettingsVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/Settings/NotificationSettingsVC.swift @@ -41,6 +41,7 @@ class NotificationSettingsVC: UIViewController { tableView.delegate = self tableView.rowHeight = 60 tableView.separatorStyle = .none + tableView.isScrollEnabled = false tableView.register(NotificationOptionCell.self, forCellReuseIdentifier: "NotificationOptionCell") view.addSubview(tableView) diff --git a/DropDrug/Sources/ViewControllers/MyPage/SettingsVC.swift b/DropDrug/Sources/ViewControllers/MyPage/SettingsVC.swift index 64056b1c..a1500752 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/SettingsVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/SettingsVC.swift @@ -43,6 +43,7 @@ class SettingsVC: UIViewController { tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") tableView.separatorStyle = .none tableView.backgroundColor = .white + tableView.isScrollEnabled = false view.addSubview(tableView) } From 2ae23e055119cfd3da5fb123eb6b4ea23e10a82c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=98=88=EC=84=B1?= Date: Fri, 22 Nov 2024 22:46:10 +0900 Subject: [PATCH 11/19] =?UTF-8?q?[feat]=20rewardVC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DropDrug/Sources/APIServices/PointAPI.swift | 8 +- DropDrug/Sources/Cells/PointHistoryCell.swift | 70 ++++++++++++ .../Sources/Models/MyPage/PointManager.swift | 6 +- .../Models/Request/AddPointRequest.swift | 8 ++ .../Response/PointHistoryResponse.swift | 14 +++ .../ViewControllers/MyPage/RewardVC.swift | 101 +++++++++++++++++- 6 files changed, 198 insertions(+), 9 deletions(-) create mode 100644 DropDrug/Sources/Cells/PointHistoryCell.swift create mode 100644 DropDrug/Sources/Models/Request/AddPointRequest.swift create mode 100644 DropDrug/Sources/Models/Response/PointHistoryResponse.swift diff --git a/DropDrug/Sources/APIServices/PointAPI.swift b/DropDrug/Sources/APIServices/PointAPI.swift index 719ff65a..efb5c17a 100644 --- a/DropDrug/Sources/APIServices/PointAPI.swift +++ b/DropDrug/Sources/APIServices/PointAPI.swift @@ -6,7 +6,7 @@ import KeychainSwift enum PointAPI { case getPoint - case postPoint + case postPoint(param: AddPointRequest) case getPointHistory case getMonthlyStats } @@ -24,7 +24,7 @@ extension PointAPI: TargetType { case .getPoint: return "points" case .postPoint: - return "members/notification" + return "points" case .getPointHistory: return "points/history" case .getMonthlyStats: @@ -45,8 +45,8 @@ extension PointAPI: TargetType { switch self { case .getPoint: return .requestPlain - case .postPoint: - return .requestPlain + case .postPoint(let param): + return .requestJSONEncodable(param) case .getPointHistory: return .requestPlain case .getMonthlyStats: diff --git a/DropDrug/Sources/Cells/PointHistoryCell.swift b/DropDrug/Sources/Cells/PointHistoryCell.swift new file mode 100644 index 00000000..32edbafd --- /dev/null +++ b/DropDrug/Sources/Cells/PointHistoryCell.swift @@ -0,0 +1,70 @@ +// Copyright © 2024 RT4. All rights reserved + +import Foundation +import UIKit +import SnapKit + +class PointHistoryCell: UITableViewCell { + static let identifier = "PointHistoryCell" + + private let dateLabel: UILabel = { + let label = UILabel() + label.font = UIFont.ptdRegularFont(ofSize: 15) + label.textColor = Constants.Colors.gray800 + label.textAlignment = .left + return label + }() + + private let descriptionLabel: UILabel = { + let label = UILabel() + label.font = UIFont.ptdRegularFont(ofSize: 15) + label.textColor = Constants.Colors.gray800 + label.textAlignment = .center + return label + }() + + private let pointsLabel: UILabel = { + let label = UILabel() + label.font = UIFont.ptdRegularFont(ofSize: 15) + label.textColor = Constants.Colors.gray800 + label.textAlignment = .right + return label + }() + + private let separatorView: UIView = { + let view = UIView() + view.backgroundColor = Constants.Colors.gray100 // 원하는 색상으로 설정 + return view + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + let stackView = UIStackView(arrangedSubviews: [dateLabel, descriptionLabel, pointsLabel]) + stackView.axis = .horizontal + stackView.distribution = .fillEqually + + contentView.addSubview(stackView) + contentView.addSubview(separatorView) // 구분선 추가 + + stackView.snp.makeConstraints { make in + make.edges.equalToSuperview().inset(20) + } + + separatorView.snp.makeConstraints { make in + make.height.equalTo(1) + make.leading.trailing.equalToSuperview().inset(20) + make.bottom.equalToSuperview() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configure(with item: PointDetail) { + dateLabel.text = item.date + descriptionLabel.text = item.type + pointsLabel.text = "\(item.point > 0 ? "+" : "")\(item.point) P" + } +} diff --git a/DropDrug/Sources/Models/MyPage/PointManager.swift b/DropDrug/Sources/Models/MyPage/PointManager.swift index ce2fb3f5..c0c912b9 100644 --- a/DropDrug/Sources/Models/MyPage/PointManager.swift +++ b/DropDrug/Sources/Models/MyPage/PointManager.swift @@ -5,14 +5,14 @@ import Moya extension RewardVC { func fetchPoint(completion: @escaping (Bool) -> Void) { - PointProvider.request(.getPoint) { result in + PointProvider.request(.getPointHistory) { result in switch result { case .success(let response): do { // JSON 디코딩 - let data = try response.map(PointResponse.self) + let data = try response.map(PointHistoryResponse.self) DispatchQueue.main.async { - self.rewardView.pointsLabel.text = "\(data.point) P" + self.rewardView.pointsLabel.text = "\(data.totalPoint) P" } } catch { print("JSON 파싱 에러: \(error)") diff --git a/DropDrug/Sources/Models/Request/AddPointRequest.swift b/DropDrug/Sources/Models/Request/AddPointRequest.swift new file mode 100644 index 00000000..9ce9de6b --- /dev/null +++ b/DropDrug/Sources/Models/Request/AddPointRequest.swift @@ -0,0 +1,8 @@ +// Copyright © 2024 RT4. All rights reserved + +import Foundation + +struct AddPointRequest : Codable{ + let point : Int + let type : String +} diff --git a/DropDrug/Sources/Models/Response/PointHistoryResponse.swift b/DropDrug/Sources/Models/Response/PointHistoryResponse.swift new file mode 100644 index 00000000..584db988 --- /dev/null +++ b/DropDrug/Sources/Models/Response/PointHistoryResponse.swift @@ -0,0 +1,14 @@ +// Copyright © 2024 RT4. All rights reserved + +import Foundation + +struct PointHistoryResponse: Codable { + let totalPoint: Int + let pointHistory : [PointDetail] +} + +struct PointDetail: Codable { + let date: String + let point: Int + let type: String +} diff --git a/DropDrug/Sources/ViewControllers/MyPage/RewardVC.swift b/DropDrug/Sources/ViewControllers/MyPage/RewardVC.swift index 820dceda..36ecc5ae 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/RewardVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/RewardVC.swift @@ -4,6 +4,8 @@ import UIKit import SnapKit import Moya +//TODO: api 연결 + class RewardVC : UIViewController { let PointProvider = MoyaProvider(plugins: [BearerTokenPlugin(), NetworkLoggerPlugin()]) @@ -20,16 +22,73 @@ class RewardVC : UIViewController { return view }() + private let headerView: UIView = { + let view = UIView() + view.backgroundColor = .white + + let dateLabel = UILabel() + dateLabel.text = "일시" + dateLabel.font = UIFont.ptdRegularFont(ofSize: 16) + dateLabel.textColor = Constants.Colors.gray500 + dateLabel.textAlignment = .left + + let detailLabel = UILabel() + detailLabel.text = "내역" + detailLabel.font = UIFont.ptdRegularFont(ofSize:16) + detailLabel.textColor = Constants.Colors.gray500 + detailLabel.textAlignment = .center + + let rewardLabel = UILabel() + rewardLabel.text = "리워드" + rewardLabel.font = UIFont.ptdRegularFont(ofSize: 16) + rewardLabel.textColor = Constants.Colors.gray500 + rewardLabel.textAlignment = .right + + let stackView = UIStackView(arrangedSubviews: [dateLabel, detailLabel, rewardLabel]) + stackView.axis = .horizontal + stackView.distribution = .fillEqually + + let separatorView = UIView() + separatorView.backgroundColor = Constants.Colors.gray100 + + view.addSubview(stackView) + view.addSubview(separatorView) + stackView.snp.makeConstraints { make in + make.top.bottom.equalToSuperview().inset(8) + make.leading.trailing.equalToSuperview().inset(20) + } + separatorView.snp.makeConstraints { make in + make.height.equalTo(1) + make.bottom.equalToSuperview() + } + return view + }() + + private let rewardTableView: UITableView = { + let tableView = UITableView() + tableView.register(PointHistoryCell.self, forCellReuseIdentifier: PointHistoryCell.identifier) + tableView.separatorStyle = .none + return tableView + }() + + private var rewardData: [PointDetail] = [] + override func viewDidLoad() { super.viewDidLoad() self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton) setupViews() setConstraints() + + // 데이터 로드 + rewardData = getMockData() + rewardTableView.delegate = self + rewardTableView.dataSource = self + rewardTableView.reloadData() } func setupViews() { view.backgroundColor = .white - [rewardView].forEach { + [rewardView, headerView, rewardTableView].forEach { view.addSubview($0) } fetchPoint { success in @@ -43,10 +102,31 @@ class RewardVC : UIViewController { func setConstraints() { rewardView.snp.makeConstraints { make in - make.top.equalTo(view.safeAreaLayoutGuide) + make.top.equalTo(view.safeAreaLayoutGuide).offset(20) make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(16) make.height.equalTo(60) } + headerView.snp.makeConstraints { make in + make.top.equalTo(rewardView.snp.bottom).offset(20) + make.leading.trailing.equalTo(view.safeAreaLayoutGuide) + make.height.equalTo(40) + } + rewardTableView.snp.makeConstraints { make in + make.top.equalTo(headerView.snp.bottom) + make.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide) + } + } + + // MARK: - Mock Data + private func getMockData() -> [PointDetail] { + return [ + PointDetail(date: "23/04/29", point: -200, type: "캐릭터 구매"), + PointDetail(date: "23/04/23", point: 200, type: "폐기 사진 인증"), + PointDetail(date: "23/04/15", point: 300, type: "폐기 장소 문의"), + PointDetail(date: "23/04/10", point: 200, type: "폐기 사진 인증"), + PointDetail(date: "23/04/08", point: -200, type: "캐릭터 구매"), + PointDetail(date: "23/04/04", point: 200, type: "폐기 사진 인증") + ] } // MARK: - Actions @@ -55,3 +135,20 @@ class RewardVC : UIViewController { navigationController?.popViewController(animated: false) } } + + +// MARK: - UITableViewDelegate, UITableViewDataSource +extension RewardVC: UITableViewDelegate, UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return rewardData.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: PointHistoryCell.identifier, for: indexPath) as? PointHistoryCell else { + return UITableViewCell() + } + let item = rewardData[indexPath.row] + cell.configure(with: item) + return cell + } +} From 3667cb7b46a1cf374215fa1daf6fea40096f345c Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Sat, 23 Nov 2024 00:19:56 +0900 Subject: [PATCH 12/19] =?UTF-8?q?#24=20=EC=95=BD=20=EB=94=94=ED=85=8C?= =?UTF-8?q?=EC=9D=BC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20UI=20=EC=99=84?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contents.json" | 4 +- .../Register/EnrollDetailViewController.swift | 66 +++++++++--- .../Register/PrescriptionDrugVC.swift | 8 +- DropDrug/Sources/Views/CustomStepper.swift | 102 ++++++++++++++++++ 4 files changed, 162 insertions(+), 18 deletions(-) create mode 100644 DropDrug/Sources/Views/CustomStepper.swift diff --git "a/DropDrug/Resources/Assets.xcassets/SeoulDistrictLogo/\352\264\221\354\247\204\352\265\254.imageset/Contents.json" "b/DropDrug/Resources/Assets.xcassets/SeoulDistrictLogo/\352\264\221\354\247\204\352\265\254.imageset/Contents.json" index d7071c37..abcd7780 100644 --- "a/DropDrug/Resources/Assets.xcassets/SeoulDistrictLogo/\352\264\221\354\247\204\352\265\254.imageset/Contents.json" +++ "b/DropDrug/Resources/Assets.xcassets/SeoulDistrictLogo/\352\264\221\354\247\204\352\265\254.imageset/Contents.json" @@ -1,12 +1,12 @@ { "images" : [ { - "filename" : "광진구.png", + "filename" : "광진구@2x.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "광진구@2x.png", + "filename" : "광진구.png", "idiom" : "universal", "scale" : "2x" }, diff --git a/DropDrug/Sources/ViewControllers/Register/EnrollDetailViewController.swift b/DropDrug/Sources/ViewControllers/Register/EnrollDetailViewController.swift index 4cb2752e..71470325 100644 --- a/DropDrug/Sources/ViewControllers/Register/EnrollDetailViewController.swift +++ b/DropDrug/Sources/ViewControllers/Register/EnrollDetailViewController.swift @@ -5,7 +5,7 @@ import UIKit class EnrollDetailViewController: UIViewController { private lazy var backButton: CustomBackButton = { - let button = CustomBackButton(title: " 의약품 삭제하기") + let button = CustomBackButton(title: " 의약품 등록하기") button.addTarget(self, action: #selector(didTapBackButton), for: .touchUpInside) return button }() @@ -23,6 +23,7 @@ class EnrollDetailViewController: UIViewController { let picker = UIDatePicker() picker.datePickerMode = .date // 날짜 선택 모드 picker.preferredDatePickerStyle = .inline + picker.tintColor = Constants.Colors.skyblue picker.locale = Locale(identifier: "ko_KR") // 한국어로 표시 picker.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged) return picker @@ -36,34 +37,64 @@ class EnrollDetailViewController: UIViewController { label.font = UIFont.ptdSemiBoldFont(ofSize: 17) return label }() + + private lazy var customStepper: CustomStepper = { + let stepper = CustomStepper() + return stepper + }() + + private lazy var completeButton: UIButton = { + let button = UIButton() + button.setTitle("저장하기", for: .normal) + button.backgroundColor = Constants.Colors.skyblue + button.setTitleColor(.white, for: .normal) + button.layer.cornerRadius = 25 + button.titleLabel?.font = UIFont.ptdSemiBoldFont(ofSize: 16) + button.addTarget(self, action: #selector(didTapCompleteButton), for: .touchUpInside) + return button + }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton) + + setupLayout() } func setupLayout() { - let views = [selectStartDateLabel, selectDateCountLabel, datePicker] + let views = [selectStartDateLabel, selectDateCountLabel, customStepper, datePicker, completeButton] views.forEach { view.addSubview($0) } - selectStartDateLabel.snp.makeConstraints { l in - l.top.equalToSuperview().offset(12) - l.leading.equalToSuperview().offset(20) - l.height.equalTo(48) + selectStartDateLabel.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide).offset(12) + make.leading.equalToSuperview().offset(20) // 좌측 여백 + make.height.equalTo(48) // 고정 높이 } - + datePicker.snp.makeConstraints { make in - make.top.equalTo(selectStartDateLabel.snp.bottom).offset(10) - make.leading.equalToSuperview().offset(30) - make.trailing.equalToSuperview().offset(-30) - make.height.equalTo(datePicker.snp.width) + make.top.equalTo(selectStartDateLabel.snp.bottom).offset(8) // 위 레이블과의 간격 + make.leading.equalToSuperview().offset(30) // 좌측 여백 + make.trailing.equalToSuperview().offset(-30) // 우측 여백 + make.height.equalTo(datePicker.snp.width) // 정사각형 } - + selectDateCountLabel.snp.makeConstraints { make in - make.top.equalTo(datePicker.snp.bottom).offset(16) - make.leading.equalTo(selectStartDateLabel.snp.leading) - make.height.equalTo(48) + make.top.equalTo(datePicker.snp.bottom).offset(16) // 데이트 피커와의 간격 + make.leading.equalTo(selectStartDateLabel.snp.leading) // 레이블 정렬 + make.height.equalTo(48) // 고정 높이 + } + + customStepper.snp.makeConstraints { make in + make.top.equalTo(selectDateCountLabel.snp.bottom).offset(28) + make.centerX.equalToSuperview() + make.height.equalTo(35) + } + + completeButton.snp.makeConstraints { make in + make.left.right.equalToSuperview().inset(16) + make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-16) + make.height.equalTo(50) } } @@ -72,11 +103,16 @@ class EnrollDetailViewController: UIViewController { navigationController?.popViewController(animated: false) } + @objc private func didTapCompleteButton() { + + } + @objc private func dateChanged(_ sender: UIDatePicker) { let formatter = DateFormatter() formatter.dateStyle = .medium formatter.locale = Locale(identifier: "ko_KR") // selectedDateLabel.text = "선택된 날짜: \(formatter.string(from: sender.date))" } + } diff --git a/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift b/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift index f2680983..0ecd2b6d 100644 --- a/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift +++ b/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift @@ -84,6 +84,11 @@ class PrescriptionDrugVC: UIViewController { setupGestures() } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.isNavigationBarHidden = true + } + // MARK: - Setup Methods func setupView() { @@ -126,15 +131,16 @@ class PrescriptionDrugVC: UIViewController { // MARK: - Actions @objc func addDrugViewTapped() { - //TODO: 약 등록하기 VC로 연결 self.navigationController?.isNavigationBarHidden = false let detailVC = EnrollDetailViewController() + detailVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(detailVC, animated: false) } @objc func discardButtonTapped(){ self.navigationController?.isNavigationBarHidden = false let DiscardPrescriptionDrugVC = DiscardPrescriptionDrugVC() + DiscardPrescriptionDrugVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(DiscardPrescriptionDrugVC, animated: false) } } diff --git a/DropDrug/Sources/Views/CustomStepper.swift b/DropDrug/Sources/Views/CustomStepper.swift new file mode 100644 index 00000000..d794cbd5 --- /dev/null +++ b/DropDrug/Sources/Views/CustomStepper.swift @@ -0,0 +1,102 @@ +// Copyright © 2024 RT4. All rights reserved + +import UIKit +import SnapKit + +class CustomStepper: UIView { + + // MARK: - UI Elements + private lazy var minusButton: UIButton = { + let button = UIButton() + button.setImage(UIImage(systemName: "minus"), for: .normal) + button.imageView?.tintColor = Constants.Colors.gray400 + button.backgroundColor = Constants.Colors.gray0 + button.layer.cornerRadius = 2 + button.contentVerticalAlignment = .center + button.contentHorizontalAlignment = .center + button.addTarget(self, action: #selector(didTapMinus), for: .touchUpInside) + return button + }() + + private lazy var plusButton: UIButton = { + let button = UIButton() + button.setImage(UIImage(systemName: "plus"), for: .normal) + button.imageView?.tintColor = Constants.Colors.skyblue + button.backgroundColor = Constants.Colors.gray0 + button.layer.cornerRadius = 2 + button.contentVerticalAlignment = .center + button.contentHorizontalAlignment = .center + button.addTarget(self, action: #selector(didTapPlus), for: .touchUpInside) + return button + }() + + private lazy var numberLabel: UILabel = { + let label = UILabel() + label.text = "0" + label.textColor = Constants.Colors.gray900 + label.backgroundColor = Constants.Colors.gray100 + label.font = UIFont.ptdSemiBoldFont(ofSize: 22) + label.textAlignment = .center + label.layer.cornerRadius = 2 + return label + }() + + // MARK: - Properties + public var currentValue: Int = 0 { + didSet { + numberLabel.text = "\(currentValue)" + } + } + + // MARK: - Initializer + override init(frame: CGRect) { + super.init(frame: frame) + setupViews() + setupConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setup Views + private func setupViews() { + addSubview(minusButton) + addSubview(numberLabel) + addSubview(plusButton) + } + + // MARK: - Setup Constraints + private func setupConstraints() { + minusButton.snp.makeConstraints { make in + make.top.bottom.leading.equalToSuperview() + make.width.equalTo(35) // 버튼 고정 너비 + make.height.equalTo(35) + } + + numberLabel.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.leading.equalTo(minusButton.snp.trailing).offset(6) + make.trailing.equalTo(plusButton.snp.leading).offset(-6) + make.width.equalTo(90) // 최소 너비 보장 + make.height.equalTo(35) + } + + plusButton.snp.makeConstraints { make in + make.top.bottom.trailing.equalToSuperview() + make.width.equalTo(35) // 버튼 고정 너비 + make.height.equalTo(35) + } + } + + // MARK: - Actions + @objc private func didTapMinus() { + if currentValue > 0 { + currentValue -= 1 + } + } + + @objc private func didTapPlus() { + currentValue += 1 + } +} From 5ed1a009b3936a87e60e4bfe766cb213e193f5d9 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Sat, 23 Nov 2024 00:22:24 +0900 Subject: [PATCH 13/19] =?UTF-8?q?[bug]=20=EC=97=90=EC=85=8B=20=ED=8A=80?= =?UTF-8?q?=EC=96=B4=EB=82=98=EA=B0=80=EB=8A=94=EA=B1=B0=20=EA=B3=A0?= =?UTF-8?q?=EC=B3=90=EB=B4=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contents.json" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/DropDrug/Resources/Assets.xcassets/SeoulDistrictLogo/\352\264\221\354\247\204\352\265\254.imageset/Contents.json" "b/DropDrug/Resources/Assets.xcassets/SeoulDistrictLogo/\352\264\221\354\247\204\352\265\254.imageset/Contents.json" index abcd7780..d7071c37 100644 --- "a/DropDrug/Resources/Assets.xcassets/SeoulDistrictLogo/\352\264\221\354\247\204\352\265\254.imageset/Contents.json" +++ "b/DropDrug/Resources/Assets.xcassets/SeoulDistrictLogo/\352\264\221\354\247\204\352\265\254.imageset/Contents.json" @@ -1,12 +1,12 @@ { "images" : [ { - "filename" : "광진구@2x.png", + "filename" : "광진구.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "광진구.png", + "filename" : "광진구@2x.png", "idiom" : "universal", "scale" : "2x" }, From 94b1ff9c1aa8cbd34c769e931d17e5ec1bca8333 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Sat, 23 Nov 2024 00:51:54 +0900 Subject: [PATCH 14/19] =?UTF-8?q?#5=20=EC=95=A0=ED=94=8C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Models/Auth/RegisterManager.swift | 4 +- .../Request/OAuthSocialLoginRequest.swift | 3 +- .../Auth/SelectLoginTypeVC.swift | 50 ++++++++++++++++--- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/DropDrug/Sources/Models/Auth/RegisterManager.swift b/DropDrug/Sources/Models/Auth/RegisterManager.swift index a3293ad3..dd05bf6e 100644 --- a/DropDrug/Sources/Models/Auth/RegisterManager.swift +++ b/DropDrug/Sources/Models/Auth/RegisterManager.swift @@ -92,9 +92,9 @@ extension SelectLoginTypeVC { } } - func setupAppleDTO(_ idToken: String) -> OAuthAppleLoginRequest? { + func setupAppleDTO(_ idToken: String, _ name: String, _ email: String) -> OAuthAppleLoginRequest? { guard let fcmToken = SelectLoginTypeVC.keychain.get("FCMToken") else { return nil } - return OAuthAppleLoginRequest(fcmToken: fcmToken, idToken: idToken) + return OAuthAppleLoginRequest(fcmToken: fcmToken, name: name, email: email) } func callAppleLoginAPI(param : OAuthAppleLoginRequest, completion: @escaping (Bool) -> Void) { provider.request(.postAppleLogin(param: param)) { result in diff --git a/DropDrug/Sources/Models/Request/OAuthSocialLoginRequest.swift b/DropDrug/Sources/Models/Request/OAuthSocialLoginRequest.swift index eaccab1e..99fef40b 100644 --- a/DropDrug/Sources/Models/Request/OAuthSocialLoginRequest.swift +++ b/DropDrug/Sources/Models/Request/OAuthSocialLoginRequest.swift @@ -10,5 +10,6 @@ struct OAuthSocialLoginRequest : Codable{ struct OAuthAppleLoginRequest : Codable{ let fcmToken : String - let idToken : String + let name : String + let email : String } diff --git a/DropDrug/Sources/ViewControllers/Auth/SelectLoginTypeVC.swift b/DropDrug/Sources/ViewControllers/Auth/SelectLoginTypeVC.swift index fdc84c90..e7d0bcf9 100644 --- a/DropDrug/Sources/ViewControllers/Auth/SelectLoginTypeVC.swift +++ b/DropDrug/Sources/ViewControllers/Auth/SelectLoginTypeVC.swift @@ -271,28 +271,66 @@ extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { switch authorization.credential { case let appleIDCredential as ASAuthorizationAppleIDCredential: let userIdentifier = appleIDCredential.user - let fullName = appleIDCredential.fullName + var formattedName: String = "" + if let authorizationCode = appleIDCredential.authorizationCode, + let codeString = String(data: authorizationCode, encoding: .utf8) { + print("Authorization Code: \(codeString)") + } else { + print("Authorization Code is nil or could not be decoded.") + } + + if let fullName = appleIDCredential.fullName, ((fullName.givenName?.isEmpty) == nil) && ((fullName.familyName?.isEmpty) == nil) { + let givenName = fullName.givenName ?? "" + let familyName = fullName.familyName ?? "" + + formattedName = "\(familyName)\(givenName)" + + print("Formatted Full Name: \(formattedName)") + } else { + print("Full name is nil") + } + + let email = appleIDCredential.email if let identityToken = appleIDCredential.identityToken, - let identityTokenString = String(data: identityToken, encoding: .utf8) { + let identityTokenString = String(data: identityToken, encoding: .utf8), + let emailString = email{ SelectLoginTypeVC.keychain.set(identityTokenString, forKey: "AppleIDToken") - callAppleLoginAPI(param: setupAppleDTO(identityTokenString)!) { isSuccess in + SelectLoginTypeVC.keychain.set(emailString, forKey: "AppleIDEmail") + SelectLoginTypeVC.keychain.set(formattedName, forKey: "AppleIDName") + callAppleLoginAPI(param: setupAppleDTO(identityTokenString, formattedName, emailString)!) { isSuccess in if isSuccess { self.handleKakaoLoginSuccess() } else { print("애플 로그인(바로 로그인) 실패") } } + } else { + guard let identityTokenString = SelectLoginTypeVC.keychain.get("AppleIDToken"), + let emailString = SelectLoginTypeVC.keychain.get("AppleIDEmail"), + let nameString = SelectLoginTypeVC.keychain.get("AppleIDName") else { return } + + callAppleLoginAPI(param: setupAppleDTO(identityTokenString, nameString, emailString)!) { isSuccess in + if isSuccess { + self.handleKakaoLoginSuccess() + } else { + print("애플 로그인 실패") + } + } + } case let passwordCredential as ASPasswordCredential: let username = passwordCredential.user let password = passwordCredential.password - guard let identityTokenString = SelectLoginTypeVC.keychain.get("AppleIDToken") else { return } - callAppleLoginAPI(param: setupAppleDTO(identityTokenString)!) { isSuccess in + guard let identityTokenString = SelectLoginTypeVC.keychain.get("AppleIDToken"), + let emailString = SelectLoginTypeVC.keychain.get("AppleIDEmail"), + let nameString = SelectLoginTypeVC.keychain.get("AppleIDName") else { return } + + callAppleLoginAPI(param: setupAppleDTO(identityTokenString, nameString, emailString)!) { isSuccess in if isSuccess { self.handleKakaoLoginSuccess() } else { - print("애플 로그인 (패스워드 시도) 실패") + print("애플 로그인 간접 실패") } } From d4f25da0c7a1014a90cc10833556248d3312870f Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Sat, 23 Nov 2024 23:44:09 +0900 Subject: [PATCH 15/19] =?UTF-8?q?#5=20=EC=95=A0=ED=94=8C=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20DTO=20=EC=9E=AC=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Models/Auth/RegisterManager.swift | 4 +- .../Request/OAuthSocialLoginRequest.swift | 3 +- .../Auth/SelectLoginTypeVC.swift | 38 ++++++------------- 3 files changed, 16 insertions(+), 29 deletions(-) diff --git a/DropDrug/Sources/Models/Auth/RegisterManager.swift b/DropDrug/Sources/Models/Auth/RegisterManager.swift index dd05bf6e..4393ffcc 100644 --- a/DropDrug/Sources/Models/Auth/RegisterManager.swift +++ b/DropDrug/Sources/Models/Auth/RegisterManager.swift @@ -92,9 +92,9 @@ extension SelectLoginTypeVC { } } - func setupAppleDTO(_ idToken: String, _ name: String, _ email: String) -> OAuthAppleLoginRequest? { + func setupAppleDTO(_ idToken: String, _ name: String, _ email: String, _ authorizationCode : String) -> OAuthAppleLoginRequest? { guard let fcmToken = SelectLoginTypeVC.keychain.get("FCMToken") else { return nil } - return OAuthAppleLoginRequest(fcmToken: fcmToken, name: name, email: email) + return OAuthAppleLoginRequest(fcmToken: fcmToken, name: name, email: email, authorizationCode: authorizationCode) } func callAppleLoginAPI(param : OAuthAppleLoginRequest, completion: @escaping (Bool) -> Void) { provider.request(.postAppleLogin(param: param)) { result in diff --git a/DropDrug/Sources/Models/Request/OAuthSocialLoginRequest.swift b/DropDrug/Sources/Models/Request/OAuthSocialLoginRequest.swift index 99fef40b..b4e21b3f 100644 --- a/DropDrug/Sources/Models/Request/OAuthSocialLoginRequest.swift +++ b/DropDrug/Sources/Models/Request/OAuthSocialLoginRequest.swift @@ -8,8 +8,9 @@ struct OAuthSocialLoginRequest : Codable{ let idToken : String } -struct OAuthAppleLoginRequest : Codable{ +struct OAuthAppleLoginRequest : Codable { let fcmToken : String let name : String let email : String + let authorizationCode : String } diff --git a/DropDrug/Sources/ViewControllers/Auth/SelectLoginTypeVC.swift b/DropDrug/Sources/ViewControllers/Auth/SelectLoginTypeVC.swift index e7d0bcf9..a1f4048c 100644 --- a/DropDrug/Sources/ViewControllers/Auth/SelectLoginTypeVC.swift +++ b/DropDrug/Sources/ViewControllers/Auth/SelectLoginTypeVC.swift @@ -272,13 +272,7 @@ extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { case let appleIDCredential as ASAuthorizationAppleIDCredential: let userIdentifier = appleIDCredential.user var formattedName: String = "" - if let authorizationCode = appleIDCredential.authorizationCode, - let codeString = String(data: authorizationCode, encoding: .utf8) { - print("Authorization Code: \(codeString)") - } else { - print("Authorization Code is nil or could not be decoded.") - } - + var authorizationCode : String = "" if let fullName = appleIDCredential.fullName, ((fullName.givenName?.isEmpty) == nil) && ((fullName.familyName?.isEmpty) == nil) { let givenName = fullName.givenName ?? "" let familyName = fullName.familyName ?? "" @@ -292,13 +286,22 @@ extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { let email = appleIDCredential.email + if let authCode = appleIDCredential.authorizationCode, + let codeString = String(data: authCode, encoding: .utf8) { + authorizationCode = codeString + print("Authorization Code: \(codeString)") + } else { + print("Authorization Code is nil or could not be decoded.") + // login 불가능 처리 로직 + } + if let identityToken = appleIDCredential.identityToken, let identityTokenString = String(data: identityToken, encoding: .utf8), let emailString = email{ SelectLoginTypeVC.keychain.set(identityTokenString, forKey: "AppleIDToken") SelectLoginTypeVC.keychain.set(emailString, forKey: "AppleIDEmail") SelectLoginTypeVC.keychain.set(formattedName, forKey: "AppleIDName") - callAppleLoginAPI(param: setupAppleDTO(identityTokenString, formattedName, emailString)!) { isSuccess in + callAppleLoginAPI(param: setupAppleDTO(identityTokenString, formattedName, emailString, authorizationCode)!) { isSuccess in if isSuccess { self.handleKakaoLoginSuccess() } else { @@ -310,7 +313,7 @@ extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { let emailString = SelectLoginTypeVC.keychain.get("AppleIDEmail"), let nameString = SelectLoginTypeVC.keychain.get("AppleIDName") else { return } - callAppleLoginAPI(param: setupAppleDTO(identityTokenString, nameString, emailString)!) { isSuccess in + callAppleLoginAPI(param: setupAppleDTO(identityTokenString, nameString, emailString, authorizationCode)!) { isSuccess in if isSuccess { self.handleKakaoLoginSuccess() } else { @@ -319,25 +322,8 @@ extension SelectLoginTypeVC : ASAuthorizationControllerDelegate { } } - case let passwordCredential as ASPasswordCredential: - let username = passwordCredential.user - let password = passwordCredential.password - guard let identityTokenString = SelectLoginTypeVC.keychain.get("AppleIDToken"), - let emailString = SelectLoginTypeVC.keychain.get("AppleIDEmail"), - let nameString = SelectLoginTypeVC.keychain.get("AppleIDName") else { return } - - callAppleLoginAPI(param: setupAppleDTO(identityTokenString, nameString, emailString)!) { isSuccess in - if isSuccess { - self.handleKakaoLoginSuccess() - } else { - print("애플 로그인 간접 실패") - } - } - default: break - - } } From 57f4e17c5ad56cb016cc18c85fcb4588382ae74c Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Sun, 24 Nov 2024 01:42:43 +0900 Subject: [PATCH 16/19] =?UTF-8?q?#23=20=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EB=B7=B0=20=EC=85=80=20+=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B3=B5=EC=A7=80=EC=82=AC=ED=95=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DropDrug/Sources/Cells/NotificationCell.swift | 63 +++++++++++++++++++ .../Sources/Models/Notice/NoticeData.swift | 9 +++ .../MyPage/Settings/NoticesVC.swift | 47 ++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 DropDrug/Sources/Cells/NotificationCell.swift create mode 100644 DropDrug/Sources/Models/Notice/NoticeData.swift diff --git a/DropDrug/Sources/Cells/NotificationCell.swift b/DropDrug/Sources/Cells/NotificationCell.swift new file mode 100644 index 00000000..3a3354ff --- /dev/null +++ b/DropDrug/Sources/Cells/NotificationCell.swift @@ -0,0 +1,63 @@ +// Copyright © 2024 RT4. All rights reserved + +import UIKit +import SnapKit + +class NotificationCell: UITableViewCell { + // UI Components + private lazy var iconImageView: UIImageView = { + let imageView = UIImageView() + imageView.image = UIImage(systemName: "circle.fill") // SF Symbol 설정 + imageView.contentMode = .scaleAspectFit // 적절한 크기 유지 + imageView.tintColor = Constants.Colors.pink + return imageView + }() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.text = "Title Label" // 기본 텍스트 + label.textColor = Constants.Colors.gray700 + label.font = UIFont.ptdRegularFont(ofSize: 16) + label.numberOfLines = 1 // 한 줄로 제한 + return label + }() + + // MARK: - Initializers + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupViews() + setupConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setup Methods + private func setupViews() { + contentView.addSubview(iconImageView) + contentView.addSubview(titleLabel) + } + + private func setupConstraints() { + iconImageView.snp.makeConstraints { make in + make.top.equalTo(contentView.safeAreaLayoutGuide).offset(16) // safeArea Top + 16 + make.leading.equalTo(contentView.safeAreaLayoutGuide).offset(20) // safeArea Leading + 20 + make.width.height.equalTo(10) // 정사각형 크기 설정 + } + + titleLabel.snp.makeConstraints { make in + make.top.equalTo(iconImageView) // 이미지뷰와 동일한 Top + make.leading.equalTo(iconImageView.snp.trailing).offset(8) // 이미지뷰의 trailing + 8 + make.trailing.equalTo(contentView.safeAreaLayoutGuide).offset(-20) // safeArea Trailing - 20 + make.bottom.equalTo(contentView.safeAreaLayoutGuide).offset(-16) + } + } + + // MARK: - Configure Method + + func configure(with title: String) { + titleLabel.text = title + } + +} diff --git a/DropDrug/Sources/Models/Notice/NoticeData.swift b/DropDrug/Sources/Models/Notice/NoticeData.swift new file mode 100644 index 00000000..8d44a8e7 --- /dev/null +++ b/DropDrug/Sources/Models/Notice/NoticeData.swift @@ -0,0 +1,9 @@ +// Copyright © 2024 RT4. All rights reserved + +import Foundation + +struct NoticeData : Codable { + let title : String + let content : String + let date : String +} diff --git a/DropDrug/Sources/ViewControllers/MyPage/Settings/NoticesVC.swift b/DropDrug/Sources/ViewControllers/MyPage/Settings/NoticesVC.swift index 4926db27..3b0f469d 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/Settings/NoticesVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/Settings/NoticesVC.swift @@ -1,23 +1,70 @@ // Copyright © 2024 RT4. All rights reserved import UIKit +import SnapKit class NoticesVC: UIViewController { + let NoticeList : [NoticeData] = [ + NoticeData(title: "테스트용 공지사항 1", content: "테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.", date: "2024-11-23T16:25:11.183Z"), + NoticeData(title: "테스트용 공지사항 2", content: "테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.", date: "2024-11-23T16:25:11.183Z"), + NoticeData(title: "테스트용 공지사항 3", content: "테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.테스트용 공지사항입니다.", date: "2024-11-23T16:25:11.183Z"), + ] // 날짜 기준으로 소팅 필수 (Get 호출 후) + private lazy var backButton: CustomBackButton = { let button = CustomBackButton(title: " 공지사항") button.addTarget(self, action: #selector(didTapBackButton), for: .touchUpInside) return button }() + private lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.register(NotificationCell.self, forCellReuseIdentifier: "NotificationCell") + tableView.dataSource = self + return tableView + }() + override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton) + + view.addSubview(tableView) + tableView.snp.makeConstraints { make in + make.edges.equalTo(view.safeAreaLayoutGuide) + } + tableView.separatorStyle = .none + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + // 선택된 셀이 있다면 해제 + if let indexPath = tableView.indexPathForSelectedRow { + tableView.deselectRow(at: indexPath, animated: true) + } } // MARK: - Actions @objc private func didTapBackButton() { navigationController?.popViewController(animated: false) } + +} + +extension NoticesVC : UITableViewDataSource, UITableViewDelegate { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return NoticeList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "NotificationCell", for: indexPath) as! NotificationCell + cell.configure(with: NoticeList[indexPath.row].title) + + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + print("Selected row: \(indexPath.row)") + } } From bd22121aeab924857918f845953fcd049aa8fe20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=98=88=EC=84=B1?= Date: Sun, 24 Nov 2024 02:37:51 +0900 Subject: [PATCH 17/19] =?UTF-8?q?[feat]=20LoginVC/=20SignUpVC=20=EB=B0=B1?= =?UTF-8?q?=20=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../APIServices/Register/DrugAPI.swift | 53 +++++++++++++ .../Sources/Models/Auth/RegisterManager.swift | 32 +++++++- .../Sources/Models/Register/DrugManager.swift | 75 +++++++++++++++++++ .../Request/Register/drugDeleteRequest.swift | 7 ++ .../Request/Register/drugSaveRequest.swift | 8 ++ .../ViewControllers/Auth/LoginVC.swift | 42 +++++++---- .../ViewControllers/Auth/SignUpVC.swift | 44 ++++++----- .../ViewControllers/Auth/SplashVC.swift | 59 ++++++++++++--- .../Main/HomeViewController.swift | 2 +- .../ViewControllers/MyPage/MyPageVC.swift | 18 +++-- .../Register/DiscardPrescriptionDrugVC.swift | 2 + .../Register/EnrollDetailViewController.swift | 4 + .../Register/PrescriptionDrugVC.swift | 5 +- DropDrug/Sources/Views/customBackButton.swift | 2 +- 14 files changed, 299 insertions(+), 54 deletions(-) create mode 100644 DropDrug/Sources/APIServices/Register/DrugAPI.swift create mode 100644 DropDrug/Sources/Models/Register/DrugManager.swift create mode 100644 DropDrug/Sources/Models/Request/Register/drugDeleteRequest.swift create mode 100644 DropDrug/Sources/Models/Request/Register/drugSaveRequest.swift diff --git a/DropDrug/Sources/APIServices/Register/DrugAPI.swift b/DropDrug/Sources/APIServices/Register/DrugAPI.swift new file mode 100644 index 00000000..397cbf2b --- /dev/null +++ b/DropDrug/Sources/APIServices/Register/DrugAPI.swift @@ -0,0 +1,53 @@ +// Copyright © 2024 RT4. All rights reserved + +import Foundation +import Moya +import KeychainSwift + +enum DrugAPI { + case getDrug + case postDrug(param: drugSaveRequest) + case deleteDrug(param: drugDeleteRequest) +} + +extension DrugAPI: TargetType { + var baseURL: URL { + guard let url = URL(string: Constants.NetworkManager.baseURL) else { + fatalError("fatal error - invalid url") + } + return url + } + + var path: String { + switch self { + case .getDrug, .postDrug, .deleteDrug: + return "drugs" + } + } + + var method: Moya.Method { + switch self { + case .getDrug: + return .get + case .postDrug: + return .post + case .deleteDrug: + return .delete + } + } + + var task: Task { + switch self { + case .getDrug: + return .requestPlain + case .postDrug(let param): + return .requestJSONEncodable(param) + case .deleteDrug(let param): + return .requestJSONEncodable(param) + } + } + + var headers: [String: String]? { + return ["Content-Type": "application/json"] + } +} diff --git a/DropDrug/Sources/Models/Auth/RegisterManager.swift b/DropDrug/Sources/Models/Auth/RegisterManager.swift index b09c938a..26dab0d7 100644 --- a/DropDrug/Sources/Models/Auth/RegisterManager.swift +++ b/DropDrug/Sources/Models/Auth/RegisterManager.swift @@ -4,8 +4,38 @@ import UIKit import Moya import KeychainSwift -extension SignUpVC { +extension SplashVC { + func refreshAccessToken(completion: @escaping (Bool) -> Void) { + guard let refreshToken = SelectLoginTypeVC.keychain.get("refreshToken") else { + print("refreshToken not found") + completion(false) + return + } + provider.request(.refreshAccessToken(token: refreshToken)) { result in + switch result { + case .success(let response): + print(response) + do { + let data = try response.map(TokenDto.self) + SelectLoginTypeVC.keychain.set(data.refreshToken, forKey: "serverRefreshToken") + SelectLoginTypeVC.keychain.set(data.accessToken, forKey: "serverAccessToken") + completion(true) + } catch { + print("Failed to map data : \(error)") + completion(false) + } + case .failure(let error): + print("Error: \(error.localizedDescription)") + if let response = error.response { + print("Response Body: \(String(data: response.data, encoding: .utf8) ?? "")") + } + completion(false) + } + } + } +} +extension SignUpVC { func setupSignUpDTO(_ emailString: String, _ pwString: String, name : String) -> MemberSignupRequest { return MemberSignupRequest(email: emailString, name: name, password: pwString) } diff --git a/DropDrug/Sources/Models/Register/DrugManager.swift b/DropDrug/Sources/Models/Register/DrugManager.swift new file mode 100644 index 00000000..e11f4da2 --- /dev/null +++ b/DropDrug/Sources/Models/Register/DrugManager.swift @@ -0,0 +1,75 @@ +// Copyright © 2024 RT4. All rights reserved + +import Foundation +import Moya + +extension PrescriptionDrugVC { //get + func fetchMemberInfo(completion: @escaping (Bool) -> Void) { + DrugProvider.request(.getDrug) { result in + switch result { + case .success(let response): + do { + let data = try response.map(MemberInfo.self) + completion(true) + } catch { + completion(false) + } + case .failure(let error): + print("Error: \(error.localizedDescription)") + if let response = error.response { + print("Response Body: \(String(data: response.data, encoding: .utf8) ?? "")") + } + completion(false) + } + } + } +} + +extension EnrollDetailViewController { //post + func setupPostDrugDTO() -> drugSaveRequest? { + return drugSaveRequest(count: 0, date: "dd") + } + + func fetchMemberInfo(_ userParameter: drugSaveRequest, completion: @escaping (Bool) -> Void) { + DrugProvider.request(.postDrug(param: userParameter)) { result in + switch result { + case .success(let response): + do { + let data = try response.map(MemberInfo.self) + DispatchQueue.main.async { + //데이터 받아오기 + } + completion(true) + } catch { + completion(false) + } + case .failure(let error): + completion(false) + } + } + } +} + +extension DiscardPrescriptionDrugVC { //delete + func setupDeleteDrugDTO() -> drugDeleteRequest? { + return drugDeleteRequest(id: [1,2,3]) + } + func fetchMemberInfo(_ userParameter: drugDeleteRequest, completion: @escaping (Bool) -> Void) { + DrugProvider.request(.deleteDrug(param: userParameter)) { result in + switch result { + case .success(let response): + do { + let data = try response.map(MemberInfo.self) + DispatchQueue.main.async { + //데이터 받아오기 + } + completion(true) + } catch { + completion(false) + } + case .failure(let error): + completion(false) + } + } + } +} diff --git a/DropDrug/Sources/Models/Request/Register/drugDeleteRequest.swift b/DropDrug/Sources/Models/Request/Register/drugDeleteRequest.swift new file mode 100644 index 00000000..a07ea809 --- /dev/null +++ b/DropDrug/Sources/Models/Request/Register/drugDeleteRequest.swift @@ -0,0 +1,7 @@ +// Copyright © 2024 RT4. All rights reserved + +import Foundation + +struct drugDeleteRequest : Codable{ + let id : [Int] +} diff --git a/DropDrug/Sources/Models/Request/Register/drugSaveRequest.swift b/DropDrug/Sources/Models/Request/Register/drugSaveRequest.swift new file mode 100644 index 00000000..96ddca4b --- /dev/null +++ b/DropDrug/Sources/Models/Request/Register/drugSaveRequest.swift @@ -0,0 +1,8 @@ +// Copyright © 2024 RT4. All rights reserved + +import Foundation + +struct drugSaveRequest : Codable{ + let count : Int + let date : String +} diff --git a/DropDrug/Sources/ViewControllers/Auth/LoginVC.swift b/DropDrug/Sources/ViewControllers/Auth/LoginVC.swift index b25fe23b..b35d5fec 100644 --- a/DropDrug/Sources/ViewControllers/Auth/LoginVC.swift +++ b/DropDrug/Sources/ViewControllers/Auth/LoginVC.swift @@ -4,8 +4,6 @@ import UIKit import SnapKit import Moya -// TODO: 토큰 저장 매니저 따로 만들기 - class LoginVC : UIViewController { let provider = MoyaProvider(plugins: [ NetworkLoggerPlugin() ]) @@ -19,6 +17,12 @@ class LoginVC : UIViewController { }() // MARK: - UI Properties + private lazy var backButton: CustomBackButton = { + let button = CustomBackButton(title: "") + button.addTarget(self, action: #selector(didTapBackButton), for: .touchUpInside) + return button + }() + private lazy var titleLabel: UILabel = { let label = UILabel() label.text = "로그인" @@ -52,21 +56,19 @@ class LoginVC : UIViewController { // MARK: - Setup Methods private func setupView() { - view.addSubview(titleLabel) - - view.addSubview(emailField) - view.addSubview(passwordField) - - view.addSubview(emailSaveCheckBox) - - view.addSubview(loginButton) - + [backButton, titleLabel, emailField, passwordField, emailSaveCheckBox, loginButton].forEach { + view.addSubview($0) + } view.backgroundColor = .white } private func setupConstraints() { + backButton.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide) + make.leading.equalToSuperview().inset(20) + } titleLabel.snp.makeConstraints { make in - make.top.equalToSuperview().offset(superViewHeight * 0.1) + make.top.equalTo(backButton.snp.bottom).offset(20) make.centerX.equalToSuperview() } emailField.snp.makeConstraints { make in @@ -103,10 +105,22 @@ class LoginVC : UIViewController { } // MARK: - Actions + + @objc func didTapBackButton() { + var currentVC: UIViewController? = self + while let presentingVC = currentVC?.presentingViewController { + if presentingVC is SelectLoginTypeVC { + presentingVC.dismiss(animated: true, completion: nil) + return + } + currentVC = presentingVC + } + print("SelectLoginTypeVC를 찾을 수 없습니다.") + } + @objc func loginButtonTapped() { if isValid { if let loginRequest = setupLoginDTO(emailField.textField.text!, passwordField.textField.text!) { -// print(SelectLoginTypeVC.keychain.get("FCMToken")) callLoginAPI(loginRequest) { isSuccess in if isSuccess { self.proceedLoginSuccessful() @@ -125,7 +139,7 @@ class LoginVC : UIViewController { } @objc func termsTapped() { - // TODO : 아이디 저장 api? + // TODO: 아이디 저장 api? emailSaveCheckBox.isSelected.toggle() if emailSaveCheckBox.isSelected { isTermsAgreeValid = true diff --git a/DropDrug/Sources/ViewControllers/Auth/SignUpVC.swift b/DropDrug/Sources/ViewControllers/Auth/SignUpVC.swift index f6274d81..4bda8d09 100644 --- a/DropDrug/Sources/ViewControllers/Auth/SignUpVC.swift +++ b/DropDrug/Sources/ViewControllers/Auth/SignUpVC.swift @@ -5,6 +5,7 @@ import SnapKit import Moya class SignUpVC : UIViewController { + let provider = MoyaProvider(plugins: [ NetworkLoggerPlugin() ]) private lazy var usernameField = CustomLabelTextFieldView(textFieldPlaceholder: "이름을 입력해 주세요", validationText: "이름을 입력해 주세요") private lazy var emailField = CustomLabelTextFieldView(textFieldPlaceholder: "이메일을 입력해 주세요", validationText: "사용할 수 없는 이메일입니다") @@ -22,6 +23,12 @@ class SignUpVC : UIViewController { }() // MARK: - UI Properties + private lazy var backButton: CustomBackButton = { + let button = CustomBackButton(title: "") + button.addTarget(self, action: #selector(didTapBackButton), for: .touchUpInside) + return button + }() + private lazy var loginButton: UIButton = { let button = UIButton(type: .system) button.setTitle("로그인", for: .normal) @@ -73,25 +80,18 @@ class SignUpVC : UIViewController { // MARK: - Setup Methods private func setupView() { - view.addSubview(loginButton) - view.addSubview(titleLabel) - - view.addSubview(usernameField) - view.addSubview(emailField) - view.addSubview(passwordField) - view.addSubview(confirmPasswordField) - - view.addSubview(termsCheckBox) - - view.addSubview(signUpButton) - view.addSubview(termsValidationLabel) - + [backButton, loginButton, titleLabel, usernameField, emailField, passwordField, confirmPasswordField, termsCheckBox, signUpButton, termsValidationLabel].forEach { + view.addSubview($0) + } view.backgroundColor = .white } - private func setupConstraints() { + backButton.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide) + make.leading.equalToSuperview().inset(20) + } loginButton.snp.makeConstraints { make in - make.top.equalToSuperview().offset(superViewHeight * 0.07) + make.centerY.equalTo(backButton) make.trailing.equalToSuperview().offset(-superViewWidth * 0.07) } titleLabel.snp.makeConstraints { make in @@ -147,12 +147,20 @@ class SignUpVC : UIViewController { } // MARK: - Actions + @objc func didTapBackButton() { + var currentVC: UIViewController? = self + while let presentingVC = currentVC?.presentingViewController { + if presentingVC is SelectLoginTypeVC { + presentingVC.dismiss(animated: true, completion: nil) + return + } + currentVC = presentingVC + } + print("SelectLoginTypeVC를 찾을 수 없습니다.") + } - let provider = MoyaProvider(plugins: [ NetworkLoggerPlugin() ]) - @objc func signUpButtonTapped() { if isValid { - let signUpRequest = setupSignUpDTO(emailField.textField.text!, passwordField.textField.text!, name: usernameField.textField.text!) callSignUpAPI(signUpRequest) { isSuccess in if isSuccess { diff --git a/DropDrug/Sources/ViewControllers/Auth/SplashVC.swift b/DropDrug/Sources/ViewControllers/Auth/SplashVC.swift index a777b550..f221cbb5 100644 --- a/DropDrug/Sources/ViewControllers/Auth/SplashVC.swift +++ b/DropDrug/Sources/ViewControllers/Auth/SplashVC.swift @@ -8,6 +8,8 @@ import KeychainSwift class SplashVC : UIViewController { + let provider = MoyaProvider(plugins: [ NetworkLoggerPlugin() ]) + private lazy var titleLabel: UILabel = { let label = UILabel() label.text = "Drop Drug" @@ -26,17 +28,6 @@ class SplashVC : UIViewController { } } - private func checkAuthenticationStatus() { - if let accessToken = SelectLoginTypeVC.keychain.get("serverAccessToken") - { - print("Access Token 존재: \(accessToken)") - navigateToMainScreen() - } else { - print("토큰 없음. 로그인 화면으로 이동.") - navigateToOnBoaringScreen() - } - } - func setupViews() { view.backgroundColor = Constants.Colors.skyblue view.addSubview(titleLabel) @@ -60,4 +51,50 @@ class SplashVC : UIViewController { } } + private func checkAuthenticationStatus() { + if let accessToken = SelectLoginTypeVC.keychain.get("serverAccessToken") { + if isTokenExpired(token: accessToken) { + refreshAccessToken { success in + if success { + print("Profile updated successfully") + self.navigateToMainScreen() + } else { + self.navigateToOnBoaringScreen() + } + } + } else { + print("Access Token 유효: \(accessToken)") + navigateToMainScreen() + } + } else { + print("토큰 없음. 로그인 화면으로 이동.") + navigateToOnBoaringScreen() + } + } + + func isTokenExpired(token: String) -> Bool { + let segments = token.split(separator: ".") + guard segments.count == 3 else { + print("Invalid JWT token format") + return true // 만료된 것으로 간주 + } + + let payloadSegment = segments[1] + guard let payloadData = Data(base64Encoded: String(payloadSegment)) else { + print("Failed to decode payload") + return true + } + + do { + if let payload = try JSONSerialization.jsonObject(with: payloadData, options: []) as? [String: Any], + let exp = payload["exp"] as? TimeInterval { + let expirationDate = Date(timeIntervalSince1970: exp) + return expirationDate < Date() + } + } catch { + print("Failed to parse payload: \(error)") + } + return true // 만료된 것으로 간주 + } + } diff --git a/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift b/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift index 334b7ae1..0cf80a8d 100644 --- a/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift +++ b/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift @@ -35,8 +35,8 @@ class HomeViewController: UIViewController, CLLocationManagerDelegate, MKMapView print("GET 호출 실패") } } - } + private let homeView: HomeView = { let hv = HomeView() hv.resetBtn.addTarget(self, action: #selector(resetBtnTapped), for: .touchUpInside) diff --git a/DropDrug/Sources/ViewControllers/MyPage/MyPageVC.swift b/DropDrug/Sources/ViewControllers/MyPage/MyPageVC.swift index 3f2a3420..f6aa7303 100644 --- a/DropDrug/Sources/ViewControllers/MyPage/MyPageVC.swift +++ b/DropDrug/Sources/ViewControllers/MyPage/MyPageVC.swift @@ -47,7 +47,18 @@ class MyPageVC : UIViewController { setConstraints() setComponents() setupGestures() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + fetchMemberInfo { success in + if success { + print("Profile updated successfully") + } else { + print("Failed to update profile") + } } + } func setComponents() { dropCardLabel.text = "나의 드롭카드" @@ -59,13 +70,6 @@ class MyPageVC : UIViewController { [titleLabel, settingButton, myPageProfileView, rewardView, dropCardLabel, disposalStateLabel].forEach { view.addSubview($0) } - fetchMemberInfo { success in - if success { - print("Profile updated successfully") - } else { - print("Failed to update profile") - } - } } func setConstraints() { diff --git a/DropDrug/Sources/ViewControllers/Register/DiscardPrescriptionDrugVC.swift b/DropDrug/Sources/ViewControllers/Register/DiscardPrescriptionDrugVC.swift index c50c7d25..a32e9236 100644 --- a/DropDrug/Sources/ViewControllers/Register/DiscardPrescriptionDrugVC.swift +++ b/DropDrug/Sources/ViewControllers/Register/DiscardPrescriptionDrugVC.swift @@ -2,8 +2,10 @@ import UIKit import SnapKit +import Moya class DiscardPrescriptionDrugVC: UIViewController { + let DrugProvider = MoyaProvider(plugins: [BearerTokenPlugin(), NetworkLoggerPlugin()]) // MARK: - UI Elements private lazy var backButton: CustomBackButton = { let button = CustomBackButton(title: " 의약품 삭제하기") diff --git a/DropDrug/Sources/ViewControllers/Register/EnrollDetailViewController.swift b/DropDrug/Sources/ViewControllers/Register/EnrollDetailViewController.swift index 71470325..b99cbf4c 100644 --- a/DropDrug/Sources/ViewControllers/Register/EnrollDetailViewController.swift +++ b/DropDrug/Sources/ViewControllers/Register/EnrollDetailViewController.swift @@ -1,9 +1,13 @@ // Copyright © 2024 RT4. All rights reserved import UIKit +import SnapKit +import Moya class EnrollDetailViewController: UIViewController { + let DrugProvider = MoyaProvider(plugins: [BearerTokenPlugin(), NetworkLoggerPlugin()]) + private lazy var backButton: CustomBackButton = { let button = CustomBackButton(title: " 의약품 등록하기") button.addTarget(self, action: #selector(didTapBackButton), for: .touchUpInside) diff --git a/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift b/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift index 0ecd2b6d..06d373f5 100644 --- a/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift +++ b/DropDrug/Sources/ViewControllers/Register/PrescriptionDrugVC.swift @@ -2,9 +2,12 @@ import UIKit import SnapKit +import Moya class PrescriptionDrugVC: UIViewController { - //TODO: api 연결 시 따로 빼기 + + let DrugProvider = MoyaProvider(plugins: [BearerTokenPlugin(), NetworkLoggerPlugin()]) + struct PrescriptionDrug { let date: String let duration: String diff --git a/DropDrug/Sources/Views/customBackButton.swift b/DropDrug/Sources/Views/customBackButton.swift index 72e0ef7e..2c1a5a4b 100644 --- a/DropDrug/Sources/Views/customBackButton.swift +++ b/DropDrug/Sources/Views/customBackButton.swift @@ -23,6 +23,6 @@ class CustomBackButton: UIButton { for: .normal) self.setTitle("\(title)", for: .normal) self.setTitleColor(.black, for: .normal) - self.titleLabel?.font = UIFont.ptdBoldFont(ofSize: 24) + self.titleLabel?.font = UIFont.ptdBoldFont(ofSize: 22) } } From 52673bfac51d439ac1fd51438d57b6a9dc35d2c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=98=88=EC=84=B1?= Date: Sun, 24 Nov 2024 02:48:31 +0900 Subject: [PATCH 18/19] =?UTF-8?q?[chore]=20refreshToken=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EB=A1=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DropDrug/Sources/ViewControllers/Auth/LoginVC.swift | 2 +- DropDrug/Sources/ViewControllers/Auth/SignUpVC.swift | 2 +- DropDrug/Sources/ViewControllers/Auth/SplashVC.swift | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/DropDrug/Sources/ViewControllers/Auth/LoginVC.swift b/DropDrug/Sources/ViewControllers/Auth/LoginVC.swift index b35d5fec..35ba6d44 100644 --- a/DropDrug/Sources/ViewControllers/Auth/LoginVC.swift +++ b/DropDrug/Sources/ViewControllers/Auth/LoginVC.swift @@ -115,7 +115,7 @@ class LoginVC : UIViewController { } currentVC = presentingVC } - print("SelectLoginTypeVC를 찾을 수 없습니다.") + print("SelectLoginTypeVC를 찾을 수 없습니다.") } @objc func loginButtonTapped() { diff --git a/DropDrug/Sources/ViewControllers/Auth/SignUpVC.swift b/DropDrug/Sources/ViewControllers/Auth/SignUpVC.swift index 4bda8d09..731302b6 100644 --- a/DropDrug/Sources/ViewControllers/Auth/SignUpVC.swift +++ b/DropDrug/Sources/ViewControllers/Auth/SignUpVC.swift @@ -156,7 +156,7 @@ class SignUpVC : UIViewController { } currentVC = presentingVC } - print("SelectLoginTypeVC를 찾을 수 없습니다.") + print("SelectLoginTypeVC를 찾을 수 없습니다.") } @objc func signUpButtonTapped() { diff --git a/DropDrug/Sources/ViewControllers/Auth/SplashVC.swift b/DropDrug/Sources/ViewControllers/Auth/SplashVC.swift index f221cbb5..a27070e2 100644 --- a/DropDrug/Sources/ViewControllers/Auth/SplashVC.swift +++ b/DropDrug/Sources/ViewControllers/Auth/SplashVC.swift @@ -56,9 +56,10 @@ class SplashVC : UIViewController { if isTokenExpired(token: accessToken) { refreshAccessToken { success in if success { - print("Profile updated successfully") + print("accessToken 재발급 성공") self.navigateToMainScreen() } else { + print("accessToken 재발급 실패") self.navigateToOnBoaringScreen() } } From 44d5525842cf47a566faaa530d3ddea9fc764f4d Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Sun, 24 Nov 2024 03:59:54 +0900 Subject: [PATCH 19/19] =?UTF-8?q?#26=20OCR=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + 홈화면 현재 위치 예외 처리 --- .../Sources/APIServices/OCR/OCRService.swift | 41 ++++++++++++ DropDrug/Sources/Models/Constants.swift | 2 + DropDrug/Sources/Models/OCR/OCR_API.swift | 67 +++++++++++++++++++ .../Main/HomeViewController.swift | 14 +++- DropDrug/Sources/Views/Main/HomeView.swift | 2 +- 5 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 DropDrug/Sources/APIServices/OCR/OCRService.swift create mode 100644 DropDrug/Sources/Models/OCR/OCR_API.swift diff --git a/DropDrug/Sources/APIServices/OCR/OCRService.swift b/DropDrug/Sources/APIServices/OCR/OCRService.swift new file mode 100644 index 00000000..a6c2e1ca --- /dev/null +++ b/DropDrug/Sources/APIServices/OCR/OCRService.swift @@ -0,0 +1,41 @@ +// Copyright © 2024 RT4. All rights reserved + +import Foundation +import Moya + +enum OCRService { + case postImage(data : OCRRequest) +} + +extension OCRService : TargetType { + var baseURL: URL { + guard let url = URL(string: Constants.NetworkManager.OCRAPIURL) else { + fatalError("fatal error - none url") + } + return url + } + + var path: String { + return "/general" + } + + var method: Moya.Method { + return .post + } + + var task: Moya.Task { + switch self { + case .postImage(let data) : + return .requestJSONEncodable(data) + } + } + + var headers: [String : String]? { + return [ + "Content-Type": "application/json", + "X-OCR-SECRET": "\(Constants.NetworkManager.OCRSecretKey)" + ] + } + + +} diff --git a/DropDrug/Sources/Models/Constants.swift b/DropDrug/Sources/Models/Constants.swift index e018bf5b..ae1b9656 100644 --- a/DropDrug/Sources/Models/Constants.swift +++ b/DropDrug/Sources/Models/Constants.swift @@ -5,6 +5,8 @@ import UIKit struct Constants { struct NetworkManager { static let baseURL = "http://54.180.45.153:8080/" + static let OCRSecretKey = "Uk1PYVR2RFVaYWVHRlpxcWpxRVZkQUl0bHRvTGpCU3Q=" + static let OCRAPIURL = "https://slpfshoip0.apigw.ntruss.com/custom/v1/36244/694e6b5877aed073a41eb1c45d2be8b6126ed5808e1311a2e48e5800ccdd233f" } static let seoulDistrictsList: [DistrictsDataModel] = [ diff --git a/DropDrug/Sources/Models/OCR/OCR_API.swift b/DropDrug/Sources/Models/OCR/OCR_API.swift new file mode 100644 index 00000000..0176141f --- /dev/null +++ b/DropDrug/Sources/Models/OCR/OCR_API.swift @@ -0,0 +1,67 @@ +// Copyright © 2024 RT4. All rights reserved + +import Foundation + +//MARK: - Request + +struct OCRRequest : Codable { + let images: [images] + + let version : String + let requestId : String + let timestamp : Int + var lang : String = "ko" +} + +struct images: Codable { + let format: String + let name: String + let data: String // base64 +} + +//MARK: - Response +struct Response: Codable { + let version : String + let requestID: String + let timestamp: Int + let images: [Image] + + enum CodingKeys: String, CodingKey { + case version + case requestID = "requestId" + case timestamp, images + } +} + +struct Image: Codable { + let uid, name, inferResult, message: String + let validationResult: ValidationResult + let convertedImageInfo: ConvertedImageInfo + let fields: [Field] +} + +struct ConvertedImageInfo: Codable { + let width, height, pageIndex: Int + let longImage: Bool +} + +struct Field: Codable { + let valueType: String + let boundingPoly: BoundingPoly + let inferText: String + let inferConfidence: Double + let type: String + let lineBreak: Bool +} + +struct BoundingPoly: Codable { + let vertices: [Vertex] +} + +struct Vertex: Codable { + let x, y: Int +} + +struct ValidationResult: Codable { + let result: String +} diff --git a/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift b/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift index 7a2121b2..e8946db0 100644 --- a/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift +++ b/DropDrug/Sources/ViewControllers/Main/HomeViewController.swift @@ -117,17 +117,25 @@ class HomeViewController: UIViewController, CLLocationManagerDelegate, MKMapView // print(self.processString(placemark.description)) var strAddr = "" var address = "" - for i in self.processString(placemark.description) { - if i.contains("대한민국") { - address = i + let addressArray = self.processString(placemark.description) + + addressArray.forEach { addr in + if addr.contains("대한민국") { + address = addr } } + if address == "" { + address = "서비스 이용 가능 지역이 아닙니다" + } + + print(address) for i in address.components(separatedBy: " ") { if !i.contains("대한민국") { strAddr += " \(i)" } } + completion(strAddr.trimmingCharacters(in: .whitespaces)) } } diff --git a/DropDrug/Sources/Views/Main/HomeView.swift b/DropDrug/Sources/Views/Main/HomeView.swift index b4ce61b5..62328d88 100644 --- a/DropDrug/Sources/Views/Main/HomeView.swift +++ b/DropDrug/Sources/Views/Main/HomeView.swift @@ -19,7 +19,7 @@ class HomeView: UIView { public var points = 100 public var name = "김드롭" - public var presentLocation = "기본 주소" + public var presentLocation = "서비스 이용 가능 지역이 아닙니다" public lazy var floatingBtn: UIButton = { let fb = UIButton()