From 9c6f0e560f67dc6bda997ad940a7d27334db4046 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 13 Sep 2024 14:46:30 +0900 Subject: [PATCH 1/7] =?UTF-8?q?[IDLE-000]=20underlyingError=ED=95=B8?= =?UTF-8?q?=EB=93=A4=EB=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/BaseNetworkService.swift | 32 ++++++++++++++++--- .../Domain/Entity/Error/DomainError.swift | 27 ++++++++++------ .../Error/NetworkError/UnderlyingError.swift | 16 ++++++++++ .../Auth/AuthInputValidationUseCase.swift | 2 +- .../UseCaseInterface/Auth/AuthUseCase.swift | 2 +- .../{UseCaseBase.swift => BaseUseCase.swift} | 17 +++++++--- .../CenterCertificateUseCase.swift | 2 +- .../RecruitmentPostUseCase.swift | 2 +- .../Setting/SettingScreenUseCase .swift | 2 +- .../UserInfo/CenterProfileUseCase.swift | 2 +- .../UserInfo/WorkerProfileUseCase.swift | 2 +- 11 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 project/Projects/Domain/Entity/Error/NetworkError/UnderlyingError.swift rename project/Projects/Domain/UseCaseInterface/{UseCaseBase.swift => BaseUseCase.swift} (76%) diff --git a/project/Projects/Data/DataSource/Service/BaseNetworkService.swift b/project/Projects/Data/DataSource/Service/BaseNetworkService.swift index 048ee8fa..90e90afc 100644 --- a/project/Projects/Data/DataSource/Service/BaseNetworkService.swift +++ b/project/Projects/Data/DataSource/Service/BaseNetworkService.swift @@ -186,14 +186,36 @@ public extension BaseNetworkService { provider.rx .request(api) .catch { error in - if let moyaError = error as? MoyaError { + + let moyaError = error as! MoyaError + + // 네트워크 에러 + if case let .statusCode(response) = moyaError { + return .error( + HTTPResponseException(response: response) + ) + } + + // 네트워에러보다 근본적인 에러 + if case let .underlying(error, response) = moyaError, let urlError = error as? URLError { - var response: Response? + var underlyingError: UnderLyingError! - if case let .underlying(_, res) = moyaError { response = res } - if case let .statusCode(res) = moyaError { response = res } - if let response { return .error(HTTPResponseException(response: response)) } + switch urlError.code { + case .notConnectedToInternet: + underlyingError = .internetNotConnected + case .networkConnectionLost: + underlyingError = .networkConnectionLost + case .timedOut: + underlyingError = .timeout + default: + underlyingError = .unHandledError + } + + return .error(underlyingError) } + + // 실행되지 않음 return .error(error) } } diff --git a/project/Projects/Domain/Entity/Error/DomainError.swift b/project/Projects/Domain/Entity/Error/DomainError.swift index 210c3aea..55fa1c8f 100644 --- a/project/Projects/Domain/Entity/Error/DomainError.swift +++ b/project/Projects/Domain/Entity/Error/DomainError.swift @@ -7,7 +7,7 @@ import Foundation -public enum DomainError: Error { +public enum DomainError: Error, Equatable { // API case invalidParameter @@ -58,10 +58,8 @@ public enum DomainError: Error { // undefinedError case undefinedCode - case undefinedError - // Not Implemented - case notImplemented + case undelyingError(error: UnderLyingError) public init(code: String) { switch code { @@ -123,7 +121,7 @@ public enum DomainError: Error { self = .geoCodingFailure default: - self = .undefinedError + self = .undefinedCode } } @@ -199,11 +197,20 @@ public enum DomainError: Error { case .geoCodingFailure: return "입력된 주소로 지리 정보를 찾을 수 없습니다. 주소를 다시 확인해주세요." - case .undefinedCode, .undefinedError: - return "예기치 않은 오류가 발생했습니다. 잠시 후 다시 시도해주세요." - - case .notImplemented: - return "아직 개발되지 않은 기능입니다." + case .undefinedCode: + return "처리되지 않은 HTTP코드입니다." + + case .undelyingError(let underlyingError): + switch underlyingError { + case .internetNotConnected: + return "인터넷이 연결되지 않았습니다." + case .timeout: + return "요청시간을 초과했습니다." + case .networkConnectionLost: + return "연결상태가 변경됬습니다." + case .unHandledError: + return "알 수 없는 문제가 발생했습니다." + } } } } diff --git a/project/Projects/Domain/Entity/Error/NetworkError/UnderlyingError.swift b/project/Projects/Domain/Entity/Error/NetworkError/UnderlyingError.swift new file mode 100644 index 00000000..0b275cbf --- /dev/null +++ b/project/Projects/Domain/Entity/Error/NetworkError/UnderlyingError.swift @@ -0,0 +1,16 @@ +// +// Entity.swift +// ConcreteUseCase +// +// Created by choijunios on 9/13/24. +// + +import Foundation + +public enum UnderLyingError: Error { + + case internetNotConnected + case timeout + case networkConnectionLost + case unHandledError +} diff --git a/project/Projects/Domain/UseCaseInterface/Auth/AuthInputValidationUseCase.swift b/project/Projects/Domain/UseCaseInterface/Auth/AuthInputValidationUseCase.swift index 2917d6c4..df830a1d 100644 --- a/project/Projects/Domain/UseCaseInterface/Auth/AuthInputValidationUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/Auth/AuthInputValidationUseCase.swift @@ -19,7 +19,7 @@ import Entity /// - #7. 아이디 중복확인 /// - #8. 패스워드 유효성 확인 -public protocol AuthInputValidationUseCase: UseCaseBase { +public protocol AuthInputValidationUseCase: BaseUseCase { // #1. /// 전화번호 인증 요청 diff --git a/project/Projects/Domain/UseCaseInterface/Auth/AuthUseCase.swift b/project/Projects/Domain/UseCaseInterface/Auth/AuthUseCase.swift index 78e31333..4f96b481 100644 --- a/project/Projects/Domain/UseCaseInterface/Auth/AuthUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/Auth/AuthUseCase.swift @@ -9,7 +9,7 @@ import Foundation import RxSwift import Entity -public protocol AuthUseCase: UseCaseBase { +public protocol AuthUseCase: BaseUseCase { /// 센터 회원가입 실행 diff --git a/project/Projects/Domain/UseCaseInterface/UseCaseBase.swift b/project/Projects/Domain/UseCaseInterface/BaseUseCase.swift similarity index 76% rename from project/Projects/Domain/UseCaseInterface/UseCaseBase.swift rename to project/Projects/Domain/UseCaseInterface/BaseUseCase.swift index 26ca6512..884fcca0 100644 --- a/project/Projects/Domain/UseCaseInterface/UseCaseBase.swift +++ b/project/Projects/Domain/UseCaseInterface/BaseUseCase.swift @@ -9,9 +9,9 @@ import Foundation import RxSwift import Entity -public protocol UseCaseBase: AnyObject { } +public protocol BaseUseCase: AnyObject { } -public extension UseCaseBase { +public extension BaseUseCase { /// Repository로 부터 전달받은 언어레벨의 에러를 도메인 특화 에러로 변경하고, error를 Result의 Failure로, 성공을 Success로 변경합니다. func convert(task: Single) -> Single> { @@ -28,7 +28,8 @@ public extension UseCaseBase { // MARK: InputValidationError private func toDomainError(error: Error) -> DomainError { - + + // 네트워크 에러 if let httpError = error as? HTTPResponseException { if let code = httpError.rawCode { @@ -49,6 +50,14 @@ public extension UseCaseBase { #endif } - return DomainError.undefinedError + // 네트워크 에러보다 근본적인 에러 + if let underlyingError = error as? UnderLyingError { + + let domainError: DomainError = .undelyingError(error: underlyingError) + + return domainError + } + + return DomainError.undelyingError(error: .unHandledError) } } diff --git a/project/Projects/Domain/UseCaseInterface/CenterCertificate/CenterCertificateUseCase.swift b/project/Projects/Domain/UseCaseInterface/CenterCertificate/CenterCertificateUseCase.swift index 4bd9c592..a82446e9 100644 --- a/project/Projects/Domain/UseCaseInterface/CenterCertificate/CenterCertificateUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/CenterCertificate/CenterCertificateUseCase.swift @@ -10,7 +10,7 @@ import Entity import RxSwift -public protocol CenterCertificateUseCase: UseCaseBase { +public protocol CenterCertificateUseCase: BaseUseCase { /// 센터 로그아웃 func signoutCenterAccount() -> Single> diff --git a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift index a1689a7a..b4fce273 100644 --- a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift @@ -9,7 +9,7 @@ import Foundation import Entity import RxSwift -public protocol RecruitmentPostUseCase: UseCaseBase { +public protocol RecruitmentPostUseCase: BaseUseCase { // MARK: Center diff --git a/project/Projects/Domain/UseCaseInterface/Setting/SettingScreenUseCase .swift b/project/Projects/Domain/UseCaseInterface/Setting/SettingScreenUseCase .swift index f6bb3874..fa6e1e62 100644 --- a/project/Projects/Domain/UseCaseInterface/Setting/SettingScreenUseCase .swift +++ b/project/Projects/Domain/UseCaseInterface/Setting/SettingScreenUseCase .swift @@ -15,7 +15,7 @@ public enum NotificationApproveAction: Equatable { case error(message: String) } -public protocol SettingScreenUseCase: UseCaseBase { +public protocol SettingScreenUseCase: BaseUseCase { /// 현재 알람수신 동의 여부를 확인합니다. func checkPushNotificationApproved() -> Single diff --git a/project/Projects/Domain/UseCaseInterface/UserInfo/CenterProfileUseCase.swift b/project/Projects/Domain/UseCaseInterface/UserInfo/CenterProfileUseCase.swift index 1193d9bc..e958d502 100644 --- a/project/Projects/Domain/UseCaseInterface/UserInfo/CenterProfileUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/UserInfo/CenterProfileUseCase.swift @@ -16,7 +16,7 @@ import Entity /// 5. 센터 프로필 최초 등록 /// 6. 특정 센터의 프로필 불러오기 -public protocol CenterProfileUseCase: UseCaseBase { +public protocol CenterProfileUseCase: BaseUseCase { /// 1. 나의 센터/다른 센터 프로필 정보 조회 /// 6. 특정 센터의 프로필 불러오기 diff --git a/project/Projects/Domain/UseCaseInterface/UserInfo/WorkerProfileUseCase.swift b/project/Projects/Domain/UseCaseInterface/UserInfo/WorkerProfileUseCase.swift index f2669f37..9bce9761 100644 --- a/project/Projects/Domain/UseCaseInterface/UserInfo/WorkerProfileUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/UserInfo/WorkerProfileUseCase.swift @@ -15,7 +15,7 @@ import Entity /// 4. 나의(요보) 프로필 정보 업데이트(이미지, pre-signed-url-callback) /// 5. 특정 요양보호사의 프로필 불러오기 -public protocol WorkerProfileUseCase: UseCaseBase { +public protocol WorkerProfileUseCase: BaseUseCase { /// 1. 나의(요보) 프로필 정보 조회 /// 5. 특정 요양보호사의 프로필 불러오기 From 905b11fe5fe204ed78f2614be760d81cd14617c9 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 13 Sep 2024 17:09:20 +0900 Subject: [PATCH 2/7] =?UTF-8?q?[IDLE-000]=20=EB=84=A4=ED=8A=B8=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=20=EB=AF=B8=EC=97=B0=EA=B2=B0=EC=8B=9C=20Alert?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/DataSource/API/AuthAPI.swift | 4 - .../Service/BaseNetworkService.swift | 57 ++++++++----- .../DefualtRecruitmentPostUseCase.swift | 3 +- .../Domain/Entity/VO/AlertContentVO.swift | 4 +- .../Base/BaseViewController.swift | 2 +- .../InitialScreen/InitialScreenVC.swift | 7 +- .../InitialScreen/InitialScreenVM.swift | 84 ++++++++++++++++++- 7 files changed, 127 insertions(+), 34 deletions(-) diff --git a/project/Projects/Data/DataSource/API/AuthAPI.swift b/project/Projects/Data/DataSource/API/AuthAPI.swift index c7b16bc7..e84925b1 100644 --- a/project/Projects/Data/DataSource/API/AuthAPI.swift +++ b/project/Projects/Data/DataSource/API/AuthAPI.swift @@ -168,10 +168,6 @@ extension AuthAPI: BaseAPI { } } - public var validationType: ValidationType { - .successCodes - } - public var task: Task { switch self { case .startPhoneNumberAuth: diff --git a/project/Projects/Data/DataSource/Service/BaseNetworkService.swift b/project/Projects/Data/DataSource/Service/BaseNetworkService.swift index 90e90afc..2472155c 100644 --- a/project/Projects/Data/DataSource/Service/BaseNetworkService.swift +++ b/project/Projects/Data/DataSource/Service/BaseNetworkService.swift @@ -189,33 +189,48 @@ public extension BaseNetworkService { let moyaError = error as! MoyaError - // 네트워크 에러 + // 재시도 실패 or 근본적인 에러(Ex 타임아웃, 네트워크 끊어짐) + if case let .underlying(error, response) = moyaError { + + // 리타리어 실패 + if let afError = error.asAFError { + + if case .requestRetryFailed = afError, let response { + + return .error( + HTTPResponseException(response: response) + ) + } + } + + // 근본적인 문제 + if let urlError = error as? URLError { + + var underlyingError: UnderLyingError! + + switch urlError.code { + case .notConnectedToInternet: + underlyingError = .internetNotConnected + case .networkConnectionLost: + underlyingError = .networkConnectionLost + case .timedOut: + underlyingError = .timeout + default: + underlyingError = .unHandledError + } + + return .error(underlyingError) + } + } + + // HTTP통신 에러 if case let .statusCode(response) = moyaError { return .error( HTTPResponseException(response: response) ) } - // 네트워에러보다 근본적인 에러 - if case let .underlying(error, response) = moyaError, let urlError = error as? URLError { - - var underlyingError: UnderLyingError! - - switch urlError.code { - case .notConnectedToInternet: - underlyingError = .internetNotConnected - case .networkConnectionLost: - underlyingError = .networkConnectionLost - case .timedOut: - underlyingError = .timeout - default: - underlyingError = .unHandledError - } - - return .error(underlyingError) - } - - // 실행되지 않음 + // 엣지 케이스 return .error(error) } } diff --git a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift index 58969877..401df140 100644 --- a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift @@ -157,7 +157,8 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { requestCnt: postCount ) case .thirdParty: - return .just(.failure(.undefinedError)) + // Presentation에서 에초에 호출하지 않음 + return .just(.failure(.undefinedCode)) } } diff --git a/project/Projects/Domain/Entity/VO/AlertContentVO.swift b/project/Projects/Domain/Entity/VO/AlertContentVO.swift index ee5d4ee4..902f0b1a 100644 --- a/project/Projects/Domain/Entity/VO/AlertContentVO.swift +++ b/project/Projects/Domain/Entity/VO/AlertContentVO.swift @@ -11,11 +11,13 @@ public struct DefaultAlertContentVO { public let title: String public let message: String + public let dismissButtonLabelText: String public let onDismiss: (() -> ())? - public init(title: String, message: String, onDismiss: (() -> ())? = nil) { + public init(title: String, message: String, dismissButtonLabelText: String = "닫기", onDismiss: (() -> ())? = nil) { self.title = title self.message = message + self.dismissButtonLabelText = dismissButtonLabelText self.onDismiss = onDismiss } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift b/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift index da7d3c69..03eb8e8b 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift @@ -91,7 +91,7 @@ public extension BaseViewController { message: vo.message, preferredStyle: .alert ) - let close = UIAlertAction(title: "닫기", style: .default) { [weak self] _ in + let close = UIAlertAction(title: vo.dismissButtonLabelText, style: .default) { [weak self] _ in self?.alert(vo: vo) // dismiss diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVC.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVC.swift index 9f1d62de..8326d50b 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVC.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVC.swift @@ -44,8 +44,11 @@ public class InitialScreenVC: BaseViewController { super.bind(viewModel: viewModel) - self.rx.viewWillAppear - .map { _ in () } + self.rx.viewDidAppear + .map { _ in + + printIfDebug("InitialScreenVC viewWillAppear") + } .bind(to: viewModel.viewWillAppear) .disposed(by: disposeBag) } diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift index 3daad6b3..8aa59865 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift @@ -5,15 +5,18 @@ // Created by choijunios on 8/25/24. // -import RxSwift -import RxCocoa import Foundation +import Network + import PresentationCore import UseCaseInterface import RepositoryInterface import BaseFeature import Entity +import RxSwift +import RxCocoa + public class InitialScreenVM: BaseViewModel { weak var coordinator: RootCoorinatable? @@ -27,6 +30,11 @@ public class InitialScreenVM: BaseViewModel { let centerProfileUseCase: CenterProfileUseCase let userInfoLocalRepository: UserInfoLocalRepository + // network monitoring + private let networkMonitor: NWPathMonitor = .init() + private let networkMonitoringQueue = DispatchQueue.global(qos: .background) + private let networtIsAvailablePublisher: PublishSubject = .init() + public init( coordinator: RootCoorinatable?, authUseCase: AuthUseCase, @@ -54,8 +62,47 @@ public class InitialScreenVM: BaseViewModel { }) .disposed(by: disposeBag) + // MARK: 네트워크 모니터링 시작 + let networkConnected: ReplaySubject = .create(bufferSize: 1) - viewWillAppear + // 최초 1회 네트워크 연결이벤트 전송 + networtIsAvailablePublisher + .filter { $0 } + .take(1) + .map { _ in } + .bind(to: networkConnected) + .disposed(by: disposeBag) + + // 네트워크가 연결되지 않은 경우 재시도 하며, 재시도 실패시 같은 플로우 반복 + networtIsAvailablePublisher.filter { !$0 } + .subscribe(onNext: { [weak self] _ in + + let alertVO = DefaultAlertContentVO( + title: "인터넷 연결이 불안정해요", + message: "Wi-Fi 또는 셀룰러 데이터 연결을 확인한 후 다시 시도해 주세요.", + dismissButtonLabelText: "다시 시도하기") { [weak self] in + + DispatchQueue.main.asyncAfter(deadline: .now()+1) { [weak self] in + guard let self else { return } + + if self.networkMonitor.currentPath.status == .unsatisfied { + + self.networtIsAvailablePublisher.onNext(false) + } + } + } + + // 네트워크 연결되지 않음 + self?.alert.onNext(alertVO) + }) + .disposed(by: disposeBag) + + startNeworkMonitoring() + + // MARK: 플로우 시작 + // 네트워크 연결이 최초 1회 확인된 이후 플로우 시작 + Observable + .combineLatest(viewWillAppear, networkConnected) .subscribe(onNext: { [weak self] _ in guard let self else { exit(0) } @@ -127,6 +174,35 @@ public class InitialScreenVM: BaseViewModel { .disposed(by: disposeBag) } + deinit { + networkMonitor.cancel() + } + + func startNeworkMonitoring() { + + networkMonitor.pathUpdateHandler = { [weak self] path in + + DispatchQueue.main.async { + self?.checkNetworkStatusAndPublish(status: path.status, delay: 0) + } + } + + networkMonitor.start(queue: networkMonitoringQueue) + } + + func checkNetworkStatusAndPublish(status: NWPath.Status, delay: Int) { + + switch status { + case .requiresConnection, .satisfied: + // requiresConnection는 일반적으로 즉시 연결이 가능한 상태 + networtIsAvailablePublisher.onNext(true) + return + default: + networtIsAvailablePublisher.onNext(false) + return + } + } + func centerInitialFlow() { // #1. 센터 상태를 확인함과 동시에 토큰 유효성 확인 @@ -144,7 +220,7 @@ public class InitialScreenVM: BaseViewModel { guard let self else { return } switch error { - case .tokenExpiredException: + case .tokenExpiredException, .tokenNotFound: // 토큰이 만료된 경우 coordinator?.auth() default: From bb23a6971142c464bf693d46d4f1c9b3f484b4b8 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 13 Sep 2024 17:44:46 +0900 Subject: [PATCH 3/7] =?UTF-8?q?[IDLE-000]=20Firebase=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dependency.swift | 1 + .../App/Resources/GoogleService-Info.plist | 30 +++++++++++++++++++ .../Projects/App/Sources/AppDelegate.swift | 5 ++++ .../ConcreteRepository/RemoteConfig/asd.swift | 24 +++++++++++++++ project/Projects/Data/Project.swift | 3 +- .../RemotConfig/RemoteConfigRepository.swift | 18 +++++++++++ project/Tuist/Package.swift | 4 ++- 7 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 project/Projects/App/Resources/GoogleService-Info.plist create mode 100644 project/Projects/Data/ConcreteRepository/RemoteConfig/asd.swift create mode 100644 project/Projects/Domain/RepositoryInterface/RemotConfig/RemoteConfigRepository.swift diff --git a/project/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency.swift b/project/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency.swift index e4c67a36..49a9b6bd 100644 --- a/project/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency.swift +++ b/project/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency.swift @@ -44,6 +44,7 @@ public extension ModuleDependency { public static let FSCalendar: TargetDependency = .external(name: "FSCalendar") public static let NaverMapSDKForSPM: TargetDependency = .external(name: "Junios.NMapSDKForSPM") public static let KingFisher: TargetDependency = .external(name: "Kingfisher") + public static let FirebaseRemoteConfig: TargetDependency = .external(name: "FirebaseRemoteConfig") } } diff --git a/project/Projects/App/Resources/GoogleService-Info.plist b/project/Projects/App/Resources/GoogleService-Info.plist new file mode 100644 index 00000000..e902d244 --- /dev/null +++ b/project/Projects/App/Resources/GoogleService-Info.plist @@ -0,0 +1,30 @@ + + + + + API_KEY + AIzaSyB4eId4P8P3XlpHIVINfVpyB-pMQmqNxQM + GCM_SENDER_ID + 740246202578 + PLIST_VERSION + 1 + BUNDLE_ID + com.idle.caremeet + PROJECT_ID + swm-3idiots + STORAGE_BUCKET + swm-3idiots.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:740246202578:ios:d5e59f4f2116f92342250b + + \ No newline at end of file diff --git a/project/Projects/App/Sources/AppDelegate.swift b/project/Projects/App/Sources/AppDelegate.swift index 2a996576..d7f956d3 100644 --- a/project/Projects/App/Sources/AppDelegate.swift +++ b/project/Projects/App/Sources/AppDelegate.swift @@ -9,6 +9,8 @@ import UIKit import AppTrackingTransparency import AdSupport import PresentationCore +import FirebaseCore + @main class AppDelegate: UIResponder, UIApplicationDelegate { @@ -21,6 +23,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { self?.requestTrackingAuthorization() }) + // FireBase setting + FirebaseApp.configure() + return true } diff --git a/project/Projects/Data/ConcreteRepository/RemoteConfig/asd.swift b/project/Projects/Data/ConcreteRepository/RemoteConfig/asd.swift new file mode 100644 index 00000000..cf7ac8cc --- /dev/null +++ b/project/Projects/Data/ConcreteRepository/RemoteConfig/asd.swift @@ -0,0 +1,24 @@ +// +// asd.swift +// ConcreteRepository +// +// Created by choijunios on 9/13/24. +// + +import Foundation +import RepositoryInterface + +import RxSwift +import FirebaseRemoteConfig + +//public class DefaultRemoteConfigRepository: RemoteConfigRepository { +// +// +// public func getConfigValue(key: String) -> RxSwift.Single { +// +// } +// +// public func checkConfigIsChange() -> RxSwift.Single { +// +// } +//} diff --git a/project/Projects/Data/Project.swift b/project/Projects/Data/Project.swift index 3745174c..68b28f0a 100644 --- a/project/Projects/Data/Project.swift +++ b/project/Projects/Data/Project.swift @@ -30,7 +30,8 @@ let project = Project( D.Data.DataSource, // ThirdParty - D.ThirdParty.RxSwift + D.ThirdParty.RxSwift, + D.ThirdParty.FirebaseRemoteConfig ], settings: .settings( base: ["ENABLE_TESTABILITY": "YES"] diff --git a/project/Projects/Domain/RepositoryInterface/RemotConfig/RemoteConfigRepository.swift b/project/Projects/Domain/RepositoryInterface/RemotConfig/RemoteConfigRepository.swift new file mode 100644 index 00000000..3d48b21c --- /dev/null +++ b/project/Projects/Domain/RepositoryInterface/RemotConfig/RemoteConfigRepository.swift @@ -0,0 +1,18 @@ +// +// RemoteConfigRepository.swift +// RepositoryInterface +// +// Created by choijunios on 9/13/24. +// + +import Foundation +import RxSwift + +public protocol RemoteConfigRepository: RepositoryBase { + + /// Remote config의 특정 키값에 매칭되는 값을 가져옵니다. + func getConfigValue(key: String) -> Single + + /// Remote config가 변경되었는지 확인합니다. + func checkConfigIsChange() -> Single +} diff --git a/project/Tuist/Package.swift b/project/Tuist/Package.swift index c57a0c65..0b515062 100644 --- a/project/Tuist/Package.swift +++ b/project/Tuist/Package.swift @@ -41,6 +41,8 @@ let package = Package( // Naver map .package(url: "https://github.com/J0onYEong/NaverMapSDKForSPM.git", from: "1.0.0"), // KingFisher - .package(url: "https://github.com/onevcat/Kingfisher.git", from: "7.12.0") + .package(url: "https://github.com/onevcat/Kingfisher.git", from: "7.12.0"), + // Firebase + .package(url: "https://github.com/firebase/firebase-ios-sdk.git", from: "11.2.0"), ] ) From f0035e8d0bd70eb0098a0cbc54d594546b9b3d40 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 13 Sep 2024 18:54:28 +0900 Subject: [PATCH 4/7] =?UTF-8?q?[IDLE-000]=20=EA=B0=95=EC=A0=9C=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProjectDescriptionHelpers/InfoPlist.swift | 2 + .../Sources/DI/Assembly/DataAssembly.swift | 5 + .../RootCoordinator/RootCoordinator.swift | 3 +- .../ConcreteRepository/RemoteConfig/asd.swift | 56 +++++++-- .../Entity/Config/ForceUpdateInfo.swift | 14 +++ .../RemotConfig/RemoteConfigRepository.swift | 10 +- .../Alert /IdleBigAlertController.swift | 7 +- .../InitialScreen/InitialScreenVM.swift | 117 +++++++++++++++--- 8 files changed, 180 insertions(+), 34 deletions(-) create mode 100644 project/Projects/Domain/Entity/Config/ForceUpdateInfo.swift diff --git a/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift b/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift index cbf692c6..84ee98d8 100644 --- a/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift +++ b/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/InfoPlist.swift @@ -13,6 +13,8 @@ public enum IdleInfoPlist { "CFBundleDisplayName": "케어밋", + "CFBundleShortVersionString" : "1.0.0", + "NSAppTransportSecurity" : [ "NSAllowsArbitraryLoads" : true ], diff --git a/project/Projects/App/Sources/DI/Assembly/DataAssembly.swift b/project/Projects/App/Sources/DI/Assembly/DataAssembly.swift index be84b0d0..7a337df4 100644 --- a/project/Projects/App/Sources/DI/Assembly/DataAssembly.swift +++ b/project/Projects/App/Sources/DI/Assembly/DataAssembly.swift @@ -46,5 +46,10 @@ public struct DataAssembly: Assembly { container.register(RecruitmentPostRepository.self) { _ in return DefaultRecruitmentPostRepository() } + + // MARK: RemoteConfig + container.register(RemoteConfigRepository.self) { _ in + return DefaultRemoteConfigRepository() + } } } diff --git a/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift index 159f158f..87e41458 100644 --- a/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift @@ -38,7 +38,8 @@ class RootCoordinator { authUseCase: injector.resolve(AuthUseCase.self), workerProfileUseCase: injector.resolve(WorkerProfileUseCase.self), centerProfileUseCase: injector.resolve(CenterProfileUseCase.self), - userInfoLocalRepository: injector.resolve(UserInfoLocalRepository.self) + userInfoLocalRepository: injector.resolve(UserInfoLocalRepository.self), + remoteConfigRepository: injector.resolve(RemoteConfigRepository.self) ) vc.bind(viewModel: vm) diff --git a/project/Projects/Data/ConcreteRepository/RemoteConfig/asd.swift b/project/Projects/Data/ConcreteRepository/RemoteConfig/asd.swift index cf7ac8cc..18804d06 100644 --- a/project/Projects/Data/ConcreteRepository/RemoteConfig/asd.swift +++ b/project/Projects/Data/ConcreteRepository/RemoteConfig/asd.swift @@ -10,15 +10,49 @@ import RepositoryInterface import RxSwift import FirebaseRemoteConfig +import Entity -//public class DefaultRemoteConfigRepository: RemoteConfigRepository { -// -// -// public func getConfigValue(key: String) -> RxSwift.Single { -// -// } -// -// public func checkConfigIsChange() -> RxSwift.Single { -// -// } -//} +public class DefaultRemoteConfigRepository: RemoteConfigRepository { + + private let remoteConfig = RemoteConfig.remoteConfig() + private let settings = RemoteConfigSettings() + + public init() { + remoteConfig.configSettings = settings + } + + public func fetchRemoteConfig() -> RxSwift.Single> { + + Single.create { [weak self] single in + + self?.remoteConfig.fetch { status, error in + + if status == .success { + self?.remoteConfig.activate() + single(.success(.success(true))) + } else { + single(.success(.success(false))) + } + } + + return Disposables.create { } + } + } + + public func getForceUpdateInfo() -> ForceUpdateInfo? { + let jsonData = remoteConfig["forceUpdate_iOS"].jsonValue + + if let jsonData { + + do { + let data = try JSONSerialization.data(withJSONObject: jsonData) + let decoded = try JSONDecoder().decode(ForceUpdateInfo.self, from: data) + return decoded + } catch { + + return nil + } + } + return nil + } +} diff --git a/project/Projects/Domain/Entity/Config/ForceUpdateInfo.swift b/project/Projects/Domain/Entity/Config/ForceUpdateInfo.swift new file mode 100644 index 00000000..00007e40 --- /dev/null +++ b/project/Projects/Domain/Entity/Config/ForceUpdateInfo.swift @@ -0,0 +1,14 @@ +// +// ForceUpdateInfo.swift +// Entity +// +// Created by choijunios on 9/13/24. +// + +import Foundation + +public struct ForceUpdateInfo: Decodable { + public let minVersion: String + public let marketUrl: String + public let noticeMsg: String +} diff --git a/project/Projects/Domain/RepositoryInterface/RemotConfig/RemoteConfigRepository.swift b/project/Projects/Domain/RepositoryInterface/RemotConfig/RemoteConfigRepository.swift index 3d48b21c..17886bc4 100644 --- a/project/Projects/Domain/RepositoryInterface/RemotConfig/RemoteConfigRepository.swift +++ b/project/Projects/Domain/RepositoryInterface/RemotConfig/RemoteConfigRepository.swift @@ -6,13 +6,15 @@ // import Foundation +import Entity + import RxSwift public protocol RemoteConfigRepository: RepositoryBase { - /// Remote config의 특정 키값에 매칭되는 값을 가져옵니다. - func getConfigValue(key: String) -> Single + /// RemoteConfig를 fetch합니다. + func fetchRemoteConfig() -> Single> - /// Remote config가 변경되었는지 확인합니다. - func checkConfigIsChange() -> Single + /// 강제업데이트 정보를 가져옵니다. + func getForceUpdateInfo() -> ForceUpdateInfo? } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Alert /IdleBigAlertController.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Alert /IdleBigAlertController.swift index 574354cf..8c9e1649 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Alert /IdleBigAlertController.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Alert /IdleBigAlertController.swift @@ -38,7 +38,8 @@ public class DefaultIdleAlertVM: IdleAlertViewModelable { description: String, acceptButtonLabelText: String, cancelButtonLabelText: String, - onAccepted: (() -> ())? = nil + onAccepted: (() -> ())? = nil, + onCanceled: (() -> ())? = nil ) { self.title = title self.description = description @@ -50,7 +51,9 @@ public class DefaultIdleAlertVM: IdleAlertViewModelable { acceptButtonClicked .map({ _ in onAccepted?() }) .asObservable(), - cancelButtonClicked.asObservable() + cancelButtonClicked + .map({ _ in onCanceled?() }) + .asObservable() ) .asDriver(onErrorDriveWith: .never()) } diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift index 8aa59865..030eb2d4 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift @@ -5,7 +5,7 @@ // Created by choijunios on 8/25/24. // -import Foundation +import UIKit import Network import PresentationCore @@ -13,6 +13,7 @@ import UseCaseInterface import RepositoryInterface import BaseFeature import Entity +import DSKit import RxSwift import RxCocoa @@ -29,6 +30,7 @@ public class InitialScreenVM: BaseViewModel { let workerProfileUseCase: WorkerProfileUseCase let centerProfileUseCase: CenterProfileUseCase let userInfoLocalRepository: UserInfoLocalRepository + let remoteConfigRepository: RemoteConfigRepository // network monitoring private let networkMonitor: NWPathMonitor = .init() @@ -40,7 +42,8 @@ public class InitialScreenVM: BaseViewModel { authUseCase: AuthUseCase, workerProfileUseCase: WorkerProfileUseCase, centerProfileUseCase: CenterProfileUseCase, - userInfoLocalRepository: UserInfoLocalRepository + userInfoLocalRepository: UserInfoLocalRepository, + remoteConfigRepository: RemoteConfigRepository ) { self.coordinator = coordinator @@ -48,20 +51,10 @@ public class InitialScreenVM: BaseViewModel { self.workerProfileUseCase = workerProfileUseCase self.centerProfileUseCase = centerProfileUseCase self.userInfoLocalRepository = userInfoLocalRepository + self.remoteConfigRepository = remoteConfigRepository super.init() - // MARK: 로그아웃, 회원탈퇴시 - NotificationCenter.default.rx.notification(.popToInitialVC) - .observe(on: MainScheduler.instance) - .subscribe(onNext: { [weak self] _ in - - guard let self else { return } - - self.coordinator?.popToRoot() - }) - .disposed(by: disposeBag) - // MARK: 네트워크 모니터링 시작 let networkConnected: ReplaySubject = .create(bufferSize: 1) @@ -99,10 +92,95 @@ public class InitialScreenVM: BaseViewModel { startNeworkMonitoring() + + // MARK: 강제업데이트 확인 + // 네트워크 확인 -> 강제업데이트 확인 + let fetchRemoteConfig = networkConnected + .flatMap { [remoteConfigRepository] _ in + remoteConfigRepository.fetchRemoteConfig() + } + + let needsForceUpdate = fetchRemoteConfig + .compactMap { $0.value } + .map { [remoteConfigRepository] isConfigFetched in + + if !isConfigFetched { + + // ‼️ Config로딩 불가시 크래쉬 + exit(0) + } + + // ‼️ fetch 불가시 크래쉬 + return remoteConfigRepository.getForceUpdateInfo()! + } + .map { info in + + let minAppVersion = info.minVersion + + let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String + print("앱 버전: \(appVersion) 최소앱버전: \(minAppVersion)") + + return minAppVersion > appVersion + } + .share() + + // 강제업데이트 필요 + needsForceUpdate + .filter { $0 } + .subscribe(onNext: { + [weak self] _ in + + guard let self else { return } + + // 네트워크 연결되지 않음 + let object = IdleAlertObject() + .setTitle("최신 버전의 앱이 있어요") + .setDescription("유저분들의 의견을 반영해 앱을 더 발전시켰어요.\n보다 좋은 서비스를 만나기 위해, 업데이트해주세요.") + .setAcceptButtonLabelText("앱 종료") + .setCancelButtonLabelText("앱 업데이트") + + object + .cancelButtonClicked + .subscribe(onNext: { [weak self] in + self?.openAppStoreForUpdate() + }) + .disposed(by: disposeBag) + + object + .acceptButtonClicked + .subscribe(onNext: { + exit(0) + }) + .disposed(by: disposeBag) + + alertObject.onNext(object) + }) + .disposed(by: disposeBag) + + // 강제업데이트 필요하지 않음 + let forceUpdateChecked = needsForceUpdate.filter { !$0 } + + // MARK: 로그아웃, 회원탈퇴시 + NotificationCenter.default.rx.notification(.popToInitialVC) + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak self] _ in + + guard let self else { return } + + self.coordinator?.popToRoot() + }) + .disposed(by: disposeBag) + // MARK: 플로우 시작 - // 네트워크 연결이 최초 1회 확인된 이후 플로우 시작 + // 네트워크 연결확인 -> 강제업데이트 확인 -> 유저별 플로우 시작 Observable - .combineLatest(viewWillAppear, networkConnected) + .combineLatest( + // 강제업데이트 확인 완료 + forceUpdateChecked, + + // viewWillAppear + viewWillAppear + ) .subscribe(onNext: { [weak self] _ in guard let self else { exit(0) } @@ -303,5 +381,12 @@ public class InitialScreenVM: BaseViewModel { }) .disposed(by: disposeBag) } - + + func openAppStoreForUpdate() { + let url = "itms-apps://itunes.apple.com/app/6670529341"; + if let url = URL(string: url), UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:]) + } + } } + From 603d04ddc4ea670652b2c389f707fe670c7b69a9 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Sat, 14 Sep 2024 17:20:09 +0900 Subject: [PATCH 5/7] =?UTF-8?q?[IDLE-000]=20FireBase=20crashlytics=20?= =?UTF-8?q?=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TargetScripts.swift | 29 +++++++++++++++++ .../Dependency.swift | 2 ++ project/Projects/App/Project.swift | 7 ++++- .../Projects/App/Sources/AppDelegate.swift | 3 -- .../{asd.swift => CrawlingPostService.swift} | 0 .../InitialScreen/InitialScreenVC.swift | 10 +++--- .../InitialScreen/InitialScreenVM.swift | 31 +++++++++++-------- 7 files changed, 59 insertions(+), 23 deletions(-) create mode 100644 project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/TargetScripts.swift rename project/Projects/Data/DataSource/Service/{asd.swift => CrawlingPostService.swift} (100%) diff --git a/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/TargetScripts.swift b/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/TargetScripts.swift new file mode 100644 index 00000000..b77038b3 --- /dev/null +++ b/project/Plugins/ConfigurationPlugin/ProjectDescriptionHelpers/TargetScripts.swift @@ -0,0 +1,29 @@ +// +// TargetScripts.swift +// ConfigurationPlugin +// +// Created by choijunios on 9/14/24. +// + +import Foundation +import ProjectDescription + +// MARK: Firebase crashlytics + +public extension TargetScript { + + static let crashlyticsScript: TargetScript = .post( + script: """ + ROOT_DIR=\(ProcessInfo.processInfo.environment["TUIST_ROOT_DIR"] ?? "") + "${ROOT_DIR}/Tuist/.build/checkouts/firebase-ios-sdk/Crashlytics/run" + """, + name: "Firebase Crashlytics", + inputPaths: [ + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}", + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist", + "$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist", + "$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)", + ], + basedOnDependencyAnalysis: false + ) +} diff --git a/project/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency.swift b/project/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency.swift index 49a9b6bd..3e33d3e8 100644 --- a/project/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency.swift +++ b/project/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency.swift @@ -45,6 +45,8 @@ public extension ModuleDependency { public static let NaverMapSDKForSPM: TargetDependency = .external(name: "Junios.NMapSDKForSPM") public static let KingFisher: TargetDependency = .external(name: "Kingfisher") public static let FirebaseRemoteConfig: TargetDependency = .external(name: "FirebaseRemoteConfig") + public static let FirebaseCrashlytics: TargetDependency = .external(name: "FirebaseCrashlytics") + public static let FirebaseAnalytics: TargetDependency = .external(name: "FirebaseAnalytics") } } diff --git a/project/Projects/App/Project.swift b/project/Projects/App/Project.swift index 2f9f9405..e2b0c6dc 100644 --- a/project/Projects/App/Project.swift +++ b/project/Projects/App/Project.swift @@ -27,6 +27,9 @@ let project = Project( infoPlist: IdleInfoPlist.appDefault, sources: ["Sources/**"], resources: ["Resources/**"], + scripts: [ + .crashlyticsScript + ], dependencies: [ // Presentation @@ -39,7 +42,9 @@ let project = Project( D.Data.ConcreteRepository, // ThirdParty - D.ThirdParty.Swinject + D.ThirdParty.Swinject, + D.ThirdParty.FirebaseCrashlytics, + D.ThirdParty.FirebaseAnalytics, ], settings: .settings( configurations: IdleConfiguration.appConfigurations diff --git a/project/Projects/App/Sources/AppDelegate.swift b/project/Projects/App/Sources/AppDelegate.swift index d7f956d3..69eba4b1 100644 --- a/project/Projects/App/Sources/AppDelegate.swift +++ b/project/Projects/App/Sources/AppDelegate.swift @@ -11,12 +11,9 @@ import AdSupport import PresentationCore import FirebaseCore - @main class AppDelegate: UIResponder, UIApplicationDelegate { - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: { [weak self] in diff --git a/project/Projects/Data/DataSource/Service/asd.swift b/project/Projects/Data/DataSource/Service/CrawlingPostService.swift similarity index 100% rename from project/Projects/Data/DataSource/Service/asd.swift rename to project/Projects/Data/DataSource/Service/CrawlingPostService.swift diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVC.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVC.swift index 8326d50b..98ade75d 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVC.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVC.swift @@ -8,11 +8,12 @@ import UIKit import BaseFeature import PresentationCore -import RxCocoa -import RxSwift import Entity import DSKit +import RxCocoa +import RxSwift + public class InitialScreenVC: BaseViewController { // Init @@ -45,10 +46,7 @@ public class InitialScreenVC: BaseViewController { super.bind(viewModel: viewModel) self.rx.viewDidAppear - .map { _ in - - printIfDebug("InitialScreenVC viewWillAppear") - } + .map({ _ in }) .bind(to: viewModel.viewWillAppear) .disposed(by: disposeBag) } diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift index 030eb2d4..a0d3866b 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift @@ -118,7 +118,8 @@ public class InitialScreenVM: BaseViewModel { let minAppVersion = info.minVersion let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String - print("앱 버전: \(appVersion) 최소앱버전: \(minAppVersion)") + + printIfDebug("앱 버전: \(appVersion) 최소앱버전: \(minAppVersion)") return minAppVersion > appVersion } @@ -159,19 +160,8 @@ public class InitialScreenVM: BaseViewModel { // 강제업데이트 필요하지 않음 let forceUpdateChecked = needsForceUpdate.filter { !$0 } - - // MARK: 로그아웃, 회원탈퇴시 - NotificationCenter.default.rx.notification(.popToInitialVC) - .observe(on: MainScheduler.instance) - .subscribe(onNext: { [weak self] _ in - - guard let self else { return } - - self.coordinator?.popToRoot() - }) - .disposed(by: disposeBag) - // MARK: 플로우 시작 + // MARK: 유저별 플로우 시작 // 네트워크 연결확인 -> 강제업데이트 확인 -> 유저별 플로우 시작 Observable .combineLatest( @@ -201,6 +191,18 @@ public class InitialScreenVM: BaseViewModel { }) .disposed(by: disposeBag) + + + // MARK: 로그아웃, 회원탈퇴시 + NotificationCenter.default.rx.notification(.popToInitialVC) + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak self] _ in + + guard let self else { return } + + self.coordinator?.popToRoot() + }) + .disposed(by: disposeBag) } func workerInitialFlow() { @@ -274,6 +276,8 @@ public class InitialScreenVM: BaseViewModel { case .requiresConnection, .satisfied: // requiresConnection는 일반적으로 즉시 연결이 가능한 상태 networtIsAvailablePublisher.onNext(true) + networtIsAvailablePublisher.onCompleted() + networkMonitor.cancel() return default: networtIsAvailablePublisher.onNext(false) @@ -382,6 +386,7 @@ public class InitialScreenVM: BaseViewModel { .disposed(by: disposeBag) } + /// 앱스토에에서 해당앱을 엽니다. func openAppStoreForUpdate() { let url = "itms-apps://itunes.apple.com/app/6670529341"; if let url = URL(string: url), UIApplication.shared.canOpenURL(url) { From 5c5e90f88278a4b9029aa3420db1ae577c5ca9b8 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Sat, 14 Sep 2024 18:02:43 +0900 Subject: [PATCH 6/7] =?UTF-8?q?[IDLE-000]=20=ED=81=AC=EB=9E=98=EC=89=AC?= =?UTF-8?q?=EB=A6=AC=ED=8B=B1=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/NativePostDetailForWorkerVM.swift | 4 ++-- .../Common/InitialScreen/InitialScreenVM.swift | 17 +++++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift index a5d44281..bb0b00ac 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift @@ -191,9 +191,9 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork title: "공고에 지원하시겠어요?", description: "", acceptButtonLabelText: "지원하기", - cancelButtonLabelText: "취소하기") { + cancelButtonLabelText: "취소하기", onCanceled: { applyRequest.accept(()) - } + }) } .asDriver(onErrorDriveWith: .never()) diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift index a0d3866b..4b3eec39 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift @@ -95,23 +95,23 @@ public class InitialScreenVM: BaseViewModel { // MARK: 강제업데이트 확인 // 네트워크 확인 -> 강제업데이트 확인 - let fetchRemoteConfig = networkConnected + let needsForceUpdate = networkConnected .flatMap { [remoteConfigRepository] _ in remoteConfigRepository.fetchRemoteConfig() } - - let needsForceUpdate = fetchRemoteConfig .compactMap { $0.value } .map { [remoteConfigRepository] isConfigFetched in if !isConfigFetched { - + printIfDebug("Remote Config 로딩 실패") + } + + guard let config = remoteConfigRepository.getForceUpdateInfo() else { // ‼️ Config로딩 불가시 크래쉬 exit(0) } - // ‼️ fetch 불가시 크래쉬 - return remoteConfigRepository.getForceUpdateInfo()! + return config } .map { info in @@ -386,6 +386,11 @@ public class InitialScreenVM: BaseViewModel { .disposed(by: disposeBag) } + func checkForceUpdate() { + + + } + /// 앱스토에에서 해당앱을 엽니다. func openAppStoreForUpdate() { let url = "itms-apps://itunes.apple.com/app/6670529341"; From 01c55aa3a4dfbb28901f96c802fabcff351612a6 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Sat, 14 Sep 2024 18:10:03 +0900 Subject: [PATCH 7/7] =?UTF-8?q?[IDLE-000]=20=ED=81=AC=EB=9E=98=EC=89=AC?= =?UTF-8?q?=EB=A6=AC=ED=8B=B1=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- project/Projects/App/Project.swift | 2 -- project/Projects/Presentation/Feature/Base/Project.swift | 2 ++ .../Screen/Common/InitialScreen/InitialScreenVM.swift | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/project/Projects/App/Project.swift b/project/Projects/App/Project.swift index e2b0c6dc..5dfd820c 100644 --- a/project/Projects/App/Project.swift +++ b/project/Projects/App/Project.swift @@ -43,8 +43,6 @@ let project = Project( // ThirdParty D.ThirdParty.Swinject, - D.ThirdParty.FirebaseCrashlytics, - D.ThirdParty.FirebaseAnalytics, ], settings: .settings( configurations: IdleConfiguration.appConfigurations diff --git a/project/Projects/Presentation/Feature/Base/Project.swift b/project/Projects/Presentation/Feature/Base/Project.swift index c1d364b0..f0c7e4e2 100644 --- a/project/Projects/Presentation/Feature/Base/Project.swift +++ b/project/Projects/Presentation/Feature/Base/Project.swift @@ -39,6 +39,8 @@ let project = Project( D.ThirdParty.RxSwift, D.ThirdParty.RxCocoa, D.ThirdParty.NaverMapSDKForSPM, + D.ThirdParty.FirebaseCrashlytics, + D.ThirdParty.FirebaseAnalytics, ], settings: .settings( configurations: IdleConfiguration.presentationConfigurations diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift index 4b3eec39..a98b924e 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift @@ -17,6 +17,8 @@ import DSKit import RxSwift import RxCocoa +import FirebaseCrashlytics + public class InitialScreenVM: BaseViewModel { @@ -103,12 +105,13 @@ public class InitialScreenVM: BaseViewModel { .map { [remoteConfigRepository] isConfigFetched in if !isConfigFetched { - printIfDebug("Remote Config 로딩 실패") + Crashlytics.crashlytics().log("Remote Config fetch실패") } guard let config = remoteConfigRepository.getForceUpdateInfo() else { // ‼️ Config로딩 불가시 크래쉬 - exit(0) + Crashlytics.crashlytics().log("Remote Config획득 실패") + fatalError("Remote Config fetching에러") } return config