diff --git a/DDANZI_iOS/DDANZI_iOS.xcodeproj/project.pbxproj b/DDANZI_iOS/DDANZI_iOS.xcodeproj/project.pbxproj index 4a48cfe..8bb5297 100644 --- a/DDANZI_iOS/DDANZI_iOS.xcodeproj/project.pbxproj +++ b/DDANZI_iOS/DDANZI_iOS.xcodeproj/project.pbxproj @@ -79,6 +79,7 @@ 363F1A252C9F1BE2007527E2 /* BankList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 363F1A242C9F1BE2007527E2 /* BankList.swift */; }; 363F1A272C9F57D4007527E2 /* CustomAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 363F1A262C9F57D4007527E2 /* CustomAlertView.swift */; }; 363F1A292CA067C7007527E2 /* RefreshTokenRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 363F1A282CA067C7007527E2 /* RefreshTokenRequestDTO.swift */; }; + 363F1A2B2CA09C5E007527E2 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 363F1A2A2CA09C59007527E2 /* PrivacyInfo.xcprivacy */; }; 3648954E2C6281BB00AAA8E2 /* HomeItemsResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3648954D2C6281BB00AAA8E2 /* HomeItemsResponseDTO.swift */; }; 364895502C62822200AAA8E2 /* ProductDetailResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3648954F2C62822200AAA8E2 /* ProductDetailResponseDTO.swift */; }; 364895522C62826200AAA8E2 /* SearchItemsResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 364895512C62826200AAA8E2 /* SearchItemsResponseDTO.swift */; }; @@ -319,6 +320,7 @@ 363F1A242C9F1BE2007527E2 /* BankList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankList.swift; sourceTree = ""; }; 363F1A262C9F57D4007527E2 /* CustomAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAlertView.swift; sourceTree = ""; }; 363F1A282CA067C7007527E2 /* RefreshTokenRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshTokenRequestDTO.swift; sourceTree = ""; }; + 363F1A2A2CA09C59007527E2 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 3648954D2C6281BB00AAA8E2 /* HomeItemsResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeItemsResponseDTO.swift; sourceTree = ""; }; 3648954F2C62822200AAA8E2 /* ProductDetailResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductDetailResponseDTO.swift; sourceTree = ""; }; 364895512C62826200AAA8E2 /* SearchItemsResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchItemsResponseDTO.swift; sourceTree = ""; }; @@ -1017,6 +1019,7 @@ 369C63982C1A7A270021E2E0 /* Info.plist */, 369C638A2C1A7A240021E2E0 /* AppDelegate.swift */, 369C638C2C1A7A240021E2E0 /* SceneDelegate.swift */, + 363F1A2A2CA09C59007527E2 /* PrivacyInfo.xcprivacy */, ); path = Application; sourceTree = ""; @@ -1497,6 +1500,7 @@ 369C63D92C1A7E320021E2E0 /* Pretendard-ExtraLight.otf in Resources */, 369C63942C1A7A270021E2E0 /* Assets.xcassets in Resources */, 369C63DB2C1A7E320021E2E0 /* Pretendard-ExtraBold.otf in Resources */, + 363F1A2B2CA09C5E007527E2 /* PrivacyInfo.xcprivacy in Resources */, 369C63D72C1A7E320021E2E0 /* Pretendard-Bold.otf in Resources */, 3664CFD72C6E1BC6007FB5DF /* splash.json in Resources */, 369C63DA2C1A7E320021E2E0 /* Pretendard-Medium.otf in Resources */, diff --git a/DDANZI_iOS/DDANZI_iOS/Application/PrivacyInfo.xcprivacy b/DDANZI_iOS/DDANZI_iOS/Application/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..67bb7fd --- /dev/null +++ b/DDANZI_iOS/DDANZI_iOS/Application/PrivacyInfo.xcprivacy @@ -0,0 +1,86 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePhotosorVideos + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeEmailAddress + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePhoneNumber + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeCreditInfo + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeName + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPITypeReasons + + C56D.1 + 1C8F.1 + CA92.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + + + diff --git a/DDANZI_iOS/DDANZI_iOS/Common/LocalStorage/Keychainwrapper.swift b/DDANZI_iOS/DDANZI_iOS/Common/LocalStorage/Keychainwrapper.swift index 401094e..4c22205 100644 --- a/DDANZI_iOS/DDANZI_iOS/Common/LocalStorage/Keychainwrapper.swift +++ b/DDANZI_iOS/DDANZI_iOS/Common/LocalStorage/Keychainwrapper.swift @@ -15,6 +15,8 @@ public final class KeychainWrapper { public let userKey = "com.orangeCo.DDANZI-iOS.user" public let UUIDKey = "com.orangeCo.DDANZI-iOS.uuid" + public var isAccessTokenEx = true + // MARK: - deviceUUID public var deviceUUID: String { @@ -138,7 +140,13 @@ public final class KeychainWrapper { ] let status = SecItemDelete(query as CFDictionary) - assert(status == errSecSuccess, "AccessToken 키체인 삭제 실패") - return status == errSecSuccess + + if status == errSecItemNotFound { + print("AccessToken이 존재하지 않아 삭제할 수 없습니다.") + return true // 이미 없으므로 성공으로 처리 + } else { + assert(status == errSecSuccess, "AccessToken 키체인 삭제 실패") + return status == errSecSuccess + } } } diff --git a/DDANZI_iOS/DDANZI_iOS/Data/Endpoint/MypageEndpoint.swift b/DDANZI_iOS/DDANZI_iOS/Data/Endpoint/MypageEndpoint.swift index 818c1b5..e770d52 100644 --- a/DDANZI_iOS/DDANZI_iOS/Data/Endpoint/MypageEndpoint.swift +++ b/DDANZI_iOS/DDANZI_iOS/Data/Endpoint/MypageEndpoint.swift @@ -120,7 +120,7 @@ extension MypageEndpoint: BaseTargetType { case let .addUserAddress(body): return .requestJSONEncodable(body) case let .editUserAddress(_, body): - return .requestPlain + return .requestJSONEncodable(body) case .deleteUserAddress: return .requestPlain case .settingUserNoti: diff --git a/DDANZI_iOS/DDANZI_iOS/Data/Network/Foundation/Interceptor.swift b/DDANZI_iOS/DDANZI_iOS/Data/Network/Foundation/Interceptor.swift index f7a4de7..1290f01 100644 --- a/DDANZI_iOS/DDANZI_iOS/Data/Network/Foundation/Interceptor.swift +++ b/DDANZI_iOS/DDANZI_iOS/Data/Network/Foundation/Interceptor.swift @@ -30,16 +30,14 @@ final class AuthInterceptor: RequestInterceptor { if request.retryCount < retryLimit { if statusCode == 401 { refreshToken { success in - if success { - // Retry the request if token refresh was successful + switch success { + case true: completion(.retry) - } else { - // Token refresh failed; do not retry + case false: completion(.doNotRetry) + self.handleTokenRefreshFailure() } } - } else { - completion(.doNotRetryWithError(error)) } } else { completion(.doNotRetry) diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Common/NavigationBar/CustomNavigationBarView.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Common/NavigationBar/CustomNavigationBarView.swift index 898ac1f..96bdd29 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Common/NavigationBar/CustomNavigationBarView.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Common/NavigationBar/CustomNavigationBarView.swift @@ -15,239 +15,242 @@ import Then ///네비게이션 버튼 유형 : @frozen enum NavigationBarType { - case normal - case home - case cancel - case search - case setting - case searching - case logo - case none + case normal + case home + case cancel + case search + case setting + case searching + case logo + case none } final class CustomNavigationBarView: UIView { - // MARK: - properties - private var navigationType: NavigationBarType = .normal - - private let disposeBag = DisposeBag() - - private let backButtonSubject = PublishSubject() - private let cancelButtonSubject = PublishSubject() - private let homeButtonSubject = PublishSubject() - private let searchButtonSubject = PublishSubject() - private let settingButtonSubject = PublishSubject() - private let alarmButtonSubject = PublishSubject() - private let searchBarSubject = PublishSubject() - - var backButtonTap: Observable { backButtonSubject.asObservable() } - var cancelButtonTap: Observable { cancelButtonSubject.asObservable() } - var homeButtonTap: Observable { homeButtonSubject.asObservable() } - var searchButtonTap: Observable { searchButtonSubject.asObservable() } - var settingButtonTap: Observable { settingButtonSubject.asObservable() } - var searchBarTextEdit: Observable { searchBarSubject.asObservable() } - var alarmButtonTap: Observable { alarmButtonSubject.asObservable() } - - // MARK: - componenets - private var leftView = UIView(frame: .init(x: 0, y: 0, width: 25, height: 25)) - private var subView = UIView(frame: .init(x: 0, y: 0, width: 25, height: 25)) - private var rightView = UIView(frame: .init(x: 0, y: 0, width: 25, height: 25)) - - private let titleLabel = UILabel().then { - $0.text = "" - $0.font = .body2Sb18 - $0.textColor = .black - } - - private let backButton = UIButton().then { - $0.setImage(.leftBtn, for: .normal) - $0.imageView?.contentMode = .scaleAspectFit - } - - private let cancelButton = UIButton().then { - $0.setImage(.icCancel, for: .normal) - $0.imageView?.contentMode = .scaleAspectFit - } - - private let homeButton = UIButton().then { - $0.setImage(.home, for: .normal) - $0.imageView?.contentMode = .scaleAspectFit - } + // MARK: - properties + private var navigationType: NavigationBarType = .normal + + private let disposeBag = DisposeBag() + + private let backButtonSubject = PublishSubject() + private let cancelButtonSubject = PublishSubject() + private let homeButtonSubject = PublishSubject() + private let searchButtonSubject = PublishSubject() + private let settingButtonSubject = PublishSubject() + private let alarmButtonSubject = PublishSubject() + private let searchBarSubject = PublishSubject() + + var backButtonTap: Observable { backButtonSubject.asObservable() } + var cancelButtonTap: Observable { cancelButtonSubject.asObservable() } + var homeButtonTap: Observable { homeButtonSubject.asObservable() } + var searchButtonTap: Observable { searchButtonSubject.asObservable() } + var settingButtonTap: Observable { settingButtonSubject.asObservable() } + var searchBarTextEdit: Observable { searchBarSubject.asObservable() } + var alarmButtonTap: Observable { alarmButtonSubject.asObservable() } + + // MARK: - componenets + private var leftView = UIView(frame: .init(x: 0, y: 0, width: 25, height: 25)) + private var subView = UIView(frame: .init(x: 0, y: 0, width: 25, height: 25)) + private var rightView = UIView(frame: .init(x: 0, y: 0, width: 25, height: 25)) + + private let titleLabel = UILabel().then { + $0.text = "" + $0.font = .body2Sb18 + $0.textColor = .black + } + + private let backButton = UIButton().then { + $0.setImage(.leftBtn, for: .normal) + $0.imageView?.contentMode = .scaleAspectFit + } + + private let cancelButton = UIButton().then { + $0.setImage(.icCancel, for: .normal) + $0.imageView?.contentMode = .scaleAspectFit + } + + private let homeButton = UIButton().then { + $0.setImage(.home, for: .normal) + $0.imageView?.contentMode = .scaleAspectFit + } private let alarmButton = UIButton().then { $0.setImage(.alarm, for: .normal) $0.imageView?.contentMode = .scaleAspectFit } - - private let logoButton = UIImageView().then { - $0.image = .logo - $0.contentMode = .scaleAspectFit - } - - private let searchButton = UIButton().then { - $0.setImage(.icSearch, for: .normal) - $0.imageView?.contentMode = .scaleAspectFit - } - - private let settingButton = UIButton().then { - $0.setImage(.icSetting, for: .normal) - $0.imageView?.contentMode = .scaleAspectFit - } - + + private let logoButton = UIImageView().then { + $0.image = .logo + $0.contentMode = .scaleAspectFit + } + + private let searchButton = UIButton().then { + $0.setImage(.icSearch, for: .normal) + $0.imageView?.contentMode = .scaleAspectFit + } + + private let settingButton = UIButton().then { + $0.setImage(.icSetting, for: .normal) + $0.imageView?.contentMode = .scaleAspectFit + } + let searchTextField = UISearchTextField().then { - $0.placeholder = "상품을 검색해보세요" + $0.placeholder = "상품을 검색해보세요" + } + + // MARK: - init + init(navigationBarType: NavigationBarType, title: String = "") { + super.init(frame: .zero) + self.backgroundColor = .white + self.titleLabel.text = title + self.navigationType = navigationBarType + setUI() + bindButtons() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + // MARK: Layout Helper + private func setUI() { + setHierarchy() + setConstraints() } + + private func setHierarchy() { + self.addSubviews(leftView, titleLabel, rightView, subView) - // MARK: - init - init(navigationBarType: NavigationBarType, title: String = "") { - super.init(frame: .zero) - self.backgroundColor = .white - self.titleLabel.text = title - self.navigationType = navigationBarType - setUI() - bindButtons() + switch navigationType { + case .normal: + leftView.addSubview(backButton) + + backButton.snp.makeConstraints { + $0.edges.equalToSuperview() + } + case .home: + leftView.addSubview(backButton) + rightView.addSubview(homeButton) + backButton.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + homeButton.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + case .cancel: + rightView.addSubview(cancelButton) + + cancelButton.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + case .search: + leftView.addSubview(logoButton) + subView.addSubview(searchButton) + rightView.addSubview(alarmButton) + + alarmButton.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + logoButton.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + searchButton.snp.makeConstraints { + $0.edges.equalToSuperview() + } + case .setting: + rightView.addSubview(settingButton) + settingButton.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + case .searching: + leftView.addSubview(backButton) + backButton.snp.makeConstraints { + $0.edges.equalToSuperview() + } + self.addSubviews(searchTextField) + searchTextField.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalTo(leftView.snp.trailing).offset(10.adjusted) + $0.trailing.equalToSuperview().inset(20.adjusted) + $0.height.equalTo(30) + } + case .logo: + leftView.addSubview(logoButton) + logoButton.snp.makeConstraints { + $0.edges.equalToSuperview() + } + case .none: + break } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") + } + + private func setConstraints() { + self.snp.makeConstraints { + $0.height.equalTo(58.adjusted) } - - // MARK: Layout Helper - private func setUI() { - setHierarchy() - setConstraints() + leftView.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().offset(20.adjusted) + $0.height.equalTo(20) } - private func setHierarchy() { - self.addSubviews(leftView, titleLabel, rightView, subView) - - switch navigationType { - case .normal: - leftView.addSubview(backButton) - - backButton.snp.makeConstraints { - $0.edges.equalToSuperview() - } - case .home: - leftView.addSubview(backButton) - rightView.addSubview(homeButton) - backButton.snp.makeConstraints { - $0.edges.equalToSuperview() - } - - homeButton.snp.makeConstraints { - $0.edges.equalToSuperview() - } - - case .cancel: - rightView.addSubview(cancelButton) - - cancelButton.snp.makeConstraints { - $0.edges.equalToSuperview() - } - - case .search: - leftView.addSubview(logoButton) - subView.addSubview(searchButton) - rightView.addSubview(alarmButton) - - alarmButton.snp.makeConstraints { - $0.edges.equalToSuperview() - } - - logoButton.snp.makeConstraints { - $0.edges.equalToSuperview() - } - - searchButton.snp.makeConstraints { - $0.edges.equalToSuperview() - } - case .setting: - rightView.addSubview(settingButton) - settingButton.snp.makeConstraints { - $0.edges.equalToSuperview() - } - - case .searching: - leftView.addSubview(backButton) - backButton.snp.makeConstraints { - $0.edges.equalToSuperview() - } - self.addSubviews(searchTextField) - searchTextField.snp.makeConstraints { - $0.centerY.equalToSuperview() - $0.leading.equalTo(leftView.snp.trailing).offset(10.adjusted) - $0.trailing.equalToSuperview().inset(20.adjusted) - $0.height.equalTo(30) - } - case .logo: - leftView.addSubview(logoButton) - logoButton.snp.makeConstraints { - $0.edges.equalToSuperview() - } - case .none: - break - } + subView.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.trailing.equalTo(rightView.snp.leading).offset(-10.adjusted) + $0.size.equalTo(20) } - private func setConstraints() { - self.snp.makeConstraints { - $0.height.equalTo(58.adjusted) - } - - leftView.snp.makeConstraints { - $0.centerY.equalToSuperview() - $0.leading.equalToSuperview().offset(20.adjusted) - $0.height.equalTo(20) - } - - subView.snp.makeConstraints { - $0.centerY.equalToSuperview() - $0.trailing.equalTo(rightView.snp.leading).offset(-10.adjusted) - $0.size.equalTo(20) - } - - rightView.snp.makeConstraints { - $0.centerY.equalToSuperview() - $0.trailing.equalToSuperview().inset(20.adjusted) - $0.height.equalTo(20) - } - - titleLabel.snp.makeConstraints { - $0.centerX.equalToSuperview() - $0.centerY.equalTo(leftView.snp.centerY) - $0.height.equalTo(20) - } - + rightView.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.trailing.equalToSuperview().inset(20.adjusted) + $0.height.equalTo(20) } - // MARK: - Bind Buttons - private func bindButtons() { - backButton.rx.tap - .bind(to: backButtonSubject) - .disposed(by: disposeBag) - - cancelButton.rx.tap - .bind(to: cancelButtonSubject) - .disposed(by: disposeBag) - - homeButton.rx.tap - .bind(to: homeButtonSubject) - .disposed(by: disposeBag) - - searchButton.rx.tap - .bind(to: searchButtonSubject) - .disposed(by: disposeBag) - - settingButton.rx.tap - .bind(to: settingButtonSubject) - .disposed(by: disposeBag) - - - alarmButton.rx.tap - .bind(to: alarmButtonSubject) - .disposed(by: disposeBag) - + titleLabel.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.centerY.equalTo(leftView.snp.centerY) + $0.height.equalTo(20) } + } + + // MARK: - Bind Buttons + private func bindButtons() { + backButton.rx.tap + .observe(on: MainScheduler.instance) // 메인 스레드에서 작업 실행 + .bind(to: backButtonSubject) + .disposed(by: disposeBag) + + cancelButton.rx.tap + .observe(on: MainScheduler.instance) // 메인 스레드에서 작업 실행 + .bind(to: cancelButtonSubject) + .disposed(by: disposeBag) + + homeButton.rx.tap + .observe(on: MainScheduler.instance) // 메인 스레드에서 작업 실행 + .bind(to: homeButtonSubject) + .disposed(by: disposeBag) + + searchButton.rx.tap + .observe(on: MainScheduler.instance) // 메인 스레드에서 작업 실행 + .bind(to: searchButtonSubject) + .disposed(by: disposeBag) + + settingButton.rx.tap + .observe(on: MainScheduler.instance) // 메인 스레드에서 작업 실행 + .bind(to: settingButtonSubject) + .disposed(by: disposeBag) + + alarmButton.rx.tap + .observe(on: MainScheduler.instance) // 메인 스레드에서 작업 실행 + .bind(to: alarmButtonSubject) + .disposed(by: disposeBag) + } } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Common/PermissionManager.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Common/PermissionManager.swift index 1e35e4d..870f97e 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Common/PermissionManager.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Common/PermissionManager.swift @@ -107,8 +107,10 @@ public final class PermissionManager { .requestAuthorization( options: [.alert, .sound, .badge] ) { isAllow, _ in - observable.onNext(isAllow) - observable.onCompleted() + DispatchQueue.main.async { + observable.onNext(isAllow) + observable.onCompleted() + } } return Disposables.create() } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Common/ReusableView/CustomAlertView.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Common/ReusableView/CustomAlertView.swift index 0021028..412a81a 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Common/ReusableView/CustomAlertView.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Common/ReusableView/CustomAlertView.swift @@ -14,6 +14,7 @@ import RxSwift final class CustomAlertViewController: UIViewController { var primaryButtonTap = PublishSubject() + var subButtonTap = PublishSubject() private let disposeBag = DisposeBag() private let dimView = UIView().then { @@ -80,6 +81,7 @@ final class CustomAlertViewController: UIViewController { } private func setUI() { + self.modalPresentationStyle = .overFullScreen setHierarchy() setConstraints() // Add tap gesture recognizer to dimView @@ -88,7 +90,7 @@ final class CustomAlertViewController: UIViewController { } @objc private func dismissAlert() { - dismiss(animated: true, completion: nil) + dismiss(animated: false, completion: nil) } private func setHierarchy() { @@ -126,6 +128,12 @@ final class CustomAlertViewController: UIViewController { self?.dismiss(animated: false, completion: nil) }) .bind(to: primaryButtonTap) + .disposed(by: disposeBag) + subButton.rx.tap + .do(onNext: { [weak self] in + self?.dismiss(animated: false, completion: nil) + }) + .bind(to: subButtonTap) .disposed(by: disposeBag) } } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountAddViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountAddViewController.swift index 88f0d76..6b08f6c 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountAddViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountAddViewController.swift @@ -170,9 +170,7 @@ final class AccountAddViewController: UIViewController { let body = UserAccountRequestDTO(accountName: accountName, bank: bank, accountNumber: accountNumber) Providers.MypageProvider.request(target: .addUserAccount(body), instance: BaseResponse.self) { response in guard let data = response.data else { return } - if response.status != 200 || response.status != 201 { - self.view.showToast(message: "계좌 등록 오류 입니다. 잠시 후 다시 시도해주세요", at: 100) - } else { + if response.status == 200 || response.status == 201 { self.accountRegisteredRelay.accept(true) self.navigationController?.popViewController(animated: true) } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountViewController.swift index e5f0499..1ca0205 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountViewController.swift @@ -96,41 +96,55 @@ extension AccountViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.row == 0 { - Providers.AuthProvider.request(target: .logout, instance: BaseResponse.self) { response in - UserApi.shared.logout {(error) in - if let error = error { - print(error) - KeychainWrapper.shared.deleteAccessToken() - UserDefaults.standard.set(false, forKey: .isLogin) - self.navigationController?.popToRootViewController(animated: true) + let alertVC = CustomAlertViewController(title: "로그아웃", content: "정말 로그아웃 하시나요?", buttonText: "계속 이용하기", subButton: "로그아웃") + alertVC.subButtonTap + .subscribe(onNext: { _ in + Providers.AuthProvider.request(target: .logout, instance: BaseResponse.self) { response in + UserApi.shared.logout {(error) in + if let error = error { + print(error) + KeychainWrapper.shared.deleteAccessToken() + UserDefaults.standard.set(false, forKey: .isLogin) + self.navigationController?.popToRootViewController(animated: true) + } + else { + UserDefaults.standard.clearAll() + KeychainWrapper.shared.deleteAccessToken() + UserDefaults.standard.set(false, forKey: .isLogin) + self.navigationController?.popToRootViewController(animated: true) + print("logout() success.") + } + } } - else { - UserDefaults.standard.clearAll() - KeychainWrapper.shared.deleteAccessToken() - UserDefaults.standard.set(false, forKey: .isLogin) - self.navigationController?.popToRootViewController(animated: true) - print("logout() success.") + }) + .disposed(by: disposeBag) + alertVC.modalPresentationStyle = .overFullScreen + self.present(alertVC, animated: false, completion: nil) + } else if indexPath.row == 1 { + let alertVC = CustomAlertViewController(title: "회원 탈퇴", content: "회원 탈퇴 시,\n계정은 삭제되며 복구되지 않습니다.", buttonText: "계속 이용하기", subButton: "탈퇴하기") + alertVC.subButtonTap + .subscribe(onNext: { _ in + Providers.AuthProvider.request(target: .revoke, instance: BaseResponse.self) { response in + UserApi.shared.unlink {(error) in + if let error = error { + print(error) + UserDefaults.standard.set(false, forKey: .isLogin) + self.navigationController?.popToRootViewController(animated: true) + } + else { + UserDefaults.standard.clearAll() + KeychainWrapper.shared.deleteAccessToken() + UserDefaults.standard.set(false, forKey: .isLogin) + self.navigationController?.popToRootViewController(animated: true) + } + } } - } - } - } - if indexPath.row == 1 { - Providers.AuthProvider.request(target: .revoke, instance: BaseResponse.self) { response in - UserApi.shared.unlink {(error) in - if let error = error { - print(error) - - UserDefaults.standard.set(false, forKey: .isLogin) - self.navigationController?.popToRootViewController(animated: true) - } - else { - UserDefaults.standard.clearAll() - KeychainWrapper.shared.deleteAccessToken() - UserDefaults.standard.set(false, forKey: .isLogin) - self.navigationController?.popToRootViewController(animated: true) - } - } - } + }) + .disposed(by: disposeBag) + + alertVC.modalPresentationStyle = .overFullScreen + self.present(alertVC, animated: false, completion: nil) + // 탈퇴 로직 처리 } } } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressFormViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressFormViewController.swift index 7ce0cf5..e77e446 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressFormViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressFormViewController.swift @@ -16,6 +16,8 @@ import RxCocoa final class AddressFormViewController: UIViewController { private let disposeBag = DisposeBag() + + private var currentAddressInfo: Address? private let titles: [String] = ["우편번호", "주소지", "상세주소", "이름", "휴대폰 번호"] private let userInfo = UserInfoModel(name: UserDefaults.standard.string(forKey: .name) ?? "", phone: UserDefaults.standard.string(forKey: .phone) ?? "", @@ -24,7 +26,7 @@ final class AddressFormViewController: UIViewController { private var roadAddress: String? private lazy var addressDetails: [String?] = [zoneCode, roadAddress, nil, userInfo.name, userInfo.phone] - + // MARK: UI private let navigationBar = CustomNavigationBarView(navigationBarType: .normal) private let addressFormTableView = UITableView(frame: .zero, style: .plain).then { $0.backgroundColor = .white @@ -39,6 +41,17 @@ final class AddressFormViewController: UIViewController { } private let nexButton = DdanziButton(title: "입력 완료", enable: false) + // MARK: Init + init(addressInfo: Address? = nil) { + self.currentAddressInfo = addressInfo + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: LifeCycle override func viewDidLoad() { super.viewDidLoad() setUI() @@ -94,21 +107,29 @@ final class AddressFormViewController: UIViewController { nexButton.rx.tap .subscribe(with: self, onNext: { owner, event in - print(owner.addressDetails) let addressInfo: [String] = owner.addressDetails.map { guard let addressInfo = $0 else { return "" } return addressInfo } let body = UserAddressRequestDTO(recipient: addressInfo[3], zipCode: addressInfo[0], type: .road, address: addressInfo[1] , detailAddress: addressInfo[2], recipientPhone: addressInfo[4]) - owner.postAddress(body: body) - + if let currentAddressInfo = self.currentAddressInfo, + let addressId = currentAddressInfo.addressId { + owner.editAddress(addressId: addressId, body: body) + } else { + owner.postAddress(body: body) + } }) .disposed(by: disposeBag) } private func postAddress(body: UserAddressRequestDTO) { Providers.MypageProvider.request(target: .addUserAddress(body), instance: BaseResponse.self) { result in - + self.navigationController?.popViewController(animated: true) + } + } + + private func editAddress(addressId: Int, body: UserAddressRequestDTO) { + Providers.MypageProvider.request(target: .editUserAddress(addressId, body), instance: BaseResponse.self) { response in self.navigationController?.popViewController(animated: true) } } @@ -178,7 +199,7 @@ extension AddressFormViewController: UITableViewDataSource { self?.addressDetails[indexPath.row] = text self?.checkIsVaild() } - + return cell } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressSettingViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressSettingViewController.swift index b9816e0..6ab6d88 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressSettingViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressSettingViewController.swift @@ -26,9 +26,8 @@ class AddressSettingViewController: UIViewController { flowLayout.scrollDirection = .vertical flowLayout.itemSize = .init(width: UIScreen.main.bounds.width - 40, height: 164) $0.collectionViewLayout = flowLayout - - $0.register(AddressCollectionViewCell.self, - forCellWithReuseIdentifier: AddressCollectionViewCell.className) + $0.register(AddressCollectionViewCell.self,forCellWithReuseIdentifier: AddressCollectionViewCell.className) + $0.isHidden = true } private let addButton = UIButton().then { $0.setTitle("+ 배송지 등록", for: .normal) @@ -37,7 +36,7 @@ class AddressSettingViewController: UIViewController { $0.makeCornerRound(radius: 10) $0.makeBorder(width: 1, color: .gray3) $0.setTitleColor(.gray4, for: .normal) - $0.isHidden = true // 초기에는 숨겨둡니다. + $0.isHidden = true } override func viewWillAppear(_ animated: Bool) { @@ -60,10 +59,12 @@ class AddressSettingViewController: UIViewController { } private func setHierarchy() { - view.addSubviews(navigationBarView, - headerView, - addButton, - collectionView) + view.addSubviews( + navigationBarView, + headerView, + addButton, + collectionView + ) } private func setConstraints() { @@ -90,41 +91,37 @@ class AddressSettingViewController: UIViewController { } private func configureCollectionView() { - collectionView.delegate = nil - let dataSource = RxCollectionViewSectionedReloadDataSource>( configureCell: { dataSource, collectionView, indexPath, item in let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AddressCollectionViewCell.className, for: indexPath) as! AddressCollectionViewCell let address = item as Address cell.configureView(name: address.name, address: address.address, phone: address.phone, isEditable: true) + + // 주소 삭제 cell.deleteButtonTap .subscribe(onNext: { [weak self] in - // 해당 주소 삭제 로직 self?.deleteAddress(at: indexPath) }) .disposed(by: cell.disposeBag) + + // 주소 변경 + cell.editButtonTap + .subscribe(onNext: { [weak self] in + self?.editAddress(at: indexPath) + }) + .disposed(by: cell.disposeBag) + return cell } ) - addressListSubject - .map { [SectionModel(model: "배송지 관리", items: $0)] } - .bind(to: collectionView.rx.items(dataSource: dataSource)) - .disposed(by: disposeBag) - headerView.setTitleLabel(title: "배송지 관리") - collectionView.rx.setDelegate(self) - .disposed(by: disposeBag) - addressListSubject - .subscribe(onNext: { [weak self] addressList in - guard let self = self else { return } - let isEmpty = addressList.isEmpty - self.collectionView.isHidden = isEmpty - self.addButton.isHidden = !isEmpty - }) + .map { [SectionModel(model: "배송지 관리", items: $0)] } + .bind(to: collectionView.rx.items(dataSource: dataSource)) .disposed(by: disposeBag) + } private func bind() { @@ -139,26 +136,37 @@ class AddressSettingViewController: UIViewController { self?.navigationController?.pushViewController(AddressFormViewController(), animated: true) }) .disposed(by: disposeBag) + + addressListSubject + .subscribe(onNext: { [weak self] addressList in + guard let self = self else { return } + let isEmpty = addressList.isEmpty + self.collectionView.isHidden = isEmpty + self.addButton.isHidden = !isEmpty + }) + .disposed(by: disposeBag) } private func fetchAddress() { Providers.MypageProvider.request(target: .fetchUserAddress, instance: BaseResponse.self) { result in guard let data = result.data else { return } - if let name = data.recipient, + + if let addressId = data.addressID, + let name = data.recipient, let zipcode = data.zipCode, let address = data.address, let detailAddress = data.detailAddress, let phone = data.recipientPhone { - let newAddress = Address(name: name, - address: "(\(zipcode) \(address), \(detailAddress))", - phone: phone) - - var currentList = (try? self.addressListSubject.value()) ?? [] - currentList.append(newAddress) - + let newAddress = Address(addressId: addressId, name: name,address: "(\(zipcode) \(address), \(detailAddress))",phone: phone) self.addressId = data.addressID ?? 0 - self.addressListSubject.onNext(currentList) + self.addressListSubject.onNext([newAddress]) + self.collectionView.reloadData() + self.collectionView.isHidden = false + self.addButton.isHidden = true + } else { + self.collectionView.isHidden = true + self.addButton.isHidden = false } } } @@ -174,6 +182,13 @@ class AddressSettingViewController: UIViewController { } } } + + private func editAddress(at indexPath: IndexPath) { + let currentList = (try? self.addressListSubject.value()) ?? [] + if indexPath.row < currentList.count { // indexPath가 유효한지 확인 + let selectedAddress = currentList[indexPath.row] + self.navigationController?.pushViewController(AddressFormViewController(addressInfo: selectedAddress), animated: true) + } + } + } - -extension AddressSettingViewController: UICollectionViewDelegate { } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/Cell/KakaoCopyTableViewCell.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/Cell/KakaoCopyTableViewCell.swift index e3df482..5aeeabd 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/Cell/KakaoCopyTableViewCell.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/Cell/KakaoCopyTableViewCell.swift @@ -12,7 +12,7 @@ import Then import RxSwift final class KakaoCopyTableViewCell: UITableViewCell { - private let disposeBag = DisposeBag() + private var disposeBag = DisposeBag() private let titleLabel = UILabel().then { $0.font = .body2Sb18 @@ -33,6 +33,7 @@ final class KakaoCopyTableViewCell: UITableViewCell { override func prepareForReuse() { super.prepareForReuse() + disposeBag = DisposeBag() copyChipButton.isHidden = false } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/KakaoCopyViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/KakaoCopyViewController.swift index ebbc276..213f59e 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/KakaoCopyViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/KakaoCopyViewController.swift @@ -163,6 +163,7 @@ final class KakaoCopyViewController: UIViewController { Providers.OrderProvider.request(target: .conformedOrderSale(id), instance: BaseResponse.self) { response in guard let data = response.data else { return } self.statusType = .init(rawValue: data.orderStatus) ?? .inProgress + self.view.showToast(message: "판매 확정이 완료되었습니다.", at: 120.adjusted) self.navigationController?.popViewController(animated: true) } } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/Cell/OptionCollectionViewCell.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/Cell/OptionCollectionViewCell.swift index 3ad02b9..3e7c024 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/Cell/OptionCollectionViewCell.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/Cell/OptionCollectionViewCell.swift @@ -39,8 +39,12 @@ final class OptionCollectionViewCell: UICollectionViewCell { fatalError("init(coder:) has not been implemented") } + override func prepareForReuse() { + super.prepareForReuse() + isSelectedRelay.accept(false) + } + private func bindUI() { - // Bind the selection state to UI changes isSelectedRelay .asDriver() .drive(onNext: { [weak self] isSelected in @@ -49,8 +53,9 @@ final class OptionCollectionViewCell: UICollectionViewCell { .disposed(by: disposeBag) } - func configureCell(text: String, isEnable: Bool) { + func configureCell(text: String, isEnable: Bool, isSelected: Bool = false) { titleLabel.text = text isUserInteractionEnabled = isEnable + isSelectedRelay.accept(isSelected) } } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/OptionSelect/OptionSelectViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/OptionSelect/OptionSelectViewController.swift index f130962..73a2522 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/OptionSelect/OptionSelectViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/OptionSelect/OptionSelectViewController.swift @@ -131,7 +131,13 @@ final class OptionSelectViewController: UIViewController { let cell = collectionView.dequeueReusableCell( withReuseIdentifier: OptionCollectionViewCell.className, for: indexPath) as! OptionCollectionViewCell - cell.configureCell(text: item.content, isEnable: item.isAvailable) + + // 해당 섹션의 선택된 옵션 인덱스 확인 + let isSelected = self.selectedOptions[indexPath.section] == item.optionDetailID + + // 선택 상태를 반영하여 셀 구성 + cell.configureCell(text: item.content, isEnable: item.isAvailable, isSelected: isSelected) + return cell }, configureSupplementaryView: { dataSource, collectionView, kind, indexPath in @@ -162,21 +168,22 @@ final class OptionSelectViewController: UIViewController { cell.isSelectedRelay.accept(false) // 이전 선택 해제 } } - + // 선택한 옵션을 섹션별로 저장 let selectedOptionId = owner.option[indexPath.section].optionDetailList[indexPath.row].optionDetailID - owner.selectedOptions[indexPath.section] = selectedOptionId // optionDetailId 저장 - + owner.selectedOptions[indexPath.section] = selectedOptionId // optionDetailId 저장 + if let cell = owner.optionCollectionView.cellForItem(at: indexPath) as? OptionCollectionViewCell { cell.isSelectedRelay.accept(true) // 새로운 선택 적용 } - + owner.updateButtonState() // 버튼 상태 업데이트 + owner.optionCollectionView.reloadData() } .disposed(by: disposeBag) - } + private func updateButtonState() { print(selectedOptions) let allSectionsSelected = selectedOptions.allSatisfy { $0 != nil } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/PurchaseCompleteViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/PurchaseCompleteViewController.swift index f2aade6..65fd11e 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/PurchaseCompleteViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/PurchaseCompleteViewController.swift @@ -77,6 +77,7 @@ final class PurchaseCompleteViewController: UIViewController { // MARK: - Lifecycles override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) self.tabBarController?.tabBar.isHidden = true } @@ -179,9 +180,12 @@ final class PurchaseCompleteViewController: UIViewController { Info(title: "수수료", info: data.charge.toKoreanWon())] self.totalPrice = data.totalPrice - self.configureCollectionView() - self.collectionView.isHidden = false - self.collectionView.reloadData() + DispatchQueue.main.async { + self.configureCollectionView() + self.collectionView.isHidden = false + self.collectionView.reloadData() + } + } } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/PurchaseViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/PurchaseViewController.swift index dd84573..135ed71 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/PurchaseViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/PurchaseViewController.swift @@ -122,7 +122,8 @@ final class PurchaseViewController: UIViewController { .disposed(by: disposeBag) button.rx.tap - .bind { + .withUnretained(self) + .bind { owner,_ in Amplitude.instance().logEvent("click_purchase_purchase") self.requestPayment(payment: self.payment) } @@ -225,11 +226,13 @@ final class PurchaseViewController: UIViewController { .subscribe(onSuccess: { [weak self] isSuccess, orderId in guard let self = self else { return } DdanziLoadingView.shared.stopAnimating() - if isSuccess, let orderId = orderId {PermissionManager.shared.checkPermission(for: .notification) + if isSuccess, let orderId = orderId { + PermissionManager.shared.checkPermission(for: .notification) + .observe(on: MainScheduler.instance) .subscribe { [weak self] isAllow in Amplitude.instance().logEvent("complete_purchase_adjustment", withEventProperties: ["item_id" : self?.payment.productId]) let nextVC = isAllow ? PurchaseCompleteViewController(orderId: orderId) : PushSettingViewController(orderId: orderId, response: .init(itemId: "", productName: "", imgUrl: "", salePrice: 0)) - self?.navigationController?.pushViewController(PurchaseCompleteViewController(orderId: orderId), animated: true) + self?.navigationController?.pushViewController(nextVC, animated: true) } .disposed(by: self.disposeBag) } else { @@ -254,7 +257,7 @@ final class PurchaseViewController: UIViewController { let address = data.addressInfo.address, let zipCode = data.addressInfo.zipCode, let recipientPhone = data.addressInfo.recipientPhone { - addresses = [Address(name: recipient, address: "\(address) (\(zipCode))", phone: recipientPhone)] + addresses = [Address(addressId: nil, name: recipient, address: "\(address) (\(zipCode))", phone: recipientPhone)] self.addressSelectedSubject.accept(true) } else { self.addressSelectedSubject.accept(false) diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/PushSettingViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/PushSettingViewController.swift index 4c577f0..c2e80c8 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/PushSettingViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/PushSettingViewController.swift @@ -77,18 +77,22 @@ final class PushSettingViewController: UIViewController { } private func setHierarchy() { - view.addSubviews(cancelButton, - innerStackView, - conformButton, - laterButton) - innerStackView.addArrangedSubviews(iconImageView, - titleLabel, - subTitleLabel) + view.addSubviews( + cancelButton, + innerStackView, + conformButton, + laterButton + ) + innerStackView.addArrangedSubviews( + iconImageView, + titleLabel, + subTitleLabel + ) } private func setConstraints() { cancelButton.snp.makeConstraints { - $0.top.trailing.equalToSuperview().inset(20) + $0.top.trailing.equalToSuperview().offset(60) } innerStackView.snp.makeConstraints { @@ -97,7 +101,7 @@ final class PushSettingViewController: UIViewController { } laterButton.snp.makeConstraints { - $0.leading.trailing.equalToSuperview().inset(20) + $0.leading.trailing.equalToSuperview().inset(60) $0.bottom.equalToSuperview().inset(28.adjusted) } @@ -116,21 +120,27 @@ final class PushSettingViewController: UIViewController { Amplitude.instance().logEvent("click_sell_push_refuse") owner.navigationController?.pushViewController(RegisteCompleteViewController(response: owner.response), animated: true) } else { - - Amplitude.instance().logEvent("click_purchase_push_refuse") + Amplitude.instance().logEvent("click_purchase_push_refuse") owner.navigationController?.pushViewController(PurchaseCompleteViewController(orderId: owner.orderId), animated: true) } - } - .disposed(by: disposeBag) + } + .disposed(by: disposeBag) conformButton.rx.tap .bind(with: self) { owner, _ in PermissionManager.shared.requestNotificationPermission() + .observe(on: MainScheduler.instance) .subscribe(with: self) { owner, isAllow in - if owner.isSelling { - owner.navigationController?.pushViewController(RegisteCompleteViewController(response: owner.response), animated: true) - } else { - owner.navigationController?.pushViewController(PurchaseCompleteViewController(orderId: owner.orderId), animated: true) + if !isAllow { + let alertVC = CustomAlertViewController(title: "알림 설정", content: "설정 > 딴지 > 알림에서 설정을 변경할 수 있습니다.", buttonText: "확인", subButton: nil) + alertVC.modalPresentationStyle = .overFullScreen + self.present(alertVC, animated: true) { + if owner.isSelling { + owner.navigationController?.pushViewController(RegisteCompleteViewController(response: owner.response), animated: true) + } else { + owner.navigationController?.pushViewController(PurchaseCompleteViewController(orderId: owner.orderId), animated: true) + } + } } } .disposed(by: owner.disposeBag) diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/LandingViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/LandingViewController.swift index 196b642..11dc177 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/LandingViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/LandingViewController.swift @@ -180,6 +180,7 @@ final class LandingViewController: UIViewController { private func checkPermissionAndPresent(){ PermissionManager.shared.checkPermission(for: .photo) + .observe(on: MainScheduler.instance) .flatMap { isGranted -> Observable in if !isGranted { return PermissionManager.shared.requestPhotoPermission() diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemCell.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemCell.swift index 5502985..b33a075 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemCell.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemCell.swift @@ -210,7 +210,7 @@ final class RegisteItemCell: UICollectionViewCell { termsTableView.snp.makeConstraints { $0.top.equalTo(fullAgreementButton.snp.bottom).offset(18.adjusted) - $0.leading.trailing.bottom.equalToSuperview() + $0.leading.trailing.bottom.equalToSuperview().inset(20.adjusted) } } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemViewController.swift index 852fb16..a891e05 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemViewController.swift @@ -110,7 +110,6 @@ final class RegisteItemViewController: UIViewController { owner.navigateToAccountAdd(dueDate: dueDate) }) .disposed(by: owner.disposeBag) - alertVC.modalPresentationStyle = .overFullScreen owner.present(alertVC, animated: false, completion: nil) } else { @@ -156,6 +155,7 @@ final class RegisteItemViewController: UIViewController { let pushVC = PushSettingViewController(isSelling: true, orderId: "", response: data) PermissionManager.shared.checkPermission(for: .notification) + .observe(on: MainScheduler.instance) .bind(with: self, onNext: { owner, isAllow in Amplitude.instance().logEvent("complete_sell_adjustment", withEventProperties: ["item_id": data.itemId]) if isAllow { diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/Cell/AddressCollectionViewCell.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/Cell/AddressCollectionViewCell.swift index 09080ef..d3414d2 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/Cell/AddressCollectionViewCell.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/Cell/AddressCollectionViewCell.swift @@ -14,10 +14,15 @@ import RxCocoa final class AddressCollectionViewCell: UICollectionViewCell { - let disposeBag = DisposeBag() - private let deleteSubject = PublishSubject() - var deleteButtonTap: Observable { deleteSubject.asObservable() } + var disposeBag = DisposeBag() + var editButtonTap: Observable { + return editButton.rx.tap.asObservable() + } + var deleteButtonTap: Observable { + return deleteButton.rx.tap.asObservable() + } + private let nameLabel = UILabel().then { $0.font = .body2Sb18 $0.textColor = .black @@ -49,13 +54,18 @@ final class AddressCollectionViewCell: UICollectionViewCell { override init(frame: CGRect) { super.init(frame: frame) setUI() - bind() + // bindButtons() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + override func prepareForReuse() { + super.prepareForReuse() + disposeBag = DisposeBag() // 셀 재사용 시 disposeBag 초기화 + } + private func setUI() { setHierarchy() setConstraints() @@ -79,11 +89,16 @@ final class AddressCollectionViewCell: UICollectionViewCell { } } - private func bind() { - deleteButton.rx.tap - .bind(to: deleteSubject) - .disposed(by: disposeBag) - } +// private func bindButtons() { +// print("asdjfhaoioj") +// editButton.rx.tap +// .bind(to: editButtonSubject) // Bind tap to Subject +// .disposed(by: disposeBag) +// +// deleteButton.rx.tap +// .bind(to: deleteButtonSubject) // Bind tap to Subject +// .disposed(by: disposeBag) +// } func configureView(name: String, address: String, phone: String, isEditable: Bool = false){ nameLabel.text = name diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/Model/PurchaseModel.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/Model/PurchaseModel.swift index f6d4f0f..16cd72d 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/Model/PurchaseModel.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/Model/PurchaseModel.swift @@ -33,7 +33,7 @@ enum StatusType { case .cancel: return "거래 취소" case .notDeposit: - return "거래 진행 중" + return "판매 중" } } @@ -76,6 +76,7 @@ struct Product { } struct Address { + var addressId: Int? var name: String var address: String var phone: String diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/ViewController/PurchaseDetailViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/ViewController/PurchaseDetailViewController.swift index 44ff576..91ba275 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/ViewController/PurchaseDetailViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/ViewController/PurchaseDetailViewController.swift @@ -40,12 +40,16 @@ final class PurchaseDetailViewController: UIViewController { $0.register(AddressCollectionViewCell.self, forCellWithReuseIdentifier: AddressCollectionViewCell.className) $0.register(InfoCollectionViewCell.self, forCellWithReuseIdentifier: InfoCollectionViewCell.className) } + private let toastImageView = UIImageView().then { + $0.image = .buyToast + $0.isHidden = true + } private let bottomButtonView = UIView().then { $0.backgroundColor = .white $0.addShadow(offset: .init(width: 0, height: 2), opacity: 0.4) } - private let button = DdanziButton(title: "구매 확정하기") + private let button = DdanziButton(title: "구매 확정하기", enable: false) init(orderId: String) { self.orderId = orderId @@ -76,7 +80,8 @@ final class PurchaseDetailViewController: UIViewController { private func setHierarchy() { view.addSubviews(navigaitonBar, collectionView, - bottomButtonView) + bottomButtonView, + toastImageView) bottomButtonView.addSubview(button) } @@ -91,6 +96,11 @@ final class PurchaseDetailViewController: UIViewController { $0.bottom.equalToSuperview().inset(100) } + toastImageView.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.bottom.equalTo(bottomButtonView.snp.top).offset(14) + } + bottomButtonView.snp.makeConstraints { $0.leading.trailing.bottom.equalToSuperview() $0.height.equalTo(92) @@ -139,6 +149,16 @@ final class PurchaseDetailViewController: UIViewController { .disposed(by: disposeBag) status.bind(with: self) { owner, _ in + let purchaseStatus = owner.status.value + if let status = purchaseStatus.first { + switch status.status { + case .delivery,.delayedShipping,.warning: + owner.button.setEnable() + owner.toastImageView.isHidden = false + default: + break + } + } owner.collectionView.reloadData() } .disposed(by: disposeBag) diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/ViewController/SalesDetailViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/ViewController/SalesDetailViewController.swift index 2e52d99..2801cee 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/ViewController/SalesDetailViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/ViewController/SalesDetailViewController.swift @@ -45,12 +45,16 @@ class SalesDetailViewController: UIViewController { $0.register(AddressCollectionViewCell.self, forCellWithReuseIdentifier: AddressCollectionViewCell.className) $0.register(InfoCollectionViewCell.self, forCellWithReuseIdentifier: InfoCollectionViewCell.className) } + private let toastImageView = UIImageView().then { + $0.image = .salesToast + $0.isHidden = true + } private let bottomButtonView = UIView().then { $0.backgroundColor = .white $0.addShadow(offset: .init(width: 0, height: 2), opacity: 0.4) } - private let button = DdanziButton(title: "판매 확정하기") + private let button = DdanziButton(title: "판매 확정하기", enable: false) init(productId: String) { super.init(nibName: nil, bundle: nil) @@ -86,7 +90,8 @@ class SalesDetailViewController: UIViewController { private func setHierarchy() { view.addSubviews(navigaitonBar, collectionView, - bottomButtonView) + bottomButtonView, + toastImageView) bottomButtonView.addSubview(button) } @@ -101,6 +106,11 @@ class SalesDetailViewController: UIViewController { $0.bottom.equalToSuperview().inset(100) } + toastImageView.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.bottom.equalTo(bottomButtonView.snp.top).offset(14) + } + bottomButtonView.snp.makeConstraints { $0.leading.trailing.bottom.equalToSuperview() $0.height.equalTo(92) @@ -160,6 +170,7 @@ class SalesDetailViewController: UIViewController { owner.button.titleLabel?.text = "판매 확정하기" case .deposit: owner.button.titleLabel?.text = "판매 확정하기" + owner.toastImageView.isHidden = false owner.button.setEnable() case .delivery, .delayedShipping, .warning: owner.button.titleLabel?.text = "배송 중인 상품입니다."