diff --git a/project/Projects/App/Resources/GoogleService-Info.plist b/project/Projects/App/Resources/GoogleService-Info.plist index e902d244..35260fe6 100644 --- a/project/Projects/App/Resources/GoogleService-Info.plist +++ b/project/Projects/App/Resources/GoogleService-Info.plist @@ -15,16 +15,16 @@ 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/Presentation/Feature/Base/Sources/DIAssembly/DataAssembly.swift b/project/Projects/App/Sources/DIAssembly/DataAssembly.swift similarity index 80% rename from project/Projects/Presentation/Feature/Base/Sources/DIAssembly/DataAssembly.swift rename to project/Projects/App/Sources/DIAssembly/DataAssembly.swift index 6bfcee48..5e13e8c9 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/DIAssembly/DataAssembly.swift +++ b/project/Projects/App/Sources/DIAssembly/DataAssembly.swift @@ -9,6 +9,7 @@ import Foundation import Domain import Repository import DataSource +import RootFeature import Swinject @@ -57,5 +58,15 @@ public struct DataAssembly: Assembly { container.register(RecruitmentPostRepository.self) { _ in return DefaultRecruitmentPostRepository() } + + // MARK: 토큰 전송 레포지토리 + container.register(NotificationTokenTransferRepository.self) { _ in + return DefaultNotificationTokenTransferRepository() + } + + // MARK: 토큰 획득 레포지토리 + container.register(NotificationTokenRepository.self) { _ in + return RootFeature.FCMTokenRepository() + } } } diff --git a/project/Projects/Presentation/Feature/Base/Sources/DIAssembly/DomainAssembly.swift b/project/Projects/App/Sources/DIAssembly/DomainAssembly.swift similarity index 67% rename from project/Projects/Presentation/Feature/Base/Sources/DIAssembly/DomainAssembly.swift rename to project/Projects/App/Sources/DIAssembly/DomainAssembly.swift index 9e70eede..0115cb24 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/DIAssembly/DomainAssembly.swift +++ b/project/Projects/App/Sources/DIAssembly/DomainAssembly.swift @@ -17,22 +17,19 @@ public struct DomainAssembly: Assembly { public func assemble(container: Container) { // MARK: UseCase + container.register(NotificationTokenUseCase.self) { _ in + return DefaultNotificationTokenUseCase() + } + .inObjectScope(.container) + container.register(AuthInputValidationUseCase.self) { resolver in let repository = resolver.resolve(AuthInputValidationRepository.self)! return DefaultAuthInputValidationUseCase(repository: repository) } - container.register(AuthUseCase.self) { resolver in - let authRepository = resolver.resolve(AuthRepository.self)! - let userProfileRepository = resolver.resolve(UserProfileRepository.self)! - let userInfoLocalRepository = resolver.resolve(UserInfoLocalRepository.self)! - - return DefaultAuthUseCase( - authRepository: authRepository, - userProfileRepository: userProfileRepository, - userInfoLocalRepository: userInfoLocalRepository - ) + container.register(AuthUseCase.self) { _ in + return DefaultAuthUseCase() } container.register(CenterProfileUseCase.self) { resolver in @@ -61,14 +58,8 @@ public struct DomainAssembly: Assembly { ) } - container.register(SettingScreenUseCase.self) { resolver in - let authRepository = resolver.resolve(AuthRepository.self)! - let userInfoLocalRepository = resolver.resolve(UserInfoLocalRepository.self)! - - return DefaultSettingUseCase( - authRepository: authRepository, - userInfoLocalRepository: userInfoLocalRepository - ) + container.register(SettingScreenUseCase.self) { _ in + return DefaultSettingUseCase() } container.register(CenterCertificateUseCase.self) { resolver in @@ -80,9 +71,5 @@ public struct DomainAssembly: Assembly { userInfoLocalRepository: userInfoLocalRepository ) } - - container.register(NotificationTokenManage.self) { resolver in - DefaultNotificationTokenManage() - } } } diff --git a/project/Projects/App/Sources/DIAssembly/LoggerAssembly.swift b/project/Projects/App/Sources/DIAssembly/LoggerAssembly.swift index 3ee4f9aa..2772a77b 100644 --- a/project/Projects/App/Sources/DIAssembly/LoggerAssembly.swift +++ b/project/Projects/App/Sources/DIAssembly/LoggerAssembly.swift @@ -8,7 +8,6 @@ import Foundation import RootFeature import AuthFeature -import CenterFeature import PresentationCore import CenterMainPageFeature diff --git a/project/Projects/App/Sources/RemoteNotification/FCMService.swift b/project/Projects/App/Sources/RemoteNotification/FCMService.swift deleted file mode 100644 index a1a3a021..00000000 --- a/project/Projects/App/Sources/RemoteNotification/FCMService.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// FCMService.swift -// Idle-iOS -// -// Created by choijunios on 9/24/24. -// - -import Foundation -import BaseFeature -import PresentationCore -import Domain -import Core - -import FirebaseMessaging - -class FCMService: NSObject { - - @Injected var notificationTokenManageUseCase: NotificationTokenManage - - override public init() { - super.init() - Messaging.messaging().delegate = self - - - // Notification설정 - subscribeNotification() - } - - func subscribeNotification() { - - NotificationCenter.default.addObserver( - forName: .requestTransportTokenToServer, - object: nil, - queue: nil) { [weak self] _ in - - guard let self else { return } - - if let token = Messaging.messaging().fcmToken { - - notificationTokenManageUseCase.setNotificationToken( - token: token) { result in - - print("FCMService 토큰 전송 \(result ? "완료" : "실패")") - } - } - } - - NotificationCenter.default.addObserver( - forName: .requestDeleteTokenFromServer, - object: nil, - queue: nil) { [weak self] _ in - - guard let self else { return } - - notificationTokenManageUseCase.deleteNotificationToken(completion: { result in - print("FCMService 토큰 삭제 \(result ? "완료" : "실패")") - }) - } - } -} - -extension FCMService: MessagingDelegate { - - func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { - - if let fcmToken { - - print("FCM토큰: \(fcmToken)") - - notificationTokenManageUseCase.setNotificationToken(token: fcmToken) { isSuccess in - - print(isSuccess ? "토큰 전송 성공" : "토큰 전송 실패") - } - } - } -} - - -extension FCMService: UNUserNotificationCenterDelegate { - - /// 앱이 포그라운드에 있는 경우, 노티페이케이션이 도착하기만 하면 호출된다. - public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - - } - - /// 앱이 백그라운드에 있는 경우, 유저가 노티피케이션을 통해 액션을 선택한 경우 호출 - public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { - - print(response.notification.request.content.userInfo) - } -} diff --git a/project/Projects/App/Sources/SceneDelegate.swift b/project/Projects/App/Sources/SceneDelegate.swift index 7474990c..95ea1ebf 100644 --- a/project/Projects/App/Sources/SceneDelegate.swift +++ b/project/Projects/App/Sources/SceneDelegate.swift @@ -23,9 +23,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { return coodinator }() - // FCMService - var fcmService: FCMService! - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = scene as? UIWindowScene else { return } @@ -41,9 +38,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { DataAssembly(), DomainAssembly(), ]) - - // FCMService - fcmService = FCMService() // Start AppCoodinator appCoordinator.start() diff --git a/project/Projects/Data/DataSource/API/BaseAPI.swift b/project/Projects/Data/DataSource/API/BaseAPI.swift index 6e0e2cb7..3c16127e 100644 --- a/project/Projects/Data/DataSource/API/BaseAPI.swift +++ b/project/Projects/Data/DataSource/API/BaseAPI.swift @@ -16,6 +16,7 @@ public enum APIType { case crawling_job_postings case external(url: String) case applys + case notificationToken } // MARK: BaseAPI @@ -43,6 +44,8 @@ public extension BaseAPI { baseStr += "/applys" case .external(let url): baseStr = url + case .notificationToken: + baseStr += "/fcm" } return URL(string: baseStr)! diff --git a/project/Projects/Data/DataSource/API/NotificationTokenAPI.swift b/project/Projects/Data/DataSource/API/NotificationTokenAPI.swift new file mode 100644 index 00000000..7be6452e --- /dev/null +++ b/project/Projects/Data/DataSource/API/NotificationTokenAPI.swift @@ -0,0 +1,62 @@ +// +// NotificationTokenAPI.swift +// DataSource +// +// Created by choijunios on 10/8/24. +// + +import Foundation +import Moya + +public enum NotificationTokenAPI { + case saveToken(deviceToken: String, userType: String) + case deleteToken(deviceToken: String) +} + +extension NotificationTokenAPI: BaseAPI { + + public var apiType: APIType { + .notificationToken + } + + public var path: String { + switch self { + case .saveToken(let deviceToken, let userType): + "token" + case .deleteToken(let deviceToken): + "token" + } + } + + public var method: Moya.Method { + switch self { + case .saveToken: + .post + case .deleteToken: + .delete + } + } + + var parameterEncoding: ParameterEncoding { + switch self { + default: + return JSONEncoding.default + } + } + + public var task: Moya.Task { + switch self { + case .saveToken(let deviceToken, let userType): + var bodyData: [String: String] = [ + "deviceToken": deviceToken, + "userType": userType + ] + return .requestParameters(parameters: bodyData, encoding: parameterEncoding) + case .deleteToken(let deviceToken): + var bodyData: [String: String] = [ + "deviceToken": deviceToken + ] + return .requestParameters(parameters: bodyData, encoding: parameterEncoding) + } + } +} diff --git a/project/Projects/Data/DataSource/Service/NotificationTokenTransferService.swift b/project/Projects/Data/DataSource/Service/NotificationTokenTransferService.swift new file mode 100644 index 00000000..1bce1ab9 --- /dev/null +++ b/project/Projects/Data/DataSource/Service/NotificationTokenTransferService.swift @@ -0,0 +1,17 @@ +// +// NotificationTokenTransferService.swift +// DataSource +// +// Created by choijunios on 10/8/24. +// + +import Foundation + +public class NotificationTokenTransferService: BaseNetworkService { + + public init() { } + + public override init(keyValueStore: KeyValueStore) { + super.init(keyValueStore: keyValueStore) + } +} diff --git a/project/Projects/Data/Repository/Cache/CacheRepository.swift b/project/Projects/Data/Repository/CacheRepository.swift similarity index 100% rename from project/Projects/Data/Repository/Cache/CacheRepository.swift rename to project/Projects/Data/Repository/CacheRepository.swift diff --git a/project/Projects/Data/Repository/Auth/DefaultAuthInputValidationRepository.swift b/project/Projects/Data/Repository/DefaultAuthInputValidationRepository.swift similarity index 51% rename from project/Projects/Data/Repository/Auth/DefaultAuthInputValidationRepository.swift rename to project/Projects/Data/Repository/DefaultAuthInputValidationRepository.swift index 0cc4734b..1f68a9e6 100644 --- a/project/Projects/Data/Repository/Auth/DefaultAuthInputValidationRepository.swift +++ b/project/Projects/Data/Repository/DefaultAuthInputValidationRepository.swift @@ -8,6 +8,7 @@ import Foundation import DataSource import Domain +import Core import RxSwift @@ -18,30 +19,40 @@ public class DefaultAuthInputValidationRepository: AuthInputValidationRepository public init() { } - public func requestPhoneNumberAuthentication(phoneNumber: String) -> Single { + public func requestPhoneNumberAuthentication(phoneNumber: String) -> Single> { - networkService + let dataTask = networkService .request(api: .startPhoneNumberAuth(phoneNumber: phoneNumber), with: .plain) - .map { _ in return () } + .map { _ in phoneNumber } + + return convertToDomain(task: dataTask) } - public func authenticateAuthNumber(phoneNumber: String, authNumber: String) -> Single { + public func authenticateAuthNumber(phoneNumber: String, authNumber: String) -> Single> { + + let dataTask = networkService.request(api: .checkAuthNumber(phoneNumber: phoneNumber, authNumber: authNumber), with: .plain) + .map { _ in phoneNumber } - networkService.request(api: .checkAuthNumber(phoneNumber: phoneNumber, authNumber: authNumber), with: .plain) - .map { _ in return () } + return convertToDomain(task: dataTask) } - public func requestBusinessNumberAuthentication(businessNumber: String) -> Single { + public func requestBusinessNumberAuthentication(businessNumber: String) -> Single> { - networkService.requestDecodable( + let dataTask = networkService.requestDecodable( api: .authenticateBusinessNumber(businessNumber: businessNumber), with: .plain ).map { (dto: BusinessInfoDTO) in dto.toEntity() } + + return convertToDomain(task: dataTask) } - public func requestCheckingIdDuplication(id: String) -> Single { - networkService.request(api: .checkIdDuplication(id: id), with: .plain) - .map { _ in return () } + public func requestCheckingIdDuplication(id: String) -> Single> { + + let dataTask = networkService + .request(api: .checkIdDuplication(id: id), with: .plain) + .mapToVoid() + + return convertToDomain(task: dataTask) } } diff --git a/project/Projects/Data/Repository/Auth/DefaultAuthRepository.swift b/project/Projects/Data/Repository/DefaultAuthRepository.swift similarity index 60% rename from project/Projects/Data/Repository/Auth/DefaultAuthRepository.swift rename to project/Projects/Data/Repository/DefaultAuthRepository.swift index 8f1244d2..d0555602 100644 --- a/project/Projects/Data/Repository/Auth/DefaultAuthRepository.swift +++ b/project/Projects/Data/Repository/DefaultAuthRepository.swift @@ -28,7 +28,7 @@ public extension DefaultAuthRepository { businessNumber: String, id: String, password: String - ) -> Single { + ) -> Single> { let dto = CenterRegistrationDTO( identifier: id, @@ -40,26 +40,34 @@ public extension DefaultAuthRepository { let data = (try? JSONEncoder().encode(dto)) ?? Data() - return networkService.request(api: .registerCenterAccount(data: data), with: .plain) - .map { _ in return () } + let dataTask = networkService.request(api: .registerCenterAccount(data: data), with: .plain) + .mapToVoid() + + return convertToDomain(task: dataTask) } - func requestCenterLogin(id: String, password: String) -> Single { - return networkService.requestDecodable(api: .centerLogin(id: id, password: password), with: .plain) - .flatMap { [unowned self] in saveTokenToStore(token: $0) } + func requestCenterLogin(id: String, password: String) -> Single> { + let dataTask = networkService.requestDecodable(api: .centerLogin(id: id, password: password), with: .plain) + .flatMap { [unowned self] in + saveTokenToStore(token: $0) + } + + return convertToDomain(task: dataTask) } - func signoutCenterAccount() -> RxSwift.Single { - networkService + func signoutCenterAccount() -> Single> { + let dataTask = networkService .request(api: .signoutCenterAccount, with: .withToken) - .map { _ in } + .mapToVoid() + + return convertToDomain(task: dataTask) } - func deregisterCenterAccount(reasons: [String], password: String) -> RxSwift.Single { + func deregisterCenterAccount(reasons: [String], password: String) -> Single> { let reasonString = reasons.joined(separator: "|") - return networkService + let dataTask = networkService .request( api: .deregisterCenterAccount( reason: reasonString, @@ -67,25 +75,33 @@ public extension DefaultAuthRepository { ), with: .withToken ) - .map { _ in } + .mapToVoid() + + return convertToDomain(task: dataTask) } - func getCenterJoinStatus() -> RxSwift.Single { - networkService + func getCenterJoinStatus() -> Single> { + let dataTask = networkService .request(api: .centerJoinStatus, with: .withToken) .map(CenterJoinStatusInfoVO.self) + + return convertToDomain(task: dataTask) } - func requestCenterJoin() -> Single { - networkService + func requestCenterJoin() -> Single> { + let dataTask = networkService .request(api: .requestCenterJoin, with: .withToken) .mapToVoid() + + return convertToDomain(task: dataTask) } - func setNewPassword(phoneNumber: String, password: String) -> Single { - networkService + func setNewPassword(phoneNumber: String, password: String) -> Single> { + let dataTask = networkService .request(api: .makeNewPassword(phoneNumber: phoneNumber, newPassword: password), with: .plain) .mapToVoid() + + return convertToDomain(task: dataTask) } } @@ -93,7 +109,7 @@ public extension DefaultAuthRepository { public extension DefaultAuthRepository { /// 요양보호사의 경우 회원가입시 곧바로 토큰을 발급받습니다. - func requestRegisterWorkerAccount(registerState: WorkerRegisterState) -> Single { + func requestRegisterWorkerAccount(registerState: WorkerRegisterState) -> Single> { let dto = WorkerRegistrationDTO( carerName: registerState.name, birthYear: registerState.birthYear, @@ -105,32 +121,37 @@ public extension DefaultAuthRepository { let data = (try? JSONEncoder().encode(dto)) ?? Data() - return networkService.requestDecodable(api: .registerWorkerAccount(data: data), with: .plain) + let dataTask = networkService.requestDecodable(api: .registerWorkerAccount(data: data), with: .plain) .flatMap { [unowned self] in saveTokenToStore(token: $0) } + + return convertToDomain(task: dataTask) } - func requestWorkerLogin(phoneNumber: String, authNumber: String) -> Single { - return networkService.requestDecodable(api: .workerLogin(phoneNumber: phoneNumber, verificationNumber: authNumber), with: .plain) + func requestWorkerLogin(phoneNumber: String, authNumber: String) -> Single> { + let dataTask = networkService.requestDecodable(api: .workerLogin(phoneNumber: phoneNumber, verificationNumber: authNumber), with: .plain) .flatMap { [unowned self] in saveTokenToStore(token: $0) } + + return convertToDomain(task: dataTask) } - func signoutWorkerAccount() -> RxSwift.Single { - networkService + func signoutWorkerAccount() -> Single> { + let dataTask = networkService .request(api: .signoutWorkerAccount, with: .withToken) - .map { _ in } + .mapToVoid() + + return convertToDomain(task: dataTask) } - func deregisterWorkerAccount(reasons: [String]) -> RxSwift.Single { + func deregisterWorkerAccount(reasons: [String]) -> Single> { let reasonString = reasons.joined(separator: "|") - - return networkService + let dataTask = networkService .request( - api: .deregisterWorkerAccount( - reason: reasonString - ), + api: .deregisterWorkerAccount(reason: reasonString), with: .withToken ) - .map { _ in } + .mapToVoid() + + return convertToDomain(task: dataTask) } } diff --git a/project/Projects/Data/Repository/DefaultNotificationTokenTransferRepository.swift b/project/Projects/Data/Repository/DefaultNotificationTokenTransferRepository.swift new file mode 100644 index 00000000..0815df2b --- /dev/null +++ b/project/Projects/Data/Repository/DefaultNotificationTokenTransferRepository.swift @@ -0,0 +1,45 @@ +// +// DefaultNotificationTokenTransferRepository.swift +// DataSource +// +// Created by choijunios on 10/8/24. +// + +import Foundation +import Domain +import DataSource + + +import RxSwift + +public class DefaultNotificationTokenTransferRepository: NotificationTokenTransferRepository { + + let transferService: NotificationTokenTransferService = .init() + + public init() { } + + public func sendToken(token: String, userType: UserType) -> Single> { + + let userTypeString = userType == .center ? "CENTER" : "CARER" + let dataTask = transferService + .request( + api: .saveToken(deviceToken: token, userType: userTypeString), + with: .withToken + ) + .mapToVoid() + + return convertToDomain(task: dataTask) + } + + public func deleteToken(token: String) -> Single> { + + let dataTask = transferService + .request( + api: .deleteToken(deviceToken: token), + with: .withToken + ) + .mapToVoid() + + return convertToDomain(task: dataTask) + } +} diff --git a/project/Projects/Data/Repository/RecruitmentPost/DefaultRecruitmentPostRepository.swift b/project/Projects/Data/Repository/DefaultRecruitmentPostRepository.swift similarity index 74% rename from project/Projects/Data/Repository/RecruitmentPost/DefaultRecruitmentPostRepository.swift rename to project/Projects/Data/Repository/DefaultRecruitmentPostRepository.swift index b0d5dca7..dbee1445 100644 --- a/project/Projects/Data/Repository/RecruitmentPost/DefaultRecruitmentPostRepository.swift +++ b/project/Projects/Data/Repository/DefaultRecruitmentPostRepository.swift @@ -13,8 +13,6 @@ import DataSource import Moya import RxSwift - - public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { private var recruitmentPostService: RecruitmentPostService = .init() @@ -29,132 +27,160 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { } } - // MARK: Center - public func registerPost(bundle: RegisterRecruitmentPostBundle) -> RxSwift.Single { + // MARK: Center + public func registerPost(bundle: RegisterRecruitmentPostBundle) -> RxSwift.Single> { let encodedData = try! JSONEncoder().encode(bundle.toDTO()) - - return recruitmentPostService.request(api: .registerPost(postData: encodedData), with: .withToken) + let dataTask = recruitmentPostService.request(api: .registerPost(postData: encodedData), with: .withToken) .mapToVoid() + + return convertToDomain(task: dataTask) } - public func getPostDetailForCenter(id: String) -> RxSwift.Single { + public func getPostDetailForCenter(id: String) -> RxSwift.Single> { - recruitmentPostService.request(api: .postDetail(id: id, userType: .center), with: .withToken) + let dataTask = recruitmentPostService.request(api: .postDetail(id: id, userType: .center), with: .withToken) .map(RecruitmentPostFetchDTO.self) .map { dto in dto.toEntity() } + + return convertToDomain(task: dataTask) } - public func editPostDetail(id: String, bundle: RegisterRecruitmentPostBundle) -> RxSwift.Single { + public func editPostDetail(id: String, bundle: RegisterRecruitmentPostBundle) -> RxSwift.Single> { let encodedData = try! JSONEncoder().encode(bundle.toDTO()) - - return recruitmentPostService.request( + let dataTask = recruitmentPostService.request( api: .editPost(id: id, postData: encodedData), with: .withToken ).map { _ in () } + + return convertToDomain(task: dataTask) } - public func getOngoingPosts() -> RxSwift.Single<[RecruitmentPostInfoForCenterVO]> { - return recruitmentPostService.request(api: .getOnGoingPosts, with: .withToken) + public func getOngoingPosts() -> RxSwift.Single> { + let dataTask = recruitmentPostService.request(api: .getOnGoingPosts, with: .withToken) .map(RecruitmentPostForCenterListDTO.self) .map({ $0.jobPostings.map { $0.toVO() } }) + + return convertToDomain(task: dataTask) } - public func getClosedPosts() -> RxSwift.Single<[RecruitmentPostInfoForCenterVO]> { - return recruitmentPostService.request(api: .getClosedPosts, with: .withToken) + public func getClosedPosts() -> RxSwift.Single> { + let dataTask = recruitmentPostService.request(api: .getClosedPosts, with: .withToken) .map(RecruitmentPostForCenterListDTO.self) .map({ $0.jobPostings.map { $0.toVO() } }) + + return convertToDomain(task: dataTask) } - public func getPostApplicantCount(id: String) -> RxSwift.Single { - recruitmentPostService.request(api: .getPostApplicantCount(id: id), with: .withToken) + public func getPostApplicantCount(id: String) -> RxSwift.Single> { + let dataTask = recruitmentPostService.request(api: .getPostApplicantCount(id: id), with: .withToken) .map(PostApplicantCountDTO.self) .map { dto in dto.applicantCount } + + return convertToDomain(task: dataTask) } - public func getPostApplicantScreenData(id: String) -> RxSwift.Single { - recruitmentPostService.request(api: .getApplicantList(id: id), with: .withToken) + public func getPostApplicantScreenData(id: String) -> RxSwift.Single> { + let dataTask = recruitmentPostService.request(api: .getApplicantList(id: id), with: .withToken) .map(PostApplicantScreenDTO.self) .map { dto in dto.toVO() } + + return convertToDomain(task: dataTask) } - public func closePost(id: String) -> RxSwift.Single { - recruitmentPostService.request(api: .closePost(id: id), with: .withToken) + public func closePost(id: String) -> RxSwift.Single> { + let dataTask = recruitmentPostService.request(api: .closePost(id: id), with: .withToken) .mapToVoid() + + return convertToDomain(task: dataTask) } - public func removePost(id: String) -> RxSwift.Single { - recruitmentPostService.request(api: .removePost(id: id), with: .withToken) + public func removePost(id: String) -> RxSwift.Single> { + let dataTask = recruitmentPostService.request(api: .removePost(id: id), with: .withToken) .mapToVoid() + + return convertToDomain(task: dataTask) } // MARK: Worker - public func getNativePostDetailForWorker(id: String) -> RxSwift.Single { - recruitmentPostService.request( + public func getNativePostDetailForWorker(id: String) -> RxSwift.Single> { + let dataTask = recruitmentPostService.request( api: .postDetail(id: id, userType: .worker), with: .withToken ) .mapToEntity(NativeRecruitmentPostDetailDTO.self) + + return convertToDomain(task: dataTask) } - public func getWorknetPostDetailForWorker(id: String) -> RxSwift.Single { - crawlingPostService + public func getWorknetPostDetailForWorker(id: String) -> RxSwift.Single> { + let dataTask = crawlingPostService .request(api: .getDetail(postId: id), with: .withToken) .mapToEntity(WorknetRecruitmentPostDetailDTO.self) + + return convertToDomain(task: dataTask) } - - public func getNativePostListForWorker(nextPageId: String?, requestCnt: Int = 10) -> RxSwift.Single { - - recruitmentPostService.request( + public func getNativePostListForWorker(nextPageId: String?, requestCnt: Int = 10) -> RxSwift.Single> { + let dataTask = recruitmentPostService.request( api: .getOnGoingNativePostListForWorker(nextPageId: nextPageId, requestCnt: String(requestCnt)), with: .withToken ) .mapToEntity(RecruitmentPostListForWorkerDTO.self) + + return convertToDomain(task: dataTask) } - public func getNativeFavoritePostListForWorker() -> RxSwift.Single<[RecruitmentPostForWorkerRepresentable]> { - recruitmentPostService.request( + public func getNativeFavoritePostListForWorker() -> RxSwift.Single> { + let dataTask = recruitmentPostService.request( api: .getNativeFavoritePost, with: .withToken ) .mapToEntity(FavoriteNativeRecruitmentPostListForWorkerDTO.self) + + return convertToDomain(task: dataTask) } - public func getWorknetFavoritePostListForWorker() -> RxSwift.Single<[RecruitmentPostForWorkerRepresentable]> { - crawlingPostService.request( + public func getWorknetFavoritePostListForWorker() -> RxSwift.Single> { + let dataTask = crawlingPostService.request( api: .getWorknetFavoritePost, with: .withToken ) .mapToEntity(FavoriteWorknetRecruitmentPostListForWorkerDTO.self) + + return convertToDomain(task: dataTask) } - public func getAppliedPostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single { - recruitmentPostService.request( + public func getAppliedPostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single> { + let dataTask = recruitmentPostService.request( api: .getAppliedPostListForWorker(nextPageId: nextPageId, requestCnt: String(requestCnt)), with: .withToken ) .mapToEntity(RecruitmentPostListForWorkerDTO.self) + + return convertToDomain(task: dataTask) } - public func getWorknetPostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single { - crawlingPostService + public func getWorknetPostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single> { + let dataTask = crawlingPostService .request( api: .getPostList(nextPageId: nextPageId, requestCnt: requestCnt), with: .withToken ) .mapToEntity(RecruitmentPostListForWorkerDTO.self) + + return convertToDomain(task: dataTask) } - public func applyToPost(postId: String, method: ApplyType) -> Single { - applyService + public func applyToPost(postId: String, method: ApplyType) -> Single> { + let dataTask = applyService .request( api: .applys( jobPostingId: postId, @@ -163,21 +189,28 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { with: .withToken ) .mapToVoid() + + return convertToDomain(task: dataTask) } - public func addFavoritePost(postId: String, type: PostOriginType) -> Single { - recruitmentPostService + public func addFavoritePost(postId: String, type: PostOriginType) -> Single> { + let dataTask = recruitmentPostService .request(api: .addFavoritePost(id: postId, jobPostingType: type), with: .withToken) .mapToVoid() + + return convertToDomain(task: dataTask) } - public func removeFavoritePost(postId: String) -> Single { - recruitmentPostService + public func removeFavoritePost(postId: String) -> Single> { + let dataTask = recruitmentPostService .request(api: .removeFavoritePost(id: postId), with: .withToken) .mapToVoid() + + return convertToDomain(task: dataTask) } } + // MARK: 공고등록 정보를 DTO로 변환하는 영역 extension RegisterRecruitmentPostBundle { diff --git a/project/Projects/Data/Repository/UserInfo/DefaultUserInfoLocalRepository.swift b/project/Projects/Data/Repository/DefaultUserInfoLocalRepository.swift similarity index 100% rename from project/Projects/Data/Repository/UserInfo/DefaultUserInfoLocalRepository.swift rename to project/Projects/Data/Repository/DefaultUserInfoLocalRepository.swift diff --git a/project/Projects/Data/Repository/UserInfo/DefaultUserProfileRepository.swift b/project/Projects/Data/Repository/DefaultUserProfileRepository.swift similarity index 57% rename from project/Projects/Data/Repository/UserInfo/DefaultUserProfileRepository.swift rename to project/Projects/Data/Repository/DefaultUserProfileRepository.swift index 4e50fc2b..bc395462 100644 --- a/project/Projects/Data/Repository/UserInfo/DefaultUserProfileRepository.swift +++ b/project/Projects/Data/Repository/DefaultUserProfileRepository.swift @@ -18,7 +18,6 @@ public class DefaultUserProfileRepository: UserProfileRepository { let externalRequestService: ExternalRequestService public init(_ keyValueStore: KeyValueStore? = nil) { - if let keyValueStore { self.userInformationService = .init(keyValueStore: keyValueStore) self.externalRequestService = .init(keyValueStore: keyValueStore) @@ -29,7 +28,7 @@ public class DefaultUserProfileRepository: UserProfileRepository { } /// 센터프로필(최초 센터정보)를 등록합니다. - public func registerCenterProfileForText(state: CenterProfileRegisterState) -> Single { + public func registerCenterProfileForText(state: CenterProfileRegisterState) -> Single> { let dto = RegisterCenterProfileDTO( centerName: state.centerName, @@ -41,12 +40,14 @@ public class DefaultUserProfileRepository: UserProfileRepository { ) let data = try! JSONEncoder().encode(dto) - return userInformationService + let dataTask = userInformationService .request(api: .registerCenterProfile(data: data), with: .withToken) .map { _ in () } + + return convertToDomain(task: dataTask) } - public func getCenterProfile(mode: ProfileMode) -> Single { + public func getCenterProfile(mode: ProfileMode) -> Single> { var api: UserInformationAPI! @@ -57,28 +58,34 @@ public class DefaultUserProfileRepository: UserProfileRepository { api = .getCenterProfile(id: id) } - return userInformationService + let dataTask = userInformationService .requestDecodable(api: api, with: .withToken) .map { (dto: CenterProfileDTO) in dto.toEntity() } + + return convertToDomain(task: dataTask) } - public func getCenterProfile(id: String) -> Single { - userInformationService + public func getCenterProfile(id: String) -> Single> { + let dataTask = userInformationService .requestDecodable(api: .getCenterProfile(id: id), with: .withToken) .map { (dto: CenterProfileDTO) in dto.toEntity() } + + return convertToDomain(task: dataTask) } - public func updateCenterProfileForText(phoneNumber: String, introduction: String?) -> Single { - userInformationService + public func updateCenterProfileForText(phoneNumber: String, introduction: String?) -> Single> { + let dataTask = userInformationService .request(api: .updateCenterProfile( officeNumber: phoneNumber, introduce: introduction ), with: .withToken) .map { _ in return () } + + return convertToDomain(task: dataTask) } /// 요양보호사 프로필 정보를 가져옵니다. - public func getWorkerProfile(mode: ProfileMode) -> RxSwift.Single { + public func getWorkerProfile(mode: ProfileMode) -> Single> { var api: UserInformationAPI! switch mode { @@ -88,13 +95,15 @@ public class DefaultUserProfileRepository: UserProfileRepository { api = .getOtherWorkerProfile(id: id) } - return userInformationService + let dataTask = userInformationService .requestDecodable(api: api, with: .withToken) .map { (dto: CarerProfileDTO) in dto.toVO() } + + return convertToDomain(task: dataTask) } /// 요양보호사 프로필 정보를 업데이트 합니다. - public func updateWorkerProfile(stateObject: WorkerProfileStateObject) -> RxSwift.Single { + public func updateWorkerProfile(stateObject: WorkerProfileStateObject) -> Single> { var availableValues: [String: Any] = [:] @@ -124,42 +133,90 @@ public class DefaultUserProfileRepository: UserProfileRepository { let encoded = try! JSONSerialization.data(withJSONObject: availableValues) - return userInformationService + let dataTask = userInformationService .request(api: .updateWorkerProfile(data: encoded), with: .withToken) .map { _ in return () } + + return convertToDomain(task: dataTask) } /// 이미지 업로드 - public func uploadImage(_ userType: UserType, imageInfo: ImageUploadInfo) -> Single { - getPreSignedUrl(userType, ext: imageInfo.ext) - .flatMap { [unowned self] dto in - self.uploadImageToPreSignedUrl(url: dto.uploadUrl, data: imageInfo.data) - .map { _ in (id: dto.imageId, ext: dto.imageFileExtension) } + public func uploadImage(_ userType: UserType, imageInfo: ImageUploadInfo) -> Single> { + + let getPreSignedUrlResult = getPreSignedUrl(userType, ext: imageInfo.ext) + .asObservable() + .share() + + let getPreSignedUrlSuccess = getPreSignedUrlResult.compactMap { $0.value } + let getPreSignedUrlFailure = getPreSignedUrlResult.compactMap { $0.error } + + let uploadImageToPreSignedUrlResult = getPreSignedUrlSuccess + .asObservable() + .unretained(self) + .flatMap { (obj, dto) in + obj + .uploadImageToPreSignedUrl(url: dto.uploadUrl, data: imageInfo.data) + .map { result -> Result in + switch result { + case .success: + return .success(dto) + case .failure(let error): + return .failure(error) + } + } } - .flatMap { (id, ext) in - self.callbackToServerForUploadImageSuccess(userType, imageId: id, ext: ext) + .share() + + let uploadImageToPreSignedUrlSuccess = uploadImageToPreSignedUrlResult.compactMap { $0.value } + let uploadImageToPreSignedUrlFailure = uploadImageToPreSignedUrlResult.compactMap { $0.error } + + let callbackToServerForUploadImageResult = uploadImageToPreSignedUrlSuccess + .unretained(self) + .flatMap { (obj, dto) in + obj.callbackToServerForUploadImageSuccess( + userType, + imageId: dto.imageId, + ext: dto.imageFileExtension + ) } + + return Observable> + .merge( + callbackToServerForUploadImageResult, + Observable.merge( + getPreSignedUrlFailure.asObservable(), + uploadImageToPreSignedUrlFailure.asObservable() + ).map { error in Result.failure(error) } + ) + .asSingle() + } - private func getPreSignedUrl(_ userType: UserType, ext: String) -> Single { - userInformationService + private func getPreSignedUrl(_ userType: UserType, ext: String) -> Single> { + let dataTask = userInformationService .request(api: .getPreSignedUrl(userType: userType, imageExt: ext), with: .withToken) .map(ProfileImageUploadInfoDTO.self) + + return convertToDomain(task: dataTask) } - private func uploadImageToPreSignedUrl(url: String, data: Data) -> Single { - externalRequestService + private func uploadImageToPreSignedUrl(url: String, data: Data) -> Single> { + let dataTask = externalRequestService .request(api: .uploadImageToS3(url: url, data: data), with: .plain) .map { _ in () } + + return convertToDomain(task: dataTask) } - private func callbackToServerForUploadImageSuccess(_ userType: UserType, imageId: String, ext: String) -> Single { - userInformationService + private func callbackToServerForUploadImageSuccess(_ userType: UserType, imageId: String, ext: String) -> Single> { + let dataTask = userInformationService .request(api: .imageUploadSuccessCallback( userType: userType, imageId: imageId, imageExt: ext), with: .withToken ) .map { _ in () } + + return convertToDomain(task: dataTask) } } diff --git a/project/Projects/Domain/Sources/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift b/project/Projects/Domain/Sources/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift index 2dbb7d0c..5f047c93 100644 --- a/project/Projects/Domain/Sources/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift +++ b/project/Projects/Domain/Sources/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift @@ -26,10 +26,8 @@ public class DefaultAuthInputValidationUseCase: AuthInputValidationUseCase { // MARK: 전화번호 인증 public func requestPhoneNumberAuthentication(phoneNumber: String) -> Single> { - convert(task: self.repository + repository .requestPhoneNumberAuthentication(phoneNumber: phoneNumber) - .map { _ in phoneNumber } - ) } public func checkPhoneNumberIsValid(phoneNumber: String) -> Bool { @@ -40,18 +38,14 @@ public class DefaultAuthInputValidationUseCase: AuthInputValidationUseCase { } public func authenticateAuthNumber(phoneNumber: String, authNumber: String) -> Single> { - convert(task: repository + repository .authenticateAuthNumber(phoneNumber: phoneNumber, authNumber: authNumber) - .map({ _ in phoneNumber }) - ) } // MARK: 사업자 번호 인증 - public func requestBusinessNumberAuthentication(businessNumber: String) -> Single> { - convert(task: repository + public func requestBusinessNumberAuthentication(businessNumber: String) -> Single> { + repository .requestBusinessNumberAuthentication(businessNumber: businessNumber) - .map({ vo in (businessNumber, vo) }) - ) } public func checkBusinessNumberIsValid(businessNumber: String) -> Bool { @@ -69,11 +63,9 @@ public class DefaultAuthInputValidationUseCase: AuthInputValidationUseCase { return predicate.evaluate(with: id) } - public func requestCheckingIdDuplication(id: String) -> Single> { - convert(task: repository + public func requestCheckingIdDuplication(id: String) -> Single> { + repository .requestCheckingIdDuplication(id: id) - .map({ _ in id }) - ) } public func checkPasswordIsValid(password: String) -> Bool { diff --git a/project/Projects/Domain/Sources/ConcreteUseCase/Auth/DefaultAuthUseCase.swift b/project/Projects/Domain/Sources/ConcreteUseCase/Auth/DefaultAuthUseCase.swift index 23b77f4b..b3c9649b 100644 --- a/project/Projects/Domain/Sources/ConcreteUseCase/Auth/DefaultAuthUseCase.swift +++ b/project/Projects/Domain/Sources/ConcreteUseCase/Auth/DefaultAuthUseCase.swift @@ -6,26 +6,29 @@ // import Foundation +import Core import RxSwift public class DefaultAuthUseCase: AuthUseCase { - let authRepository: AuthRepository - let userProfileRepository: UserProfileRepository - let userInfoLocalRepository: UserInfoLocalRepository + // UseCase + @Injected var notificationTokenUseCase: NotificationTokenUseCase - public init(authRepository: AuthRepository, userProfileRepository: UserProfileRepository, userInfoLocalRepository: UserInfoLocalRepository) { - self.authRepository = authRepository - self.userProfileRepository = userProfileRepository - self.userInfoLocalRepository = userInfoLocalRepository - } + // Repository + @Injected var authRepository: AuthRepository + @Injected var userProfileRepository: UserProfileRepository + @Injected var userInfoLocalRepository: UserInfoLocalRepository + + public init() { } // 센터 회원가입 실행 public func registerCenterAccount(registerState: CenterRegisterState) -> Single> { - let task = authRepository.requestRegisterCenterAccount( + // #1. 회원가입 실행 + authRepository + .requestRegisterCenterAccount( managerName: registerState.name, phoneNumber: registerState.phoneNumber, businessNumber: registerState.businessNumber, @@ -33,56 +36,103 @@ public class DefaultAuthUseCase: AuthUseCase { password: registerState.password ) .map { [userInfoLocalRepository] _ in + // #2. 유저정보 로컬에 저장 userInfoLocalRepository.updateUserType(.center) } - return convert(task: task) + .flatMap { [notificationTokenUseCase] _ in + // #3. 원격알림 토큰을 서버에 전송 + notificationTokenUseCase.setNotificationToken() + } } // 센터 로그인 실행 public func loginCenterAccount(id: String, password: String) -> Single> { - let task = authRepository.requestCenterLogin(id: id, password: password) + authRepository.requestCenterLogin(id: id, password: password) .map { [userInfoLocalRepository] vo in userInfoLocalRepository.updateUserType(.center) } - return convert(task: task) + .flatMap { [notificationTokenUseCase] _ in + // 원격알림 토큰을 서버에 전송 + notificationTokenUseCase.setNotificationToken() + } } // 요양 보호사 회원가입 실행, 성공한 경우 프로필 Fetch후 저장 public func registerWorkerAccount(registerState: WorkerRegisterState) -> Single> { - let task = authRepository - .requestRegisterWorkerAccount(registerState: registerState) + let registerResult = authRepository.requestRegisterWorkerAccount(registerState: registerState) + + let registerSuccess = registerResult.compactMap({ $0.value }) + let registerFailure = registerResult.compactMap({ $0.error }) + + let fetchProfileResult = registerSuccess + .asObservable() .flatMap { [userProfileRepository] _ in - userProfileRepository.getWorkerProfile(mode: .myProfile) + userProfileRepository + .getWorkerProfile(mode: .myProfile) } + .share() + + let fetchProfileSuccess = fetchProfileResult.compactMap { $0.value} + let fetchProfileFailure = fetchProfileResult.compactMap { $0.error} + + let saveAndNotificationResult = fetchProfileSuccess .map { [userInfoLocalRepository] vo in userInfoLocalRepository.updateUserType(.worker) userInfoLocalRepository.updateCurrentWorkerData(vo: vo) } + .flatMap { [notificationTokenUseCase] _ in + // 원격알림 토큰을 서버에 전송 + notificationTokenUseCase.setNotificationToken() + } - return convert(task: task) + return Observable.merge( + saveAndNotificationResult, + Observable + .merge(registerFailure.asObservable(), fetchProfileFailure.asObservable()) + .map { error -> Result in .failure(error) } + ).asSingle() } // 요양 보호사 로그인 실행, 성공한 경우 프로필 Fetch후 저장 public func loginWorkerAccount(phoneNumber: String, authNumber: String) -> Single> { - let task = authRepository.requestWorkerLogin(phoneNumber: phoneNumber, authNumber: authNumber) + let loginResult = authRepository.requestWorkerLogin(phoneNumber: phoneNumber, authNumber: authNumber) + .asObservable() .flatMap { [userProfileRepository] _ in userProfileRepository.getWorkerProfile(mode: .myProfile) } - .map { [userInfoLocalRepository] vo in - userInfoLocalRepository.updateUserType(.worker) - userInfoLocalRepository.updateCurrentWorkerData(vo: vo) - } + .share() - return convert(task: task) + let loginSuccess = loginResult.compactMap { $0.value } + let loginFailure = loginResult.compactMap { $0.error } + + let tokenTransferResult = loginSuccess + .asObservable() + .map { [userInfoLocalRepository] vo in + userInfoLocalRepository.updateUserType(.worker) + userInfoLocalRepository.updateCurrentWorkerData(vo: vo) + } + .flatMap { [notificationTokenUseCase] _ in + // 원격알림 토큰을 서버에 전송 + notificationTokenUseCase.setNotificationToken() + } + + return Observable + .merge( + tokenTransferResult, + loginFailure.map({ error -> Result in + .failure(error) + }).asObservable() + ) + .asSingle() } public func checkCenterJoinStatus() -> Single> { - convert(task: authRepository.getCenterJoinStatus()) + authRepository.getCenterJoinStatus() } public func setNewPassword(phoneNumber: String, password: String) -> Single> { - convert(task: authRepository.setNewPassword(phoneNumber: phoneNumber, password: password)) + authRepository.setNewPassword(phoneNumber: phoneNumber, password: password) } } diff --git a/project/Projects/Domain/Sources/ConcreteUseCase/CenterCertificate/DefaultCenterCertificateUseCase.swift b/project/Projects/Domain/Sources/ConcreteUseCase/CenterCertificate/DefaultCenterCertificateUseCase.swift index 02d8d557..b6853563 100644 --- a/project/Projects/Domain/Sources/ConcreteUseCase/CenterCertificate/DefaultCenterCertificateUseCase.swift +++ b/project/Projects/Domain/Sources/ConcreteUseCase/CenterCertificate/DefaultCenterCertificateUseCase.swift @@ -22,37 +22,35 @@ public class DefaultCenterCertificateUseCase: CenterCertificateUseCase { public func requestCenterCertificate() -> RxSwift.Single> { - convert(task: authRepository.requestCenterJoin()) + authRepository.requestCenterJoin() } public func getCenterJoinStatus() -> RxSwift.Single> { - convert(task: authRepository.getCenterJoinStatus()) + + authRepository.getCenterJoinStatus() } // 센터 로그아웃 public func signoutCenterAccount() -> RxSwift.Single> { - let task = authRepository + authRepository .signoutCenterAccount() - .map { [weak self] _ in - self?.removeAllLocalData() - - return () + .map { [weak self] result in + if case .success = result { + self?.userInfoLocalRepository.removeAllData() + } + return result } - return convert(task: task) } // 요양보호사 로그아웃 public func signoutWorkerAccount() -> RxSwift.Single> { - let task = authRepository + authRepository .signoutWorkerAccount() - .map { [weak self] _ in - self?.removeAllLocalData() - - return () + .map { [weak self] result in + if case .success = result { + self?.userInfoLocalRepository.removeAllData() + } + return result } - return convert(task: task) - } - private func removeAllLocalData() { - userInfoLocalRepository.removeAllData() } } diff --git a/project/Projects/Domain/Sources/ConcreteUseCase/NotificationTokenManage/DefaultNotificationTokenManage.swift b/project/Projects/Domain/Sources/ConcreteUseCase/NotificationTokenManage/DefaultNotificationTokenManage.swift deleted file mode 100644 index 4fa9dc98..00000000 --- a/project/Projects/Domain/Sources/ConcreteUseCase/NotificationTokenManage/DefaultNotificationTokenManage.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// DefaultNotificationTokenManage.swift -// ConcreteUseCase -// -// Created by choijunios on 9/26/24. -// - -import Foundation - -public class DefaultNotificationTokenManage: NotificationTokenManage { - - public init() { } - - public func setNotificationToken(token: String, completion: @escaping (Bool) -> ()) { - - //TODO: 구체적 스팩 산정 후 구현 - completion(true) - } - - public func deleteNotificationToken(completion: @escaping (Bool) -> ()) { - - //TODO: 구체적 스팩 산정 후 구현 - completion(true) - } -} diff --git a/project/Projects/Domain/Sources/ConcreteUseCase/NotificationTokenManage/DefaultNotificationTokenUseCase.swift b/project/Projects/Domain/Sources/ConcreteUseCase/NotificationTokenManage/DefaultNotificationTokenUseCase.swift new file mode 100644 index 00000000..592c811d --- /dev/null +++ b/project/Projects/Domain/Sources/ConcreteUseCase/NotificationTokenManage/DefaultNotificationTokenUseCase.swift @@ -0,0 +1,69 @@ +// +// DefaultNotificationTokenUseCase.swift +// ConcreteUseCase +// +// Created by choijunios on 9/26/24. +// + +import Foundation +import Core + + +import RxSwift + +public class DefaultNotificationTokenUseCase: NotificationTokenUseCase { + + // Repositories + @Injected var tokenTransferRepository: NotificationTokenTransferRepository + @Injected var tokenRepository: NotificationTokenRepository + @Injected var userInfoLocalRepository: UserInfoLocalRepository + + let disposeBag = DisposeBag() + + public init() { + // set deleage + self.tokenRepository.delegate = self + } + + public func setNotificationToken() -> Single> { + + guard let userType = userInfoLocalRepository.getUserType() else { + return .just(.failure(.clientException)) + } + + guard let notificationToken = tokenRepository.getToken() else { + return .just(.failure(.resourceNotFound)) + } + + return tokenTransferRepository + .sendToken(token: notificationToken, userType: userType) + } + + public func deleteNotificationToken() -> Single> { + + guard let notificationToken = tokenRepository.getToken() else { + return .just(.failure(.resourceNotFound)) + } + + return tokenTransferRepository + .deleteToken(token: notificationToken) + } +} + +extension DefaultNotificationTokenUseCase: NotificationTokenRepositoryDelegate { + + public func notificationToken(freshToken: String) { + setNotificationToken() + .subscribe { result in + + switch result { + case .success: + printIfDebug("자동 리프래쉬 토큰 전송 성공") + case .failure(let error): + printIfDebug("자동 리프래쉬 토큰 전송 실패: \(error.localizedDescription)") + } + + } + .disposed(by: disposeBag) + } +} diff --git a/project/Projects/Domain/Sources/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift b/project/Projects/Domain/Sources/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift index e3634fe8..371f79c0 100644 --- a/project/Projects/Domain/Sources/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift +++ b/project/Projects/Domain/Sources/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift @@ -26,11 +26,7 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { inputs.applicationDetail.deadlineDate = oneMonthLater } - return convert( - task: repository.registerPost( - bundle: inputs - ) - ) + return repository.registerPost(bundle: inputs) } public func editRecruitmentPost(id: String, inputs: RegisterRecruitmentPostBundle) -> RxSwift.Single> { @@ -40,65 +36,68 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { inputs.applicationDetail.deadlineDate = oneMonthLater } - return convert( - task: repository.editPostDetail( - id: id, - bundle: inputs - ) - ) + return repository.editPostDetail(id: id, bundle: inputs) } public func getPostDetailForCenter(id: String) -> RxSwift.Single> { - convert(task: repository.getPostDetailForCenter(id: id)) + repository.getPostDetailForCenter(id: id) } public func getNativePostDetailForWorker(id: String) -> RxSwift.Single> { - convert(task: repository.getNativePostDetailForWorker(id: id)) + repository.getNativePostDetailForWorker(id: id) } public func getWorknetPostDetailForWorker(id: String) -> RxSwift.Single> { - convert(task: repository.getWorknetPostDetailForWorker(id: id)) + repository.getWorknetPostDetailForWorker(id: id) } - public func getOngoingPosts() -> RxSwift.Single> { - let task = repository + public func getOngoingPosts() -> Single> { + + repository .getOngoingPosts() - .map { postInfo in - postInfo.forEach { vo in vo.state = .onGoing } - return postInfo + .map { result -> Result<[RecruitmentPostInfoForCenterVO], DomainError> in + + if case .success(let postInfos) = result { + + return .success(postInfos.map { $0.setState(.onGoing) }) + } + return result } - return convert(task: task) } public func getClosedPosts() -> RxSwift.Single> { - let task = repository + + repository .getClosedPosts() - .map { postInfo in - postInfo.forEach { vo in vo.state = .closed } - return postInfo + .map { result -> Result<[RecruitmentPostInfoForCenterVO], DomainError> in + + if case .success(let postInfos) = result { + + return .success(postInfos.map { $0.setState(.closed) }) + } + return result } - return convert(task: task) } public func closePost(id: String) -> Single> { - convert(task: repository.closePost(id: id)) + repository.closePost(id: id) } public func removePost(id: String) -> Single> { - convert(task: repository.removePost(id: id)) + repository.removePost(id: id) } public func getPostApplicantCount(id: String) -> RxSwift.Single> { - convert(task: repository.getPostApplicantCount(id: id)) + repository.getPostApplicantCount(id: id) } public func getPostApplicantScreenData(id: String) -> RxSwift.Single> { - convert(task: repository.getPostApplicantScreenData(id: id)) + repository.getPostApplicantScreenData(id: id) } public func getPostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> Single> { - let stream: Single! + let stream: Single>! switch request { case .initial: @@ -121,26 +120,43 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { } } - return convert(task: stream) + return stream } public func getFavoritePostListForWorker() -> RxSwift.Single> { - let nativeList = repository.getNativeFavoritePostListForWorker() - let worknetList = repository.getWorknetFavoritePostListForWorker() + let fetchNativeListResult = repository.getNativeFavoritePostListForWorker() + let nativeSuccess = fetchNativeListResult.compactMap { $0.value } + let nativeFailure = fetchNativeListResult.compactMap { $0.error } + + let fetchWorknetListResult = repository + .getWorknetFavoritePostListForWorker() + .asObservable() + .share() + let worknetSuccess = fetchWorknetListResult.compactMap { $0.value } + let worknetFailure = fetchWorknetListResult.compactMap { $0.error } + + + let successZip = Observable + .zip(nativeSuccess.asObservable(), worknetSuccess.asObservable()) + .map { (native, worknet) -> Result<[RecruitmentPostForWorkerRepresentable], DomainError> in + .success(native + worknet) + } - let task = Single - .zip(nativeList, worknetList) - .map { (native, worknet) in - native + worknet + let failureMerge = Observable + .merge(nativeFailure.asObservable(), worknetFailure.asObservable()) + .map { error -> Result<[RecruitmentPostForWorkerRepresentable], DomainError> in + .failure(error) } - return convert(task: task) + return Observable + .merge(successZip, failureMerge) + .asSingle() } public func getAppliedPostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> RxSwift.Single> { - let stream: Single! + let stream: Single>! switch request { case .initial: @@ -161,18 +177,18 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { } } - return convert(task: stream) + return stream } public func applyToPost(postId: String, method: ApplyType) -> RxSwift.Single> { - convert(task: repository.applyToPost(postId: postId, method: method)) + repository.applyToPost(postId: postId, method: method) } public func addFavoritePost(postId: String, type: PostOriginType) -> Single> { - convert(task: repository.addFavoritePost(postId: postId, type: type)) + repository.addFavoritePost(postId: postId, type: type) } public func removeFavoritePost(postId: String) -> Single> { - convert(task: repository.removeFavoritePost(postId: postId)) + repository.removeFavoritePost(postId: postId) } } diff --git a/project/Projects/Domain/Sources/ConcreteUseCase/Setting/DefaultSettingUseCase.swift b/project/Projects/Domain/Sources/ConcreteUseCase/Setting/DefaultSettingUseCase.swift index cc83fb4f..f9304d03 100644 --- a/project/Projects/Domain/Sources/ConcreteUseCase/Setting/DefaultSettingUseCase.swift +++ b/project/Projects/Domain/Sources/ConcreteUseCase/Setting/DefaultSettingUseCase.swift @@ -7,19 +7,21 @@ import Foundation import UserNotifications +import Core import RxSwift public class DefaultSettingUseCase: SettingScreenUseCase { - let authRepository: AuthRepository - let userInfoLocalRepository: UserInfoLocalRepository + // UseCase + @Injected var notificationTokenUseCase: NotificationTokenUseCase - public init(authRepository: AuthRepository, userInfoLocalRepository: UserInfoLocalRepository) { - self.authRepository = authRepository - self.userInfoLocalRepository = userInfoLocalRepository - } + // Repository + @Injected var authRepository: AuthRepository + @Injected var userInfoLocalRepository: UserInfoLocalRepository + + public init() { } public func checkPushNotificationApproved() -> Single { Single.create { single in @@ -71,63 +73,74 @@ public class DefaultSettingUseCase: SettingScreenUseCase { } } - public func getPersonalDataUsageDescriptionUrl() -> URL { - // MARK: TODO - URL(string: "")! - } - - public func getApplicationPolicyUrl() -> URL { - // MARK: TODO - URL(string: "")! - } - // MARK: 회원탈퇴 & 로그아웃 // 센터 회원탈퇴 public func deregisterCenterAccount(reasons: [String], password: String) -> RxSwift.Single> { - let task = authRepository - .deregisterCenterAccount(reasons: reasons, password: password) - .map { [weak self] _ in - self?.removeAllLocalData() + + notificationTokenUseCase + .deleteNotificationToken() + .flatMap{ [authRepository] result in - return () + switch result { + case .success: + authRepository + .deregisterCenterAccount(reasons: reasons, password: password) + .map { [weak self] result in + if case .success = result { + self?.removeAllLocalData() + } + return result + } + case .failure: + Single.just(result) + } } - return convert(task: task) } // 센터 로그아웃 public func signoutCenterAccount() -> RxSwift.Single> { - let task = authRepository - .signoutCenterAccount() - .map { [weak self] _ in - self?.removeAllLocalData() + notificationTokenUseCase + .deleteNotificationToken() + .flatMap{ [authRepository] result in - return () + switch result { + case .success: + authRepository + .signoutCenterAccount() + .map { [weak self] result in + if case .success = result { + self?.removeAllLocalData() + } + return result + } + case .failure: + Single.just(result) + } } - return convert(task: task) } // 요양보호사 회원탈퇴 public func deregisterWorkerAccount(reasons: [String]) -> RxSwift.Single> { - let task = authRepository + authRepository .deregisterWorkerAccount(reasons: reasons) - .map { [weak self] _ in - self?.removeAllLocalData() - - return () + .map { [weak self] result in + if case .success = result { + self?.removeAllLocalData() + } + return result } - return convert(task: task) } // 요양보호사 로그아웃 public func signoutWorkerAccount() -> RxSwift.Single> { - let task = authRepository + authRepository .signoutWorkerAccount() - .map { [weak self] _ in - self?.removeAllLocalData() - - return () + .map { [weak self] result in + if case .success = result { + self?.removeAllLocalData() + } + return result } - return convert(task: task) } private func removeAllLocalData() { diff --git a/project/Projects/Domain/Sources/ConcreteUseCase/UserInfo/DefaultCenterProfileUseCase.swift b/project/Projects/Domain/Sources/ConcreteUseCase/UserInfo/DefaultCenterProfileUseCase.swift index bcbca84a..280a40fe 100644 --- a/project/Projects/Domain/Sources/ConcreteUseCase/UserInfo/DefaultCenterProfileUseCase.swift +++ b/project/Projects/Domain/Sources/ConcreteUseCase/UserInfo/DefaultCenterProfileUseCase.swift @@ -31,21 +31,23 @@ public class DefaultCenterProfileUseCase: CenterProfileUseCase { } public func getFreshProfile(mode: ProfileMode) -> RxSwift.Single> { - convert(task: userProfileRepository.getCenterProfile(mode: mode)) + userProfileRepository.getCenterProfile(mode: mode) } public func updateProfile(phoneNumber: String?, introduction: String?, imageInfo: ImageUploadInfo?) -> Single> { - var updateTextTask: Single! - var updateImageTask: Single! + var updateTextTask: Observable>! + var updateImageTask: Observable>! if let phoneNumber { updateTextTask = userProfileRepository.updateCenterProfileForText( phoneNumber: phoneNumber, introduction: introduction ) + .asObservable() + .share() } else { - updateTextTask = .just(()) + updateTextTask = .just(.success(())) } if let imageInfo { @@ -53,30 +55,59 @@ public class DefaultCenterProfileUseCase: CenterProfileUseCase { .center, imageInfo: imageInfo ) + .asObservable() + .share() } else { - updateImageTask = .just(()) + updateImageTask = .just(.success(())) } - let task = Observable + let textSuccess = updateTextTask.compactMap { $0.value } + let textFailure = updateTextTask.compactMap { $0.error } + + let imageSuccess = updateImageTask.compactMap { $0.value } + let imageFailure = updateImageTask.compactMap { $0.error } + + let fetchProfileResult = Observable .zip( - updateTextTask.asObservable(), - updateImageTask.asObservable() + textSuccess.asObservable(), + imageSuccess.asObservable() ) .flatMap { [userProfileRepository] _ in // 등록성공후 내프로필 불러오기 userProfileRepository.getCenterProfile(mode: .myProfile) } - .map({ [userInfoLocalRepository] vo in - userInfoLocalRepository.updateCurrentCenterData(vo: vo) - }) - .asSingle() + .share() + + let task = fetchProfileResult + .map { [userInfoLocalRepository] result -> Result in + switch result { + case .success(let vo): + userInfoLocalRepository.updateCurrentCenterData(vo: vo) + return .success(()) + case .failure(let error): + return .failure(error) + } + } - return convert(task: task) + let failures = Observable + .merge( + textFailure.asObservable(), + imageFailure.asObservable() + ) + .map { error -> Result in + .failure(error) + } + + return Observable.merge( + task.asObservable(), + failures + ) + .asSingle() } public func registerCenterProfile(state: CenterProfileRegisterState) -> Single> { - var registerImageTask: Single! + var registerImageTask: Observable>! let imageInfo = state.imageInfo @@ -85,26 +116,57 @@ public class DefaultCenterProfileUseCase: CenterProfileUseCase { .center, imageInfo: imageInfo ) + .asObservable() + .share() } else { - registerImageTask = .just(()) + registerImageTask = .just(.success(())) } - let registerTextTask = userProfileRepository.registerCenterProfileForText(state: state) - - let task = Observable + let registerImageTaskSuccess = registerImageTask.compactMap { $0.value } + let registerImageTaskFailure = registerImageTask.compactMap { $0.error } + + let registerTextTask = userProfileRepository + .registerCenterProfileForText(state: state) + .asObservable() + .share() + let registerTextTaskSuccess = registerTextTask.compactMap { $0.value } + let registerTextTaskFailure = registerTextTask.compactMap { $0.error } + + let fetchProfileResult = Observable .zip( - registerTextTask.asObservable(), - registerImageTask.asObservable() + registerImageTaskSuccess.asObservable(), + registerTextTaskSuccess.asObservable() ) .flatMap { [userProfileRepository] _ in // 등록성공후 내프로필 불러오기 userProfileRepository.getCenterProfile(mode: .myProfile) } - .map({ [userInfoLocalRepository] vo in - userInfoLocalRepository.updateCurrentCenterData(vo: vo) - }) - .asSingle() + .share() + + let task = fetchProfileResult + .map { [userInfoLocalRepository] result -> Result in + switch result { + case .success(let vo): + userInfoLocalRepository.updateCurrentCenterData(vo: vo) + return .success(()) + case .failure(let error): + return .failure(error) + } + } + + let failures = Observable + .merge( + registerImageTaskFailure.asObservable(), + registerTextTaskFailure.asObservable() + ) + .map { error -> Result in + .failure(error) + } - return convert(task: task) + return Observable.merge( + task.asObservable(), + failures + ) + .asSingle() } } diff --git a/project/Projects/Domain/Sources/ConcreteUseCase/UserInfo/DefaultWorkerProfileUseCase.swift b/project/Projects/Domain/Sources/ConcreteUseCase/UserInfo/DefaultWorkerProfileUseCase.swift index 2ecfd620..eb607214 100644 --- a/project/Projects/Domain/Sources/ConcreteUseCase/UserInfo/DefaultWorkerProfileUseCase.swift +++ b/project/Projects/Domain/Sources/ConcreteUseCase/UserInfo/DefaultWorkerProfileUseCase.swift @@ -31,40 +31,70 @@ public class DefaultWorkerProfileUseCase: WorkerProfileUseCase { } public func getFreshProfile(mode: ProfileMode) -> RxSwift.Single> { - convert(task: userProfileRepository.getWorkerProfile(mode: mode)) + userProfileRepository.getWorkerProfile(mode: mode) } public func updateProfile(stateObject: WorkerProfileStateObject, imageInfo: ImageUploadInfo?) -> Single> { - var updateTextTask: Single! - var updateImageTask: Single! + var updateTextTask: Observable>! + var updateImageTask: Observable>! - updateTextTask = userProfileRepository.updateWorkerProfile( - stateObject: stateObject - ) + updateTextTask = userProfileRepository.updateWorkerProfile(stateObject: stateObject) + .asObservable() + .share() if let imageInfo { updateImageTask = userProfileRepository.uploadImage( .worker, imageInfo: imageInfo ) + .asObservable() + .share() } else { - updateImageTask = .just(()) + updateImageTask = .just(.success(())) } - let task = Observable + let textSuccess = updateTextTask.compactMap { $0.value } + let textFailure = updateTextTask.compactMap { $0.error } + + let imageSuccess = updateImageTask.compactMap { $0.value } + let imageFailure = updateImageTask.compactMap { $0.error } + + let fetchProfileResult = Observable .zip( - updateTextTask.asObservable(), - updateImageTask.asObservable() + textSuccess.asObservable(), + imageSuccess.asObservable() ) .flatMap { [userProfileRepository] _ in + // 등록성공후 내프로필 불러오기 userProfileRepository.getWorkerProfile(mode: .myProfile) } - .map({ [userInfoLocalRepository] vo in - userInfoLocalRepository.updateCurrentWorkerData(vo: vo) - }) - .asSingle() + .share() + + let task = fetchProfileResult + .map { [userInfoLocalRepository] result -> Result in + switch result { + case .success(let vo): + userInfoLocalRepository.updateCurrentWorkerData(vo: vo) + return .success(()) + case .failure(let error): + return .failure(error) + } + } - return convert(task: task) + let failures = Observable + .merge( + textFailure.asObservable(), + imageFailure.asObservable() + ) + .map { error -> Result in + .failure(error) + } + + return Observable.merge( + task.asObservable(), + failures + ) + .asSingle() } } diff --git a/project/Projects/Domain/Sources/Entity/VO/Post/RecruitmentPostInfoForCenterVO.swift b/project/Projects/Domain/Sources/Entity/VO/Post/RecruitmentPostInfoForCenterVO.swift index d627cf5f..01b8cd34 100644 --- a/project/Projects/Domain/Sources/Entity/VO/Post/RecruitmentPostInfoForCenterVO.swift +++ b/project/Projects/Domain/Sources/Entity/VO/Post/RecruitmentPostInfoForCenterVO.swift @@ -40,4 +40,9 @@ public class RecruitmentPostInfoForCenterVO { self.applyDeadline = applyDeadline self.createdAt = createdAt } + + func setState(_ to: PostState) -> Self { + state = to + return self + } } diff --git a/project/Projects/Domain/Sources/RepositoryInterface/Auth/AuthInputValidationRepository.swift b/project/Projects/Domain/Sources/RepositoryInterface/Auth/AuthInputValidationRepository.swift index 602c6b10..e9884b45 100644 --- a/project/Projects/Domain/Sources/RepositoryInterface/Auth/AuthInputValidationRepository.swift +++ b/project/Projects/Domain/Sources/RepositoryInterface/Auth/AuthInputValidationRepository.swift @@ -12,8 +12,8 @@ import RxSwift public protocol AuthInputValidationRepository: RepositoryBase { - func requestPhoneNumberAuthentication(phoneNumber: String) -> Single - func authenticateAuthNumber(phoneNumber: String, authNumber: String) -> Single - func requestBusinessNumberAuthentication(businessNumber: String) -> Single - func requestCheckingIdDuplication(id: String) -> Single + func requestPhoneNumberAuthentication(phoneNumber: String) -> Single> + func authenticateAuthNumber(phoneNumber: String, authNumber: String) -> Single> + func requestBusinessNumberAuthentication(businessNumber: String) -> Single> + func requestCheckingIdDuplication(id: String) -> Single> } diff --git a/project/Projects/Domain/Sources/RepositoryInterface/Auth/AuthRepository.swift b/project/Projects/Domain/Sources/RepositoryInterface/Auth/AuthRepository.swift index d0cd139e..9f1a80c5 100644 --- a/project/Projects/Domain/Sources/RepositoryInterface/Auth/AuthRepository.swift +++ b/project/Projects/Domain/Sources/RepositoryInterface/Auth/AuthRepository.swift @@ -10,18 +10,18 @@ import RxSwift public protocol AuthRepository: RepositoryBase { // MARK: Center - func requestRegisterCenterAccount(managerName: String, phoneNumber: String, businessNumber: String, id: String, password: String) -> Single - func requestCenterLogin(id: String, password: String) -> Single - func signoutCenterAccount() -> Single - func deregisterCenterAccount(reasons: [String], password: String) -> Single - func getCenterJoinStatus() -> Single - func requestCenterJoin() -> Single - func setNewPassword(phoneNumber: String, password: String) -> Single + func requestRegisterCenterAccount(managerName: String, phoneNumber: String, businessNumber: String, id: String, password: String) -> Single> + func requestCenterLogin(id: String, password: String) -> Single> + func signoutCenterAccount() -> Single> + func deregisterCenterAccount(reasons: [String], password: String) -> Single> + func getCenterJoinStatus() -> Single> + func requestCenterJoin() -> Single> + func setNewPassword(phoneNumber: String, password: String) -> Single> // MARK: Worker - func requestRegisterWorkerAccount(registerState: WorkerRegisterState) -> Single - func requestWorkerLogin(phoneNumber: String, authNumber: String) -> Single - func signoutWorkerAccount() -> Single - func deregisterWorkerAccount(reasons: [String]) -> Single + func requestRegisterWorkerAccount(registerState: WorkerRegisterState) -> Single> + func requestWorkerLogin(phoneNumber: String, authNumber: String) -> Single> + func signoutWorkerAccount() -> Single> + func deregisterWorkerAccount(reasons: [String]) -> Single> } diff --git a/project/Projects/Domain/Sources/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift b/project/Projects/Domain/Sources/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift index 8f71678f..bec59e22 100644 --- a/project/Projects/Domain/Sources/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift +++ b/project/Projects/Domain/Sources/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift @@ -14,65 +14,65 @@ public protocol RecruitmentPostRepository: RepositoryBase { // MARK: Center - post crud /// 공고를 등록합니다. - func registerPost(bundle: RegisterRecruitmentPostBundle) -> Single - + func registerPost(bundle: RegisterRecruitmentPostBundle) -> Single> + /// 센터측에서 등록한 공고의 상세내역을 확인합니다. - func getPostDetailForCenter(id: String) -> Single - + func getPostDetailForCenter(id: String) -> Single> + /// 센터가 등록한 공고의 상세정보를 수정합니다. - func editPostDetail(id: String, bundle: RegisterRecruitmentPostBundle) -> Single - + func editPostDetail(id: String, bundle: RegisterRecruitmentPostBundle) -> Single> + // MARK: Center - check posts /// 현재 진행중인 공고를 획득합니다. - func getOngoingPosts() -> Single<[RecruitmentPostInfoForCenterVO]> - - /// 현재 진행중인 공고를 획득합니다. - func getClosedPosts() -> Single<[RecruitmentPostInfoForCenterVO]> - + func getOngoingPosts() -> Single> + + /// 닫힌 공고를 획득합니다. + func getClosedPosts() -> Single> + /// 특정 공고의 지원자 수를 확인합니다. - func getPostApplicantCount(id: String) -> Single - + func getPostApplicantCount(id: String) -> Single> + /// 특정 공고의 지원자 리스트를 조회합니다. 요약된 공고정보가 포함되어 있습니다. - func getPostApplicantScreenData(id: String) -> Single - + func getPostApplicantScreenData(id: String) -> Single> + /// 공고를 종료합니다. - func closePost(id: String) -> Single - + func closePost(id: String) -> Single> + /// 공고를 삭제합니다. - func removePost(id: String) -> Single - + func removePost(id: String) -> Single> + // MARK: Worker /// 요양보호사 앱내 공고의 상세정보를 조회합니다. - func getNativePostDetailForWorker(id: String) -> Single - + func getNativePostDetailForWorker(id: String) -> Single> + /// 요양보호사 워크넷 공고의 상세정보를 조회합니다. - func getWorknetPostDetailForWorker(id: String) -> Single - + func getWorknetPostDetailForWorker(id: String) -> Single> + // MARK: Native post - + /// 요양보호사가 확인하는 케어밋 자체 공고정보를 가져옵니다. - func getNativePostListForWorker(nextPageId: String?, requestCnt: Int) -> Single - + func getNativePostListForWorker(nextPageId: String?, requestCnt: Int) -> Single> + /// 요양보호사가 즐겨찾는 케어밋 자체 공고정보를 가져옵니다. - func getNativeFavoritePostListForWorker() -> Single<[RecruitmentPostForWorkerRepresentable]> - + func getNativeFavoritePostListForWorker() -> Single> + /// 요양보호사가 즐겨찾는 워크넷 공고정보를 가져옵니다. - func getWorknetFavoritePostListForWorker() -> Single<[RecruitmentPostForWorkerRepresentable]> - + func getWorknetFavoritePostListForWorker() -> Single> + /// 요양보호사가 확인하는 케어밋 자체 공고정보를 가져옵니다. - func getAppliedPostListForWorker(nextPageId: String?, requestCnt: Int) -> Single - + func getAppliedPostListForWorker(nextPageId: String?, requestCnt: Int) -> Single> + // MARK: Worknet Post - + /// 요양보호사가 확인하는 워크넷 공고정보를 가져옵니다. - func getWorknetPostListForWorker(nextPageId: String?, requestCnt: Int) -> Single - + func getWorknetPostListForWorker(nextPageId: String?, requestCnt: Int) -> Single> + /// 요양보호사가 인앱 공고에 지원합니다. - func applyToPost(postId: String, method: ApplyType) -> Single - + func applyToPost(postId: String, method: ApplyType) -> Single> + /// 요양보호사 즐겨찾기 공고 추가 - func addFavoritePost(postId: String, type: PostOriginType) -> Single - + func addFavoritePost(postId: String, type: PostOriginType) -> Single> + /// 요양보호사 즐겨찾기 공고 삭제 - func removeFavoritePost(postId: String) -> Single + func removeFavoritePost(postId: String) -> Single> } diff --git a/project/Projects/Domain/Sources/RepositoryInterface/RemoteNotification/NotificationTokenRepository.swift b/project/Projects/Domain/Sources/RepositoryInterface/RemoteNotification/NotificationTokenRepository.swift new file mode 100644 index 00000000..ae183f86 --- /dev/null +++ b/project/Projects/Domain/Sources/RepositoryInterface/RemoteNotification/NotificationTokenRepository.swift @@ -0,0 +1,21 @@ +// +// NotificationTokenRepository.swift +// Domain +// +// Created by choijunios on 10/8/24. +// + +import Foundation + +public protocol NotificationTokenRepository: AnyObject { + + // delegate + var delegate: NotificationTokenRepositoryDelegate? { get set } + + func getToken() -> String? +} + +public protocol NotificationTokenRepositoryDelegate: AnyObject { + + func notificationToken(freshToken: String) +} diff --git a/project/Projects/Domain/Sources/RepositoryInterface/RemoteNotification/NotificationTokenTransferRepository.swift b/project/Projects/Domain/Sources/RepositoryInterface/RemoteNotification/NotificationTokenTransferRepository.swift new file mode 100644 index 00000000..465aeadc --- /dev/null +++ b/project/Projects/Domain/Sources/RepositoryInterface/RemoteNotification/NotificationTokenTransferRepository.swift @@ -0,0 +1,17 @@ +// +// NotificationTokenTransportRepository.swift +// Domain +// +// Created by choijunios on 10/8/24. +// + +import Foundation + + +import RxSwift + +public protocol NotificationTokenTransferRepository: RepositoryBase { + + func sendToken(token: String, userType: UserType) -> Single> + func deleteToken(token: String) -> Single> +} diff --git a/project/Projects/Domain/Sources/RepositoryInterface/RepositoryBase.swift b/project/Projects/Domain/Sources/RepositoryInterface/RepositoryBase.swift index 65a2b474..5dfcd20d 100644 --- a/project/Projects/Domain/Sources/RepositoryInterface/RepositoryBase.swift +++ b/project/Projects/Domain/Sources/RepositoryInterface/RepositoryBase.swift @@ -7,4 +7,59 @@ import Foundation + +import RxSwift + public protocol RepositoryBase { } + +public extension RepositoryBase { + + /// Repository로 부터 전달받은 언어레벨의 에러를 도메인 특화 에러로 변경하고, error를 Result의 Failure로, 성공을 Success로 변경합니다. + func convertToDomain(task: Single) -> Single> { + Single.create { single in + let disposable = task + .subscribe { success in + single(.success(.success(success))) + } onFailure: { error in + single(.success(.failure(self.toDomainError(error: error)))) + } + return Disposables.create { disposable.dispose() } + } + } + + // MARK: InputValidationError + private func toDomainError(error: Error) -> DomainError { + + // 네트워크 에러 + if let httpError = error as? HTTPResponseException { + + if let code = httpError.rawCode { + + let domainError = DomainError(code: code) + + if domainError == .undefinedCode { + #if DEBUG + print("‼️ 정의되지 않은 에러코드가 발견되었습니다. 노션을 확인해주세요") + #endif + } + + return domainError + } + + #if DEBUG + print("InputValidationError변환실패 Error: \(httpError)") + #endif + } + + // 네트워크 에러보다 근본적인 에러 + 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/Sources/RepositoryInterface/UserInfo/UserProfileRepository.swift b/project/Projects/Domain/Sources/RepositoryInterface/UserInfo/UserProfileRepository.swift index 6f0a6767..8e2b9d12 100644 --- a/project/Projects/Domain/Sources/RepositoryInterface/UserInfo/UserProfileRepository.swift +++ b/project/Projects/Domain/Sources/RepositoryInterface/UserInfo/UserProfileRepository.swift @@ -11,15 +11,22 @@ import Foundation import RxSwift public protocol UserProfileRepository: RepositoryBase { - - func registerCenterProfileForText(state: CenterProfileRegisterState) -> Single - - func getCenterProfile(mode: ProfileMode) -> Single - func updateCenterProfileForText(phoneNumber: String, introduction: String?) -> Single - - // ImageUpload - func uploadImage(_ userType: UserType, imageInfo: ImageUploadInfo) -> Single - - func getWorkerProfile(mode: ProfileMode) -> Single - func updateWorkerProfile(stateObject: WorkerProfileStateObject) -> Single + + // 센터 프로필 등록 (텍스트 정보) + func registerCenterProfileForText(state: CenterProfileRegisterState) -> Single> + + // 센터 프로필 가져오기 + func getCenterProfile(mode: ProfileMode) -> Single> + + // 센터 프로필 업데이트 (텍스트 정보) + func updateCenterProfileForText(phoneNumber: String, introduction: String?) -> Single> + + // 이미지 업로드 + func uploadImage(_ userType: UserType, imageInfo: ImageUploadInfo) -> Single> + + // 요양보호사 프로필 가져오기 + func getWorkerProfile(mode: ProfileMode) -> Single> + + // 요양보호사 프로필 업데이트 + func updateWorkerProfile(stateObject: WorkerProfileStateObject) -> Single> } diff --git a/project/Projects/Domain/Sources/UseCaseInterface/Auth/AuthInputValidationUseCase.swift b/project/Projects/Domain/Sources/UseCaseInterface/Auth/AuthInputValidationUseCase.swift index 79774a35..3c8bcd47 100644 --- a/project/Projects/Domain/Sources/UseCaseInterface/Auth/AuthInputValidationUseCase.swift +++ b/project/Projects/Domain/Sources/UseCaseInterface/Auth/AuthInputValidationUseCase.swift @@ -55,7 +55,7 @@ public protocol AuthInputValidationUseCase: BaseUseCase { /// - Observable // MARK: 사업자 번호 조회 - func requestBusinessNumberAuthentication(businessNumber: String) -> Single> + func requestBusinessNumberAuthentication(businessNumber: String) -> Single> // #5. /// 사업자 번호 유효성 로직 @@ -79,7 +79,7 @@ public protocol AuthInputValidationUseCase: BaseUseCase { /// - id : "idle1234" /// - returns: /// - Bool, true: 가능, flase: 증복 - func requestCheckingIdDuplication(id: String) -> Single> + func requestCheckingIdDuplication(id: String) -> Single> // #8. /// 아이디 유효성확인 로직 diff --git a/project/Projects/Domain/Sources/UseCaseInterface/BaseUseCase.swift b/project/Projects/Domain/Sources/UseCaseInterface/BaseUseCase.swift index 52ba9da4..0d5c7793 100644 --- a/project/Projects/Domain/Sources/UseCaseInterface/BaseUseCase.swift +++ b/project/Projects/Domain/Sources/UseCaseInterface/BaseUseCase.swift @@ -11,54 +11,3 @@ import Foundation import RxSwift public protocol BaseUseCase: AnyObject { } - -public extension BaseUseCase { - - /// Repository로 부터 전달받은 언어레벨의 에러를 도메인 특화 에러로 변경하고, error를 Result의 Failure로, 성공을 Success로 변경합니다. - func convert(task: Single) -> Single> { - Single.create { single in - let disposable = task - .subscribe { success in - single(.success(.success(success))) - } onFailure: { error in - single(.success(.failure(self.toDomainError(error: error)))) - } - return Disposables.create { disposable.dispose() } - } - } - - // MARK: InputValidationError - private func toDomainError(error: Error) -> DomainError { - - // 네트워크 에러 - if let httpError = error as? HTTPResponseException { - - if let code = httpError.rawCode { - - let domainError = DomainError(code: code) - - if domainError == .undefinedCode { -#if DEBUG - print("‼️ 정의되지 않은 에러코드가 발견되었습니다. 노션을 확인해주세요") -#endif - } - - return domainError - } - - #if DEBUG - print("InputValidationError변환실패 Error: \(httpError)") - #endif - } - - // 네트워크 에러보다 근본적인 에러 - 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/Sources/UseCaseInterface/NotificationTokenManage/NotificationTokenManage.swift b/project/Projects/Domain/Sources/UseCaseInterface/NotificationTokenManage/NotificationTokenUseCase.swift similarity index 54% rename from project/Projects/Domain/Sources/UseCaseInterface/NotificationTokenManage/NotificationTokenManage.swift rename to project/Projects/Domain/Sources/UseCaseInterface/NotificationTokenManage/NotificationTokenUseCase.swift index 40b4c1ff..31f9ec2b 100644 --- a/project/Projects/Domain/Sources/UseCaseInterface/NotificationTokenManage/NotificationTokenManage.swift +++ b/project/Projects/Domain/Sources/UseCaseInterface/NotificationTokenManage/NotificationTokenUseCase.swift @@ -1,5 +1,5 @@ // -// NotificationTokenManage.swift +// NotificationTokenUseCase.swift // UseCaseInterface // // Created by choijunios on 9/26/24. @@ -7,11 +7,14 @@ import Foundation -public protocol NotificationTokenManage { + +import RxSwift + +public protocol NotificationTokenUseCase { /// 유저와 매치되는 노티피케이션 토큰을 서버로 전송합니다. - func setNotificationToken(token: String, completion: @escaping (Bool) -> ()) + func setNotificationToken() -> Single> /// 유저와 매치되는 노티피케이션 토큰을 서버로부터 제거합니다. - func deleteNotificationToken(completion: @escaping (Bool) -> ()) + func deleteNotificationToken() -> Single> } diff --git a/project/Projects/Domain/Sources/UseCaseInterface/Setting/SettingScreenUseCase .swift b/project/Projects/Domain/Sources/UseCaseInterface/Setting/SettingScreenUseCase .swift index 2e89c32b..8b34341e 100644 --- a/project/Projects/Domain/Sources/UseCaseInterface/Setting/SettingScreenUseCase .swift +++ b/project/Projects/Domain/Sources/UseCaseInterface/Setting/SettingScreenUseCase .swift @@ -24,12 +24,6 @@ public protocol SettingScreenUseCase: BaseUseCase { /// 알림동의를 요청합니다. func requestNotificationPermission() -> Maybe - /// 개인정보 처리방침 웹 URL을 가져옵니다. - func getPersonalDataUsageDescriptionUrl() -> URL - - /// 어플리케이션 이용약관을 가져옵니다. - func getApplicationPolicyUrl() -> URL - // MARK: Worker /// 요양보호사 회원 탈퇴 diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBarProto.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBarProto.swift deleted file mode 100644 index 99c407c2..00000000 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBarProto.swift +++ /dev/null @@ -1,170 +0,0 @@ -// -// IdleTabBarProto.swift -// PresentationCore -// -// Created by choijunios on 7/25/24. -// - -import UIKit -import RxSwift -import RxCocoa -import PresentationCore - -public class IdleTabBarProto: UIViewController { - - // Coordinator - public weak var coordinator: ParentCoordinator? - - // 탭바구성 - public private(set) var viewControllers: [UIViewController] = [] - private var tabBarItems: [IdleTabBarItemProto] = [] - - // View - var tabBarItemStack: UIView! - - // 탭바 아이템 - private var tabBarItemViews: [IdleTabBarItemViewable] = [] - - private var currentIndex: Int = -1 - - public var selectedIndex: Int { - get { - currentIndex - } - set { - moveTo(index: newValue) - currentIndex = newValue - } - } - - private let disposeBag = DisposeBag() - - public init() { - - super.init(nibName: nil, bundle: nil) - - setAppearance() - } - - public required init?(coder: NSCoder) { fatalError() } - - /// 생성시 한번만 호출해야 합니다. - public func setViewControllers(info: [TabBarInfo]) { - - viewControllers = [] - tabBarItems = [] - - info.forEach { - viewControllers.append($0.viewController) - tabBarItems.append($0.tabBarItem) - } - - // 뷰컨트롤러들 추가 - viewControllers - .forEach { - self.addChild($0) - $0.didMove(toParent: self) - } - - setTabBarItems() - } - - private func setTabBarItems() { - - tabBarItemViews = tabBarItems.map { item in - TextButtonType1(labelText: item.name) - } - - let tabBarItemStack = HStack( - tabBarItemViews, - alignment: .fill, - distribution: .fillEqually - ) - - self.tabBarItemStack = tabBarItemStack - - view.addSubview(tabBarItemStack) - tabBarItemStack.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - tabBarItemStack.topAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor), - tabBarItemStack.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tabBarItemStack.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tabBarItemStack.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) - ]) - - setTabBarItemObservable() - } - - private func setTabBarItemObservable() { - - let observers = tabBarItemViews.enumerated().map { (index, element) in - element.eventPublisher.map { _ in index } - } - - Observable - .merge(observers) - .asDriver(onErrorJustReturn: 0) - .drive(onNext: { [weak self] index in - self?.selectedIndex = index - }) - .disposed(by: disposeBag) - } - - private func moveTo(index: Int) { - - if currentIndex == index { return } - - if currentIndex != -1 { - let prevVC = viewControllers[currentIndex] - prevVC.view.removeFromSuperview() - } - - let currentVC = viewControllers[index] - view.addSubview(currentVC.view) - currentVC.view.translatesAutoresizingMaskIntoConstraints = false - - let currentView = currentVC.view! - - NSLayoutConstraint.activate([ - currentView.topAnchor.constraint(equalTo: view.topAnchor), - currentView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - currentView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - currentView.bottomAnchor.constraint(equalTo: tabBarItemStack.topAnchor) - ]) - } - - // MARK: ViewController 세팅 - private func setAppearance() { - - view.backgroundColor = .white - view.layoutMargins = .init(top: 0, left: 0, bottom: 56, right: 0) - } -} - -// 임시적 세팅 -extension TextButtonType1: IdleTabBarItemViewable { } - -// 임시적 세팅 -public protocol IdleTabBarItemViewable: UIView { - var eventPublisher: Observable { get } -} - - -public struct IdleTabBarItemProto { - let name: String - - public init(name: String) { - self.name = name - } -} - -public struct TabBarInfo { - let viewController: UIViewController - let tabBarItem: IdleTabBarItemProto - - public init(viewController: UIViewController, tabBarItem: IdleTabBarItemProto) { - self.viewController = viewController - self.tabBarItem = tabBarItem - } -} diff --git a/project/Projects/Presentation/Feature/AccountDeregister/Sources/AccountDeregisterCoordinator.swift b/project/Projects/Presentation/Feature/AccountDeregister/Sources/AccountDeregisterCoordinator.swift index f936467c..c78965d7 100644 --- a/project/Projects/Presentation/Feature/AccountDeregister/Sources/AccountDeregisterCoordinator.swift +++ b/project/Projects/Presentation/Feature/AccountDeregister/Sources/AccountDeregisterCoordinator.swift @@ -13,7 +13,7 @@ public enum AccountDeregisterCoordinatorDestination { case accountAuthFlow } -public class AccountDeregisterCoordinator: Coordinator2 { +public class AccountDeregisterCoordinator: Coordinator { public var onFinish: (() -> ())? diff --git a/project/Projects/Presentation/Feature/AccountDeregister/Sources/AuthPasswordPage/PasswordForDeregisterVM.swift b/project/Projects/Presentation/Feature/AccountDeregister/Sources/AuthPasswordPage/PasswordForDeregisterVM.swift index 96003d52..d6714978 100644 --- a/project/Projects/Presentation/Feature/AccountDeregister/Sources/AuthPasswordPage/PasswordForDeregisterVM.swift +++ b/project/Projects/Presentation/Feature/AccountDeregister/Sources/AuthPasswordPage/PasswordForDeregisterVM.swift @@ -45,10 +45,6 @@ public class PasswordForDeregisterVM: BaseViewModel { .observe(on: MainScheduler.asyncInstance) .unretained(self) .subscribe(onNext: { (obj, _) in - - // 회원탈퇴 성공 -> 원격알림 토큰 제거 - NotificationCenter.default.post(name: .requestDeleteTokenFromServer, object: nil) - // RootCoordinator로 이동 obj.changeToAuthFlow?() }) diff --git a/project/Projects/Presentation/Feature/AccountDeregister/Sources/AuthPhoneNumberPage/PhoneNumberValidationForDeregisterVM.swift b/project/Projects/Presentation/Feature/AccountDeregister/Sources/AuthPhoneNumberPage/PhoneNumberValidationForDeregisterVM.swift index f6336889..1102e983 100644 --- a/project/Projects/Presentation/Feature/AccountDeregister/Sources/AuthPhoneNumberPage/PhoneNumberValidationForDeregisterVM.swift +++ b/project/Projects/Presentation/Feature/AccountDeregister/Sources/AuthPhoneNumberPage/PhoneNumberValidationForDeregisterVM.swift @@ -76,10 +76,6 @@ class PhoneNumberValidationForDeregisterVM: BaseViewModel, PhoneNumberValidation .observe(on: MainScheduler.asyncInstance) .unretained(self) .subscribe(onNext: { (obj, _) in - - // 회원탈퇴 성공 -> 원격알림 토큰 제거 - NotificationCenter.default.post(name: .requestDeleteTokenFromServer, object: nil) - // RootCoordinator로 이동 obj.changeToAuthFlow?() }) diff --git a/project/Projects/Presentation/Feature/Auth/Sources/AuthCoordinator.swift b/project/Projects/Presentation/Feature/Auth/Sources/AuthCoordinator.swift index 869aea32..13b8492e 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/AuthCoordinator.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/AuthCoordinator.swift @@ -15,7 +15,7 @@ public enum AuthCoordinatorDestination { case loginPage } -public class AuthCoordinator: Coordinator2 { +public class AuthCoordinator: Coordinator { public var onFinish: (() -> ())? diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterCoordinator.swift b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterCoordinator.swift index e0d04596..8c3cdd82 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterCoordinator.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterCoordinator.swift @@ -41,7 +41,7 @@ public enum CenterAccountRegisterCoordinatorDestination { case centerMainPage } -public class CenterAccountRegisterCoordinator: Coordinator2 { +public class CenterAccountRegisterCoordinator: Coordinator { public var onFinish: (() -> ())? diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterViewModel.swift b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterViewModel.swift index 93664bfa..acc0288b 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterViewModel.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterViewModel.swift @@ -228,19 +228,23 @@ extension CenterAccountRegisterViewModel { } .asDriver(onErrorJustReturn: false) - let businessNumberValidationResult = input + let requestingBusinessNumber = input .requestBusinessNumberValidation + .withLatestFrom(input.editingBusinessNumber) + .map { unformedNumber in + let formatted = AuthInOutStreamManager.formatBusinessNumber(businessNumber: unformedNumber) + return formatted + } + + let businessNumberValidationResult = requestingBusinessNumber .compactMap { $0 } - .flatMap { [weak self, input, inputValidationUseCase] _ in + .flatMap { [weak self, inputValidationUseCase] businessNumber in // 로딩 시작 self?.showLoading.onNext(()) - let businessNumber = input.editingBusinessNumber.value - let formatted = AuthInOutStreamManager.formatBusinessNumber(businessNumber: businessNumber) - printIfDebug("[CenterAccountRegisterViewModel] 사업자 번호 인증 요청: \(formatted)") return inputValidationUseCase - .requestBusinessNumberAuthentication(businessNumber: formatted) + .requestBusinessNumberAuthentication(businessNumber: businessNumber) } .share() @@ -251,9 +255,11 @@ extension CenterAccountRegisterViewModel { }) .disposed(by: disposeBag) - - output.businessNumberVO = businessNumberValidationResult + let businessNumberValidationSuccess = businessNumberValidationResult .compactMap { $0.value } + + output.businessNumberVO = Observable + .combineLatest(requestingBusinessNumber, businessNumberValidationSuccess) .map { [stateObject] (businessNumber, infoVO) in printIfDebug("✅ 사업자번호 검색 성공") // 🚀 상태추적 🚀 diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/View/AuthInOutStreamManager+Signin.swift b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/View/AuthInOutStreamManager+Signin.swift index c771e54b..9a71a863 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/View/AuthInOutStreamManager+Signin.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/View/AuthInOutStreamManager+Signin.swift @@ -37,7 +37,7 @@ extension AuthInOutStreamManager { // 중복성 검사 let idDuplicationValidation = input .requestIdDuplicationValidation - .flatMap { [unowned useCase] id in + .flatMap { [useCase] id in printIfDebug("[CenterRegisterViewModel] 중복성 검사 대상 id: \(id)") @@ -46,16 +46,17 @@ extension AuthInOutStreamManager { print("✅ 디버그모드에서 아이디 중복검사 미실시") // ☑️ 상태추적 ☑️ stateTracker(id) - return Single.just(Result.success(id)) + return Single.just(Result.success(())) #endif return useCase.requestCheckingIdDuplication(id: id) } - output.idDuplicationValidation = idDuplicationValidation - .map { [stateTracker] result in + output.idDuplicationValidation = Observable + .combineLatest(idDuplicationValidation, input.requestIdDuplicationValidation) + .map { [stateTracker] (result, id) in switch result { - case .success(let id): + case .success: printIfDebug("[CenterRegisterViewModel] 중복체크 결과: ✅ 성공") // 🚀 상태추적 🚀 stateTracker(id) diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Center/SetNewPassword/CenterSetupNewPasswordCoordinator.swift b/project/Projects/Presentation/Feature/Auth/Sources/Center/SetNewPassword/CenterSetupNewPasswordCoordinator.swift index ef7977c7..c62d846c 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/Center/SetNewPassword/CenterSetupNewPasswordCoordinator.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/Center/SetNewPassword/CenterSetupNewPasswordCoordinator.swift @@ -19,7 +19,7 @@ enum SetNewPasswordStage: Int { case finish } -public class CenterSetupNewPasswordCoordinator: Coordinator2 { +public class CenterSetupNewPasswordCoordinator: Coordinator { public var onFinish: (() -> ())? diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Center/SignIn/CenterLoginViewModel.swift b/project/Projects/Presentation/Feature/Auth/Sources/Center/SignIn/CenterLoginViewModel.swift index 95e5991e..2e0a51e2 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/Center/SignIn/CenterLoginViewModel.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/Center/SignIn/CenterLoginViewModel.swift @@ -62,10 +62,6 @@ public class CenterLoginViewModel: BaseViewModel, ViewModelType { loginSuccess .unretained(self) .subscribe(onNext: { (obj, _) in - - // 원격 알림 토큰 저장요청 - NotificationCenter.default.post(name: .requestTransportTokenToServer, object: nil) - // 센터 메인화면으로 이동 obj.presentCenterMainPage() }) diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Worker/WorkerAccountRegisterCoordinator.swift b/project/Projects/Presentation/Feature/Auth/Sources/Worker/WorkerAccountRegisterCoordinator.swift index 4c7fdda4..d7462861 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/Worker/WorkerAccountRegisterCoordinator.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/Worker/WorkerAccountRegisterCoordinator.swift @@ -38,7 +38,7 @@ public enum WorkerAccountRegisterCoordinatorDestination { case workerMainPage } -public class WorkerAccountRegisterCoordinator: Coordinator2 { +public class WorkerAccountRegisterCoordinator: Coordinator { public var onFinish: (() -> ())? diff --git a/project/Projects/Presentation/Feature/Base/ExampleApp/Sources/ViewController.swift b/project/Projects/Presentation/Feature/Base/ExampleApp/Sources/ViewController.swift index c04d8514..25750709 100644 --- a/project/Projects/Presentation/Feature/Base/ExampleApp/Sources/ViewController.swift +++ b/project/Projects/Presentation/Feature/Base/ExampleApp/Sources/ViewController.swift @@ -30,10 +30,6 @@ class ViewController: BaseViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - - self.viewModel? - .snackBar - .onNext(.init(titleText: "테스트테스트테스트")) } } diff --git a/project/Projects/Presentation/Feature/Base/Sources/BaseVC/Loading/DefaultLoadingVC.swift b/project/Projects/Presentation/Feature/Base/Sources/BaseVC/LoadingVC/DefaultLoadingVC.swift similarity index 100% rename from project/Projects/Presentation/Feature/Base/Sources/BaseVC/Loading/DefaultLoadingVC.swift rename to project/Projects/Presentation/Feature/Base/Sources/BaseVC/LoadingVC/DefaultLoadingVC.swift diff --git a/project/Projects/Presentation/Feature/Base/Sources/BaseVM/BaseViewModel.swift b/project/Projects/Presentation/Feature/Base/Sources/BaseVM/BaseViewModel.swift index 3f37155a..719b3e20 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/BaseVM/BaseViewModel.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/BaseVM/BaseViewModel.swift @@ -50,7 +50,7 @@ open class BaseViewModel { .asDriver(onErrorDriveWith: .never()) } - public func mapStartLoading(_ target: Observable) -> Observable { + public func mapStartLoading(_ target: T) -> Observable { target .throttle(.milliseconds(500), scheduler: MainScheduler.instance) @@ -62,7 +62,7 @@ open class BaseViewModel { } } - public func mapEndLoading(_ target: Observable) -> Observable { + public func mapEndLoading(_ target: T) -> Observable { target .map { [weak self] item in diff --git a/project/Projects/Presentation/Feature/Base/Sources/CommonView/AuthInOutStreamManager/AuthInOutStreamManager+PhoneNumber.swift b/project/Projects/Presentation/Feature/Base/Sources/CommonView/AuthInOutStreamManager/AuthInOutStreamManager+PhoneNumber.swift index 4d3a661b..c2d1058d 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/CommonView/AuthInOutStreamManager/AuthInOutStreamManager+PhoneNumber.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/CommonView/AuthInOutStreamManager/AuthInOutStreamManager+PhoneNumber.swift @@ -97,10 +97,6 @@ public extension AuthInOutStreamManager { output.loginSuccess = loginResult .compactMap { $0.value } .map { phoneNumber in - - // 원격 알림 토큰 전송 - NotificationCenter.default.post(name: .requestTransportTokenToServer, object: nil) - printIfDebug("✅ 요양보호사 로그인 성공") return () } diff --git a/project/Projects/Presentation/Feature/Base/Sources/Coordinator/BaseCoordinator.swift b/project/Projects/Presentation/Feature/Base/Sources/Coordinator/BaseCoordinator.swift index 83a6b190..9715519b 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/Coordinator/BaseCoordinator.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/Coordinator/BaseCoordinator.swift @@ -7,35 +7,25 @@ import Foundation - -public protocol Coordinator2: AnyObject { - - var onFinish: (() -> ())? { get set } - - /// Coordinator를 시작한다. - func start() -} - - /// 자식 코디네이터를 가질 수 있는 코디네이터 -open class BaseCoordinator: Coordinator2 { +open class BaseCoordinator: Coordinator { public var onFinish: (() -> ())? - var children: [Coordinator2] = [] + var children: [Coordinator] = [] - public init(children: [Coordinator2] = []) { + public init(children: [Coordinator] = []) { self.children = children } open func start() { } - public func addChild(_ coordinator: Coordinator2) { + public func addChild(_ coordinator: Coordinator) { children.append(coordinator) } - public func removeChild(_ coordinator: Coordinator2) { + public func removeChild(_ coordinator: Coordinator) { children.removeAll { $0 === coordinator} } } diff --git a/project/Projects/Presentation/Feature/Base/Sources/Coordinator/Coordinator.swift b/project/Projects/Presentation/Feature/Base/Sources/Coordinator/Coordinator.swift new file mode 100644 index 00000000..ee393269 --- /dev/null +++ b/project/Projects/Presentation/Feature/Base/Sources/Coordinator/Coordinator.swift @@ -0,0 +1,16 @@ +// +// Coordinator.swift +// BaseFeature +// +// Created by choijunios on 10/6/24. +// + +import Foundation + +public protocol Coordinator: AnyObject { + + var onFinish: (() -> ())? { get set } + + /// Coordinator를 시작한다. + func start() +} diff --git a/project/Projects/Presentation/Feature/Base/Sources/DeepLink/DeepLinkExecutable.swift b/project/Projects/Presentation/Feature/Base/Sources/DeepLink/DeepLinkExecutable.swift deleted file mode 100644 index 17ee5ec5..00000000 --- a/project/Projects/Presentation/Feature/Base/Sources/DeepLink/DeepLinkExecutable.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// DeepLinkExecutable.swift -// BaseFeature -// -// Created by choijunios on 10/2/24. -// - -import Foundation - -public protocol DeepLinkTreeNode { - - var name: String { get } - var children: [DeepLinkExecutable] { get } -// var isDestination: Bool { get set } - - func findChild(name: String) -> DeepLinkExecutable? -} - -public protocol DeepLinkExecutable: DeepLinkTreeNode { - @discardableResult - func execute(with coordinator: Coordinator2, userInfo: [String: String]?) -> Coordinator2? -} - -extension DeepLinkExecutable { - - func findChild(name: String) -> DeepLinkExecutable? { - children.first(where: { $0.name == name }) - } -} - - diff --git a/project/Projects/Presentation/Feature/Base/Sources/DeepLink/DeeplinkExecutable.swift b/project/Projects/Presentation/Feature/Base/Sources/DeepLink/DeeplinkExecutable.swift new file mode 100644 index 00000000..a1676c6d --- /dev/null +++ b/project/Projects/Presentation/Feature/Base/Sources/DeepLink/DeeplinkExecutable.swift @@ -0,0 +1,31 @@ +// +// DeepLinkExecutable.swift +// BaseFeature +// +// Created by choijunios on 10/2/24. +// + +import Foundation + +public protocol DeeplinkTreeNode { + + var name: String { get } + var children: [DeeplinkExecutable] { get } + var isDestination: Bool { get set } + + func findChild(name: String) -> DeeplinkExecutable? +} + +public protocol DeeplinkExecutable: DeeplinkTreeNode { + @discardableResult + func execute(with coordinator: Coordinator, userInfo: [AnyHashable: Any]?) -> Coordinator? +} + +public extension DeeplinkExecutable { + + func findChild(name: String) -> DeeplinkExecutable? { + children.first(where: { $0.name == name }) + } +} + + diff --git a/project/Projects/Presentation/Feature/Base/Sources/Router/Router.swift b/project/Projects/Presentation/Feature/Base/Sources/Router/Router.swift index 2f954685..970b8854 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/Router/Router.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/Router/Router.swift @@ -190,25 +190,15 @@ public final class Router: NSObject, RouterProtocol { self.rootController = navigationController if let willReplacedModule = keyWindow.rootViewController { - + // 이미 키윈도우가 존재했던 경우 dismissCompletion실행 let pointer = willReplacedModule.getRawPointer completion[pointer]?() completion.removeValue(forKey: pointer) } - if !animated { - // 애니메이션이 없는 경우 - setRootModuleTo(module: module) - completion[module.getRawPointer] = dismissCompletion - return - } - - if let snapshot = keyWindow.snapshotView(afterScreenUpdates: true) { + if animated, let snapshot = keyWindow.snapshotView(afterScreenUpdates: true) { module.view.addSubview(snapshot) - keyWindow.rootViewController = navigationController - - completion[navigationController.getRawPointer] = dismissCompletion UIView.animate(withDuration: 0.35, animations: { snapshot.layer.opacity = 0 @@ -216,6 +206,10 @@ public final class Router: NSObject, RouterProtocol { snapshot.removeFromSuperview() }) } + + keyWindow.rootViewController = navigationController + keyWindow.makeKeyAndVisible() + completion[navigationController.getRawPointer] = dismissCompletion } public func setRootModuleTo(module: Module, popCompletion: RoutingCompletion? = nil) { diff --git a/project/Projects/Presentation/Feature/CenterCetificatePage/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/CenterCetificatePage/ExampleApp/Sources/SceneDelegate.swift index 80826e82..ff722baa 100644 --- a/project/Projects/Presentation/Feature/CenterCetificatePage/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/CenterCetificatePage/ExampleApp/Sources/SceneDelegate.swift @@ -25,10 +25,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: windowScene) window?.makeKeyAndVisible() - DependencyInjector.shared.assemble([ - DataAssembly(), - DomainAssembly() - ]) +// DependencyInjector.shared.assemble([ +// DataAssembly(), +// DomainAssembly() +// ]) coordiantor.start() diff --git a/project/Projects/Presentation/Feature/CenterCetificatePage/Sources/MakeProfile/MakeCenterProfilePageCoordinator.swift b/project/Projects/Presentation/Feature/CenterCetificatePage/Sources/MakeProfile/MakeCenterProfilePageCoordinator.swift index 028ea837..03ef67f1 100644 --- a/project/Projects/Presentation/Feature/CenterCetificatePage/Sources/MakeProfile/MakeCenterProfilePageCoordinator.swift +++ b/project/Projects/Presentation/Feature/CenterCetificatePage/Sources/MakeProfile/MakeCenterProfilePageCoordinator.swift @@ -15,7 +15,7 @@ public enum MakeCenterProfilePageCoordinatorDestination { case authFlow } -public class MakeCenterProfilePageCoordinator: Coordinator2 { +public class MakeCenterProfilePageCoordinator: Coordinator { public var onFinish: (() -> ())? diff --git a/project/Projects/Presentation/Feature/CenterCetificatePage/Sources/WaitCertificatePage/WaitCertificatePageCoordinator.swift b/project/Projects/Presentation/Feature/CenterCetificatePage/Sources/WaitCertificatePage/WaitCertificatePageCoordinator.swift index 0ed2b0f7..cacda336 100644 --- a/project/Projects/Presentation/Feature/CenterCetificatePage/Sources/WaitCertificatePage/WaitCertificatePageCoordinator.swift +++ b/project/Projects/Presentation/Feature/CenterCetificatePage/Sources/WaitCertificatePage/WaitCertificatePageCoordinator.swift @@ -15,7 +15,7 @@ public enum WaitCertificatePageCoordinatorDestination { case makeProfileFlow } -public class WaitCertificatePageCoordinator: Coordinator2 { +public class WaitCertificatePageCoordinator: Coordinator { public var onFinish: (() -> ())? diff --git a/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostApplicantPage/PostApplicantPageCoordinator.swift b/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostApplicantPage/PostApplicantPageCoordinator.swift index 07dcb58c..ecbf6226 100644 --- a/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostApplicantPage/PostApplicantPageCoordinator.swift +++ b/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostApplicantPage/PostApplicantPageCoordinator.swift @@ -14,7 +14,7 @@ enum PostApplicantPageCoordinatorDestination { case applicantDetail(id: String) } -class PostApplicantPageCoordinator: Coordinator2 { +class PostApplicantPageCoordinator: Coordinator { var onFinish: (() -> ())? diff --git a/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostBoardPage/PostBoardPageViewModel.swift b/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostBoardPage/PostBoardPageViewModel.swift index 04f5038e..159e74f0 100644 --- a/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostBoardPage/PostBoardPageViewModel.swift +++ b/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostBoardPage/PostBoardPageViewModel.swift @@ -1,6 +1,6 @@ // // CenterRecruitmentPostBoardVM.swift -// CenterFeature +// CenterMainPageFeature // // Created by choijunios on 8/13/24. // @@ -263,3 +263,7 @@ class CenterEmployCardVM: CenterEmployCardViewModelable { .disposed(by: disposeBag) } } + +extension Notification.Name { + static let removePostRequestFromCell: Notification.Name = .init("removePostRequestFromCell") +} diff --git a/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostRegisterPage/CreatePostCoordinator.swift b/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostRegisterPage/CreatePostCoordinator.swift index 51294f6e..49dbacfa 100644 --- a/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostRegisterPage/CreatePostCoordinator.swift +++ b/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostRegisterPage/CreatePostCoordinator.swift @@ -11,7 +11,7 @@ import Domain import BaseFeature import Core -public class CreatePostCoordinator: Coordinator2 { +public class CreatePostCoordinator: Coordinator { public var onFinish: (() -> ())? diff --git a/project/Projects/Presentation/Feature/CenterMainPage/Sources/SettingPage/CenterSettingViewModel.swift b/project/Projects/Presentation/Feature/CenterMainPage/Sources/SettingPage/CenterSettingViewModel.swift index a2b66c10..80f1fe08 100644 --- a/project/Projects/Presentation/Feature/CenterMainPage/Sources/SettingPage/CenterSettingViewModel.swift +++ b/project/Projects/Presentation/Feature/CenterMainPage/Sources/SettingPage/CenterSettingViewModel.swift @@ -173,10 +173,6 @@ class CenterSettingViewModel: BaseViewModel, CenterSettingVMable { signOutSuccess .unretained(self) .subscribe(onNext: { (obj, _) in - - // 로그이아웃 성공 -> 원격알림 토큰 제거 - NotificationCenter.default.post(name: .requestDeleteTokenFromServer, object: nil) - obj.changeToAuthFlow?() }) .disposed(by: disposeBag) diff --git a/project/Projects/Presentation/Feature/NotificationPage/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/NotificationPage/ExampleApp/Sources/SceneDelegate.swift index e1065453..0d4c53a6 100644 --- a/project/Projects/Presentation/Feature/NotificationPage/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/NotificationPage/ExampleApp/Sources/SceneDelegate.swift @@ -58,7 +58,7 @@ public class TestNotificationPageUseCase: NotificationPageUseCase { public func getNotificationList() -> Single> { - let task = Single<[NotificationCellInfo]>.create { observer in + let task = Single>.create { observer in var mockData: [NotificationCellInfo] = [] @@ -77,12 +77,12 @@ public class TestNotificationPageUseCase: NotificationPageUseCase { contentsOf: (0..<5).map { _ in NotificationCellInfo.create(createdDay: -15) } ) - observer(.success(mockData)) + observer(.success(.success(mockData))) return Disposables.create { } } - return convert(task: task) + return task } } diff --git a/project/Projects/Presentation/Feature/PostDetailForWorker/Sources/RecruitmentPost/PostDetailForWorkerCoodinator.swift b/project/Projects/Presentation/Feature/PostDetailForWorker/Sources/RecruitmentPost/PostDetailForWorkerCoodinator.swift index 168156bc..06294ffc 100644 --- a/project/Projects/Presentation/Feature/PostDetailForWorker/Sources/RecruitmentPost/PostDetailForWorkerCoodinator.swift +++ b/project/Projects/Presentation/Feature/PostDetailForWorker/Sources/RecruitmentPost/PostDetailForWorkerCoodinator.swift @@ -14,7 +14,7 @@ public enum PostDetailForWorkerCoodinatorDestination { case centerProfile(mode: ProfileMode) } -public class PostDetailForWorkerCoodinator: Coordinator2 { +public class PostDetailForWorkerCoodinator: Coordinator { public var startFlow: ((PostDetailForWorkerCoodinatorDestination) -> ())! diff --git a/project/Projects/Presentation/Feature/Root/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Root/ExampleApp/Sources/SceneDelegate.swift index 6c0a2ac2..3339b518 100644 --- a/project/Projects/Presentation/Feature/Root/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/Root/ExampleApp/Sources/SceneDelegate.swift @@ -12,8 +12,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? - var coordinator: DeRegisterCoordinator! - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = scene as? UIWindowScene else { return } diff --git a/project/Projects/Presentation/Feature/Root/Sources/Application/AppCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Application/AppCoordinator.swift index c314fe9f..d60362b5 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Application/AppCoordinator.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Application/AppCoordinator.swift @@ -15,15 +15,22 @@ import CenterCetificatePageFeature import AccountDeregisterFeature import PostDetailForWorkerFeature import UserProfileFeature - import Domain import Core +import RxSwift + public class AppCoordinator: BaseCoordinator { + /// main router let router: Router + /// notification helper + let notificationHelper: RemoteNotificationHelper = .init() + + let disposeBag: DisposeBag = .init() + public init(router: Router) { self.router = router } @@ -36,7 +43,7 @@ public class AppCoordinator: BaseCoordinator { extension AppCoordinator { - func executeChild(_ coordinator: Coordinator2) { + func executeChild(_ coordinator: Coordinator) { coordinator.onFinish = { [weak self, weak coordinator] in if let coordinator { self?.removeChild(coordinator) @@ -51,6 +58,7 @@ extension AppCoordinator { func runSplashFlow() -> SplashCoordinator { let coordinator = SplashCoordinator(router: router) + coordinator.delegate = self coordinator.startFlow = { [weak self] destination in @@ -322,3 +330,32 @@ extension AppCoordinator { return coordinator } } + + +// MARK: watch push notifications +extension AppCoordinator: SplashCoordinatorDelegate { + + public func splashCoordinator(satisfiedAllCondition: Bool) { + + if !satisfiedAllCondition { return } + + notificationHelper + .deeplinks + .observe(on: MainScheduler.instance) + .subscribe (onNext: { [weak self] bundle in + + guard let self else { return } + + var currentCoordinator: Coordinator? = self + + bundle.deeplinks + .forEach { deeplink in + currentCoordinator = deeplink.execute( + with: currentCoordinator!, + userInfo: bundle.userInfo + ) + } + }) + .disposed(by: disposeBag) + } +} diff --git a/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/CenterMainPageDeeplink.swift b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/CenterMainPageDeeplink.swift new file mode 100644 index 00000000..50178f79 --- /dev/null +++ b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/CenterMainPageDeeplink.swift @@ -0,0 +1,33 @@ +// +// CenterMainPageDeeplink.swift +// RootFeature +// +// Created by choijunios on 10/9/24. +// + +import Foundation +import BaseFeature + +class CenterMainPageDeeplink: DeeplinkExecutable { + + var name: String = "CenterMainPage" + + var children: [DeeplinkExecutable] = [ + PostApplicantDeeplink() + ] + + var isDestination: Bool = false + + init() { } + + func execute(with coordinator: any BaseFeature.Coordinator, userInfo: [AnyHashable : Any]?) -> Coordinator? { + + guard let appCoordinator = coordinator as? AppCoordinator else { + return nil + } + + let mainPageCoordinator = appCoordinator.runCenterMainPageFlow() + + return mainPageCoordinator + } +} diff --git a/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/PostApplicantDeeplink.swift b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/PostApplicantDeeplink.swift new file mode 100644 index 00000000..9a4e2f95 --- /dev/null +++ b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/PostApplicantDeeplink.swift @@ -0,0 +1,34 @@ +// +// PostApplicantDeeplink.swift +// RootFeature +// +// Created by choijunios on 10/9/24. +// + +import Foundation +import CenterMainPageFeature +import BaseFeature + +class PostApplicantDeeplink: DeeplinkExecutable { + + var name: String = "PostApplicantPage" + + var children: [DeeplinkExecutable] = [] + + var isDestination: Bool = false + + init() { } + + func execute(with coordinator: any BaseFeature.Coordinator, userInfo: [AnyHashable : Any]?) -> Coordinator? { + + guard let centerMainPageCoordinator = coordinator as? CenterMainPageCoordinator else { + return nil + } + + guard let postId = userInfo?["postId"] as? String else { return nil } + + centerMainPageCoordinator.presentPostApplicantPage(postId: postId) + + return centerMainPageCoordinator + } +} diff --git a/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/SplashDeeplink.swift b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/SplashDeeplink.swift new file mode 100644 index 00000000..5cf2c264 --- /dev/null +++ b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/SplashDeeplink.swift @@ -0,0 +1,33 @@ +// +// SplashDeeplink.swift +// RootFeature +// +// Created by choijunios on 10/9/24. +// + +import Foundation +import BaseFeature + + +class SplashDeeplink: DeeplinkExecutable { + + var name: String = "SplashPage" + + var children: [DeeplinkExecutable] = [] + + var isDestination: Bool = false + + init() { } + + func execute(with coordinator: any BaseFeature.Coordinator, userInfo: [AnyHashable : Any]?) -> (any BaseFeature.Coordinator)? { + guard let appCoordinator = coordinator as? AppCoordinator else { + return nil + } + + + appCoordinator.runSplashFlow() + + + return appCoordinator + } +} diff --git a/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/DeeplinkParser.swift b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/DeeplinkParser.swift new file mode 100644 index 00000000..58c2f93a --- /dev/null +++ b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/DeeplinkParser.swift @@ -0,0 +1,50 @@ +// +// DeeplinkParser.swift +// RootFeature +// +// Created by choijunios on 10/9/24. +// + +import Foundation +import BaseFeature + +enum DeeplinkParserError: Error { + case rootNotFound + case childNotFound +} + +class DeeplinkParser { + + func makeDeeplinkList(components: [String]) throws -> [DeeplinkExecutable] { + + var deeplinks: [DeeplinkExecutable] = [] + + for component in components { + + if deeplinks.isEmpty { + let root = try findRoot(name: component) + deeplinks.append(root) + continue + } + + guard let parent = deeplinks.last, let child = parent.findChild(name: component) else { + throw DeeplinkParserError.childNotFound + } + + deeplinks.append(child) + } + + return deeplinks + } + + private func findRoot(name: String) throws -> DeeplinkExecutable { + switch name { + case "CenterMainPage": + return CenterMainPageDeeplink() + default: + throw DeeplinkParserError.rootNotFound + } + } +} + + diff --git a/project/Projects/Presentation/Feature/Root/Sources/RemoteNotification/FireBaseTokenRepository.swift b/project/Projects/Presentation/Feature/Root/Sources/RemoteNotification/FireBaseTokenRepository.swift new file mode 100644 index 00000000..d539bdd3 --- /dev/null +++ b/project/Projects/Presentation/Feature/Root/Sources/RemoteNotification/FireBaseTokenRepository.swift @@ -0,0 +1,39 @@ +// +// FireBaseTokenRepository.swift +// RootFeature +// +// Created by choijunios on 10/8/24. +// + +import Foundation +import Domain +import Core + + +import FirebaseMessaging + +public class FCMTokenRepository: NSObject, NotificationTokenRepository { + + public weak var delegate: NotificationTokenRepositoryDelegate? + + public override init() { + super.init() + Messaging.messaging().delegate = self + } + + public func getToken() -> String? { + Messaging.messaging().fcmToken + } +} + +extension FCMTokenRepository: MessagingDelegate { + + // 새로운 토큰 수신 + public func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { + + if let fcmToken { + printIfDebug(fcmToken) + delegate?.notificationToken(freshToken: fcmToken) + } + } +} diff --git a/project/Projects/Presentation/Feature/Root/Sources/RemoteNotification/RemoteNotificationHelper.swift b/project/Projects/Presentation/Feature/Root/Sources/RemoteNotification/RemoteNotificationHelper.swift new file mode 100644 index 00000000..f0d0a4a3 --- /dev/null +++ b/project/Projects/Presentation/Feature/Root/Sources/RemoteNotification/RemoteNotificationHelper.swift @@ -0,0 +1,64 @@ +// +// RemoteNotificationHelper.swift +// RootFeature +// +// Created by choijunios on 10/8/24. +// + +import Foundation +import UserNotifications +import BaseFeature +import Core + + +import RxSwift + +struct DeeplinkBundle { + let deeplinks: [DeeplinkExecutable] + let userInfo: [AnyHashable: Any]? +} + +class RemoteNotificationHelper: NSObject { + + // Observable + let deeplinks: BehaviorSubject = .init( + value: .init(deeplinks: [], userInfo: nil) + ) + + let deeplinkParser: DeeplinkParser = .init() + + override init() { + super.init() + UNUserNotificationCenter.current().delegate = self + } +} + +extension RemoteNotificationHelper: UNUserNotificationCenterDelegate { + + /// 앱이 포그라운드에 있는 경우, 노티페이케이션이 도착하기만 하면 호출된다. + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + print("!!") + } + + /// 앱이 백그라운드에 있는 경우, 유저가 노티피케이션을 통해 액션을 선택한 경우 호출 + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + + let userInfo = response.notification.request.content.userInfo + + if let linkInfo = userInfo["link"] as? String { + + let testLinks = linkInfo.split(separator: "/").map(String.init) + print(testLinks) + do { + let parsedLinks = try deeplinkParser.makeDeeplinkList(components: testLinks) + deeplinks.onNext(.init( + deeplinks: parsedLinks, + userInfo: userInfo + )) + + } catch { + printIfDebug("딥링크 파싱실패 \(error.localizedDescription)") + } + } + } +} diff --git a/project/Projects/Presentation/Feature/Splash/Sources/SplashCoordinator.swift b/project/Projects/Presentation/Feature/Splash/Sources/SplashCoordinator.swift index fde2c16d..6bf0d149 100644 --- a/project/Projects/Presentation/Feature/Splash/Sources/SplashCoordinator.swift +++ b/project/Projects/Presentation/Feature/Splash/Sources/SplashCoordinator.swift @@ -36,6 +36,8 @@ public class SplashCoordinator: BaseCoordinator { public var startFlow: ((SplashCoordinatorDestination) -> ())! + public weak var delegate: SplashCoordinatorDelegate? + // #1. 네트워크 연결상태 확인 private let networkCheckingPassed: PublishSubject = .init() @@ -85,6 +87,7 @@ public class SplashCoordinator: BaseCoordinator { .subscribe(onNext: { (object, arg1) in let (_, _, userType) = arg1 object.startFlow(.mainPage(userType: userType)) + object.delegate?.splashCoordinator(satisfiedAllCondition: true) }) .disposed(by: disposeBag) } @@ -431,3 +434,7 @@ private extension SplashCoordinator { } } +public protocol SplashCoordinatorDelegate: AnyObject { + + func splashCoordinator(satisfiedAllCondition: Bool) +} diff --git a/project/Projects/Presentation/Feature/UserProfile/Sources/CenterProfile/CenterProfileCoordinator.swift b/project/Projects/Presentation/Feature/UserProfile/Sources/CenterProfile/CenterProfileCoordinator.swift index 309a8c2b..8e36c070 100644 --- a/project/Projects/Presentation/Feature/UserProfile/Sources/CenterProfile/CenterProfileCoordinator.swift +++ b/project/Projects/Presentation/Feature/UserProfile/Sources/CenterProfile/CenterProfileCoordinator.swift @@ -11,7 +11,7 @@ import BaseFeature import Domain /// 내센터, 다른 센터를 모두 불러올 수 있습니다. -public class CenterProfileCoordinator: Coordinator2 { +public class CenterProfileCoordinator: Coordinator { public var onFinish: (() -> ())? diff --git a/project/Projects/Presentation/Feature/UserProfile/Sources/CenterProfile/CenterProfileViewModel.swift b/project/Projects/Presentation/Feature/UserProfile/Sources/CenterProfile/CenterProfileViewModel.swift index 9c2ec2bb..06b181f2 100644 --- a/project/Projects/Presentation/Feature/UserProfile/Sources/CenterProfile/CenterProfileViewModel.swift +++ b/project/Projects/Presentation/Feature/UserProfile/Sources/CenterProfile/CenterProfileViewModel.swift @@ -33,7 +33,7 @@ protocol CenterProfileInputable { var editingFinishButtonPressed: PublishRelay { get } var editingPhoneNumber: BehaviorRelay { get } var editingInstruction: BehaviorRelay { get } - var selectedImage: PublishRelay { get } + var selectedImage: BehaviorRelay { get } var exitButtonClicked: PublishRelay { get } } @@ -73,7 +73,7 @@ class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { var editingFinishButtonPressed: PublishRelay = .init() var editingPhoneNumber: BehaviorRelay = .init(value: "") var editingInstruction: BehaviorRelay = .init(value: "") - var selectedImage: PublishRelay = .init() + var selectedImage: BehaviorRelay = .init(value: nil) var exitButtonClicked: RxRelay.PublishRelay = .init() // 기본 데이터 @@ -84,6 +84,8 @@ class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { var centerIntroduction: Driver? var displayingImage: Driver? + var originalPhonenumber: String = "" + // 수정 상태 여부 var isEditingMode: Driver? @@ -93,18 +95,6 @@ class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { // Image private let imageDownLoadScheduler = ConcurrentDispatchQueueScheduler(qos: .userInitiated) - func checkModification() -> (String?, String?, ImageUploadInfo?) { - - let phoneNumber = editingPhoneNumber.value - let instruction = editingInstruction.value - - return ( - phoneNumber == fetchedPhoneNumber ? nil : phoneNumber, - instruction == fetchedIntroduction ? nil : instruction, - editingImageInfo - ) - } - init(mode: ProfileMode) { self.mode = mode @@ -115,18 +105,18 @@ class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { super.init() // MARK: fetch from server - let profileRequestResult = readyToFetch + let profileRequestResult = mapEndLoading(mapStartLoading(readyToFetch) .unretained(self) .flatMap { (obj, _) in obj.profileUseCase.getProfile(mode: obj.mode) - } + }) .share() let profileRequestSuccess = profileRequestResult .compactMap { $0.value } .share() - let profileRequestFailure = profileRequestResult + let profileRequestFailureAlert = profileRequestResult .compactMap { $0.error } .map { error in DefaultAlertContentVO( @@ -156,64 +146,72 @@ class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { .map { [weak self] in let phoneNumber = $0.officeNumber self?.fetchedPhoneNumber = phoneNumber + self?.originalPhonenumber = phoneNumber return phoneNumber } .asDriver(onErrorJustReturn: "") - let fetchCenterImageInfo = profileRequestSuccess + let fetchCenterImageInfo = mapEndLoading(mapStartLoading(profileRequestSuccess) .compactMap { $0.profileImageInfo } .observe(on: imageDownLoadScheduler) .flatMap { [cacheRepository] downloadInfo in cacheRepository .getImage(imageInfo: downloadInfo) - }.map { image -> UIImage? in - image - } + }.map { image -> UIImage? in image }) // MARK: image validation - let imageValidationResult = selectedImage - .map { [unowned self] image -> UIImage? in - guard let imageInfo = self.validateSelectedImage(image: image) else { return nil } - printIfDebug("✅ 업로드 가능한 이미지 타입 \(imageInfo.ext)") - self.editingImageInfo = imageInfo - return image - } - .share() - - let imageValidationFailure = imageValidationResult - .filter { $0 == nil } - .map { _ in - DefaultAlertContentVO( - title: "이미지 선택 오류", - message: "지원하지 않는 이미지 형식입니다." - ) - } - let displayingImageDriver = Observable .merge( fetchCenterImageInfo, - imageValidationResult + selectedImage.compactMap { $0 } ) .asDriver(onErrorJustReturn: .init()) // 최신 값들 + 버튼이 눌릴 경우 변경 로직이 실행된다. - let editingRequestResult = mapEndLoading(mapStartLoading(editingFinishButtonPressed.asObservable()) - .map({ [unowned self] _ in - checkModification() - }) - .flatMap { [profileUseCase, editingPhoneNumber] (inputs) in - - let (phoneNumber, introduction, imageInfo) = inputs + let checkImageSelectionResult = mapStartLoading(editingFinishButtonPressed) + .withLatestFrom(selectedImage) + .share() + + let imageSelected = checkImageSelectionResult.compactMap { $0 } + let imageDoesntSelected = checkImageSelectionResult + .filter { $0 == nil } + .map { _ -> ImageUploadInfo? in nil } + + let imageEncodingResult = imageSelected + .observe(on: ConcurrentDispatchQueueScheduler(qos: .userInitiated)) + .map { image -> ImageUploadInfo? in + ImageUploadInfo.create(image: image) + } + .share() + + + let imageEcodingSuccess = imageEncodingResult.filter { $0 != nil } + let imageEcodingFailure = mapEndLoading(imageEncodingResult.filter { $0 == nil }) + + let imageEncodingFailureAlert = imageEcodingFailure.map { _ in + DefaultAlertContentVO( + title: "이미지 선택 오류", + message: "지원하지 않는 이미지 형식입니다." + ) + } + + let imageProcessingFinishWithSuccess = Observable.merge(imageEcodingSuccess, imageDoesntSelected) + + + let editingRequestResult = mapEndLoading(imageProcessingFinishWithSuccess + .unretained(self) + .flatMap { (obj, imageInfo) in + let (phoneNumber, introduction) = obj.checkTextInputModification() // 변경이 발생하지 않은 곳은 nil값이 전달된다. if let _ = phoneNumber { printIfDebug("✅ 전화번호 변경되었음") } if let _ = introduction { printIfDebug("✅ 센터소개 변경되었음") } if let _ = imageInfo { printIfDebug("✅ 센터 이미지 변경되었음") } - // 전화번호는 무조건 포함시켜야 함으로 아래와 같이 포함합니다. - return profileUseCase.updateProfile( - phoneNumber: phoneNumber ?? editingPhoneNumber.value, + // 전화번호는 무조건 포함시켜야 함으로 변경이 발생하지 않더라도 아래와 같이 포함합니다. + return obj.profileUseCase.updateProfile( + phoneNumber: phoneNumber ?? obj.originalPhonenumber, introduction: introduction, imageInfo: imageInfo ) @@ -233,7 +231,7 @@ class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { } .asDriver(onErrorJustReturn: ()) - let editingRequestFailure = editingRequestResult + let editingRequestFailureAlert = editingRequestResult .compactMap({ $0.error }) .map({ error in // 변경 실패 Alert @@ -272,9 +270,9 @@ class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { Observable .merge( - profileRequestFailure, - editingRequestFailure, - imageValidationFailure + profileRequestFailureAlert, + editingRequestFailureAlert, + imageEncodingFailureAlert ) .subscribe(self.alert) .disposed(by: disposeBag) @@ -296,7 +294,14 @@ class CenterProfileViewModel: BaseViewModel, CenterProfileViewModelable { self.editingValidation = editingValidation } - func validateSelectedImage(image: UIImage) -> ImageUploadInfo? { - .create(image: image) + func checkTextInputModification() -> (String?, String?) { + + let phoneNumber = editingPhoneNumber.value + let instruction = editingInstruction.value + + return ( + phoneNumber == fetchedPhoneNumber ? nil : phoneNumber, + instruction == fetchedIntroduction ? nil : instruction + ) } } diff --git a/project/Projects/Presentation/Feature/UserProfile/Sources/WorkerProfile/WorkerMyProfileCoordinator.swift b/project/Projects/Presentation/Feature/UserProfile/Sources/WorkerProfile/WorkerMyProfileCoordinator.swift index a9e6f4e1..4cd31864 100644 --- a/project/Projects/Presentation/Feature/UserProfile/Sources/WorkerProfile/WorkerMyProfileCoordinator.swift +++ b/project/Projects/Presentation/Feature/UserProfile/Sources/WorkerProfile/WorkerMyProfileCoordinator.swift @@ -8,7 +8,7 @@ import Foundation import BaseFeature -public class WorkerMyProfileCoordinator: Coordinator2 { +public class WorkerMyProfileCoordinator: Coordinator { public var onFinish: (() -> ())? let router: Router diff --git a/project/Projects/Presentation/Feature/UserProfile/Sources/WorkerProfile/WorkerProfileCoordinator.swift b/project/Projects/Presentation/Feature/UserProfile/Sources/WorkerProfile/WorkerProfileCoordinator.swift index 1cc1940b..5ccb1031 100644 --- a/project/Projects/Presentation/Feature/UserProfile/Sources/WorkerProfile/WorkerProfileCoordinator.swift +++ b/project/Projects/Presentation/Feature/UserProfile/Sources/WorkerProfile/WorkerProfileCoordinator.swift @@ -10,7 +10,7 @@ import BaseFeature import Domain import Core -public class WorkerProfileCoordinator: Coordinator2 { +public class WorkerProfileCoordinator: Coordinator { public var onFinish: (() -> ())? let router: Router diff --git a/project/Projects/Presentation/Feature/WorkerMainPage/Sources/Setting/SettingPageViewModel.swift b/project/Projects/Presentation/Feature/WorkerMainPage/Sources/Setting/SettingPageViewModel.swift index 9d24c1a8..906294a7 100644 --- a/project/Projects/Presentation/Feature/WorkerMainPage/Sources/Setting/SettingPageViewModel.swift +++ b/project/Projects/Presentation/Feature/WorkerMainPage/Sources/Setting/SettingPageViewModel.swift @@ -133,8 +133,6 @@ class SettingPageViewModel: BaseViewModel { signOutSuccess .unretained(self) .subscribe(onNext: { (obj, _) in - // 로그이아웃 성공 -> 원격알림 토큰 제거 - NotificationCenter.default.post(name: .requestDeleteTokenFromServer, object: nil) obj.changeToAuthFlow?() }) .disposed(by: disposeBag) diff --git a/project/Projects/Presentation/PresentationCore/Sources/NotificationName+Extension/RemoteNotification.swift b/project/Projects/Presentation/PresentationCore/Sources/NotificationName+Extension/RemoteNotification.swift deleted file mode 100644 index a4f64697..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/NotificationName+Extension/RemoteNotification.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// RemoteNotification.swift -// PresentationCore -// -// Created by choijunios on 9/26/24. -// - -import Foundation - -public extension Notification.Name { - - static let requestTransportTokenToServer: Self = .init("requestTransportTokenToServer") - static let requestDeleteTokenFromServer: Self = .init("requestDeleteTokenFromServer") -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Coordinator.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Coordinator.swift deleted file mode 100644 index 8909325f..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Coordinator.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// Coordinator.swift -// PresentationCore -// -// Created by 최준영 on 6/21/24. -// - -import UIKit -import Core - -// MARK: Coordinator -public protocol Coordinator: AnyObject { - - var parent: ParentCoordinator? { get set } - var navigationController: UINavigationController { get } - - func start() - func popViewController(animated: Bool) -} - -public extension Coordinator { - - func popViewController(animated: Bool = true) { - navigationController.popViewController(animated: animated) - } -} - -// MARK: ParentCoordinator -public protocol ParentCoordinator: Coordinator { - - var childCoordinators: [Coordinator] { get set } - - func addChildCoordinator(_ coordinator: Coordinator) - func removeChildCoordinator(_ coordinator: Coordinator) - - func clearChildren() -} - -public extension ParentCoordinator { - - func addChildCoordinator(_ coordinator: Coordinator) { - childCoordinators.append(coordinator) - coordinator.parent = self - } - - func removeChildCoordinator(_ coordinator: Coordinator) { - childCoordinators = childCoordinators.filter { $0 !== coordinator } - } - - func clearChildren() { - - printIfDebug(self, childCoordinators, navigationController.viewControllers) - - let lastCoordinator = childCoordinators.popLast() - - var middleViewControllers: [UIViewController?] = [] - - childCoordinators.reversed().forEach { coordinator in - - if coordinator is ChildCoordinator { - - let child = coordinator as! ChildCoordinator - - if let middleViewController = child.viewControllerRef { - - middleViewControllers.append(middleViewController) - } - - self.removeChildCoordinator(child) - } - } - - navigationController.viewControllers = navigationController.viewControllers.filter({ viewController in - !middleViewControllers.contains(where: { $0 === viewController }) - }) - - if lastCoordinator is ParentCoordinator { - - (lastCoordinator as! ParentCoordinator).clearChildren() - - } else { - - if let lastCoordinator { - - self.removeChildCoordinator(lastCoordinator) - lastCoordinator.popViewController() - } - } - - print("종료", self, childCoordinators, navigationController.viewControllers) - } -} - -// MARK: ChildCoordinator -public protocol ChildCoordinator: Coordinator { - - var viewControllerRef: UIViewController? { get } - - func coordinatorDidFinish() -} - diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/CoordinatorWrapper.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/CoordinatorWrapper.swift deleted file mode 100644 index cd607f79..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/CoordinatorWrapper.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// CoordinatorWrapper.swift -// PresentationCore -// -// Created by choijunios on 8/29/24. -// - -import UIKit - - -public class CoordinatorWrapper: ChildCoordinator { - - public weak var viewControllerRef: UIViewController? - public weak var parent: ParentCoordinator? - - public let navigationController: UINavigationController - - let animated: Bool - - public init( - nav: UINavigationController, - vc: UIViewController, - animated: Bool = true - ) { - self.navigationController = nav - self.viewControllerRef = vc - self.animated = animated - } - - public func start() { - navigationController.pushViewController(viewControllerRef!, animated: animated) - } - - public func coordinatorDidFinish() { - popViewController() - parent?.removeChildCoordinator(self) - } -} - diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Auth/AuthCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Auth/AuthCoordinatable.swift deleted file mode 100644 index 75894939..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Auth/AuthCoordinatable.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// AuthCoordinatable.swift -// PresentationCore -// -// Created by choijunios on 7/1/24. -// - -import Foundation - -public enum AuthType { - - case worker - case center -} - -public protocol AuthCoordinatable: ParentCoordinator { - - func authFinished() - func showCompleteScreen(ro: AnonymousCompleteVCRenderObject) - func registerAsWorker() - func registerAsCenter() - func startCenterLoginFlow() -} - - diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Auth/CanterLoginFlowable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Auth/CanterLoginFlowable.swift deleted file mode 100644 index 31f371a6..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Auth/CanterLoginFlowable.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// CanterLoginFlowable.swift -// PresentationCore -// -// Created by choijunios on 8/27/24. -// - -import Foundation - -public protocol CanterLoginFlowable: ParentCoordinator { - func login() - func setNewPassword() - func authFinished() -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Auth/CenterAuthCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Auth/CenterAuthCoordinatable.swift deleted file mode 100644 index bfe5bda2..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Auth/CenterAuthCoordinatable.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// CenterAuthCoordinatable.swift -// PresentationCore -// -// Created by choijunios on 7/1/24. -// - -import Foundation - -public protocol CanterLoginCoordinatable: ParentCoordinator { - - func login() - func setNewPassword() - func authFinished() -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Auth/WorkerAuthCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Auth/WorkerAuthCoordinatable.swift deleted file mode 100644 index 96c39c08..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Auth/WorkerAuthCoordinatable.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// WorkerAuthCoordinatable.swift -// PresentationCore -// -// Created by choijunios on 7/1/24. -// - -import Foundation - -public protocol WorkerAuthCoordinatable: ParentCoordinator { - - func register() - func authFinished() -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Main/CenterMainCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Main/CenterMainCoordinatable.swift deleted file mode 100644 index 7fdb10a3..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Main/CenterMainCoordinatable.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// CenterMainCoordinatable.swift -// PresentationCore -// -// Created by choijunios on 7/27/24. -// - -import Foundation - -public protocol CenterMainCoordinatable: ParentCoordinator { - -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Profile/CenterProfileRegisterCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Profile/CenterProfileRegisterCoordinatable.swift deleted file mode 100644 index 55a7ebb2..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Profile/CenterProfileRegisterCoordinatable.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// CenterProfileRegisterCoordinatable.swift -// PresentationCore -// -// Created by choijunios on 7/27/24. -// - -import Foundation -import Domain - - -public protocol CenterProfileRegisterCoordinatable: ParentCoordinator { - - func registerFinished() - func showPreviewScreen(stateObject: CenterProfileRegisterState) - func showCompleteScreen() -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/CenterPostBoardCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/CenterPostBoardCoordinatable.swift deleted file mode 100644 index ea8a3374..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/CenterPostBoardCoordinatable.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// CenterPostBoardCoordinatable.swift -// PresentationCore -// -// Created by choijunios on 8/13/24. -// - -import Foundation - -public protocol CenterPostBoardTabCoordinatable: ParentCoordinator { - - func showApplicantScreen() - func postDetailScreen() - func showPostEditScreen() -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/RecruitmentManagementCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/RecruitmentManagementCoordinatable.swift deleted file mode 100644 index f975524f..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/RecruitmentManagementCoordinatable.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// RecruitmentManagementCoordinatable.swift -// PresentationCore -// -// Created by choijunios on 8/13/24. -// - -import Domain - - -public protocol RecruitmentManagementCoordinatable: ParentCoordinator { - - func showCheckingApplicantScreen(postId: String) - func showPostDetailScreenForCenter(postId: String, postState: PostState) - func showEditScreen(postId: String) - func showRegisterPostScrean() -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/RegisterRecruitmentPostCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/RegisterRecruitmentPostCoordinatable.swift deleted file mode 100644 index 9cf386f9..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/RegisterRecruitmentPostCoordinatable.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// RegisterRecruitmentPostCoordinatable.swift -// PresentationCore -// -// Created by choijunios on 8/5/24. -// - -import Foundation - -public protocol RegisterRecruitmentPostCoordinatable: ParentCoordinator { - - func showOverViewScreen() - func showEditPostScreen() - func showRegisterCompleteScreen() - func registerFinished() -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Worker/WorkerRecruitmentBoardCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Worker/WorkerRecruitmentBoardCoordinatable.swift deleted file mode 100644 index f54ffff6..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Worker/WorkerRecruitmentBoardCoordinatable.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// WorkerRecruitmentBoardCoordinatable.swift -// PresentationCore -// -// Created by choijunios on 8/15/24. -// - -import Foundation -import Domain - - -public protocol WorkerRecruitmentBoardCoordinatable: ParentCoordinator { - /// 요양보호사가 볼 수 있는 공고 상세정보를 표시합니다. - func showPostDetail(postInfo: RecruitmentPostInfo) - - /// 센터 프로필을 표시합니다. - func showCenterProfile(centerId: String) - - /// 요양보호사의 프로필을 표시합니다. - func showWorkerProfile() -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Root/RootCoorinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Root/RootCoorinatable.swift deleted file mode 100644 index effd5d48..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Root/RootCoorinatable.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// asd.swift -// PresentationCore -// -// Created by choijunios on 8/25/24. -// - -import Foundation - -public protocol RootCoorinatable: ParentCoordinator { - func auth() - func centerAuth() - func workerMain() - func centerMain() - func makeCenterProfile() - func popToRoot() -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Setting/CenterSettingCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Setting/CenterSettingCoordinatable.swift deleted file mode 100644 index f982d60a..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Setting/CenterSettingCoordinatable.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// CenterSettingCoordinatable.swift -// PresentationCore -// -// Created by choijunios on 8/25/24. -// - -import Foundation - -public protocol CenterSettingCoordinatable: ParentCoordinator { - /// 시설 관리자 계정을 지우는 작업을 시작합니다. - func startRemoveCenterAccountFlow() - - /// 현재 센터 프로필을 표시합니다. - func showMyCenterProfile() -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Setting/Deregister/DeregisterCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Setting/Deregister/DeregisterCoordinatable.swift deleted file mode 100644 index 17089d71..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Setting/Deregister/DeregisterCoordinatable.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// asd.swift -// PresentationCore -// -// Created by choijunios on 8/25/24. -// - -import Foundation - - -public protocol DeregisterCoordinatable: ParentCoordinator { - - /// 공통: 탈퇴 이유를 선택합니다. - func showSelectReasonScreen() - - /// 공통: 탈퇴를 취소합니다. - func cancelDeregister() - - /// 센터관리자: 마지막으로 비밀번호를 입력합니다. - func showFinalPasswordScreen(reasons: [String]) - - /// 요양보호사: 마지막으로 전화번호를 입력합니다. - func showFinalPhoneAuthScreen(reasons: [String]) - - /// 최초화면으로 돌아갑니다. - func popToRoot() -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Setting/WorkerSettingScreenCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Setting/WorkerSettingScreenCoordinatable.swift deleted file mode 100644 index 5d3ef1a2..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Setting/WorkerSettingScreenCoordinatable.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// WorkerSettingScreenCoordinatable.swift -// PresentationCore -// -// Created by choijunios on 8/25/24. -// - -import Foundation - -public protocol WorkerSettingScreenCoordinatable: ParentCoordinator { - /// 요양보호사 계정을 지우는 작업을 시작합니다. - func startRemoveWorkerAccountFlow() - - /// 요양보호사 프로필을 열람합니다. - func showMyProfileScreen() -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Notification/CoordinatingNotifications.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Notification/CoordinatingNotifications.swift deleted file mode 100644 index a10550e3..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Notification/CoordinatingNotifications.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// CoordinatingNotifications.swift -// PresentationCore -// -// Created by choijunios on 8/25/24. -// - -import Foundation - -public extension Notification.Name { - - static let popToInitialVC: Notification.Name = .init("popToInitialVC") -} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Notification/RecruitmentPostNotifications.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Notification/RecruitmentPostNotifications.swift deleted file mode 100644 index b6e0d67e..00000000 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Notification/RecruitmentPostNotifications.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// RecruitmentPost.swift -// PresentationCore -// -// Created by choijunios on 8/29/24. -// - -import Foundation - -public extension Notification.Name { - - static let removePostRequestFromCell: Notification.Name = .init("removePostRequestFromCell") -} diff --git a/project/graph.png b/project/graph.png index 4da4ae8d..53be928f 100644 Binary files a/project/graph.png and b/project/graph.png differ