From 8c4e2dea1b9d1646086475ea7e1f6f62f1875947 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 29 Aug 2024 15:24:07 +0900 Subject: [PATCH 01/21] =?UTF-8?q?[IDLE-000]=20=EC=9A=94=EC=96=91=EB=B3=B4?= =?UTF-8?q?=ED=98=B8=EC=82=AC=20=EC=84=A4=EC=A0=95=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=82=B4=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WorkerSettingCoordinator.swift | 13 ++++++++++++ .../RecuritmentPostListForWorkerDTO.swift | 20 ++----------------- .../Entity/VO/Employ/WorkerEmployCardVO.swift | 10 +++++----- .../Post/RecruitmentPostListForWorkerVO.swift | 8 ++++---- .../Card/Post/Worker/WorkerEmployCard.swift | 10 +++++++++- .../WorkerSettingScreenCoordinator.swift | 4 ++++ .../Profile/WorkerProfileViewController.swift | 3 +-- .../ViewModel/Seting/WorkerSettingVM.swift | 8 ++++++++ .../WorkerSettingScreenCoordinatable.swift | 3 +++ 9 files changed, 49 insertions(+), 30 deletions(-) diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerSettingCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerSettingCoordinator.swift index 8d68e1c0..fc171c1b 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerSettingCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerSettingCoordinator.swift @@ -60,4 +60,17 @@ class WorkerSettingCoordinaator: WorkerSettingScreenCoordinatable { coordinator.parent = self coordinator.start() } + + public func showMyProfileScreen() { + let coordinator = WorkerProfileCoordinator( + dependency: .init( + profileMode: .myProfile, + navigationController: navigationController, + workerProfileUseCase: injector.resolve(WorkerProfileUseCase.self) + ) + ) + addChildCoordinator(coordinator) + coordinator.parent = self + coordinator.start() + } } diff --git a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift index 47ca3703..4949ff03 100644 --- a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift +++ b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift @@ -47,26 +47,10 @@ public struct RecruitmentPostForWorkerDTO: Codable { WorkDay.toEntity(text: dayText) }) - let payAmount = String(payAmount) - var formedPayAmount = "" - for (index, char) in payAmount.reversed().enumerated() { - if (index % 3) == 0, index != 0 { - formedPayAmount += "," - } - formedPayAmount += String(char) - } - let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" let deadlineDate = applyDeadline != nil ? dateFormatter.date(from: applyDeadline!) : nil - // distance는 미터단위입니다. - var distanceText: String = "\(distance)m" - if distance >= 1000 { - let kilometers = Double(distance)/1000.0 - distanceText = String(format: "%.1fkm", kilometers) - } - return .init( postId: id, workDays: workDayList, @@ -81,8 +65,8 @@ public struct RecruitmentPostForWorkerDTO: Codable { applyDeadlineType: ApplyDeadlineType.toEntity(text: applyDeadlineType), applyDeadlineDate: deadlineDate, payType: PaymentType.toEntity(text: payType), - payAmount: formedPayAmount, - distanceFromWorkPlace: distanceText + payAmount: String(payAmount), + distanceFromWorkPlace: distance ) } } diff --git a/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift b/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift index 658f24cc..15f61271 100644 --- a/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift +++ b/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift @@ -11,7 +11,7 @@ public struct WorkerEmployCardVO { public let dayLeft: Int public let isBeginnerPossible: Bool - public let distanceFromWorkPlace: String + public let distanceFromWorkPlace: Int public let title: String public let targetAge: Int public let careGrade: CareGrade @@ -25,7 +25,7 @@ public struct WorkerEmployCardVO { public init( dayLeft: Int, isBeginnerPossible: Bool, - distanceFromWorkPlace: String, + distanceFromWorkPlace: Int, title: String, targetAge: Int, careGrade: CareGrade, @@ -104,7 +104,7 @@ public struct WorkerEmployCardVO { return WorkerEmployCardVO( dayLeft: leftDay ?? 31, isBeginnerPossible: isBeginnerPossible, - distanceFromWorkPlace: "500", + distanceFromWorkPlace: 500, title: title, targetAge: targetAge, careGrade: careGrade, @@ -160,7 +160,7 @@ public extension WorkerEmployCardVO { static let mock = WorkerEmployCardVO( dayLeft: 10, isBeginnerPossible: true, - distanceFromWorkPlace: "500m", + distanceFromWorkPlace: 500, title: "서울특별시 강남구 신사동", targetAge: 78, careGrade: .four, @@ -175,7 +175,7 @@ public extension WorkerEmployCardVO { static let `default` = WorkerEmployCardVO( dayLeft: 0, isBeginnerPossible: true, - distanceFromWorkPlace: "8km", + distanceFromWorkPlace: 8000, title: "기본값", targetAge: 10, careGrade: .one, diff --git a/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift b/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift index 6ffcb8c1..3dee879a 100644 --- a/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift +++ b/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift @@ -40,7 +40,7 @@ public struct RecruitmentPostForWorkerVO { public let payType: PaymentType public let payAmount: String - public let distanceFromWorkPlace: String + public let distanceFromWorkPlace: Int public init( postId: String, @@ -57,7 +57,7 @@ public struct RecruitmentPostForWorkerVO { applyDeadlineDate: Date?, payType: PaymentType, payAmount: String, - distanceFromWorkPlace: String + distanceFromWorkPlace: Int ) { self.postId = postId self.workDays = workDays @@ -90,7 +90,7 @@ public struct RecruitmentPostForWorkerVO { applyDeadlineType: .specificDate, applyDeadlineDate: Calendar.current.date(byAdding: .day, value: 7, to: Date()), payType: .hourly, - payAmount: "15,000", - distanceFromWorkPlace: "2.5km" + payAmount: "15000", + distanceFromWorkPlace: 2500 ) } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift index 3ab19f13..ddca38dd 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift @@ -69,12 +69,20 @@ public class WorkerEmployCardRO { } let addressTitle = splittedAddress.joined(separator: " ") + // distance는 미터단위입니다. + var distanceText: String = "\(vo.distanceFromWorkPlace)m" + + if vo.distanceFromWorkPlace >= 1000 { + let kilometers = Double(vo.distanceFromWorkPlace)/1000.0 + distanceText = String(format: "%.1fkm", kilometers) + } + return .init( showBiginnerTag: vo.isBeginnerPossible, showDayLeftTag: showDayLeftTag, dayLeftTagText: dayLeftTagText, titleText: addressTitle, - distanceFromWorkPlace: "\(vo.distanceFromWorkPlace)m", + distanceFromWorkPlace: distanceText, targetInfoText: targetInfoText, workDaysText: workDaysText, workTimeText: workTimeText, diff --git a/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Setting/WorkerSettingScreenCoordinator.swift b/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Setting/WorkerSettingScreenCoordinator.swift index 828d360d..0639c7cc 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Setting/WorkerSettingScreenCoordinator.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Setting/WorkerSettingScreenCoordinator.swift @@ -70,4 +70,8 @@ public class WorkerSettingScreenCoordinator: ChildCoordinator { func startRemoveWorkerAccountFlow() { parent?.startRemoveWorkerAccountFlow() } + + func showMyProfileScreen() { + parent?.showMyProfileScreen() + } } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift index 2572f87e..eaff3ba7 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift @@ -310,8 +310,7 @@ public class WorkerProfileViewController: DisposableViewController { // starButton, tagNameStack, humanInfoStack, - contactButtonContainer, - divider, + VStack([contactButtonContainer, divider], spacing: 24, alignment: .fill), employeeInfoTitleLabel, employeeInfoStack diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Seting/WorkerSettingVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Seting/WorkerSettingVM.swift index a597dc27..31e48ce2 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Seting/WorkerSettingVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Seting/WorkerSettingVM.swift @@ -111,6 +111,14 @@ public class WorkerSettingVM: WorkerSettingVMable { ) .asDriver(onErrorJustReturn: ()) + // MARK: 내프로필 보기 + myProfileButtonClicked + .subscribe(onNext: { [weak self] _ in + + self?.coordinator?.showMyProfileScreen() + }) + .disposed(by: disposeBag) + // MARK: 로그아웃 let signOutRequestResult = signOutButtonComfirmed.flatMap({ [settingUseCase] _ in settingUseCase.signoutWorkerAccount() diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Setting/WorkerSettingScreenCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Setting/WorkerSettingScreenCoordinatable.swift index 9bbf7d77..5d3ef1a2 100644 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Setting/WorkerSettingScreenCoordinatable.swift +++ b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Setting/WorkerSettingScreenCoordinatable.swift @@ -10,4 +10,7 @@ import Foundation public protocol WorkerSettingScreenCoordinatable: ParentCoordinator { /// 요양보호사 계정을 지우는 작업을 시작합니다. func startRemoveWorkerAccountFlow() + + /// 요양보호사 프로필을 열람합니다. + func showMyProfileScreen() } From fd867ed3055d8b6c7adf144015e246688f5709f9 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 29 Aug 2024 15:47:13 +0900 Subject: [PATCH 02/21] =?UTF-8?q?[IDLE-000]=20=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=EB=B0=8F=20=EC=A7=80=EC=9B=90=ED=95=9C=20=EA=B3=B5?= =?UTF-8?q?=EA=B3=A0=20=EC=A1=B0=ED=9A=8C=20UseCase/Repository/API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefaultRecruitmentPostRepository.swift | 42 ++++++++++++++++++- .../DataSource/API/RcruitmentPostAPI.swift | 20 +++++++++ .../DefualtRecruitmentPostUseCase.swift | 14 +++++++ .../RecruitmentPostRepository.swift | 8 +++- .../RecruitmentPostUseCase.swift | 6 +++ 5 files changed, 88 insertions(+), 2 deletions(-) diff --git a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift index 20e3ad76..4126546e 100644 --- a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift +++ b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift @@ -109,7 +109,47 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { .map(RecruitmentPostListForWorkerDTO.self) .catch({ error in if let moyaError = error as? MoyaError, case .objectMapping(let error, _) = moyaError { - print(error.localizedDescription) + #if DEBUG + print("앱용 공고 전체조회 에러:", error.localizedDescription) + #endif + } + return .error(error) + }) + .map { dto in + dto.toEntity() + } + } + + public func getFavoritePostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single { + service.request( + api: .getFavoritePostListForWorker(nextPageId: nextPageId, requestCnt: String(requestCnt)), + with: .withToken + ) + .map(RecruitmentPostListForWorkerDTO.self) + .catch({ error in + if let moyaError = error as? MoyaError, case .objectMapping(let error, _) = moyaError { + #if DEBUG + print("즐겨찾기한 공고 전체조회 에러:",error.localizedDescription) + #endif + } + return .error(error) + }) + .map { dto in + dto.toEntity() + } + } + + public func getAppliedPostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single { + service.request( + api: .getAppliedPostListForWorker(nextPageId: nextPageId, requestCnt: String(requestCnt)), + with: .withToken + ) + .map(RecruitmentPostListForWorkerDTO.self) + .catch({ error in + if let moyaError = error as? MoyaError, case .objectMapping(let error, _) = moyaError { + #if DEBUG + print("지원한 공고 전체조회 에러:", error.localizedDescription) + #endif } return .error(error) }) diff --git a/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift b/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift index d7ce29ee..ca86fb5d 100644 --- a/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift +++ b/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift @@ -32,6 +32,8 @@ public enum RcruitmentPostAPI { // Worker case getOnGoingNativePostListForWorker(nextPageId: String?, requestCnt: String) + case getFavoritePostListForWorker(nextPageId: String?, requestCnt: String) + case getAppliedPostListForWorker(nextPageId: String?, requestCnt: String) } extension RcruitmentPostAPI: BaseAPI { @@ -70,6 +72,10 @@ extension RcruitmentPostAPI: BaseAPI { case .getOnGoingNativePostListForWorker: "" + case .getFavoritePostListForWorker: + "/my/favorites" + case .getAppliedPostListForWorker: + "/carer/my/applied" } } @@ -103,6 +109,10 @@ extension RcruitmentPostAPI: BaseAPI { case .getOnGoingNativePostListForWorker: .get + case .getFavoritePostListForWorker: + .get + case .getAppliedPostListForWorker: + .get } } @@ -114,6 +124,16 @@ extension RcruitmentPostAPI: BaseAPI { params["next"] = nextPageId } params["limit"] = requestCnt + case .getFavoritePostListForWorker(let nextPageId, let requestCnt): + if let nextPageId { + params["next"] = nextPageId + } + params["limit"] = requestCnt + case .getAppliedPostListForWorker(let nextPageId, let requestCnt): + if let nextPageId { + params["next"] = nextPageId + } + params["limit"] = requestCnt default: break } diff --git a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift index 99fe5d6c..0869cdbf 100644 --- a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift @@ -110,4 +110,18 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { return convert(task: stream) } + + public func getFavoritePostListForWorker(pageId: String?, postCount: Int) -> RxSwift.Single> { + convert(task: repository.getFavoritePostListForWorker( + nextPageId: pageId, + requestCnt: postCount + )) + } + + public func getAppliedPostListForWorker(pageId: String?, postCount: Int) -> RxSwift.Single> { + convert(task: repository.getAppliedPostListForWorker( + nextPageId: pageId, + requestCnt: postCount + )) + } } diff --git a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift index ff3a9ece..3f2eccb5 100644 --- a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift +++ b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift @@ -44,6 +44,12 @@ public protocol RecruitmentPostRepository: RepositoryBase { /// 요양보호사 공고의 상세정보를 조회합니다. func getPostDetailForWorker(id: String) -> Single - /// 요샹보호사가 확인하는 케어밋 자체 공고정보를 가져옵니다. + /// 요양보호사가 확인하는 케어밋 자체 공고정보를 가져옵니다. func getNativePostListForWorker(nextPageId: String?, requestCnt: Int) -> Single + + /// 요양보호사가 확인하는 케어밋 자체 공고정보를 가져옵니다. + func getFavoritePostListForWorker(nextPageId: String?, requestCnt: Int) -> Single + + /// 요양보호사가 확인하는 케어밋 자체 공고정보를 가져옵니다. + func getAppliedPostListForWorker(nextPageId: String?, requestCnt: Int) -> Single } diff --git a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift index 917b5571..7e411667 100644 --- a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift @@ -51,4 +51,10 @@ public protocol RecruitmentPostUseCase: UseCaseBase { /// 요양보호사가 메인화면에 사용할 공고리스트를 호출합니다. func getPostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> Single> + + /// 요양보호사가 즐겨찾기한 공고리스트를 호출합니다. + func getFavoritePostListForWorker(pageId: String?, postCount: Int) -> Single> + + /// 요양보호사가 지원한 공고리스트를 호출합니다. + func getAppliedPostListForWorker(pageId: String?, postCount: Int) -> Single> } From cb2582e7b43f07babf4e670f8bbba8facef776a6 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 29 Aug 2024 16:48:17 +0900 Subject: [PATCH 03/21] =?UTF-8?q?[IDLE-000]=20=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=EB=B0=8F=20=EC=A7=80=EC=9B=90=ED=95=9C=20=EA=B3=B5?= =?UTF-8?q?=EA=B3=A0=20=EC=A1=B0=ED=9A=8C=20=ED=8E=98=EC=9D=B4=EC=A7=95=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefualtRecruitmentPostUseCase.swift | 76 +++++++++++++++---- .../Paging/PostPagingRequestForWorker.swift | 8 +- .../RecruitmentPostUseCase.swift | 4 +- .../LikedAndApplied/StarredAndAppliedVC.swift | 18 ++--- ...C.swift => WorkerPagablePostBoardVC.swift} | 49 +++++++++--- .../RecruitmentPost/AppliedPostBoardVM.swift | 67 +++++++++++++--- .../WorkerRecruitmentPostBoardVM.swift | 37 +++++---- .../RecruitmentPost/StarredPostBoardVM.swift | 68 ++++++++++++++--- 8 files changed, 248 insertions(+), 79 deletions(-) rename project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/{WorkerStaticPostBoardVC.swift => WorkerPagablePostBoardVC.swift} (69%) diff --git a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift index 0869cdbf..44ffbe65 100644 --- a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift @@ -98,30 +98,76 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { let stream: Single! switch request { - case .native(let nextPageId): + case .initial: stream = repository.getNativePostListForWorker( - nextPageId: nextPageId, + nextPageId: nil, requestCnt: postCount ) - case .thirdParty(let nextPageId): - /// 워크넷 가져오기 미구현 - fatalError() + case .paging(let source, let nextPageId): + switch source { + case .native: + stream = repository.getNativePostListForWorker( + nextPageId: nextPageId, + requestCnt: postCount + ) + case .thirdParty: + // TODO: ‼️ ‼️워크넷 가져오기 미구현 + fatalError() + } } return convert(task: stream) } - public func getFavoritePostListForWorker(pageId: String?, postCount: Int) -> RxSwift.Single> { - convert(task: repository.getFavoritePostListForWorker( - nextPageId: pageId, - requestCnt: postCount - )) + public func getFavoritePostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> RxSwift.Single> { + + let stream: Single! + + switch request { + case .initial: + stream = repository.getFavoritePostListForWorker( + nextPageId: nil, + requestCnt: postCount + ) + case .paging(let source, let nextPageId): + switch source { + case .native: + stream = repository.getFavoritePostListForWorker( + nextPageId: nextPageId, + requestCnt: postCount + ) + case .thirdParty: + // TODO: ‼️ ‼️워크넷 가져오기 미구현 + fatalError() + } + } + + return convert(task: stream) } - public func getAppliedPostListForWorker(pageId: String?, postCount: Int) -> RxSwift.Single> { - convert(task: repository.getAppliedPostListForWorker( - nextPageId: pageId, - requestCnt: postCount - )) + public func getAppliedPostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> RxSwift.Single> { + + let stream: Single! + + switch request { + case .initial: + stream = repository.getAppliedPostListForWorker( + nextPageId: nil, + requestCnt: postCount + ) + case .paging(let source, let nextPageId): + switch source { + case .native: + stream = repository.getAppliedPostListForWorker( + nextPageId: nextPageId, + requestCnt: postCount + ) + case .thirdParty: + // TODO: ‼️ ‼️워크넷 가져오기 미구현 + fatalError() + } + } + + return convert(task: stream) } } diff --git a/project/Projects/Domain/Entity/State/RecruitmentPost/Paging/PostPagingRequestForWorker.swift b/project/Projects/Domain/Entity/State/RecruitmentPost/Paging/PostPagingRequestForWorker.swift index e2c2602d..57b03488 100644 --- a/project/Projects/Domain/Entity/State/RecruitmentPost/Paging/PostPagingRequestForWorker.swift +++ b/project/Projects/Domain/Entity/State/RecruitmentPost/Paging/PostPagingRequestForWorker.swift @@ -8,6 +8,10 @@ import Foundation public enum PostPagingRequestForWorker { - case native(nextPageId: String?) - case thirdParty(nextPageId: String?) + public enum Source { + case native + case thirdParty + } + case initial + case paging(source: Source, nextPageId: String?) } diff --git a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift index 7e411667..32984435 100644 --- a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift @@ -53,8 +53,8 @@ public protocol RecruitmentPostUseCase: UseCaseBase { func getPostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> Single> /// 요양보호사가 즐겨찾기한 공고리스트를 호출합니다. - func getFavoritePostListForWorker(pageId: String?, postCount: Int) -> Single> + func getFavoritePostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> Single> /// 요양보호사가 지원한 공고리스트를 호출합니다. - func getAppliedPostListForWorker(pageId: String?, postCount: Int) -> Single> + func getAppliedPostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> Single> } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift index c0e7857a..3bfe9efd 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift @@ -13,14 +13,6 @@ import RxSwift import Entity import DSKit -public protocol WorkerStaticPostBoardVMable { - - var postBoardData: Driver<[WorkerEmployCardViewModelable]>? { get } - var postViewWillAppear: PublishRelay { get } - - var alert: Driver? { get } -} - public class StarredAndAppliedVC: BaseViewController { enum TabBarState: Int, CaseIterable { case applied = 0 @@ -46,9 +38,9 @@ public class StarredAndAppliedVC: BaseViewController { } private var currentState: TabBarState = .applied - private let viewControllerDict: [TabBarState: WorkerStaticPostBoardVC] = [ - .applied : WorkerStaticPostBoardVC(), - .starred : WorkerStaticPostBoardVC() + private let viewControllerDict: [TabBarState: WorkerPagablePostBoardVC] = [ + .applied : WorkerPagablePostBoardVC(), + .starred : WorkerPagablePostBoardVC() ] // Init @@ -166,8 +158,8 @@ public class StarredAndAppliedVC: BaseViewController { } public func bind( - appliedPostVM: WorkerStaticPostBoardVMable, - starredPostVM: WorkerStaticPostBoardVMable + appliedPostVM: WorkerPagablePostBoardVMable, + starredPostVM: WorkerPagablePostBoardVMable ) { viewControllerDict[.applied]?.bind(viewModel: appliedPostVM) diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerStaticPostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift similarity index 69% rename from project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerStaticPostBoardVC.swift rename to project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift index 6fe0db72..1c0f4fc1 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerStaticPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift @@ -1,5 +1,5 @@ // -// WorkerStaticPostBoardVC.swift +// WorkerPagablePostBoardVC.swift // WorkerFeature // // Created by choijunios on 8/16/24. @@ -13,11 +13,11 @@ import RxSwift import Entity import DSKit -public class WorkerStaticPostBoardVC: BaseViewController { +public class WorkerPagablePostBoardVC: BaseViewController { typealias Cell = WorkerEmployCardCell - var viewModel: WorkerStaticPostBoardVMable? + var viewModel: WorkerPagablePostBoardVMable? // View let postTableView: UITableView = { @@ -29,9 +29,13 @@ public class WorkerStaticPostBoardVC: BaseViewController { let tableHeader = BoardSortigHeaderView() - let postViewModels: BehaviorRelay<[WorkerEmployCardViewModelable]> = .init(value: []) + // Paging + var isPaging = true // Observable + let postViewModels: BehaviorRelay<[WorkerEmployCardViewModelable]> = .init(value: []) + let requestNextPage: PublishRelay = .init() + private let disposeBag = DisposeBag() public init() { @@ -87,7 +91,7 @@ public class WorkerStaticPostBoardVC: BaseViewController { } - func bind(viewModel: WorkerStaticPostBoardVMable) { + func bind(viewModel: WorkerPagablePostBoardVMable) { self.viewModel = viewModel @@ -101,15 +105,25 @@ public class WorkerStaticPostBoardVC: BaseViewController { }) .disposed(by: disposeBag) + viewModel + .alert? + .drive(onNext: { [weak self] alertVO in + self?.showAlert(vo: alertVO) + }) + .disposed(by: disposeBag) + // Input - rx.viewWillAppear - .map { _ in } - .bind(to: viewModel.postViewWillAppear) + self.rx.viewDidLoad + .bind(to: viewModel.viewDidLoad) + .disposed(by: disposeBag) + + self.requestNextPage + .bind(to: viewModel.requestNextPage) .disposed(by: disposeBag) } } -extension WorkerStaticPostBoardVC: UITableViewDataSource, UITableViewDelegate { +extension WorkerPagablePostBoardVC: UITableViewDataSource, UITableViewDelegate { public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { postViewModels.value.count @@ -126,3 +140,20 @@ extension WorkerStaticPostBoardVC: UITableViewDataSource, UITableViewDelegate { return cell } } + +// MARK: ScrollView관련 +extension WorkerPagablePostBoardVC { + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + let offsetY = scrollView.contentOffset.y + let contentHeight = scrollView.contentSize.height + let height = scrollView.frame.height + + // 스크롤이 테이블 뷰 Offset의 끝에 가게 되면 다음 페이지를 호출 + if offsetY > (contentHeight - height) { + if !isPaging { + isPaging = true + requestNextPage.accept(()) + } + } + } +} diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift index e0b4a477..6f81cb56 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift @@ -15,10 +15,13 @@ import DSKit import UseCaseInterface -public class AppliedPostBoardVM: WorkerStaticPostBoardVMable { +public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { - public var postViewWillAppear: RxRelay.PublishRelay = .init() + // Input + public var viewDidLoad: RxRelay.PublishRelay = .init() + public var requestNextPage: RxRelay.PublishRelay = .init() + // Output public var postBoardData: RxCocoa.Driver<[any DSKit.WorkerEmployCardViewModelable]>? public var alert: RxCocoa.Driver? @@ -26,27 +29,67 @@ public class AppliedPostBoardVM: WorkerStaticPostBoardVMable { weak var coordinator: WorkerRecruitmentBoardCoordinatable? let recruitmentPostUseCase: RecruitmentPostUseCase + // Paging + /// 값이 nil이라면 요청을 보내지 않습니다. + var nextPagingRequest: PostPagingRequestForWorker? + /// 가장최신의 데이터를 홀드, 다음 요청시 해당데이터에 새로운 데이터를 더해서 방출 + private let currentPostVO: BehaviorRelay<[RecruitmentPostForWorkerVO]> = .init(value: []) + public init(recruitmentPostUseCase: RecruitmentPostUseCase) { self.recruitmentPostUseCase = recruitmentPostUseCase + self.nextPagingRequest = .initial - let requestPostResult = postViewWillAppear - .flatMap { [unowned self] _ in - self.publishAppliedPostMocks() + let postPageReqeustResult = Observable + .merge( + viewDidLoad.asObservable(), + requestNextPage.asObservable() + ) + .compactMap { [weak self] _ in + // 요청이 없는 경우 요청을 보내지 않는다. + // ThirdPatry에서도 불러올 데이터가 없는 경우입니다. + self?.nextPagingRequest + } + .flatMap { [recruitmentPostUseCase] request in + recruitmentPostUseCase + .getAppliedPostListForWorker( + request: request, + postCount: 10 + ) } .share() - let requestPostSuccess = requestPostResult.compactMap { $0.value } - let requestPostFailure = requestPostResult.compactMap { $0.error } + let requestPostListSuccess = postPageReqeustResult.compactMap { $0.value } + let requestPostListFailure = postPageReqeustResult.compactMap { $0.error } - postBoardData = requestPostSuccess - .map { postForWorkerVos in + postBoardData = Observable + .zip( + currentPostVO, + requestPostListSuccess + ) + .compactMap { [weak self] (prevPostList, fetchedData) -> [WorkerEmployCardViewModelable]? in + + guard let self else { return nil } + + // MARK: 지원 공고의 경우 써드파티에서 불러올 데이터가 없다. + self.nextPagingRequest = .paging( + source: .native, + nextPageId: fetchedData.nextPageId + ) + + // 화면에 표시할 전체리스트 도출 + let fetchedPosts = fetchedData.posts + var mergedPosts = currentPostVO.value + mergedPosts.append(contentsOf: fetchedPosts) + + // 최근값 업데이트 + self.currentPostVO.accept(mergedPosts) // ViewModel 생성 - let viewModels = postForWorkerVos.map { vo in + let viewModels = mergedPosts.map { vo in let cardVO: WorkerEmployCardVO = .create(vo: vo) - let vm: AppliedWorkerEmployCardVM = .init( + let vm: OngoindWorkerEmployCardVM = .init( postId: vo.postId, vo: cardVO, coordinator: self.coordinator @@ -59,7 +102,7 @@ public class AppliedPostBoardVM: WorkerStaticPostBoardVMable { } .asDriver(onErrorJustReturn: []) - alert = requestPostFailure + alert = requestPostListFailure .map { error in DefaultAlertContentVO( title: "지원한 공고 불러오기 오류", diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift index 9f8a5dab..ce704ef4 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift @@ -14,16 +14,19 @@ import Entity import DSKit import UseCaseInterface -public protocol WorkerRecruitmentPostBoardVMable: DefaultAlertOutputable { - +public protocol WorkerPagablePostBoardVMable: DefaultAlertOutputable { /// 다음 페이지를 요청합니다. var requestNextPage: PublishRelay { get } - /// ViewDidLoad + /// ViewDidLoad, 최초 요청을 위해 사용합니다. var viewDidLoad: PublishRelay { get } /// 페이지요청에 대한 결과를 전달합니다. var postBoardData: Driver<[WorkerEmployCardViewModelable]>? { get } +} + +public protocol WorkerRecruitmentPostBoardVMable: WorkerPagablePostBoardVMable { + /// 요양보호사 위치 정보를 전달합니다. var workerLocationTitleText: Driver? { get } } @@ -63,7 +66,7 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { { self.coordinator = coordinator self.recruitmentPostUseCase = recruitmentPostUseCase - self.nextPagingRequest = .native(nextPageId: nil) + self.nextPagingRequest = .initial // 상단 위치정보 workerLocationTitleText = viewDidLoad @@ -83,7 +86,6 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { // ThirdPatry에서도 불러올 데이터가 없는 경우입니다. self?.nextPagingRequest } - .share() .flatMap { [recruitmentPostUseCase] request in recruitmentPostUseCase .getPostListForWorker( @@ -113,19 +115,26 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { if let nextPageId = fetchedData.nextPageId { // 다음값이 있는 경우 switch prevRequest { - case .native: - nextRequest = .native(nextPageId: nextPageId) - case .thirdParty: - nextRequest = .thirdParty(nextPageId: nextPageId) + case .initial: + nextRequest = .paging(source: .native, nextPageId: nextPageId) + case .paging(let source, let nextPageId): + nextRequest = .paging(source: .thirdParty, nextPageId: nextPageId) } } else { // 다음값이 없는 경우 switch prevRequest { - case .native: - nextRequest = .thirdParty(nextPageId: nil) - case .thirdParty: - // 페이징 종료 - nextRequest = nil + case .initial: + // 써드파티 데이터 호출 + nextRequest = .paging(source: .thirdParty, nextPageId: nil) + case .paging(let source, _): + switch source { + case .native: + // 써드파티 데이터 호출 + nextRequest = .paging(source: .thirdParty, nextPageId: nil) + case .thirdParty: + // 페이징 종료 + nextRequest = nil + } } } } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift index ab4c359f..ef5b90bc 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift @@ -14,10 +14,13 @@ import Entity import DSKit import UseCaseInterface -public class StarredPostBoardVM: WorkerStaticPostBoardVMable { +public class StarredPostBoardVM: WorkerPagablePostBoardVMable { - public var postViewWillAppear: RxRelay.PublishRelay = .init() + // Input + public var viewDidLoad: RxRelay.PublishRelay = .init() + public var requestNextPage: RxRelay.PublishRelay = .init() + // Output public var postBoardData: RxCocoa.Driver<[any DSKit.WorkerEmployCardViewModelable]>? public var alert: RxCocoa.Driver? @@ -25,27 +28,68 @@ public class StarredPostBoardVM: WorkerStaticPostBoardVMable { weak var coordinator: WorkerRecruitmentBoardCoordinatable? let recruitmentPostUseCase: RecruitmentPostUseCase + // Paging + /// 값이 nil이라면 요청을 보내지 않습니다. + var nextPagingRequest: PostPagingRequestForWorker? + /// 가장최신의 데이터를 홀드, 다음 요청시 해당데이터에 새로운 데이터를 더해서 방출 + private let currentPostVO: BehaviorRelay<[RecruitmentPostForWorkerVO]> = .init(value: []) + public init(recruitmentPostUseCase: RecruitmentPostUseCase) { self.recruitmentPostUseCase = recruitmentPostUseCase + self.nextPagingRequest = .initial - let requestPostResult = postViewWillAppear - .flatMap { [unowned self] _ in - self.publishStarredPostMocks() + let postPageReqeustResult = Observable + .merge( + viewDidLoad.asObservable(), + requestNextPage.asObservable() + ) + .compactMap { [weak self] _ in + // 요청이 없는 경우 요청을 보내지 않는다. + // ThirdPatry에서도 불러올 데이터가 없는 경우입니다. + self?.nextPagingRequest + } + .flatMap { [recruitmentPostUseCase] request in + recruitmentPostUseCase + .getFavoritePostListForWorker( + request: request, + postCount: 10 + ) } .share() - let requestPostSuccess = requestPostResult.compactMap { $0.value } - let requestPostFailure = requestPostResult.compactMap { $0.error } + let requestPostListSuccess = postPageReqeustResult.compactMap { $0.value } + let requestPostListFailure = postPageReqeustResult.compactMap { $0.error } - postBoardData = requestPostSuccess - .map { postForWorkerVos in + postBoardData = Observable + .zip( + currentPostVO, + requestPostListSuccess + ) + .compactMap { [weak self] (prevPostList, fetchedData) -> [WorkerEmployCardViewModelable]? in + + guard let self else { return nil } + + // TODO: ‼️ ‼️ 즐겨찾기 공고의 경우 서버에서 아직 워크넷 공고를 처리하는 방법을 정하지 못했음으로 추후에 수정할 예정입니다. + + self.nextPagingRequest = .paging( + source: .native, + nextPageId: fetchedData.nextPageId + ) + + // 화면에 표시할 전체리스트 도출 + let fetchedPosts = fetchedData.posts + var mergedPosts = currentPostVO.value + mergedPosts.append(contentsOf: fetchedPosts) + + // 최근값 업데이트 + self.currentPostVO.accept(mergedPosts) // ViewModel 생성 - let viewModels = postForWorkerVos.map { vo in + let viewModels = mergedPosts.map { vo in let cardVO: WorkerEmployCardVO = .create(vo: vo) - let vm: StarredWorkerEmployCardVM = .init( + let vm: OngoindWorkerEmployCardVM = .init( postId: vo.postId, vo: cardVO, coordinator: self.coordinator @@ -58,7 +102,7 @@ public class StarredPostBoardVM: WorkerStaticPostBoardVMable { } .asDriver(onErrorJustReturn: []) - alert = requestPostFailure + alert = requestPostListFailure .map { error in DefaultAlertContentVO( title: "즐겨찾기한 공고 불러오기 오류", From d3805e601832f8b28cf470d3bde714e2086841cd Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 29 Aug 2024 17:13:48 +0900 Subject: [PATCH 04/21] =?UTF-8?q?[IDLE-000]=20=EA=B3=B5=EA=B3=A0=EC=A7=80?= =?UTF-8?q?=EC=9B=90=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift | 2 +- .../Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift b/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift index ca86fb5d..c29476a8 100644 --- a/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift +++ b/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift @@ -29,7 +29,6 @@ public enum RcruitmentPostAPI { // - 공고 지원자 관련 case getPostApplicantCount(id: String) - // Worker case getOnGoingNativePostListForWorker(nextPageId: String?, requestCnt: String) case getFavoritePostListForWorker(nextPageId: String?, requestCnt: String) @@ -76,6 +75,7 @@ extension RcruitmentPostAPI: BaseAPI { "/my/favorites" case .getAppliedPostListForWorker: "/carer/my/applied" + } } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift index ef5b90bc..c553a33d 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift @@ -172,7 +172,7 @@ class StarredWorkerEmployCardVM: WorkerEmployCardViewModelable { .subscribe(onNext: { [weak self] _ in guard let self else { return } - coordinator?.showPostDetail( + self.coordinator?.showPostDetail( postId: postId ) }) From 82d9eaf6f9f20a4032becf9dd403e1c62e179d2e Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 29 Aug 2024 17:29:02 +0900 Subject: [PATCH 05/21] =?UTF-8?q?[IDLE-000]=20=EC=84=BC=ED=84=B0=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EC=A0=95=EB=B3=B4=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefaultUserInfoLocalRepository.swift | 13 +++++++++++++ .../State/Auth/Center/CenterAuthState.swift | 17 +++++++++++++++++ .../UserInfo/UserInfoLocalRepository.swift | 9 +++++++++ 3 files changed, 39 insertions(+) create mode 100644 project/Projects/Domain/Entity/State/Auth/Center/CenterAuthState.swift diff --git a/project/Projects/Data/ConcreteRepository/UserInfo/DefaultUserInfoLocalRepository.swift b/project/Projects/Data/ConcreteRepository/UserInfo/DefaultUserInfoLocalRepository.swift index 6b8eaa95..a4a84054 100644 --- a/project/Projects/Data/ConcreteRepository/UserInfo/DefaultUserInfoLocalRepository.swift +++ b/project/Projects/Data/ConcreteRepository/UserInfo/DefaultUserInfoLocalRepository.swift @@ -21,6 +21,7 @@ enum UserInfoStorageKey: String, Hashable, CaseIterable { // Center case currentCenter = "currentCenter" + case currentCenterAuthState = "currentCenterAuthState" } public class DefaultUserInfoLocalRepository: UserInfoLocalRepository { @@ -83,6 +84,18 @@ public class DefaultUserInfoLocalRepository: UserInfoLocalRepository { localStorageService.saveData(key: K.currentCenter.rawValue, value: encoded) } + public func setCenterAuthState(state: CenterAuthState) { + localStorageService.saveData(key: K.currentCenterAuthState.rawValue, value: state.rawValue) + } + + public func getCenterAuthState() -> Entity.CenterAuthState? { + if let centerState: String = localStorageService.fetchData(key: K.currentCenterAuthState.rawValue) { + + return CenterAuthState(rawValue: centerState) + } + return nil + } + public func removeAllData() { UserInfoStorageKey.allCases.forEach { key in diff --git a/project/Projects/Domain/Entity/State/Auth/Center/CenterAuthState.swift b/project/Projects/Domain/Entity/State/Auth/Center/CenterAuthState.swift new file mode 100644 index 00000000..1dd09f43 --- /dev/null +++ b/project/Projects/Domain/Entity/State/Auth/Center/CenterAuthState.swift @@ -0,0 +1,17 @@ +// +// CenterAuthState.swift +// Entity +// +// Created by choijunios on 8/29/24. +// + +import Foundation + +public enum CenterAuthState: String { + /// #1. 인증 요청이 되지 않은 상태입니다. + case notRequested + /// #2. 프로필이 입력되지 않은 상태입니다. + case noProfile + /// #3. 인증이 완료된 단계입니다. + case authFinished +} diff --git a/project/Projects/Domain/RepositoryInterface/UserInfo/UserInfoLocalRepository.swift b/project/Projects/Domain/RepositoryInterface/UserInfo/UserInfoLocalRepository.swift index b93c6983..078d343c 100644 --- a/project/Projects/Domain/RepositoryInterface/UserInfo/UserInfoLocalRepository.swift +++ b/project/Projects/Domain/RepositoryInterface/UserInfo/UserInfoLocalRepository.swift @@ -28,6 +28,15 @@ public protocol UserInfoLocalRepository { /// 로컬에 저장될 유저정보를 업데이트합니다. func updateCurrentCenterData(vo: CenterProfileVO) + /// 센터의 인증 정보를 설정합니다. + func setCenterAuthState(state: CenterAuthState) + + /// 센터의 인증 정보를 가져옵니다. + func getCenterAuthState() -> Entity.CenterAuthState? + + /// 센터의 인증 정보를 가져옵니다. + func getCenterAuthState() -> CenterAuthState + /// 유저타입, 정보를 모두 삭제합니다. func removeAllData() } From 380b673e1a3b1c6f3d94452817f7f8801bde2a5c Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Sat, 31 Aug 2024 02:43:05 +0900 Subject: [PATCH 06/21] =?UTF-8?q?[IDLE-000]=20=EB=94=94=EC=BD=94=EB=94=A9?= =?UTF-8?q?=20=EC=98=A4=ED=83=88=EC=9E=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift | 2 +- .../RepositoryInterface/UserInfo/UserInfoLocalRepository.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift index 164df625..f1513a0d 100644 --- a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift +++ b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift @@ -76,7 +76,7 @@ public struct RecruitmentPostDTO: Codable { let currentYear = Calendar.current.component(.year, from: Date()) customerInfo.birthYear = String(currentYear - age) - customerInfo.weight = (weight == nil) ? String(weight!) : "" + customerInfo.weight = (weight == nil) ? "" : String(weight!) customerInfo.careGrade = CareGrade(rawValue: careLevel-1)! customerInfo.cognitionState = CognitionDegree.toEntity(text: mentalStatus) diff --git a/project/Projects/Domain/RepositoryInterface/UserInfo/UserInfoLocalRepository.swift b/project/Projects/Domain/RepositoryInterface/UserInfo/UserInfoLocalRepository.swift index 078d343c..c03addd1 100644 --- a/project/Projects/Domain/RepositoryInterface/UserInfo/UserInfoLocalRepository.swift +++ b/project/Projects/Domain/RepositoryInterface/UserInfo/UserInfoLocalRepository.swift @@ -35,7 +35,7 @@ public protocol UserInfoLocalRepository { func getCenterAuthState() -> Entity.CenterAuthState? /// 센터의 인증 정보를 가져옵니다. - func getCenterAuthState() -> CenterAuthState +// func getCenterAuthState() -> CenterAuthState /// 유저타입, 정보를 모두 삭제합니다. func removeAllData() From 5e9f23a2e7df21f240c0c6b1e49d410604ce7e16 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Sat, 31 Aug 2024 02:57:59 +0900 Subject: [PATCH 07/21] =?UTF-8?q?[IDLE-000]=20=EC=9A=94=EC=96=91=EB=B3=B4?= =?UTF-8?q?=ED=98=B8=EC=82=AC=20=EC=A7=84=ED=96=89=EC=A4=91=EC=9D=B8=20?= =?UTF-8?q?=EA=B3=B5=EA=B3=A0=20UI=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecruitmentPostDetailForWorkerDTO.swift | 11 +--------- .../RecuritmentPostListForWorkerDTO.swift | 20 ++----------------- .../WorkerRecruitmentPostBoardVM.swift | 3 ++- 3 files changed, 5 insertions(+), 29 deletions(-) diff --git a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift index f1513a0d..f215a1c8 100644 --- a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift +++ b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift @@ -54,16 +54,7 @@ public struct RecruitmentPostDTO: Codable { workTimeAndPay.workStartTime = IdleDateComponent.toEntity(text: startTime) workTimeAndPay.workEndTime = IdleDateComponent.toEntity(text: endTime) workTimeAndPay.paymentType = PaymentType.toEntity(text: payType) - - let payAmount = String(payAmount) - var formedPayAmount = "" - for (index, char) in payAmount.reversed().enumerated() { - if (index % 3) == 0, index != 0 { - formedPayAmount += "," - } - formedPayAmount += String(char) - } - workTimeAndPay.paymentAmount = formedPayAmount + workTimeAndPay.paymentAmount = String(payAmount) let addressInfo: AddressInputStateObject = .init() addressInfo.addressInfo = .init( diff --git a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift index 47ca3703..d37a4828 100644 --- a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift +++ b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift @@ -47,26 +47,10 @@ public struct RecruitmentPostForWorkerDTO: Codable { WorkDay.toEntity(text: dayText) }) - let payAmount = String(payAmount) - var formedPayAmount = "" - for (index, char) in payAmount.reversed().enumerated() { - if (index % 3) == 0, index != 0 { - formedPayAmount += "," - } - formedPayAmount += String(char) - } - let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" let deadlineDate = applyDeadline != nil ? dateFormatter.date(from: applyDeadline!) : nil - // distance는 미터단위입니다. - var distanceText: String = "\(distance)m" - if distance >= 1000 { - let kilometers = Double(distance)/1000.0 - distanceText = String(format: "%.1fkm", kilometers) - } - return .init( postId: id, workDays: workDayList, @@ -81,8 +65,8 @@ public struct RecruitmentPostForWorkerDTO: Codable { applyDeadlineType: ApplyDeadlineType.toEntity(text: applyDeadlineType), applyDeadlineDate: deadlineDate, payType: PaymentType.toEntity(text: payType), - payAmount: formedPayAmount, - distanceFromWorkPlace: distanceText + payAmount: String(payAmount), + distanceFromWorkPlace: String(distance) ) } } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift index 9f8a5dab..6e5a5ed1 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift @@ -143,7 +143,6 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { let viewModels = mergedPosts.map { vo in let cardVO: WorkerEmployCardVO = .create(vo: vo) - let vm: OngoindWorkerEmployCardVM = .init( postId: vo.postId, vo: cardVO, @@ -173,6 +172,8 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { } } + +// MARK: ViewModelForCell class OngoindWorkerEmployCardVM: WorkerEmployCardViewModelable { weak var coordinator: WorkerRecruitmentBoardCoordinatable? From 6cec4ccf415e4c688048c7afba6142a0030d3aee Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 3 Sep 2024 09:56:43 +0900 Subject: [PATCH 08/21] =?UTF-8?q?[IDLE-000]=20=EC=95=B1=EB=82=B4=20?= =?UTF-8?q?=EA=B3=B5=EA=B3=A0=EC=99=80=20=EC=99=B8=EB=B6=80=EA=B3=B5?= =?UTF-8?q?=EA=B3=A0=20=EA=B5=AC=EB=B6=84=EC=A7=93=EB=8A=94=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...O.swift => WorkerNativeEmployCardVO.swift} | 37 ++++++--- ...wift => NativePostDetailForWorkerVC.swift} | 6 +- ...wift => NativePostDetailForWorkerVM.swift} | 4 +- .../OngoindWorkerEmployCardVM.swift | 82 +++++++++++++++++++ 4 files changed, 112 insertions(+), 17 deletions(-) rename project/Projects/Domain/Entity/VO/Employ/{WorkerEmployCardVO.swift => WorkerNativeEmployCardVO.swift} (87%) rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/{PostDetailForWorkerVC.swift => NativePostDetailForWorkerVC.swift} (98%) rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/{PostDetailForWorkerVM.swift => NativePostDetailForWorkerVM.swift} (97%) create mode 100644 project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/OngoindWorkerEmployCardVM.swift diff --git a/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift b/project/Projects/Domain/Entity/VO/Employ/WorkerNativeEmployCardVO.swift similarity index 87% rename from project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift rename to project/Projects/Domain/Entity/VO/Employ/WorkerNativeEmployCardVO.swift index 15f61271..cc046b2b 100644 --- a/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift +++ b/project/Projects/Domain/Entity/VO/Employ/WorkerNativeEmployCardVO.swift @@ -1,5 +1,5 @@ // -// WorkerEmployCardVO.swift +// WorkerNativeEmployCardVO.swift // Entity // // Created by choijunios on 7/19/24. @@ -7,7 +7,7 @@ import Foundation -public struct WorkerEmployCardVO { +public struct WorkerNativeEmployCardVO { public let dayLeft: Int public let isBeginnerPossible: Bool @@ -21,6 +21,8 @@ public struct WorkerEmployCardVO { public let endTime: String public let paymentType: PaymentType public let paymentAmount: String + public let applyDate: Date? + public let isFavorite: Bool public init( dayLeft: Int, @@ -34,9 +36,10 @@ public struct WorkerEmployCardVO { startTime: String, endTime: String, paymentType: PaymentType, - paymentAmount: String + paymentAmount: String, + applyDate: Date?, + isFavorite: Bool ) { - self.dayLeft = dayLeft self.isBeginnerPossible = isBeginnerPossible self.distanceFromWorkPlace = distanceFromWorkPlace @@ -49,16 +52,18 @@ public struct WorkerEmployCardVO { self.endTime = endTime self.paymentType = paymentType self.paymentAmount = paymentAmount + self.applyDate = applyDate + self.isFavorite = isFavorite } - /// 서버가 입력중인 공고의 확인화면에 사용됩니다. + /// 공고 상세화면에서 사용됩니다. public static func create( workTimeAndPay: WorkTimeAndPayStateObject, customerRequirement: CustomerRequirementStateObject, customerInformation: CustomerInformationStateObject, applicationDetail: ApplicationDetailStateObject, addressInfo: AddressInputStateObject - ) -> WorkerEmployCardVO { + ) -> WorkerNativeEmployCardVO { // 남은 일수 var leftDay: Int? = nil @@ -101,7 +106,7 @@ public struct WorkerEmployCardVO { let paymentType = workTimeAndPay.paymentType ?? .hourly let paymentAmount = workTimeAndPay.paymentAmount - return WorkerEmployCardVO( + return WorkerNativeEmployCardVO( dayLeft: leftDay ?? 31, isBeginnerPossible: isBeginnerPossible, distanceFromWorkPlace: 500, @@ -113,11 +118,13 @@ public struct WorkerEmployCardVO { startTime: startTime, endTime: workEndTime, paymentType: paymentType, - paymentAmount: paymentAmount + paymentAmount: paymentAmount, + applyDate: nil, + isFavorite: false ) } - public static func create(vo: RecruitmentPostForWorkerVO) -> WorkerEmployCardVO { + public static func create(vo: RecruitmentPostForWorkerVO) -> WorkerNativeEmployCardVO { // 남은 일수 var leftDay: Int? = nil @@ -142,7 +149,9 @@ public struct WorkerEmployCardVO { startTime: vo.startTime, endTime: vo.endTime, paymentType: vo.payType, - paymentAmount: vo.payAmount + paymentAmount: vo.payAmount, + applyDate: nil, + isFavorite: false ) } } @@ -169,7 +178,9 @@ public extension WorkerEmployCardVO { startTime: "09:00", endTime: "15:00", paymentType: .hourly, - paymentAmount: "12,500" + paymentAmount: "12,500", + applyDate: nil, + isFavorite: false ) static let `default` = WorkerEmployCardVO( @@ -184,6 +195,8 @@ public extension WorkerEmployCardVO { startTime: "00:00", endTime: "00:00", paymentType: .hourly, - paymentAmount: "12,500" + paymentAmount: "12,500", + applyDate: nil, + isFavorite: false ) } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/NativePostDetailForWorkerVC.swift similarity index 98% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/NativePostDetailForWorkerVC.swift index 187c09a2..080a564a 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/NativePostDetailForWorkerVC.swift @@ -1,5 +1,5 @@ // -// PostDetailForWorkerVC.swift +// NativePostDetailForWorkerVC.swift // BaseFeature // // Created by choijunios on 8/7/24. @@ -13,9 +13,9 @@ import Entity import DSKit /// 센토도 요양보호사가 보는 공고화면을 볼 수 있기 때문에 해당뷰를 BaseFeature에 구현하였습니다. -public class PostDetailForWorkerVC: BaseViewController { +public class NativePostDetailForWorkerVC: BaseViewController { - var viewModel: PostDetailForWorkerViewModelable? + var viewModel: NativePostDetailForWorkerViewModelable? // Init diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift similarity index 97% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift index ee16d246..972fc470 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift @@ -13,7 +13,7 @@ import PresentationCore import UseCaseInterface import DSKit -public protocol PostDetailForWorkerViewModelable { +public protocol NativePostDetailForWorkerViewModelable { // Output var postForWorkerBundle: Driver? { get } @@ -29,7 +29,7 @@ public protocol PostDetailForWorkerViewModelable { var centerCardClicked: PublishRelay { get } } -public class PostDetailForWorkerVM: PostDetailForWorkerViewModelable { +public class NativePostDetailForWorkerVM: NativePostDetailForWorkerViewModelable { public weak var coordinator: PostDetailForWorkerCoodinator? diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/OngoindWorkerEmployCardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/OngoindWorkerEmployCardVM.swift new file mode 100644 index 00000000..8ac46166 --- /dev/null +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/OngoindWorkerEmployCardVM.swift @@ -0,0 +1,82 @@ +// +// ViewModelForCell.swift +// WorkerFeature +// +// Created by choijunios on 9/2/24. +// + +import UIKit +import BaseFeature +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit +import UseCaseInterface + + +// MARK: ViewModelForCell +class OngoindWorkerEmployCardVM: WorkerEmployCardViewModelable { + + weak var coordinator: WorkerRecruitmentBoardCoordinatable? + + // Init + let postId: String + + public var renderObject: RxCocoa.Driver? + public var applicationInformation: RxCocoa.Driver? + + public var cardClicked: RxRelay.PublishRelay = .init() + public var applyButtonClicked: RxRelay.PublishRelay = .init() + public var starButtonClicked: RxRelay.PublishRelay = .init() + + let disposeBag = DisposeBag() + + public init + ( + postId: String, + vo: WorkerEmployCardVO, + coordinator: WorkerRecruitmentBoardCoordinatable? = nil + ) + { + self.postId = postId + self.coordinator = coordinator + + // MARK: 지원여부 + let applicationInformation: BehaviorRelay = .init(value: .mock) + self.applicationInformation = applicationInformation.asDriver() + + // MARK: Card RenderObject + let workerEmployCardRO: BehaviorRelay = .init(value: .mock) + renderObject = workerEmployCardRO.asDriver(onErrorJustReturn: .mock) + + workerEmployCardRO.accept(WorkerEmployCardRO.create(vo: vo)) + + // MARK: 버튼 처리 + applyButtonClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + + // 지원하기 버튼 눌림 + }) + .disposed(by: disposeBag) + + cardClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + + coordinator?.showPostDetail( + postId: postId + ) + }) + .disposed(by: disposeBag) + + starButtonClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + + // 즐겨찾기 버튼눌림 + }) + .disposed(by: disposeBag) + } +} From 93c797910a59cd68877dfaeab3f98e19cc51f0da Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 3 Sep 2024 09:56:50 +0900 Subject: [PATCH 09/21] =?UTF-8?q?[IDLE-000]=20=EC=95=B1=EB=82=B4=20?= =?UTF-8?q?=EA=B3=B5=EA=B3=A0=EC=99=80=20=EC=99=B8=EB=B6=80=EA=B3=B5?= =?UTF-8?q?=EA=B3=A0=20=EA=B5=AC=EB=B6=84=EC=A7=93=EB=8A=94=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=EC=88=98=EC=A0=95(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecuritmentPostListForWorkerDTO.swift | 12 ++- .../VO/Employ/WorkerNativeEmployCardVO.swift | 8 +- .../Post/RecruitmentPostListForWorkerVO.swift | 34 +++----- .../Card/Post/Worker/WorkerEmployCard.swift | 28 ++++--- .../Post/Worker/WorkerEmployCardCell.swift | 60 ++++++-------- .../PostDetailForWorkerCoodinator.swift | 4 +- .../View/NativePostDetailForWorkerVC.swift | 21 +++-- .../NativePostDetailForWorkerVM.swift | 2 +- .../Overview/PostOverviewVC.swift | 2 +- .../PostDetailForCenterVM.swift | 28 +++---- .../RegisterRecruitmentPostVM.swift | 6 +- .../RecruitmentPost/AppliedPostBoardVM.swift | 30 ++----- .../OngoindWorkerEmployCardVM.swift | 22 ++---- .../WorkerRecruitmentPostBoardVM.swift | 78 ++----------------- .../RecruitmentPost/StarredPostBoardVM.swift | 30 ++----- 15 files changed, 122 insertions(+), 243 deletions(-) diff --git a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift index d37a4828..87211adf 100644 --- a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift +++ b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift @@ -40,8 +40,10 @@ public struct RecruitmentPostForWorkerDTO: Codable { public let applyDeadlineType: String public let applyDeadline: String? public let distance: Int + public let applyTime: String? + public let isFavorite: Bool - public func toEntity() -> RecruitmentPostForWorkerVO { + public func toEntity() -> NativeRecruitmentPostForWorkerVO { let workDayList = weekdays.map({ dayText in WorkDay.toEntity(text: dayText) @@ -49,7 +51,9 @@ public struct RecruitmentPostForWorkerDTO: Codable { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" - let deadlineDate = applyDeadline != nil ? dateFormatter.date(from: applyDeadline!) : nil + + let deadlineDate = self.applyDeadline != nil ? dateFormatter.date(from: self.applyDeadline!) : nil + let applyDate = self.applyTime != nil ? dateFormatter.date(from: self.applyDeadline!) : nil return .init( postId: id, @@ -66,7 +70,9 @@ public struct RecruitmentPostForWorkerDTO: Codable { applyDeadlineDate: deadlineDate, payType: PaymentType.toEntity(text: payType), payAmount: String(payAmount), - distanceFromWorkPlace: String(distance) + distanceFromWorkPlace: distance, + applyTime: applyDate, + isFavorite: isFavorite ) } } diff --git a/project/Projects/Domain/Entity/VO/Employ/WorkerNativeEmployCardVO.swift b/project/Projects/Domain/Entity/VO/Employ/WorkerNativeEmployCardVO.swift index cc046b2b..3e0cecde 100644 --- a/project/Projects/Domain/Entity/VO/Employ/WorkerNativeEmployCardVO.swift +++ b/project/Projects/Domain/Entity/VO/Employ/WorkerNativeEmployCardVO.swift @@ -124,7 +124,7 @@ public struct WorkerNativeEmployCardVO { ) } - public static func create(vo: RecruitmentPostForWorkerVO) -> WorkerNativeEmployCardVO { + public static func create(vo: NativeRecruitmentPostForWorkerVO) -> WorkerNativeEmployCardVO { // 남은 일수 var leftDay: Int? = nil @@ -164,9 +164,9 @@ fileprivate extension String { } } -public extension WorkerEmployCardVO { +public extension WorkerNativeEmployCardVO { - static let mock = WorkerEmployCardVO( + static let mock: WorkerNativeEmployCardVO = .init( dayLeft: 10, isBeginnerPossible: true, distanceFromWorkPlace: 500, @@ -183,7 +183,7 @@ public extension WorkerEmployCardVO { isFavorite: false ) - static let `default` = WorkerEmployCardVO( + static let `default`: WorkerNativeEmployCardVO = .init( dayLeft: 0, isBeginnerPossible: true, distanceFromWorkPlace: 8000, diff --git a/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift b/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift index 3dee879a..9d366113 100644 --- a/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift +++ b/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift @@ -9,18 +9,18 @@ import Foundation public struct RecruitmentPostListForWorkerVO { - public let posts: [RecruitmentPostForWorkerVO] + public let posts: [NativeRecruitmentPostForWorkerVO] public let nextPageId: String? public let fetchedPostCount: Int - public init(posts: [RecruitmentPostForWorkerVO], nextPageId: String?, fetchedPostCount: Int) { + public init(posts: [NativeRecruitmentPostForWorkerVO], nextPageId: String?, fetchedPostCount: Int) { self.posts = posts self.nextPageId = nextPageId self.fetchedPostCount = fetchedPostCount } } -public struct RecruitmentPostForWorkerVO { +public struct NativeRecruitmentPostForWorkerVO { public let postId: String public let workDays: [WorkDay] @@ -41,24 +41,10 @@ public struct RecruitmentPostForWorkerVO { public let payAmount: String public let distanceFromWorkPlace: Int + public let applyTime: Date? + public let isFavorite: Bool - public init( - postId: String, - workDays: [WorkDay], - startTime: String, - endTime: String, - roadNameAddress: String, - lotNumberAddress: String, - gender: Gender, - age: Int, - cardGrade: CareGrade, - isExperiencePreferred: Bool, - applyDeadlineType: ApplyDeadlineType, - applyDeadlineDate: Date?, - payType: PaymentType, - payAmount: String, - distanceFromWorkPlace: Int - ) { + public init(postId: String, workDays: [WorkDay], startTime: String, endTime: String, roadNameAddress: String, lotNumberAddress: String, gender: Gender, age: Int, cardGrade: CareGrade, isExperiencePreferred: Bool, applyDeadlineType: ApplyDeadlineType, applyDeadlineDate: Date?, payType: PaymentType, payAmount: String, distanceFromWorkPlace: Int, applyTime: Date?, isFavorite: Bool) { self.postId = postId self.workDays = workDays self.startTime = startTime @@ -74,9 +60,11 @@ public struct RecruitmentPostForWorkerVO { self.payType = payType self.payAmount = payAmount self.distanceFromWorkPlace = distanceFromWorkPlace + self.applyTime = applyTime + self.isFavorite = isFavorite } - public static let mock = RecruitmentPostForWorkerVO( + public static let mock = NativeRecruitmentPostForWorkerVO( postId: "test-post-id", workDays: [.mon, .wed, .fri], startTime: "09:00", @@ -91,6 +79,8 @@ public struct RecruitmentPostForWorkerVO { applyDeadlineDate: Calendar.current.date(byAdding: .day, value: 7, to: Date()), payType: .hourly, payAmount: "15000", - distanceFromWorkPlace: 2500 + distanceFromWorkPlace: 2500, + applyTime: Date(), + isFavorite: true ) } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift index ddca38dd..255e2a94 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift @@ -1,5 +1,5 @@ // -// WorkerEmployCard.swift +// WorkerNativeEmployCardRO.swift // DSKit // // Created by choijunios on 7/19/24. @@ -10,7 +10,7 @@ import RxSwift import RxCocoa import Entity -public class WorkerEmployCardRO { +public class WorkerNativeEmployCardRO { let showBiginnerTag: Bool let showDayLeftTag: Bool @@ -22,19 +22,29 @@ public class WorkerEmployCardRO { let workTimeText: String let payText: String - init(showBiginnerTag: Bool, showDayLeftTag: Bool, dayLeftTagText: String?, titleText: String, distanceFromWorkPlace: String, targetInfoText: String, workDaysText: String, workTimeText: String, payText: String) { + init( + showBiginnerTag: Bool, + showDayLeftTag: Bool, + dayLeftTagText: String?, + titleText: String, + distanceFromWorkPlaceText: String, + targetInfoText: String, + workDaysText: String, + workTimeText: String, + payText: String + ) { self.showBiginnerTag = showBiginnerTag self.showDayLeftTag = showDayLeftTag self.dayLeftTagText = dayLeftTagText self.titleText = titleText - self.distanceFromWorkPlaceText = distanceFromWorkPlace + self.distanceFromWorkPlaceText = distanceFromWorkPlaceText self.targetInfoText = targetInfoText self.workDaysText = workDaysText self.workTimeText = workTimeText self.payText = payText } - public static func create(vo: WorkerEmployCardVO) -> WorkerEmployCardRO { + public static func create(vo: WorkerNativeEmployCardVO) -> WorkerNativeEmployCardRO { var dayLeftTagText: String? = nil var showDayLeftTag: Bool = false @@ -82,7 +92,7 @@ public class WorkerEmployCardRO { showDayLeftTag: showDayLeftTag, dayLeftTagText: dayLeftTagText, titleText: addressTitle, - distanceFromWorkPlace: distanceText, + distanceFromWorkPlaceText: distanceText, targetInfoText: targetInfoText, workDaysText: workDaysText, workTimeText: workTimeText, @@ -90,12 +100,12 @@ public class WorkerEmployCardRO { ) } - public static let `mock`: WorkerEmployCardRO = .init( + public static let `mock`: WorkerNativeEmployCardRO = .init( showBiginnerTag: true, showDayLeftTag: true, dayLeftTagText: "D-14", titleText: "사울시 강남동", - distanceFromWorkPlace: "1.1km", + distanceFromWorkPlaceText: "1.1km", targetInfoText: "1등급 54세 여성", workDaysText: "", workTimeText: "월, 화, 수", @@ -328,7 +338,7 @@ public class WorkerEmployCard: UIView { payLabel.typography = .Body2 } - public func bind(ro: WorkerEmployCardRO) { + public func bind(ro: WorkerNativeEmployCardRO) { beginnerTag.isHidden = !ro.showBiginnerTag dayLeftTag.isHidden = !ro.showDayLeftTag diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift index 4277bc3a..1ad979c6 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift @@ -10,27 +10,15 @@ import RxSwift import RxCocoa import Entity -/// WorkerEmployCardCell에서 사용됩니다. -public struct ApplicationInfo { - let isApplied: Bool - let applicationDateText: String - - public static let mock: ApplicationInfo = .init( - isApplied: true, - applicationDateText: "2024. 10. 22" - ) - - public init(isApplied: Bool, applicationDateText: String) { - self.isApplied = isApplied - self.applicationDateText = applicationDateText - } +public enum PostAppliedState { + case applied + case notApplied } public protocol WorkerEmployCardViewModelable { - + // Output - var renderObject: Driver? { get } - var applicationInformation: Driver? { get } + var cellViewObject: WorkerNativeEmployCardVO { get } // Input var cardClicked: PublishRelay { get } @@ -59,7 +47,7 @@ public class WorkerEmployCardCell: UITableViewCell { let cardView = WorkerEmployCard() let applyButton: IdlePrimaryCardButton = { let btn = IdlePrimaryCardButton(level: .large) - btn.label.textString = "" + btn.label.textString = "지원하기" return btn }() @@ -118,27 +106,25 @@ public class WorkerEmployCardCell: UITableViewCell { self.viewModel = viewModel + // Output + let cardVO = viewModel.cellViewObject + + // 지원 여부 + if let appliedDate = cardVO.applyDate { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "지원완료 yyyy. MM. dd" + let applyButtonLabelString = dateFormatter.string(from: appliedDate) + applyButton.label.textString = applyButtonLabelString + applyButton.setEnabled(false) + } + + // 카드 컨텐츠 바인딩 + let cardRO = WorkerNativeEmployCardRO.create(vo: cardVO) + cardView.bind(ro: cardRO) + // input let disposables: [Disposable?] = [ - // Output - viewModel - .applicationInformation? - .drive(onNext: { [weak self] info in - guard let self else { return } - if info.isApplied { - applyButton.setEnabled(false) - applyButton.label.textString = "지원완료 \(info.applicationDateText)" - } else { - applyButton.setEnabled(true) - applyButton.label.textString = "지원하기" - } - }), - viewModel - .renderObject? - .drive(onNext: { [cardView] ro in - cardView.bind(ro: ro) - }), - + // Input tappableArea .rx.tap diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/PostDetailForWorkerCoodinator.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/PostDetailForWorkerCoodinator.swift index ee198bc6..843d9a28 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/PostDetailForWorkerCoodinator.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/PostDetailForWorkerCoodinator.swift @@ -47,8 +47,8 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { } public func start() { - let vc = PostDetailForWorkerVC() - let vm = PostDetailForWorkerVM( + let vc = NativePostDetailForWorkerVC() + let vm = NativePostDetailForWorkerVM( postId: postId, coordinator: self, recruitmentPostUseCase: recruitmentPostUseCase diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/NativePostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/NativePostDetailForWorkerVC.swift index 080a564a..e23a61cc 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/NativePostDetailForWorkerVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/NativePostDetailForWorkerVC.swift @@ -124,7 +124,7 @@ public class NativePostDetailForWorkerVC: BaseViewController { .disposed(by: disposeBag) } - public func bind(viewModel: PostDetailForWorkerViewModelable) { + public func bind(viewModel: NativePostDetailForWorkerViewModelable) { self.viewModel = viewModel @@ -136,17 +136,16 @@ public class NativePostDetailForWorkerVC: BaseViewController { guard let self else { return } // 상단 구인공고 카드 - contentView.cardView.bind( - ro: WorkerEmployCardRO.create( - vo: .create( - workTimeAndPay: bundle.workTimeAndPay, - customerRequirement: bundle.customerRequirement, - customerInformation: bundle.customerInformation, - applicationDetail: bundle.applicationDetail, - addressInfo: bundle.addressInfo - ) - ) + let cardVO: WorkerNativeEmployCardVO = .create( + workTimeAndPay: bundle.workTimeAndPay, + customerRequirement: bundle.customerRequirement, + customerInformation: bundle.customerInformation, + applicationDetail: bundle.applicationDetail, + addressInfo: bundle.addressInfo ) + let cardRO: WorkerNativeEmployCardRO = .create(vo: cardVO) + + contentView.cardView.bind(ro: cardRO) // 근무 조건 contentView.workConditionView.bind( diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift index 972fc470..f6b01f3b 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift @@ -1,5 +1,5 @@ // -// asd.swift +// NativePostDetailForWorkerVM.swift // BaseFeature // // Created by choijunios on 8/15/24. diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Overview/PostOverviewVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Overview/PostOverviewVC.swift index 9b341542..a1113854 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Overview/PostOverviewVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Overview/PostOverviewVC.swift @@ -29,7 +29,7 @@ public protocol PostOverviewViewModelable: var postOverviewCoordinator: PostOverviewCoordinator? { get set } /// 공고등록에 성공한 경우 해당 이벤트를 전달 받습니다 - var workerEmployCardVO: Driver? { get } + var workerEmployCardVO: Driver? { get } /// 유효한 값을 가져옵니다. func fetchFromState() diff --git a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/PostDetailForCenterVM.swift b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/PostDetailForCenterVM.swift index b5483b8c..1f7696b5 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/PostDetailForCenterVM.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/PostDetailForCenterVM.swift @@ -19,7 +19,7 @@ public protocol PostDetailViewModelable: { // Output var applicantCountText: Driver? { get } - var workerEmployCardVO: Driver? { get } + var workerEmployCardVO: Driver? { get } var requestDetailFailure: Driver? { get } var showOptionSheet: Driver? { get } @@ -60,20 +60,20 @@ public class PostDetailForCenterVM: PostDetailViewModelable { // MARK: DetailVC Interaction public var applicantCount: Int? - public var workerEmployCardVO: RxCocoa.Driver? - public var requestDetailFailure: RxCocoa.Driver? - public var showOptionSheet: RxCocoa.Driver? - public var alert: RxCocoa.Driver? + public var workerEmployCardVO: Driver? + public var requestDetailFailure: Driver? + public var showOptionSheet: Driver? + public var alert: Driver? - public let postEditButtonClicked: RxRelay.PublishRelay = .init() - public let exitButtonClicked: RxRelay.PublishRelay = .init() - public let checkApplicationButtonClicked: RxRelay.PublishRelay = .init() - public let optionButtonClicked: RxRelay.PublishRelay = .init() - public let removePostButtonClicked: RxRelay.PublishRelay = .init() - public let closePostButtonClicked: RxRelay.PublishRelay = .init() - public let showAsWorkerButtonClicked: RxRelay.PublishRelay = .init() + public let postEditButtonClicked: PublishRelay = .init() + public let exitButtonClicked: PublishRelay = .init() + public let checkApplicationButtonClicked: PublishRelay = .init() + public let optionButtonClicked: PublishRelay = .init() + public let removePostButtonClicked: PublishRelay = .init() + public let closePostButtonClicked: PublishRelay = .init() + public let showAsWorkerButtonClicked: PublishRelay = .init() - public let viewWillAppear: RxRelay.PublishRelay = .init() + public let viewWillAppear: PublishRelay = .init() // MARK: fetched @@ -175,7 +175,7 @@ public class PostDetailForCenterVM: PostDetailViewModelable { fetched_applicationDetail.accept(bundle.applicationDetail) fetched_addressInfo.accept(bundle.addressInfo) - return WorkerEmployCardVO.create( + return WorkerNativeEmployCardVO.create( workTimeAndPay: fetched_workTimeAndPay.value, customerRequirement: fetched_customerRequirement.value, customerInformation: fetched_customerInformation.value, diff --git a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift index c63f41b6..173d49e1 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift @@ -54,7 +54,7 @@ public class RegisterRecruitmentPostVM: RegisterRecruitmentPostViewModelable { public var registerButtonClicked: PublishRelay = .init() public var overViewWillAppear: RxRelay.PublishRelay = .init() - public let workerEmployCardVO: Driver? + public let workerEmployCardVO: Driver? // MARK: register request public var postRegistrationSuccess: Driver? @@ -385,7 +385,7 @@ public class RegisterRecruitmentPostVM: RegisterRecruitmentPostViewModelable { // MARK: ----- Over view ----- - workerEmployCardVO = Observable + workerEmployCardVO = Observable .create { [ editing_workTimeAndPay, editing_customerInformation, @@ -394,7 +394,7 @@ public class RegisterRecruitmentPostVM: RegisterRecruitmentPostViewModelable { editing_addressInfo ] emitter in - let vo = WorkerEmployCardVO.create( + let vo = WorkerNativeEmployCardVO.create( workTimeAndPay: editing_workTimeAndPay.value, customerRequirement: editing_customerRequirement.value, customerInformation: editing_customerInformation.value, diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift index 6f81cb56..7dee83cc 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift @@ -33,7 +33,7 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { /// 값이 nil이라면 요청을 보내지 않습니다. var nextPagingRequest: PostPagingRequestForWorker? /// 가장최신의 데이터를 홀드, 다음 요청시 해당데이터에 새로운 데이터를 더해서 방출 - private let currentPostVO: BehaviorRelay<[RecruitmentPostForWorkerVO]> = .init(value: []) + private let currentPostVO: BehaviorRelay<[NativeRecruitmentPostForWorkerVO]> = .init(value: []) public init(recruitmentPostUseCase: RecruitmentPostUseCase) { self.recruitmentPostUseCase = recruitmentPostUseCase @@ -87,7 +87,7 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { // ViewModel 생성 let viewModels = mergedPosts.map { vo in - let cardVO: WorkerEmployCardVO = .create(vo: vo) + let cardVO: WorkerNativeEmployCardVO = .create(vo: vo) let vm: OngoindWorkerEmployCardVM = .init( postId: vo.postId, @@ -111,11 +111,6 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { } .asDriver(onErrorJustReturn: .default) } - - - func publishAppliedPostMocks() -> Single> { - return .just(.success((0..<10).map { _ in .mock })) - } } class AppliedWorkerEmployCardVM: WorkerEmployCardViewModelable { @@ -125,8 +120,7 @@ class AppliedWorkerEmployCardVM: WorkerEmployCardViewModelable { // Init let postId: String - public var renderObject: RxCocoa.Driver? - public var applicationInformation: RxCocoa.Driver? + var cellViewObject: WorkerNativeEmployCardVO public var cardClicked: RxRelay.PublishRelay = .init() public var applyButtonClicked: RxRelay.PublishRelay = .init() @@ -137,28 +131,14 @@ class AppliedWorkerEmployCardVM: WorkerEmployCardViewModelable { public init ( postId: String, - vo: WorkerEmployCardVO, + vo: WorkerNativeEmployCardVO, coordinator: WorkerRecruitmentBoardCoordinatable? = nil ) { self.postId = postId + self.cellViewObject = vo self.coordinator = coordinator - // MARK: 지원여부 - let applicationInformation: BehaviorRelay = .init( - value: .init( - isApplied: true, - applicationDateText: "날자정보 (미구현)" - ) - ) - self.applicationInformation = applicationInformation.asDriver() - - // MARK: Card RenderObject - let workerEmployCardRO: BehaviorRelay = .init(value: .mock) - renderObject = workerEmployCardRO.asDriver(onErrorJustReturn: .mock) - - workerEmployCardRO.accept(WorkerEmployCardRO.create(vo: vo)) - // MARK: 버튼 처리 applyButtonClicked .subscribe(onNext: { [weak self] _ in diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/OngoindWorkerEmployCardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/OngoindWorkerEmployCardVM.swift index 8ac46166..15c176cb 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/OngoindWorkerEmployCardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/OngoindWorkerEmployCardVM.swift @@ -1,5 +1,5 @@ // -// ViewModelForCell.swift +// OngoindWorkerEmployCardVM.swift // WorkerFeature // // Created by choijunios on 9/2/24. @@ -18,13 +18,10 @@ import UseCaseInterface // MARK: ViewModelForCell class OngoindWorkerEmployCardVM: WorkerEmployCardViewModelable { - weak var coordinator: WorkerRecruitmentBoardCoordinatable? - // Init let postId: String - - public var renderObject: RxCocoa.Driver? - public var applicationInformation: RxCocoa.Driver? + var cellViewObject: Entity.WorkerNativeEmployCardVO + weak var coordinator: WorkerRecruitmentBoardCoordinatable? public var cardClicked: RxRelay.PublishRelay = .init() public var applyButtonClicked: RxRelay.PublishRelay = .init() @@ -35,23 +32,14 @@ class OngoindWorkerEmployCardVM: WorkerEmployCardViewModelable { public init ( postId: String, - vo: WorkerEmployCardVO, + vo: WorkerNativeEmployCardVO, coordinator: WorkerRecruitmentBoardCoordinatable? = nil ) { self.postId = postId + self.cellViewObject = vo self.coordinator = coordinator - // MARK: 지원여부 - let applicationInformation: BehaviorRelay = .init(value: .mock) - self.applicationInformation = applicationInformation.asDriver() - - // MARK: Card RenderObject - let workerEmployCardRO: BehaviorRelay = .init(value: .mock) - renderObject = workerEmployCardRO.asDriver(onErrorJustReturn: .mock) - - workerEmployCardRO.accept(WorkerEmployCardRO.create(vo: vo)) - // MARK: 버튼 처리 applyButtonClicked .subscribe(onNext: { [weak self] _ in diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift index 2df6dfee..92024ea1 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift @@ -54,7 +54,7 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { /// 값이 nil이라면 요청을 보내지 않습니다. var nextPagingRequest: PostPagingRequestForWorker? /// 가장최신의 데이터를 홀드, 다음 요청시 해당데이터에 새로운 데이터를 더해서 방출 - private let currentPostVO: BehaviorRelay<[RecruitmentPostForWorkerVO]> = .init(value: []) + private let currentPostVO: BehaviorRelay<[NativeRecruitmentPostForWorkerVO]> = .init(value: []) // Observable let dispostBag = DisposeBag() @@ -149,16 +149,16 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { self.currentPostVO.accept(mergedPosts) // ViewModel 생성 - let viewModels = mergedPosts.map { vo in + let viewModels = mergedPosts.map { postVO in - let cardVO: WorkerEmployCardVO = .create(vo: vo) - let vm: OngoindWorkerEmployCardVM = .init( - postId: vo.postId, + let cardVO: WorkerNativeEmployCardVO = .create(vo: postVO) + let cardViewModel: OngoindWorkerEmployCardVM = .init( + postId: postVO.postId, vo: cardVO, coordinator: self.coordinator ) - return vm + return cardViewModel } return viewModels @@ -181,69 +181,3 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { } } - -// MARK: ViewModelForCell -class OngoindWorkerEmployCardVM: WorkerEmployCardViewModelable { - - weak var coordinator: WorkerRecruitmentBoardCoordinatable? - - // Init - let postId: String - - public var renderObject: RxCocoa.Driver? - public var applicationInformation: RxCocoa.Driver? - - public var cardClicked: RxRelay.PublishRelay = .init() - public var applyButtonClicked: RxRelay.PublishRelay = .init() - public var starButtonClicked: RxRelay.PublishRelay = .init() - - let disposeBag = DisposeBag() - - public init - ( - postId: String, - vo: WorkerEmployCardVO, - coordinator: WorkerRecruitmentBoardCoordinatable? = nil - ) - { - self.postId = postId - self.coordinator = coordinator - - // MARK: 지원여부 - let applicationInformation: BehaviorRelay = .init(value: .mock) - self.applicationInformation = applicationInformation.asDriver() - - // MARK: Card RenderObject - let workerEmployCardRO: BehaviorRelay = .init(value: .mock) - renderObject = workerEmployCardRO.asDriver(onErrorJustReturn: .mock) - - workerEmployCardRO.accept(WorkerEmployCardRO.create(vo: vo)) - - // MARK: 버튼 처리 - applyButtonClicked - .subscribe(onNext: { [weak self] _ in - guard let self else { return } - - // 지원하기 버튼 눌림 - }) - .disposed(by: disposeBag) - - cardClicked - .subscribe(onNext: { [weak self] _ in - guard let self else { return } - - coordinator?.showPostDetail( - postId: postId - ) - }) - .disposed(by: disposeBag) - - starButtonClicked - .subscribe(onNext: { [weak self] _ in - guard let self else { return } - - // 즐겨찾기 버튼눌림 - }) - .disposed(by: disposeBag) - } -} diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift index c553a33d..bf92f7c3 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift @@ -32,7 +32,7 @@ public class StarredPostBoardVM: WorkerPagablePostBoardVMable { /// 값이 nil이라면 요청을 보내지 않습니다. var nextPagingRequest: PostPagingRequestForWorker? /// 가장최신의 데이터를 홀드, 다음 요청시 해당데이터에 새로운 데이터를 더해서 방출 - private let currentPostVO: BehaviorRelay<[RecruitmentPostForWorkerVO]> = .init(value: []) + private let currentPostVO: BehaviorRelay<[NativeRecruitmentPostForWorkerVO]> = .init(value: []) public init(recruitmentPostUseCase: RecruitmentPostUseCase) { self.recruitmentPostUseCase = recruitmentPostUseCase @@ -87,7 +87,7 @@ public class StarredPostBoardVM: WorkerPagablePostBoardVMable { // ViewModel 생성 let viewModels = mergedPosts.map { vo in - let cardVO: WorkerEmployCardVO = .create(vo: vo) + let cardVO: WorkerNativeEmployCardVO = .create(vo: vo) let vm: OngoindWorkerEmployCardVM = .init( postId: vo.postId, @@ -113,20 +113,20 @@ public class StarredPostBoardVM: WorkerPagablePostBoardVMable { } - func publishStarredPostMocks() -> Single> { + func publishStarredPostMocks() -> Single> { return .just(.success((0..<10).map { _ in .mock })) } } class StarredWorkerEmployCardVM: WorkerEmployCardViewModelable { + + weak var coordinator: WorkerRecruitmentBoardCoordinatable? // Init let postId: String - - public var renderObject: RxCocoa.Driver? - public var applicationInformation: RxCocoa.Driver? + var cellViewObject: Entity.WorkerNativeEmployCardVO public var cardClicked: RxRelay.PublishRelay = .init() public var applyButtonClicked: RxRelay.PublishRelay = .init() @@ -137,28 +137,14 @@ class StarredWorkerEmployCardVM: WorkerEmployCardViewModelable { public init ( postId: String, - vo: WorkerEmployCardVO, + vo: WorkerNativeEmployCardVO, coordinator: WorkerRecruitmentBoardCoordinatable? = nil ) { self.postId = postId + self.cellViewObject = vo self.coordinator = coordinator - // MARK: 지원여부 - let applicationInformation: BehaviorRelay = .init( - value: .init( - isApplied: false, - applicationDateText: "" - ) - ) - self.applicationInformation = applicationInformation.asDriver() - - // MARK: Card RenderObject - let workerEmployCardRO: BehaviorRelay = .init(value: .mock) - renderObject = workerEmployCardRO.asDriver(onErrorJustReturn: .mock) - - workerEmployCardRO.accept(WorkerEmployCardRO.create(vo: vo)) - // MARK: 버튼 처리 applyButtonClicked .subscribe(onNext: { [weak self] _ in From 3a452e4d2ac1e9180ae3637601d3a0bf0ff4781a Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 3 Sep 2024 10:22:46 +0900 Subject: [PATCH 10/21] =?UTF-8?q?[IDLE-000]=20=ED=8E=98=EC=9D=B4=EC=A7=95?= =?UTF-8?q?=20=EB=82=B4=EC=97=AD=20=EC=83=88=EB=A1=9C=EA=B3=A0=EC=B9=A8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SubVC/WorkerPagablePostBoardVC.swift | 5 ++- .../WorkerRecruitmentPostBoardVC.swift | 12 ++++- .../RecruitmentPost/AppliedPostBoardVM.swift | 4 +- .../WorkerRecruitmentPostBoardVM.swift | 44 ++++++++++++------- .../RecruitmentPost/StarredPostBoardVM.swift | 8 ++-- 5 files changed, 48 insertions(+), 25 deletions(-) diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift index 1c0f4fc1..53cfa145 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift @@ -113,8 +113,9 @@ public class WorkerPagablePostBoardVC: BaseViewController { .disposed(by: disposeBag) // Input - self.rx.viewDidLoad - .bind(to: viewModel.viewDidLoad) + Observable + .merge(self.rx.viewWillAppear.map { _ in () }) + .bind(to: viewModel.requestInitialPageRequest) .disposed(by: disposeBag) self.requestNextPage diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift index 1189da2d..6d56842f 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift @@ -53,6 +53,11 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { setLayout() } + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + postTableView.setContentOffset(.zero, animated: false) + } + public func bind(viewModel: WorkerRecruitmentPostBoardVMable) { self.viewModel = viewModel @@ -84,7 +89,12 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { // Input self.rx.viewDidLoad - .bind(to: viewModel.viewDidLoad) + .bind(to: viewModel.requestWorkerLocation) + .disposed(by: disposeBag) + + self.rx.viewWillAppear + .map { _ in () } + .bind(to: viewModel.requestInitialPageRequest) .disposed(by: disposeBag) self.requestNextPage diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift index 7dee83cc..7a4be920 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift @@ -18,7 +18,7 @@ import UseCaseInterface public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { // Input - public var viewDidLoad: RxRelay.PublishRelay = .init() + public var requestInitialPageRequest: RxRelay.PublishRelay = .init() public var requestNextPage: RxRelay.PublishRelay = .init() // Output @@ -41,7 +41,7 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { let postPageReqeustResult = Observable .merge( - viewDidLoad.asObservable(), + requestInitialPageRequest.asObservable(), requestNextPage.asObservable() ) .compactMap { [weak self] _ in diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift index 92024ea1..07a13ff5 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift @@ -17,9 +17,8 @@ import UseCaseInterface public protocol WorkerPagablePostBoardVMable: DefaultAlertOutputable { /// 다음 페이지를 요청합니다. var requestNextPage: PublishRelay { get } - /// ViewDidLoad, 최초 요청을 위해 사용합니다. - var viewDidLoad: PublishRelay { get } - + /// 화면이 등장할 때마다 리스트를 초기화합니다. + var requestInitialPageRequest: PublishRelay { get } /// 페이지요청에 대한 결과를 전달합니다. var postBoardData: Driver<[WorkerEmployCardViewModelable]>? { get } @@ -27,6 +26,9 @@ public protocol WorkerPagablePostBoardVMable: DefaultAlertOutputable { public protocol WorkerRecruitmentPostBoardVMable: WorkerPagablePostBoardVMable { + /// 요양보호사 위치정보를 요청합니다. + var requestWorkerLocation: PublishRelay { get } + /// 요양보호사 위치 정보를 전달합니다. var workerLocationTitleText: Driver? { get } } @@ -39,20 +41,19 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { public var workerLocationTitleText: Driver? - // Input - public var viewDidLoad: PublishRelay = .init() + public var requestInitialPageRequest: PublishRelay = .init() + public var requestWorkerLocation: PublishRelay = .init() public var requestNextPage: PublishRelay = .init() - // Init weak var coordinator: WorkerRecruitmentBoardCoordinatable? let recruitmentPostUseCase: RecruitmentPostUseCase // Paging /// 값이 nil이라면 요청을 보내지 않습니다. - var nextPagingRequest: PostPagingRequestForWorker? + var nextPagingRequest: PostPagingRequestForWorker? = .initial /// 가장최신의 데이터를 홀드, 다음 요청시 해당데이터에 새로운 데이터를 더해서 방출 private let currentPostVO: BehaviorRelay<[NativeRecruitmentPostForWorkerVO]> = .init(value: []) @@ -66,21 +67,30 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { { self.coordinator = coordinator self.recruitmentPostUseCase = recruitmentPostUseCase - self.nextPagingRequest = .initial // 상단 위치정보 - workerLocationTitleText = viewDidLoad + workerLocationTitleText = requestWorkerLocation .compactMap { [weak self] _ in self?.fetchWorkerLocation() } - .asDriver(onErrorJustReturn: "반갑습니다.") + .asDriver(onErrorJustReturn: "위치정보확인불가") - let postPageReqeustResult = Observable - .merge( - viewDidLoad.asObservable(), - requestNextPage.asObservable() - ) + let initialRequest = requestInitialPageRequest + .flatMap { [weak self, recruitmentPostUseCase] request in + + self?.currentPostVO.accept([]) + self?.nextPagingRequest = .initial + + return recruitmentPostUseCase + .getPostListForWorker( + request: .initial, + postCount: 10 + ) + } + + + let pagingRequest = requestNextPage .compactMap { [weak self] _ in // 요청이 없는 경우 요청을 보내지 않는다. // ThirdPatry에서도 불러올 데이터가 없는 경우입니다. @@ -93,8 +103,10 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { postCount: 10 ) } + + let postPageReqeustResult = Observable + .merge(initialRequest, pagingRequest) .share() - let requestPostListSuccess = postPageReqeustResult.compactMap { $0.value } let requestPostListFailure = postPageReqeustResult.compactMap { $0.error } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift index bf92f7c3..87ebedd0 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift @@ -14,10 +14,10 @@ import Entity import DSKit import UseCaseInterface -public class StarredPostBoardVM: WorkerPagablePostBoardVMable { - +public class StarredPostBoardVM: WorkerPagablePostBoardVMable { + // Input - public var viewDidLoad: RxRelay.PublishRelay = .init() + public var requestInitialPageRequest: RxRelay.PublishRelay = .init() public var requestNextPage: RxRelay.PublishRelay = .init() // Output @@ -40,7 +40,7 @@ public class StarredPostBoardVM: WorkerPagablePostBoardVMable { let postPageReqeustResult = Observable .merge( - viewDidLoad.asObservable(), + requestInitialPageRequest.asObservable(), requestNextPage.asObservable() ) .compactMap { [weak self] _ in From 5a480a638f4e6c990e08957c6fe0f971c66c9aef Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 3 Sep 2024 14:02:03 +0900 Subject: [PATCH 11/21] =?UTF-8?q?[IDLE-000]=20=EA=B3=B5=EA=B3=A0=EC=A7=80?= =?UTF-8?q?=EC=9B=90=ED=95=98=EA=B8=B0=20UseCase=20/=20Repository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefaultRecruitmentPostRepository.swift | 44 ++++--- .../Data/DataSource/API/ApplyAPI.swift | 60 +++++++++ .../Data/DataSource/API/BaseAPI.swift | 3 + .../DataSource/Service/ApplyService.swift | 17 +++ .../DefualtRecruitmentPostUseCase.swift | 4 + .../RecruitmentPostRepository.swift | 3 + .../RecruitmentPostUseCase.swift | 3 + ...swift => WorkerNativeEmployCardCell.swift} | 49 +++---- .../LikedAndApplied/StarredAndAppliedVC.swift | 4 +- .../SubVC/WorkerPagablePostBoardVC.swift | 49 ++++++- .../WorkerRecruitmentPostBoardVC.swift | 18 ++- .../RecruitmentPost/AppliedPostBoardVM.swift | 74 ++--------- .../OngoindWorkerEmployCardVM.swift | 70 ---------- .../WorkerRecruitmentPostBoardVM.swift | 92 ++++++++++--- .../RecruitmentPost/StarredPostBoardVM.swift | 124 +++++++----------- 15 files changed, 322 insertions(+), 292 deletions(-) create mode 100644 project/Projects/Data/DataSource/API/ApplyAPI.swift create mode 100644 project/Projects/Data/DataSource/Service/ApplyService.swift rename project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/{WorkerEmployCardCell.swift => WorkerNativeEmployCardCell.swift} (75%) delete mode 100644 project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/OngoindWorkerEmployCardVM.swift diff --git a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift index 4126546e..ba5f0104 100644 --- a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift +++ b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift @@ -14,11 +14,13 @@ import Moya public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { - private var service: RecruitmentPostService = .init() + private var recruitmentPostService: RecruitmentPostService = .init() + private var applyService: ApplyService = .init() public init(_ store: KeyValueStore? = nil) { if let store { - self.service = RecruitmentPostService(keyValueStore: store) + self.recruitmentPostService = RecruitmentPostService(keyValueStore: store) + self.applyService = ApplyService(keyValueStore: store) } } @@ -27,13 +29,13 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { let encodedData = try! JSONEncoder().encode(bundle.toDTO()) - return service.request(api: .registerPost(postData: encodedData), with: .withToken) + return recruitmentPostService.request(api: .registerPost(postData: encodedData), with: .withToken) .mapToVoid() } public func getPostDetailForCenter(id: String) -> RxSwift.Single { - service.request(api: .postDetail(id: id, userType: .center), with: .withToken) + recruitmentPostService.request(api: .postDetail(id: id, userType: .center), with: .withToken) .map(RecruitmentPostFetchDTO.self) .map { dto in dto.toEntity() @@ -44,26 +46,26 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { let encodedData = try! JSONEncoder().encode(bundle.toDTO()) - return service.request( + return recruitmentPostService.request( api: .editPost(id: id, postData: encodedData), with: .withToken ).map { _ in () } } public func getOngoingPosts() -> RxSwift.Single<[Entity.RecruitmentPostInfoForCenterVO]> { - return service.request(api: .getOnGoingPosts, with: .withToken) + return recruitmentPostService.request(api: .getOnGoingPosts, with: .withToken) .map(RecruitmentPostForCenterListDTO.self) .map({ $0.jobPostings.map { $0.toVO() } }) } public func getClosedPosts() -> RxSwift.Single<[Entity.RecruitmentPostInfoForCenterVO]> { - return service.request(api: .getClosedPosts, with: .withToken) + return recruitmentPostService.request(api: .getClosedPosts, with: .withToken) .map(RecruitmentPostForCenterListDTO.self) .map({ $0.jobPostings.map { $0.toVO() } }) } public func getPostApplicantCount(id: String) -> RxSwift.Single { - service.request(api: .getPostApplicantCount(id: id), with: .withToken) + recruitmentPostService.request(api: .getPostApplicantCount(id: id), with: .withToken) .map(PostApplicantCountDTO.self) .map { dto in dto.applicantCount @@ -71,7 +73,7 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { } public func getPostApplicantScreenData(id: String) -> RxSwift.Single { - service.request(api: .getApplicantList(id: id), with: .withToken) + recruitmentPostService.request(api: .getApplicantList(id: id), with: .withToken) .map(PostApplicantScreenDTO.self) .map { dto in dto.toVO() @@ -79,18 +81,18 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { } public func closePost(id: String) -> RxSwift.Single { - service.request(api: .closePost(id: id), with: .withToken) + recruitmentPostService.request(api: .closePost(id: id), with: .withToken) .mapToVoid() } public func removePost(id: String) -> RxSwift.Single { - service.request(api: .removePost(id: id), with: .withToken) + recruitmentPostService.request(api: .removePost(id: id), with: .withToken) .mapToVoid() } // MARK: Worker public func getPostDetailForWorker(id: String) -> RxSwift.Single { - service.request( + recruitmentPostService.request( api: .postDetail(id: id, userType: .worker), with: .withToken ) @@ -102,7 +104,7 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { public func getNativePostListForWorker(nextPageId: String?, requestCnt: Int = 10) -> RxSwift.Single { - service.request( + recruitmentPostService.request( api: .getOnGoingNativePostListForWorker(nextPageId: nextPageId, requestCnt: String(requestCnt)), with: .withToken ) @@ -121,7 +123,7 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { } public func getFavoritePostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single { - service.request( + recruitmentPostService.request( api: .getFavoritePostListForWorker(nextPageId: nextPageId, requestCnt: String(requestCnt)), with: .withToken ) @@ -140,7 +142,7 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { } public func getAppliedPostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single { - service.request( + recruitmentPostService.request( api: .getAppliedPostListForWorker(nextPageId: nextPageId, requestCnt: String(requestCnt)), with: .withToken ) @@ -157,6 +159,18 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { dto.toEntity() } } + + public func ApplyToPost(postId: String, method: ApplyType) -> Single { + applyService + .request( + api: .applys( + jobPostingId: postId, + applyMethodType: method.dtoFormString + ), + with: .withToken + ) + .mapToVoid() + } } // MARK: 공고등록 정보를 DTO로 변환하는 영역 diff --git a/project/Projects/Data/DataSource/API/ApplyAPI.swift b/project/Projects/Data/DataSource/API/ApplyAPI.swift new file mode 100644 index 00000000..b0c92951 --- /dev/null +++ b/project/Projects/Data/DataSource/API/ApplyAPI.swift @@ -0,0 +1,60 @@ +// +// ApplyAPI.swift +// DataSource +// +// Created by choijunios on 9/3/24. +// + +import Foundation +import Moya +import Alamofire +import Entity + +public enum ApplyAPI { + case applys(jobPostingId: String, applyMethodType: String) +} + +extension ApplyAPI: BaseAPI { + + public var apiType: APIType { + .applys + } + + public var path: String { + switch self { + case .applys: + "" + } + } + + public var method: Moya.Method { + switch self { + case .applys: + .post + } + } + + var bodyParameters: Parameters? { + var params: Parameters = [:] + switch self { + case .applys(let jobPostingId, let applyMethodType): + params["jobPostingId"] = jobPostingId + params["applyMethodType"] = applyMethodType + return params + } + } + + var parameterEncoding: ParameterEncoding { + switch self { + default: + return JSONEncoding.default + } + } + + public var task: Moya.Task { + switch self { + case .applys: + return .requestParameters(parameters: bodyParameters ?? [:], encoding: parameterEncoding) + } + } +} diff --git a/project/Projects/Data/DataSource/API/BaseAPI.swift b/project/Projects/Data/DataSource/API/BaseAPI.swift index 50c67249..a3d2fe31 100644 --- a/project/Projects/Data/DataSource/API/BaseAPI.swift +++ b/project/Projects/Data/DataSource/API/BaseAPI.swift @@ -14,6 +14,7 @@ public enum APIType { case users case job_postings case external(url: String) + case applys } // MARK: BaseAPI @@ -35,6 +36,8 @@ public extension BaseAPI { baseStr += "/users" case .job_postings: baseStr += "/job-postings" + case .applys: + baseStr += "/applys" case .external(let url): baseStr = url } diff --git a/project/Projects/Data/DataSource/Service/ApplyService.swift b/project/Projects/Data/DataSource/Service/ApplyService.swift new file mode 100644 index 00000000..487e19e7 --- /dev/null +++ b/project/Projects/Data/DataSource/Service/ApplyService.swift @@ -0,0 +1,17 @@ +// +// ApplyService.swift +// DataSource +// +// Created by choijunios on 9/3/24. +// + +import Foundation + +public class ApplyService: BaseNetworkService { + + public init() { } + + public override init(keyValueStore: KeyValueStore) { + super.init(keyValueStore: keyValueStore) + } +} diff --git a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift index 44ffbe65..7001eb08 100644 --- a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift @@ -170,4 +170,8 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { return convert(task: stream) } + + public func applyToPost(postId: String, method: Entity.ApplyType) -> RxSwift.Single> { + convert(task: repository.ApplyToPost(postId: postId, method: method)) + } } diff --git a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift index 3f2eccb5..fbdccd82 100644 --- a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift +++ b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift @@ -52,4 +52,7 @@ public protocol RecruitmentPostRepository: RepositoryBase { /// 요양보호사가 확인하는 케어밋 자체 공고정보를 가져옵니다. func getAppliedPostListForWorker(nextPageId: String?, requestCnt: Int) -> Single + + /// 요양보호사가 인앱 공고에 지원합니다. + func ApplyToPost(postId: String, method: ApplyType) -> Single } diff --git a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift index 32984435..ca84ec1c 100644 --- a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift @@ -57,4 +57,7 @@ public protocol RecruitmentPostUseCase: UseCaseBase { /// 요양보호사가 지원한 공고리스트를 호출합니다. func getAppliedPostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> Single> + + /// 요양보호사가 인앱공고에 지원합니다. + func applyToPost(postId: String, method: ApplyType) -> Single> } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerNativeEmployCardCell.swift similarity index 75% rename from project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift rename to project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerNativeEmployCardCell.swift index 1ad979c6..0fd1aafe 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerNativeEmployCardCell.swift @@ -1,5 +1,5 @@ // -// WorkerEmployCard.swift +// WorkerNativeEmployCardCell.swift // DSKit // // Created by choijunios on 7/19/24. @@ -15,25 +15,21 @@ public enum PostAppliedState { case notApplied } -public protocol WorkerEmployCardViewModelable { +public protocol WorkerNativeEmployCardViewModelable: AnyObject { - // Output - var cellViewObject: WorkerNativeEmployCardVO { get } + /// '지원하기' 버튼이 눌렸을 때, 공고 id를 전달합니다. + var applyButtonClicked: PublishRelay<(postId: String, postTitle: String)> { get } - // Input - var cardClicked: PublishRelay { get } - var applyButtonClicked: PublishRelay { get } - - /// true일 경우 즐겨 찾기에 등록됩니다. - var starButtonClicked: PublishRelay { get } + /// 공고상세보기 + func showPostDetail(id: String) } -public class WorkerEmployCardCell: UITableViewCell { +public class WorkerNativeEmployCardCell: UITableViewCell { - public static let identifier = String(describing: WorkerEmployCardCell.self) + public static let identifier = String(describing: WorkerNativeEmployCardCell.self) - var viewModel: WorkerEmployCardViewModelable? + var viewModel: WorkerNativeEmployCardViewModelable? private var disposables: [Disposable?]? public override func layoutSubviews() { @@ -102,15 +98,10 @@ public class WorkerEmployCardCell: UITableViewCell { ]) } - public func bind(viewModel: WorkerEmployCardViewModelable) { - - self.viewModel = viewModel - - // Output - let cardVO = viewModel.cellViewObject + public func bind(postId: String, vo: WorkerNativeEmployCardVO, viewModel: WorkerNativeEmployCardViewModelable) { // 지원 여부 - if let appliedDate = cardVO.applyDate { + if let appliedDate = vo.applyDate { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "지원완료 yyyy. MM. dd" let applyButtonLabelString = dateFormatter.string(from: appliedDate) @@ -119,7 +110,7 @@ public class WorkerEmployCardCell: UITableViewCell { } // 카드 컨텐츠 바인딩 - let cardRO = WorkerNativeEmployCardRO.create(vo: cardVO) + let cardRO = WorkerNativeEmployCardRO.create(vo: vo) cardView.bind(ro: cardRO) // input @@ -128,17 +119,15 @@ public class WorkerEmployCardCell: UITableViewCell { // Input tappableArea .rx.tap - .bind(to: viewModel.cardClicked), - - applyButton - .rx.tap + .subscribe(onNext: { [weak viewModel] _ in + viewModel?.showPostDetail(id: postId) + }), + + applyButton.rx.tap + .map({ _ in (postId, vo.title) }) .bind(to: viewModel.applyButtonClicked), - cardView - .starButton - .eventPublisher - .map { $0 == .accent } - .bind(to: viewModel.starButtonClicked), + //TODO: 즐겨찾기 구현예정 ] self.disposables = disposables diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift index 3bfe9efd..a7a066e0 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift @@ -158,8 +158,8 @@ public class StarredAndAppliedVC: BaseViewController { } public func bind( - appliedPostVM: WorkerPagablePostBoardVMable, - starredPostVM: WorkerPagablePostBoardVMable + appliedPostVM: AppliedPostBoardVM, + starredPostVM: StarredPostBoardVM ) { viewControllerDict[.applied]?.bind(viewModel: appliedPostVM) diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift index 53cfa145..5bfb6f0c 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift @@ -15,7 +15,7 @@ import DSKit public class WorkerPagablePostBoardVC: BaseViewController { - typealias Cell = WorkerEmployCardCell + typealias Cell = WorkerNativeEmployCardCell var viewModel: WorkerPagablePostBoardVMable? @@ -33,7 +33,7 @@ public class WorkerPagablePostBoardVC: BaseViewController { var isPaging = true // Observable - let postViewModels: BehaviorRelay<[WorkerEmployCardViewModelable]> = .init(value: []) + let cellData: BehaviorRelay<[PostBoardCellData]> = .init(value: []) let requestNextPage: PublishRelay = .init() private let disposeBag = DisposeBag() @@ -98,9 +98,41 @@ public class WorkerPagablePostBoardVC: BaseViewController { // Output viewModel .postBoardData? - .drive(onNext: { [weak self] viewModels in + .drive(onNext: { [weak self] cellData in guard let self else { return } - self.postViewModels.accept(viewModels) + self.cellData.accept(cellData) + self.postTableView.reloadData() + }) + .disposed(by: disposeBag) + + viewModel + .alert? + .drive(onNext: { [weak self] alertVO in + self?.showAlert(vo: alertVO) + }) + .disposed(by: disposeBag) + + // Input + Observable + .merge(self.rx.viewWillAppear.map { _ in () }) + .bind(to: viewModel.requestInitialPageRequest) + .disposed(by: disposeBag) + + self.requestNextPage + .bind(to: viewModel.requestNextPage) + .disposed(by: disposeBag) + } + + func bind(viewModel: WorkerAppliablePostBoardVMable) { + + self.viewModel = viewModel + + // Output + viewModel + .postBoardData? + .drive(onNext: { [weak self] cellData in + guard let self else { return } + self.cellData.accept(cellData) self.postTableView.reloadData() }) .disposed(by: disposeBag) @@ -127,7 +159,7 @@ public class WorkerPagablePostBoardVC: BaseViewController { extension WorkerPagablePostBoardVC: UITableViewDataSource, UITableViewDelegate { public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - postViewModels.value.count + cellData.value.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -135,8 +167,11 @@ extension WorkerPagablePostBoardVC: UITableViewDataSource, UITableViewDelegate { let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell cell.selectionStyle = .none - let vm = postViewModels.value[indexPath.row] - cell.bind(viewModel: vm) + let cellData = cellData.value[indexPath.row] + + if let vm = viewModel { + cell.bind(postId: cellData.postId, vo: cellData.cardVO, viewModel: vm) + } return cell } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift index 6d56842f..0ecdb46f 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift @@ -15,7 +15,7 @@ import DSKit public class WorkerRecruitmentPostBoardVC: BaseViewController { - typealias Cell = WorkerEmployCardCell + typealias Cell = WorkerNativeEmployCardCell var viewModel: WorkerRecruitmentPostBoardVMable? @@ -36,7 +36,7 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { var isPaging = true // Observable - let postViewModels: BehaviorRelay<[WorkerEmployCardViewModelable]> = .init(value: []) + let cellData: BehaviorRelay<[PostBoardCellData]> = .init(value: []) let requestNextPage: PublishRelay = .init() private let disposeBag = DisposeBag() @@ -72,9 +72,9 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { viewModel .postBoardData? - .drive(onNext: { [weak self] viewModels in + .drive(onNext: { [weak self] cellData in guard let self else { return } - self.postViewModels.accept(viewModels) + self.cellData.accept(cellData) self.postTableView.reloadData() self.isPaging = false }) @@ -146,7 +146,7 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { extension WorkerRecruitmentPostBoardVC: UITableViewDataSource, UITableViewDelegate { public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - postViewModels.value.count + cellData.value.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -154,8 +154,12 @@ extension WorkerRecruitmentPostBoardVC: UITableViewDataSource, UITableViewDelega let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell cell.selectionStyle = .none - let vm = postViewModels.value[indexPath.row] - cell.bind(viewModel: vm) + let cellData = cellData.value[indexPath.row] + + if let vm = viewModel { + + cell.bind(postId: cellData.postId, vo: cellData.cardVO, viewModel: vm) + } return cell } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift index 7a4be920..186383c3 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift @@ -20,9 +20,10 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { // Input public var requestInitialPageRequest: RxRelay.PublishRelay = .init() public var requestNextPage: RxRelay.PublishRelay = .init() + public var applyButtonClicked: RxRelay.PublishRelay<(postId: String, postTitle: String)> = .init() // Output - public var postBoardData: RxCocoa.Driver<[any DSKit.WorkerEmployCardViewModelable]>? + public var postBoardData: RxCocoa.Driver<[PostBoardCellData]>? public var alert: RxCocoa.Driver? // Init @@ -66,7 +67,7 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { currentPostVO, requestPostListSuccess ) - .compactMap { [weak self] (prevPostList, fetchedData) -> [WorkerEmployCardViewModelable]? in + .compactMap { [weak self] (prevPostList, fetchedData) -> [PostBoardCellData]? in guard let self else { return nil } @@ -84,21 +85,15 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { // 최근값 업데이트 self.currentPostVO.accept(mergedPosts) - // ViewModel 생성 - let viewModels = mergedPosts.map { vo in + // cellData 생성 + let cellData: [PostBoardCellData] = mergedPosts.map { vo in let cardVO: WorkerNativeEmployCardVO = .create(vo: vo) - let vm: OngoindWorkerEmployCardVM = .init( - postId: vo.postId, - vo: cardVO, - coordinator: self.coordinator - ) - - return vm + return .init(postId: vo.postId, cardVO: cardVO) } - return viewModels + return cellData } .asDriver(onErrorJustReturn: []) @@ -111,59 +106,8 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { } .asDriver(onErrorJustReturn: .default) } -} - -class AppliedWorkerEmployCardVM: WorkerEmployCardViewModelable { - - weak var coordinator: WorkerRecruitmentBoardCoordinatable? - - // Init - let postId: String - - var cellViewObject: WorkerNativeEmployCardVO - public var cardClicked: RxRelay.PublishRelay = .init() - public var applyButtonClicked: RxRelay.PublishRelay = .init() - public var starButtonClicked: RxRelay.PublishRelay = .init() - - let disposeBag = DisposeBag() - - public init - ( - postId: String, - vo: WorkerNativeEmployCardVO, - coordinator: WorkerRecruitmentBoardCoordinatable? = nil - ) - { - self.postId = postId - self.cellViewObject = vo - self.coordinator = coordinator - - // MARK: 버튼 처리 - applyButtonClicked - .subscribe(onNext: { [weak self] _ in - guard let self else { return } - - // 지원하기 버튼 눌림 - }) - .disposed(by: disposeBag) - - cardClicked - .subscribe(onNext: { [weak self] _ in - guard let self else { return } - - coordinator?.showPostDetail( - postId: postId - ) - }) - .disposed(by: disposeBag) - - starButtonClicked - .subscribe(onNext: { [weak self] _ in - guard let self else { return } - - // 즐겨찾기 버튼눌림 - }) - .disposed(by: disposeBag) + public func showPostDetail(id: String) { + coordinator?.showPostDetail(postId: id) } } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/OngoindWorkerEmployCardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/OngoindWorkerEmployCardVM.swift deleted file mode 100644 index 15c176cb..00000000 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/OngoindWorkerEmployCardVM.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// OngoindWorkerEmployCardVM.swift -// WorkerFeature -// -// Created by choijunios on 9/2/24. -// - -import UIKit -import BaseFeature -import PresentationCore -import RxCocoa -import RxSwift -import Entity -import DSKit -import UseCaseInterface - - -// MARK: ViewModelForCell -class OngoindWorkerEmployCardVM: WorkerEmployCardViewModelable { - - // Init - let postId: String - var cellViewObject: Entity.WorkerNativeEmployCardVO - weak var coordinator: WorkerRecruitmentBoardCoordinatable? - - public var cardClicked: RxRelay.PublishRelay = .init() - public var applyButtonClicked: RxRelay.PublishRelay = .init() - public var starButtonClicked: RxRelay.PublishRelay = .init() - - let disposeBag = DisposeBag() - - public init - ( - postId: String, - vo: WorkerNativeEmployCardVO, - coordinator: WorkerRecruitmentBoardCoordinatable? = nil - ) - { - self.postId = postId - self.cellViewObject = vo - self.coordinator = coordinator - - // MARK: 버튼 처리 - applyButtonClicked - .subscribe(onNext: { [weak self] _ in - guard let self else { return } - - // 지원하기 버튼 눌림 - }) - .disposed(by: disposeBag) - - cardClicked - .subscribe(onNext: { [weak self] _ in - guard let self else { return } - - coordinator?.showPostDetail( - postId: postId - ) - }) - .disposed(by: disposeBag) - - starButtonClicked - .subscribe(onNext: { [weak self] _ in - guard let self else { return } - - // 즐겨찾기 버튼눌림 - }) - .disposed(by: disposeBag) - } -} diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift index 07a13ff5..805b3777 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift @@ -14,17 +14,31 @@ import Entity import DSKit import UseCaseInterface -public protocol WorkerPagablePostBoardVMable: DefaultAlertOutputable { +public struct PostBoardCellData { + let postId: String + let cardVO: WorkerNativeEmployCardVO +} + +/// 페이징 보드 +public protocol WorkerPagablePostBoardVMable: DefaultAlertOutputable & WorkerNativeEmployCardViewModelable { /// 다음 페이지를 요청합니다. var requestNextPage: PublishRelay { get } + /// 화면이 등장할 때마다 리스트를 초기화합니다. var requestInitialPageRequest: PublishRelay { get } /// 페이지요청에 대한 결과를 전달합니다. - var postBoardData: Driver<[WorkerEmployCardViewModelable]>? { get } + var postBoardData: Driver<[PostBoardCellData]>? { get } } -public protocol WorkerRecruitmentPostBoardVMable: WorkerPagablePostBoardVMable { +/// 페이징 + 지원하기 +public protocol WorkerAppliablePostBoardVMable: WorkerPagablePostBoardVMable { + /// 지원하기 Alert + var idleAlertVM: Driver? { get } +} + +/// 페이징 + 지원하기 + 요양보호사 위치정보 +public protocol WorkerRecruitmentPostBoardVMable: WorkerAppliablePostBoardVMable { /// 요양보호사 위치정보를 요청합니다. var requestWorkerLocation: PublishRelay { get } @@ -36,15 +50,16 @@ public protocol WorkerRecruitmentPostBoardVMable: WorkerPagablePostBoardVMable { public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { // Output - public var postBoardData: Driver<[WorkerEmployCardViewModelable]>? - public var alert: Driver? + public var postBoardData: Driver<[PostBoardCellData]>? public var workerLocationTitleText: Driver? - + public var idleAlertVM: RxCocoa.Driver? + public var alert: Driver? // Input public var requestInitialPageRequest: PublishRelay = .init() public var requestWorkerLocation: PublishRelay = .init() public var requestNextPage: PublishRelay = .init() + public var applyButtonClicked: PublishRelay<(postId: String, postTitle: String)> = .init() // Init @@ -116,7 +131,7 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { currentPostVO, requestPostListSuccess ) - .compactMap { [weak self] (prevPostList, fetchedData) -> [WorkerEmployCardViewModelable]? in + .compactMap { [weak self] (prevPostList, fetchedData) -> [PostBoardCellData]? in guard let self else { return nil } @@ -160,36 +175,71 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { // 최근값 업데이트 self.currentPostVO.accept(mergedPosts) - // ViewModel 생성 - let viewModels = mergedPosts.map { postVO in + // cellData생성 + let cellData: [PostBoardCellData] = mergedPosts.map { postVO in let cardVO: WorkerNativeEmployCardVO = .create(vo: postVO) - let cardViewModel: OngoindWorkerEmployCardVM = .init( - postId: postVO.postId, - vo: cardVO, - coordinator: self.coordinator - ) - - return cardViewModel + return .init(postId: postVO.postId, cardVO: cardVO) } - return viewModels + return cellData } .asDriver(onErrorJustReturn: []) - alert = requestPostListFailure + // MARK: 지원하기 + let applyRequest: PublishRelay = .init() + self.idleAlertVM = applyButtonClicked + .map { (postId: String, postTitle: String) in + DefaultIdleAlertVM( + title: "'postTitle'\n공고에 지원하시겠어요?", + description: "", + acceptButtonLabelText: "지원하기", + cancelButtonLabelText: "취소하기") { [applyRequest] in + applyRequest.accept(postId) + } + } + .asDriver(onErrorDriveWith: .never()) + + let applyRequestResult = applyRequest + .flatMap { [recruitmentPostUseCase] postId in + // 리스트화면에서는 앱내 지원만 지원합니다. + recruitmentPostUseCase + .applyToPost(postId: postId, method: .app) + } + .share() + + let applyRequestFailure = applyRequestResult.compactMap { $0.error } + + let applyRequestFailureAlert = applyRequestFailure + .map { error in + DefaultAlertContentVO( + title: "지원하기 실패", + message: error.message + ) + } + + let requestPostListFailureAlert = requestPostListFailure .map { error in - return DefaultAlertContentVO( - title: "시스템 오류", + DefaultAlertContentVO( + title: "공고 불러오기 오류", message: error.message ) } + + self.alert = Observable + .merge( + applyRequestFailureAlert, + requestPostListFailureAlert + ) .asDriver(onErrorJustReturn: .default) } + public func showPostDetail(id: String) { + coordinator?.showPostDetail(postId: id) + } + /// Test func fetchWorkerLocation() -> String { "서울시 영등포구" } } - diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift index 87ebedd0..1c1f8396 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift @@ -14,15 +14,17 @@ import Entity import DSKit import UseCaseInterface -public class StarredPostBoardVM: WorkerPagablePostBoardVMable { - +public class StarredPostBoardVM: WorkerAppliablePostBoardVMable { + // Input public var requestInitialPageRequest: RxRelay.PublishRelay = .init() public var requestNextPage: RxRelay.PublishRelay = .init() + public var applyButtonClicked: RxRelay.PublishRelay<(postId: String, postTitle: String)> = .init() // Output - public var postBoardData: RxCocoa.Driver<[any DSKit.WorkerEmployCardViewModelable]>? - public var alert: RxCocoa.Driver? + public var postBoardData: Driver<[PostBoardCellData]>? + public var alert: Driver? + public var idleAlertVM: RxCocoa.Driver? // Init weak var coordinator: WorkerRecruitmentBoardCoordinatable? @@ -65,7 +67,7 @@ public class StarredPostBoardVM: WorkerPagablePostBoardVMable { currentPostVO, requestPostListSuccess ) - .compactMap { [weak self] (prevPostList, fetchedData) -> [WorkerEmployCardViewModelable]? in + .compactMap { [weak self] (prevPostList, fetchedData) -> [PostBoardCellData]? in guard let self else { return nil } @@ -84,92 +86,64 @@ public class StarredPostBoardVM: WorkerPagablePostBoardVMable { // 최근값 업데이트 self.currentPostVO.accept(mergedPosts) - // ViewModel 생성 - let viewModels = mergedPosts.map { vo in + // cellData 생성 + let cellData: [PostBoardCellData] = mergedPosts.map { vo in let cardVO: WorkerNativeEmployCardVO = .create(vo: vo) - let vm: OngoindWorkerEmployCardVM = .init( - postId: vo.postId, - vo: cardVO, - coordinator: self.coordinator - ) - - return vm + return .init(postId: vo.postId, cardVO: cardVO) } - return viewModels + return cellData } .asDriver(onErrorJustReturn: []) - alert = requestPostListFailure + // MARK: 지원하기 + let applyRequest: PublishRelay = .init() + self.idleAlertVM = applyButtonClicked + .map { (postId: String, postTitle: String) in + DefaultIdleAlertVM( + title: "'postTitle'\n공고에 지원하시겠어요?", + description: "", + acceptButtonLabelText: "지원하기", + cancelButtonLabelText: "취소하기") { [applyRequest] in + applyRequest.accept(postId) + } + } + .asDriver(onErrorDriveWith: .never()) + + let applyRequestResult = applyRequest + .flatMap { [recruitmentPostUseCase] postId in + // 리스트화면에서는 앱내 지원만 지원합니다. + recruitmentPostUseCase + .applyToPost(postId: postId, method: .app) + } + .share() + + let applyRequestFailure = applyRequestResult.compactMap { $0.error } + + let applyRequestFailureAlert = applyRequestFailure + .map { error in + DefaultAlertContentVO( + title: "지원하기 실패", + message: error.message + ) + } + + let requestPostListFailureAlert = requestPostListFailure .map { error in DefaultAlertContentVO( title: "즐겨찾기한 공고 불러오기 오류", message: error.message ) } + + alert = Observable + .merge(applyRequestFailureAlert, requestPostListFailureAlert) .asDriver(onErrorJustReturn: .default) } - - func publishStarredPostMocks() -> Single> { - return .just(.success((0..<10).map { _ in .mock })) - } -} - -class StarredWorkerEmployCardVM: WorkerEmployCardViewModelable { - - - - weak var coordinator: WorkerRecruitmentBoardCoordinatable? - - // Init - let postId: String - var cellViewObject: Entity.WorkerNativeEmployCardVO - - public var cardClicked: RxRelay.PublishRelay = .init() - public var applyButtonClicked: RxRelay.PublishRelay = .init() - public var starButtonClicked: RxRelay.PublishRelay = .init() - - let disposeBag = DisposeBag() - - public init - ( - postId: String, - vo: WorkerNativeEmployCardVO, - coordinator: WorkerRecruitmentBoardCoordinatable? = nil - ) - { - self.postId = postId - self.cellViewObject = vo - self.coordinator = coordinator - - // MARK: 버튼 처리 - applyButtonClicked - .subscribe(onNext: { [weak self] _ in - guard let self else { return } - - // 지원하기 버튼 눌림 - }) - .disposed(by: disposeBag) - - cardClicked - .subscribe(onNext: { [weak self] _ in - guard let self else { return } - - self.coordinator?.showPostDetail( - postId: postId - ) - }) - .disposed(by: disposeBag) - - starButtonClicked - .subscribe(onNext: { [weak self] _ in - guard let self else { return } - - // 즐겨찾기 버튼눌림 - }) - .disposed(by: disposeBag) + public func showPostDetail(id: String) { + coordinator?.showCenterProfile(centerId: id) } } From 8ef26000b6ef54c529f9c7e91ca33b02e5453fc4 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 3 Sep 2024 14:52:47 +0900 Subject: [PATCH 12/21] =?UTF-8?q?[IDLE-000]=20=EC=A7=80=EC=9B=90=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Alert /IdleBigAlertController.swift | 42 ++++++++++--------- .../Component/Button/IdlePrimaryButton.swift | 4 +- .../Base/BaseViewController.swift | 3 +- .../WorkerRecruitmentPostBoardVC.swift | 7 ++++ .../WorkerRecruitmentPostBoardVM.swift | 2 +- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Alert /IdleBigAlertController.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Alert /IdleBigAlertController.swift index 7b2eabbf..6f3153b0 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Alert /IdleBigAlertController.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Alert /IdleBigAlertController.swift @@ -59,14 +59,23 @@ public class DefaultIdleAlertVM: IdleAlertViewModelable { public class IdleBigAlertController: UIViewController { + public enum ButtonType { + case orange + case red + } + let customTranstionDelegate = CustomTransitionDelegate() + // Init + let type: ButtonType + // Not init private let disposeBag = DisposeBag() // View let titleLabel: IdleLabel = { let label = IdleLabel(typography: .Subtitle1) + label.numberOfLines = 0 label.textAlignment = .center return label }() @@ -74,25 +83,24 @@ public class IdleBigAlertController: UIViewController { let descriptionLabel: IdleLabel = { let label = IdleLabel(typography: .Body3) label.attrTextColor = DSKitAsset.Colors.gray500.color - label.lineBreakMode = .byWordWrapping - label.textAlignment = .center label.numberOfLines = 0 + label.textAlignment = .center return label }() - public let cancelButton: IdleThirdinaryButton = { + public lazy var cancelButton: IdleThirdinaryButton = { let button = IdleThirdinaryButton(level: .medium) button.label.textString = "" return button }() - public let acceptButton: IdlePrimaryButton = { - let button = IdlePrimaryButton(level: .mediumRed) + public lazy var acceptButton: IdlePrimaryButton = { + let button = IdlePrimaryButton(level: type == .orange ? .medium : .mediumRed) button.label.textString = "" return button }() - public init() { - + public init(type: ButtonType) { + self.type = type super.init(nibName: nil, bundle: nil) self.transitioningDelegate = customTranstionDelegate @@ -105,18 +113,17 @@ public class IdleBigAlertController: UIViewController { private func setAppearance() { view.backgroundColor = DSKitAsset.Colors.gray500.color.withAlphaComponent(0.5) - - // TODO: 미정으로 변동가능합니다. - view.layoutMargins = .init(top: 0, left: 24, bottom: 0, right: 24) } private func setAutoLayout() { + view.layoutMargins = .init(top: 0, left: 24, bottom: 0, right: 24) + // 라벨 스택 let textStack = VStack( [ - Spacer(height: 8), - titleLabel + titleLabel, + descriptionLabel ], spacing: 8, alignment: .center @@ -136,11 +143,7 @@ public class IdleBigAlertController: UIViewController { // 라벨 + 버튼 스택 let alertContentsStack = VStack( [ - HStack([ - Spacer(width: 41.5), - textStack, - Spacer(width: 41.5) - ], alignment: .fill), + textStack, buttonStack ], spacing: 24, @@ -150,7 +153,6 @@ public class IdleBigAlertController: UIViewController { NSLayoutConstraint.activate([ // 버튼 스택 높이 지정 buttonStack.heightAnchor.constraint(equalToConstant: 52), - ]) // 전체 스택 @@ -193,7 +195,9 @@ public class IdleBigAlertController: UIViewController { public func bind(viewModel vm: IdleAlertViewModelable) { titleLabel.textString = vm.title + titleLabel.textAlignment = .center descriptionLabel.textString = vm.description + descriptionLabel.textAlignment = .center acceptButton.label.textString = vm.acceptButtonLabelText cancelButton.label.textString = vm.cancelButtonLabelText @@ -286,5 +290,5 @@ class CustomTransitionDelegate: NSObject, UIViewControllerTransitioningDelegate @available(iOS 17.0, *) #Preview("Preview", traits: .defaultLayout) { - IdleBigAlertController() + IdleBigAlertController(type: .orange) } diff --git a/project/Projects/Presentation/DSKit/Sources/Component/Button/IdlePrimaryButton.swift b/project/Projects/Presentation/DSKit/Sources/Component/Button/IdlePrimaryButton.swift index 86ecba89..6092bb29 100644 --- a/project/Projects/Presentation/DSKit/Sources/Component/Button/IdlePrimaryButton.swift +++ b/project/Projects/Presentation/DSKit/Sources/Component/Button/IdlePrimaryButton.swift @@ -102,9 +102,7 @@ public class IdlePrimaryButton: TappableUIView { private func setApearance() { self.layer.cornerRadius = 8 self.clipsToBounds = true - - // InitialSetting - backgroundColor = level.idleColor + self.backgroundColor = level.idleColor } private func setAutoLayout() { diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift b/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift index b6bf9b04..d3ceb3e5 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift @@ -36,9 +36,10 @@ public extension BaseViewController { } func showIdleModal( + type: IdleBigAlertController.ButtonType = .red, viewModel: IdleAlertViewModelable ) { - let alertVC = IdleBigAlertController() + let alertVC = IdleBigAlertController(type: type) alertVC.bind(viewModel: viewModel) alertVC.modalPresentationStyle = .custom present(alertVC, animated: true, completion: nil) diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift index 0ecdb46f..b41cfd38 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift @@ -87,6 +87,13 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { }) .disposed(by: disposeBag) + viewModel + .idleAlertVM? + .drive(onNext: { [weak self] vm in + self?.showIdleModal(type: .orange, viewModel: vm) + }) + .disposed(by: disposeBag) + // Input self.rx.viewDidLoad .bind(to: viewModel.requestWorkerLocation) diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift index 805b3777..984bbd22 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift @@ -191,7 +191,7 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { self.idleAlertVM = applyButtonClicked .map { (postId: String, postTitle: String) in DefaultIdleAlertVM( - title: "'postTitle'\n공고에 지원하시겠어요?", + title: "'\(postTitle)'\n공고에 지원하시겠어요?", description: "", acceptButtonLabelText: "지원하기", cancelButtonLabelText: "취소하기") { [applyRequest] in From bd74b80140ee0435c90a7d34a39c4f90147d33ca Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 3 Sep 2024 15:20:39 +0900 Subject: [PATCH 13/21] =?UTF-8?q?[IDLE-000]=20=EC=A7=80=EC=9B=90=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=84=B1=EA=B3=B5=EC=8B=9C=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Paging/PostPagingRequestForWorker.swift | 2 +- .../SubVC/WorkerPagablePostBoardVC.swift | 14 ++- .../WorkerRecruitmentPostBoardVC.swift | 17 ++-- .../RecruitmentPost/AppliedPostBoardVM.swift | 10 ++- .../WorkerRecruitmentPostBoardVM.swift | 89 ++++++++++--------- .../RecruitmentPost/StarredPostBoardVM.swift | 10 ++- 6 files changed, 83 insertions(+), 59 deletions(-) diff --git a/project/Projects/Domain/Entity/State/RecruitmentPost/Paging/PostPagingRequestForWorker.swift b/project/Projects/Domain/Entity/State/RecruitmentPost/Paging/PostPagingRequestForWorker.swift index 57b03488..b73cdefb 100644 --- a/project/Projects/Domain/Entity/State/RecruitmentPost/Paging/PostPagingRequestForWorker.swift +++ b/project/Projects/Domain/Entity/State/RecruitmentPost/Paging/PostPagingRequestForWorker.swift @@ -7,7 +7,7 @@ import Foundation -public enum PostPagingRequestForWorker { +public enum PostPagingRequestForWorker: Equatable { public enum Source { case native case thirdParty diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift index 5bfb6f0c..30df2333 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift @@ -98,10 +98,16 @@ public class WorkerPagablePostBoardVC: BaseViewController { // Output viewModel .postBoardData? - .drive(onNext: { [weak self] cellData in + .drive(onNext: { [weak self] (isRefreshed, cellData) in guard let self else { return } self.cellData.accept(cellData) self.postTableView.reloadData() + + if isRefreshed { + DispatchQueue.main.async { [weak self] in + self?.postTableView.setContentOffset(.zero, animated: false) + } + } }) .disposed(by: disposeBag) @@ -130,10 +136,14 @@ public class WorkerPagablePostBoardVC: BaseViewController { // Output viewModel .postBoardData? - .drive(onNext: { [weak self] cellData in + .drive(onNext: { [weak self] (isRefreshed, cellData) in guard let self else { return } self.cellData.accept(cellData) self.postTableView.reloadData() + + if isRefreshed { + postTableView.setContentOffset(.zero, animated: false) + } }) .disposed(by: disposeBag) diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift index b41cfd38..cf9aeffe 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift @@ -53,11 +53,6 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { setLayout() } - public override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - postTableView.setContentOffset(.zero, animated: false) - } - public func bind(viewModel: WorkerRecruitmentPostBoardVMable) { self.viewModel = viewModel @@ -72,11 +67,17 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { viewModel .postBoardData? - .drive(onNext: { [weak self] cellData in + .drive(onNext: { [weak self] (isRefreshed: Bool, cellData) in guard let self else { return } self.cellData.accept(cellData) - self.postTableView.reloadData() - self.isPaging = false + postTableView.reloadData() + isPaging = false + + if isRefreshed { + DispatchQueue.main.async { [weak self] in + self?.postTableView.setContentOffset(.zero, animated: false) + } + } }) .disposed(by: disposeBag) diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift index 186383c3..2b7dca22 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift @@ -23,7 +23,7 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { public var applyButtonClicked: RxRelay.PublishRelay<(postId: String, postTitle: String)> = .init() // Output - public var postBoardData: RxCocoa.Driver<[PostBoardCellData]>? + public var postBoardData: RxCocoa.Driver<(isRefreshed: Bool, cellData: [PostBoardCellData])>? public var alert: RxCocoa.Driver? // Init @@ -67,10 +67,12 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { currentPostVO, requestPostListSuccess ) - .compactMap { [weak self] (prevPostList, fetchedData) -> [PostBoardCellData]? in + .compactMap { [weak self] (prevPostList, fetchedData) -> (Bool, [PostBoardCellData])? in guard let self else { return nil } + let isRefreshed: Bool = self.nextPagingRequest == .initial + // MARK: 지원 공고의 경우 써드파티에서 불러올 데이터가 없다. self.nextPagingRequest = .paging( source: .native, @@ -93,9 +95,9 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { return .init(postId: vo.postId, cardVO: cardVO) } - return cellData + return (isRefreshed, cellData) } - .asDriver(onErrorJustReturn: []) + .asDriver(onErrorDriveWith: .never()) alert = requestPostListFailure .map { error in diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift index 984bbd22..0c13c8d1 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift @@ -28,7 +28,7 @@ public protocol WorkerPagablePostBoardVMable: DefaultAlertOutputable & WorkerNat var requestInitialPageRequest: PublishRelay { get } /// 페이지요청에 대한 결과를 전달합니다. - var postBoardData: Driver<[PostBoardCellData]>? { get } + var postBoardData: Driver<(isRefreshed: Bool, cellData: [PostBoardCellData])>? { get } } /// 페이징 + 지원하기 @@ -50,7 +50,7 @@ public protocol WorkerRecruitmentPostBoardVMable: WorkerAppliablePostBoardVMable public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { // Output - public var postBoardData: Driver<[PostBoardCellData]>? + public var postBoardData: Driver<(isRefreshed: Bool, cellData: [PostBoardCellData])>? public var workerLocationTitleText: Driver? public var idleAlertVM: RxCocoa.Driver? public var alert: Driver? @@ -83,14 +83,53 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { self.coordinator = coordinator self.recruitmentPostUseCase = recruitmentPostUseCase - // 상단 위치정보 + // MARK: 상단 위치정보 불러오기 workerLocationTitleText = requestWorkerLocation .compactMap { [weak self] _ in self?.fetchWorkerLocation() } .asDriver(onErrorJustReturn: "위치정보확인불가") + // MARK: 지원하기 + let applyRequest: PublishRelay = .init() + self.idleAlertVM = applyButtonClicked + .map { (postId: String, postTitle: String) in + DefaultIdleAlertVM( + title: "'\(postTitle)'\n공고에 지원하시겠어요?", + description: "", + acceptButtonLabelText: "지원하기", + cancelButtonLabelText: "취소하기") { [applyRequest] in + applyRequest.accept(postId) + } + } + .asDriver(onErrorDriveWith: .never()) + + let applyRequestResult = applyRequest + .flatMap { [recruitmentPostUseCase] postId in + // 리스트화면에서는 앱내 지원만 지원합니다. + recruitmentPostUseCase + .applyToPost(postId: postId, method: .app) + } + .share() + + // 지원하기 성공시 새로고침 + applyRequestResult + .compactMap { $0.value } + .bind(to: requestInitialPageRequest) + .disposed(by: dispostBag) + let applyRequestFailure = applyRequestResult.compactMap { $0.error } + + let applyRequestFailureAlert = applyRequestFailure + .map { error in + DefaultAlertContentVO( + title: "지원하기 실패", + message: error.message + ) + } + + + // MARK: 공고리스트 처음부터 요청하기 let initialRequest = requestInitialPageRequest .flatMap { [weak self, recruitmentPostUseCase] request in @@ -104,7 +143,7 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { ) } - + // MARK: 공고리스트 페이징 요청 let pagingRequest = requestNextPage .compactMap { [weak self] _ in // 요청이 없는 경우 요청을 보내지 않는다. @@ -131,10 +170,12 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { currentPostVO, requestPostListSuccess ) - .compactMap { [weak self] (prevPostList, fetchedData) -> [PostBoardCellData]? in + .compactMap { [weak self] (prevPostList, fetchedData) -> (Bool, [PostBoardCellData])? in guard let self else { return nil } + let isRefreshed: Bool = self.nextPagingRequest == .initial + // 다음 요청설정 var nextRequest: PostPagingRequestForWorker? if let prevRequest = self.nextPagingRequest { @@ -145,7 +186,7 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { case .initial: nextRequest = .paging(source: .native, nextPageId: nextPageId) case .paging(let source, let nextPageId): - nextRequest = .paging(source: .thirdParty, nextPageId: nextPageId) + nextRequest = .paging(source: source, nextPageId: nextPageId) } } else { // 다음값이 없는 경우 @@ -182,41 +223,9 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { return .init(postId: postVO.postId, cardVO: cardVO) } - return cellData - } - .asDriver(onErrorJustReturn: []) - - // MARK: 지원하기 - let applyRequest: PublishRelay = .init() - self.idleAlertVM = applyButtonClicked - .map { (postId: String, postTitle: String) in - DefaultIdleAlertVM( - title: "'\(postTitle)'\n공고에 지원하시겠어요?", - description: "", - acceptButtonLabelText: "지원하기", - cancelButtonLabelText: "취소하기") { [applyRequest] in - applyRequest.accept(postId) - } + return (isRefreshed, cellData) } .asDriver(onErrorDriveWith: .never()) - - let applyRequestResult = applyRequest - .flatMap { [recruitmentPostUseCase] postId in - // 리스트화면에서는 앱내 지원만 지원합니다. - recruitmentPostUseCase - .applyToPost(postId: postId, method: .app) - } - .share() - - let applyRequestFailure = applyRequestResult.compactMap { $0.error } - - let applyRequestFailureAlert = applyRequestFailure - .map { error in - DefaultAlertContentVO( - title: "지원하기 실패", - message: error.message - ) - } let requestPostListFailureAlert = requestPostListFailure .map { error in @@ -225,7 +234,7 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { message: error.message ) } - + self.alert = Observable .merge( applyRequestFailureAlert, diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift index 1c1f8396..c63b607f 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift @@ -22,7 +22,7 @@ public class StarredPostBoardVM: WorkerAppliablePostBoardVMable { public var applyButtonClicked: RxRelay.PublishRelay<(postId: String, postTitle: String)> = .init() // Output - public var postBoardData: Driver<[PostBoardCellData]>? + public var postBoardData: RxCocoa.Driver<(isRefreshed: Bool, cellData: [PostBoardCellData])>? public var alert: Driver? public var idleAlertVM: RxCocoa.Driver? @@ -67,10 +67,12 @@ public class StarredPostBoardVM: WorkerAppliablePostBoardVMable { currentPostVO, requestPostListSuccess ) - .compactMap { [weak self] (prevPostList, fetchedData) -> [PostBoardCellData]? in + .compactMap { [weak self] (prevPostList, fetchedData) -> (Bool, [PostBoardCellData])? in guard let self else { return nil } + let isRefreshed: Bool = self.nextPagingRequest == .initial + // TODO: ‼️ ‼️ 즐겨찾기 공고의 경우 서버에서 아직 워크넷 공고를 처리하는 방법을 정하지 못했음으로 추후에 수정할 예정입니다. self.nextPagingRequest = .paging( @@ -94,9 +96,9 @@ public class StarredPostBoardVM: WorkerAppliablePostBoardVMable { return .init(postId: vo.postId, cardVO: cardVO) } - return cellData + return (isRefreshed, cellData) } - .asDriver(onErrorJustReturn: []) + .asDriver(onErrorDriveWith: .never()) // MARK: 지원하기 let applyRequest: PublishRelay = .init() From 84c7e11d2415b3129e0840c6ecd198483a580d58 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 3 Sep 2024 15:31:21 +0900 Subject: [PATCH 14/21] =?UTF-8?q?[IDLE-000]=20DefaultLoadingVC=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Alert /IdleBigAlertController.swift | 18 ++--- .../Loading/DefaultLoadingVC.swift | 67 +++++++++++++++++++ 2 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Loading/DefaultLoadingVC.swift diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Alert /IdleBigAlertController.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Alert /IdleBigAlertController.swift index 6f3153b0..9d61f69d 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Alert /IdleBigAlertController.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Alert /IdleBigAlertController.swift @@ -222,13 +222,13 @@ public class IdleBigAlertController: UIViewController { } } -class FadeInAnimator: NSObject, UIViewControllerAnimatedTransitioning { +public class FadeInAnimator: NSObject, UIViewControllerAnimatedTransitioning { - func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.35 // 애니메이션 지속 시간 } - func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let fromView = transitionContext.view(forKey: .from) else { return } let containerView = transitionContext.containerView @@ -249,13 +249,13 @@ class FadeInAnimator: NSObject, UIViewControllerAnimatedTransitioning { } } -class FadeOutAnimator: NSObject, UIViewControllerAnimatedTransitioning { +public class FadeOutAnimator: NSObject, UIViewControllerAnimatedTransitioning { - func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.35 // 애니메이션 지속 시간 } - func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let toView = transitionContext.view(forKey: .to) else { return } let containerView = transitionContext.containerView @@ -275,13 +275,13 @@ class FadeOutAnimator: NSObject, UIViewControllerAnimatedTransitioning { } } -class CustomTransitionDelegate: NSObject, UIViewControllerTransitioningDelegate { +public class CustomTransitionDelegate: NSObject, UIViewControllerTransitioningDelegate { - func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return FadeInAnimator() // 우리가 만든 사용자 정의 애니메이터를 반환 } - func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> (any UIViewControllerAnimatedTransitioning)? { + public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> (any UIViewControllerAnimatedTransitioning)? { return FadeOutAnimator() } } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Loading/DefaultLoadingVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Loading/DefaultLoadingVC.swift new file mode 100644 index 00000000..6c51dda5 --- /dev/null +++ b/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Loading/DefaultLoadingVC.swift @@ -0,0 +1,67 @@ +// +// DefaultLoadingVC.swift +// BaseFeature +// +// Created by choijunios on 9/3/24. +// + +import UIKit +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit + +public class DefaultLoadingVC: BaseViewController { + + let customTranstionDelegate = CustomTransitionDelegate() + + // Init + + // View + private let loadingView: UIActivityIndicatorView = .init() + + + // Observable + private let disposeBag = DisposeBag() + + public init() { + super.init(nibName: nil, bundle: nil) + self.transitioningDelegate = customTranstionDelegate + } + + public required init?(coder: NSCoder) { fatalError() } + + public override func viewDidLoad() { + super.viewDidLoad() + setAppearance() + setLayout() + } + + private func setAppearance() { + view.backgroundColor = DSColor.gray050.color.withAlphaComponent(0.5) + } + + private func setLayout() { + [ + loadingView + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + view.addSubview($0) + } + + NSLayoutConstraint.activate([ + loadingView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + loadingView.centerYAnchor.constraint(equalTo: view.centerYAnchor), + ]) + } + + public func on() { + loadingView.startAnimating() + } + + public func off() { + loadingView.stopAnimating() + } +} + From 69926adb53f0815e86d4f1bf6e78437e2d5cf887 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 3 Sep 2024 16:55:06 +0900 Subject: [PATCH 15/21] =?UTF-8?q?[IDLE-000]=20=EC=A7=80=EC=9B=90=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EB=B0=8F=20=EA=B3=B5=EA=B3=A0=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=97=90=20=EB=A1=9C=EB=94=A9=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit present한 ViewController를 곧바로 dismiss할경우 올바르게 dismiss되지 못한다, 등장/퇴장 애니메이션이 충돌할 수 있기 때문이다. --- .../Base/BaseViewController.swift | 62 ++++++++++++++++++- .../Loading/DefaultLoadingVC.swift | 18 +++--- .../WorkerRecruitmentPostBoardVC.swift | 2 + .../RecruitmentPost/AppliedPostBoardVM.swift | 2 + .../WorkerRecruitmentPostBoardVM.swift | 44 +++++++++++-- .../RecruitmentPost/StarredPostBoardVM.swift | 4 +- 6 files changed, 116 insertions(+), 16 deletions(-) diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift b/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift index d3ceb3e5..6f16b5bd 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Base/BaseViewController.swift @@ -8,8 +8,16 @@ import UIKit import Entity import DSKit +import RxSwift -open class BaseViewController: UIViewController { } +open class BaseViewController: UIViewController { + + // MARK: loading trigger + /// present애니메이션중 dismiss가 발동될 경우 충돌이 발생하여 예상치못한 동작유발 + private var isLoadingPresenting: Bool = false + private var loadingDimissionRequested: Bool = false + private var loadingVC: UIViewController? +} // MARK: Alert public extension BaseViewController { @@ -44,4 +52,56 @@ public extension BaseViewController { alertVC.modalPresentationStyle = .custom present(alertVC, animated: true, completion: nil) } + + func showDefaultLoadingScreen() { + + let vc = DefaultLoadingVC() + loadingVC = vc + vc.modalPresentationStyle = .custom + + isLoadingPresenting = true + present(vc, animated: true) { + + if self.loadingDimissionRequested { + DispatchQueue.main.async { [weak self] in + vc.dismiss(animated: true) { + self?.loadingVC = nil + } + self?.isLoadingPresenting = false + } + } else { + self.isLoadingPresenting = false + } + } + } + + func dismissDefaultLoadingScreen() { + if let loadingVC { + + if !isLoadingPresenting { + loadingVC.dismiss(animated: true) { + self.loadingVC = nil + } + } else { + loadingDimissionRequested = true + } + } + } + + func bind(viewModel: DefaultLoadingVMable, disposeBag: DisposeBag) { + + viewModel + .showLoading? + .drive(onNext: { [weak self] _ in + self?.showDefaultLoadingScreen() + }) + .disposed(by: disposeBag) + + viewModel + .dismissLoading? + .drive(onNext: { [weak self] _ in + self?.dismissDefaultLoadingScreen() + }) + .disposed(by: disposeBag) + } } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Loading/DefaultLoadingVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Loading/DefaultLoadingVC.swift index 6c51dda5..24768b81 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Loading/DefaultLoadingVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/ViewController/Loading/DefaultLoadingVC.swift @@ -12,7 +12,13 @@ import RxSwift import Entity import DSKit -public class DefaultLoadingVC: BaseViewController { +public protocol DefaultLoadingVMable { + + var showLoading: Driver? { get } + var dismissLoading: Driver? { get } +} + +public class DefaultLoadingVC: UIViewController { let customTranstionDelegate = CustomTransitionDelegate() @@ -36,6 +42,8 @@ public class DefaultLoadingVC: BaseViewController { super.viewDidLoad() setAppearance() setLayout() + + loadingView.startAnimating() } private func setAppearance() { @@ -55,13 +63,5 @@ public class DefaultLoadingVC: BaseViewController { loadingView.centerYAnchor.constraint(equalTo: view.centerYAnchor), ]) } - - public func on() { - loadingView.startAnimating() - } - - public func off() { - loadingView.stopAnimating() - } } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift index cf9aeffe..9405887c 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift @@ -57,6 +57,8 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { self.viewModel = viewModel + super.bind(viewModel: viewModel, disposeBag: disposeBag) + // Output viewModel .workerLocationTitleText? diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift index 2b7dca22..6eb7a0d9 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift @@ -25,6 +25,8 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { // Output public var postBoardData: RxCocoa.Driver<(isRefreshed: Bool, cellData: [PostBoardCellData])>? public var alert: RxCocoa.Driver? + public var showLoading: RxCocoa.Driver? + public var dismissLoading: RxCocoa.Driver? // Init weak var coordinator: WorkerRecruitmentBoardCoordinatable? diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift index 0c13c8d1..7d216bff 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift @@ -20,7 +20,7 @@ public struct PostBoardCellData { } /// 페이징 보드 -public protocol WorkerPagablePostBoardVMable: DefaultAlertOutputable & WorkerNativeEmployCardViewModelable { +public protocol WorkerPagablePostBoardVMable: DefaultAlertOutputable & WorkerNativeEmployCardViewModelable & DefaultLoadingVMable { /// 다음 페이지를 요청합니다. var requestNextPage: PublishRelay { get } @@ -49,11 +49,17 @@ public protocol WorkerRecruitmentPostBoardVMable: WorkerAppliablePostBoardVMable public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { + + // Output public var postBoardData: Driver<(isRefreshed: Bool, cellData: [PostBoardCellData])>? public var workerLocationTitleText: Driver? public var idleAlertVM: RxCocoa.Driver? + + // Default public var alert: Driver? + public var showLoading: Driver? + public var dismissLoading: Driver? // Input public var requestInitialPageRequest: PublishRelay = .init() @@ -83,6 +89,9 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { self.coordinator = coordinator self.recruitmentPostUseCase = recruitmentPostUseCase + var loadingStartObservables: [Observable] = [] + var loadingEndObservables: [Observable] = [] + // MARK: 상단 위치정보 불러오기 workerLocationTitleText = requestWorkerLocation .compactMap { [weak self] _ in @@ -103,18 +112,26 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { } } .asDriver(onErrorDriveWith: .never()) - + + // 로딩 시작 + loadingStartObservables.append(applyRequest.map { _ in }) + let applyRequestResult = applyRequest .flatMap { [recruitmentPostUseCase] postId in + // 리스트화면에서는 앱내 지원만 지원합니다. - recruitmentPostUseCase + return recruitmentPostUseCase .applyToPost(postId: postId, method: .app) } .share() + // 로딩 종료 + loadingEndObservables.append(applyRequestResult.map { _ in }) + + let applyRequestSuccess = applyRequestResult.compactMap { $0.value } + // 지원하기 성공시 새로고침 - applyRequestResult - .compactMap { $0.value } + applyRequestSuccess .bind(to: requestInitialPageRequest) .disposed(by: dispostBag) @@ -142,6 +159,10 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { postCount: 10 ) } + .share() + + // 로딩 시작 + loadingStartObservables.append(initialRequest.map { _ in }) // MARK: 공고리스트 페이징 요청 let pagingRequest = requestNextPage @@ -162,6 +183,9 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { .merge(initialRequest, pagingRequest) .share() + // 로딩 종료 + loadingEndObservables.append(postPageReqeustResult.map { _ in }) + let requestPostListSuccess = postPageReqeustResult.compactMap { $0.value } let requestPostListFailure = postPageReqeustResult.compactMap { $0.error } @@ -241,6 +265,16 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { requestPostListFailureAlert ) .asDriver(onErrorJustReturn: .default) + + // MARK: 로딩 + showLoading = Observable + .merge(loadingStartObservables) + .asDriver(onErrorDriveWith: .never()) + + dismissLoading = Observable + .merge(loadingEndObservables) + .delay(.milliseconds(500), scheduler: MainScheduler.instance) + .asDriver(onErrorDriveWith: .never()) } public func showPostDetail(id: String) { diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift index c63b607f..5ada36f6 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift @@ -24,7 +24,9 @@ public class StarredPostBoardVM: WorkerAppliablePostBoardVMable { // Output public var postBoardData: RxCocoa.Driver<(isRefreshed: Bool, cellData: [PostBoardCellData])>? public var alert: Driver? - public var idleAlertVM: RxCocoa.Driver? + public var idleAlertVM: RxCocoa.Driver? + public var showLoading: RxCocoa.Driver? + public var dismissLoading: RxCocoa.Driver? // Init weak var coordinator: WorkerRecruitmentBoardCoordinatable? From ccc24444c823283ae9e4151b559ad338a1dd37f8 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 3 Sep 2024 17:12:31 +0900 Subject: [PATCH 16/21] =?UTF-8?q?[IDLE-000]=20=EC=A7=80=EC=9B=90=ED=95=9C?= =?UTF-8?q?=20=EA=B3=B5=EA=B3=A0=20=EB=B0=91=20=EC=A6=90=EA=B2=A8=EC=B0=BE?= =?UTF-8?q?=EA=B8=B0=ED=95=9C=20=EA=B3=B5=EA=B3=A0=20=EB=A1=9C=EB=94=A9=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SubVC/WorkerPagablePostBoardVC.swift | 6 +++ .../RecruitmentPost/AppliedPostBoardVM.swift | 46 +++++++++++++--- .../RecruitmentPost/StarredPostBoardVM.swift | 52 ++++++++++++++++--- 3 files changed, 92 insertions(+), 12 deletions(-) diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift index 30df2333..eea38451 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift @@ -95,6 +95,9 @@ public class WorkerPagablePostBoardVC: BaseViewController { self.viewModel = viewModel + // 로딩 바인딩 + super.bind(viewModel: viewModel, disposeBag: disposeBag) + // Output viewModel .postBoardData? @@ -133,6 +136,9 @@ public class WorkerPagablePostBoardVC: BaseViewController { self.viewModel = viewModel + // 로딩 바인딩 + super.bind(viewModel: viewModel, disposeBag: disposeBag) + // Output viewModel .postBoardData? diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift index 6eb7a0d9..ccc0ce59 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift @@ -42,11 +42,29 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { self.recruitmentPostUseCase = recruitmentPostUseCase self.nextPagingRequest = .initial - let postPageReqeustResult = Observable - .merge( - requestInitialPageRequest.asObservable(), - requestNextPage.asObservable() - ) + var loadingStartObservables: [Observable] = [] + var loadingEndObservables: [Observable] = [] + + // MARK: 공고리스트 처음부터 요청하기 + let initialRequest = requestInitialPageRequest + .flatMap { [weak self, recruitmentPostUseCase] request in + + self?.currentPostVO.accept([]) + self?.nextPagingRequest = .initial + + return recruitmentPostUseCase + .getPostListForWorker( + request: .initial, + postCount: 10 + ) + } + .share() + + // 로딩 시작 + loadingStartObservables.append(initialRequest.map { _ in }) + + // MARK: 공고리스트 페이징 요청 + let pagingRequest = requestNextPage .compactMap { [weak self] _ in // 요청이 없는 경우 요청을 보내지 않는다. // ThirdPatry에서도 불러올 데이터가 없는 경우입니다. @@ -54,13 +72,19 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { } .flatMap { [recruitmentPostUseCase] request in recruitmentPostUseCase - .getAppliedPostListForWorker( + .getPostListForWorker( request: request, postCount: 10 ) } + + let postPageReqeustResult = Observable + .merge(initialRequest, pagingRequest) .share() + // 로딩 종료 + loadingEndObservables.append(postPageReqeustResult.map { _ in }) + let requestPostListSuccess = postPageReqeustResult.compactMap { $0.value } let requestPostListFailure = postPageReqeustResult.compactMap { $0.error } @@ -109,6 +133,16 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { ) } .asDriver(onErrorJustReturn: .default) + + // MARK: 로딩 + showLoading = Observable + .merge(loadingStartObservables) + .asDriver(onErrorDriveWith: .never()) + + dismissLoading = Observable + .merge(loadingEndObservables) + .delay(.milliseconds(500), scheduler: MainScheduler.instance) + .asDriver(onErrorDriveWith: .never()) } public func showPostDetail(id: String) { diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift index 5ada36f6..e838c6b2 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift @@ -42,11 +42,29 @@ public class StarredPostBoardVM: WorkerAppliablePostBoardVMable { self.recruitmentPostUseCase = recruitmentPostUseCase self.nextPagingRequest = .initial - let postPageReqeustResult = Observable - .merge( - requestInitialPageRequest.asObservable(), - requestNextPage.asObservable() - ) + var loadingStartObservables: [Observable] = [] + var loadingEndObservables: [Observable] = [] + + // MARK: 공고리스트 처음부터 요청하기 + let initialRequest = requestInitialPageRequest + .flatMap { [weak self, recruitmentPostUseCase] request in + + self?.currentPostVO.accept([]) + self?.nextPagingRequest = .initial + + return recruitmentPostUseCase + .getPostListForWorker( + request: .initial, + postCount: 10 + ) + } + .share() + + // 로딩 시작 + loadingStartObservables.append(initialRequest.map { _ in }) + + // MARK: 공고리스트 페이징 요청 + let pagingRequest = requestNextPage .compactMap { [weak self] _ in // 요청이 없는 경우 요청을 보내지 않는다. // ThirdPatry에서도 불러올 데이터가 없는 경우입니다. @@ -54,13 +72,19 @@ public class StarredPostBoardVM: WorkerAppliablePostBoardVMable { } .flatMap { [recruitmentPostUseCase] request in recruitmentPostUseCase - .getFavoritePostListForWorker( + .getPostListForWorker( request: request, postCount: 10 ) } + + let postPageReqeustResult = Observable + .merge(initialRequest, pagingRequest) .share() + // 로딩 종료 + loadingEndObservables.append(postPageReqeustResult.map { _ in }) + let requestPostListSuccess = postPageReqeustResult.compactMap { $0.value } let requestPostListFailure = postPageReqeustResult.compactMap { $0.error } @@ -115,6 +139,9 @@ public class StarredPostBoardVM: WorkerAppliablePostBoardVMable { } } .asDriver(onErrorDriveWith: .never()) + + // 로딩 시작 + loadingStartObservables.append(applyRequest.map { _ in }) let applyRequestResult = applyRequest .flatMap { [recruitmentPostUseCase] postId in @@ -124,6 +151,9 @@ public class StarredPostBoardVM: WorkerAppliablePostBoardVMable { } .share() + // 로딩 종료 + loadingEndObservables.append(applyRequestResult.map { _ in }) + let applyRequestFailure = applyRequestResult.compactMap { $0.error } let applyRequestFailureAlert = applyRequestFailure @@ -145,6 +175,16 @@ public class StarredPostBoardVM: WorkerAppliablePostBoardVMable { alert = Observable .merge(applyRequestFailureAlert, requestPostListFailureAlert) .asDriver(onErrorJustReturn: .default) + + // MARK: 로딩 + showLoading = Observable + .merge(loadingStartObservables) + .asDriver(onErrorDriveWith: .never()) + + dismissLoading = Observable + .merge(loadingEndObservables) + .delay(.milliseconds(500), scheduler: MainScheduler.instance) + .asDriver(onErrorDriveWith: .never()) } public func showPostDetail(id: String) { From 35c5c68e8021300b2016e251260433e6d089f9cb Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 3 Sep 2024 17:37:37 +0900 Subject: [PATCH 17/21] =?UTF-8?q?[IDLE-000]=20=EA=B3=B5=EA=B3=A0=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=ED=99=94=EB=A9=B4=EC=95=88=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EC=A7=80=EC=9B=90=ED=95=98=EA=B8=B0=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/NativePostDetailForWorkerVC.swift | 10 ++ .../View/WorkPlaceAndWorkerLocationView.swift | 4 +- .../NativePostDetailForWorkerVM.swift | 106 ++++++++++++++---- 3 files changed, 97 insertions(+), 23 deletions(-) diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/NativePostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/NativePostDetailForWorkerVC.swift index e23a61cc..61f25986 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/NativePostDetailForWorkerVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/NativePostDetailForWorkerVC.swift @@ -128,6 +128,8 @@ public class NativePostDetailForWorkerVC: BaseViewController { self.viewModel = viewModel + super.bind(viewModel: viewModel, disposeBag: disposeBag) + // Output viewModel .postForWorkerBundle? @@ -196,6 +198,14 @@ public class NativePostDetailForWorkerVC: BaseViewController { .disposed(by: disposeBag) } + viewModel + .idleAlertVM? + .drive(onNext: { [weak self] vm in + self?.showIdleModal(type: .orange, viewModel: vm) + }) + .disposed(by: disposeBag) + + viewModel .alert? .drive(onNext: { [weak self] alertVO in diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkPlaceAndWorkerLocationView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkPlaceAndWorkerLocationView.swift index 23e74079..bbc6d96f 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkPlaceAndWorkerLocationView.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkPlaceAndWorkerLocationView.swift @@ -43,13 +43,13 @@ public class WorkPlaceAndWorkerLocationView: VStack { public let mapViewBackGround: TappableUIView = { let view = TappableUIView() view.backgroundColor = DSColor.gray050.color + view.layer.cornerRadius = 8 + view.clipsToBounds = true return view }() let mapView: NMFNaverMapView = { let view = NMFNaverMapView(frame: .zero) view.backgroundColor = DSColor.gray050.color - view.layer.cornerRadius = 8 - view.clipsToBounds = true view.isUserInteractionEnabled = false return view }() diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift index f6b01f3b..a6cc969c 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift @@ -13,12 +13,13 @@ import PresentationCore import UseCaseInterface import DSKit -public protocol NativePostDetailForWorkerViewModelable { +public protocol NativePostDetailForWorkerViewModelable: DefaultLoadingVMable { // Output var postForWorkerBundle: Driver? { get } var locationInfo: Driver? { get } var alert: Driver? { get } + var idleAlertVM: Driver? { get } // Input var viewWillAppear: PublishRelay { get } @@ -37,13 +38,15 @@ public class NativePostDetailForWorkerVM: NativePostDetailForWorkerViewModelable private let postId: String private let recruitmentPostUseCase: RecruitmentPostUseCase - + // Ouput public var postForWorkerBundle: RxCocoa.Driver? public var locationInfo: RxCocoa.Driver? - public var alert: RxCocoa.Driver? + public var showLoading: Driver? + public var dismissLoading: Driver? + public var idleAlertVM: Driver? - + // Input public var backButtonClicked: RxRelay.PublishRelay = .init() public var applyButtonClicked: RxRelay.PublishRelay = .init() public var startButtonClicked: RxRelay.PublishRelay = .init() @@ -62,6 +65,12 @@ public class NativePostDetailForWorkerVM: NativePostDetailForWorkerViewModelable self.coordinator = coordinator self.recruitmentPostUseCase = recruitmentPostUseCase + + // MARK: 로딩 옵저버블 + var loadingStartObservables: [Observable] = [] + var loadingEndObservables: [Observable] = [] + + let getPostDetailResult = viewWillAppear .flatMap { [recruitmentPostUseCase] _ in recruitmentPostUseCase @@ -72,6 +81,17 @@ public class NativePostDetailForWorkerVM: NativePostDetailForWorkerViewModelable let getPostDetailSuccess = getPostDetailResult.compactMap { $0.value } let getPostDetailFailure = getPostDetailResult.compactMap { $0.error } + let getPostDetailFailureAlert = getPostDetailFailure + .map { error in + AlertWithCompletionVO( + title: "공고 불러오기 실패", + message: error.message, + buttonInfo: [ + ("닫기", { [weak self] in self?.coordinator?.coordinatorDidFinish() }) + ] + ) + } + postForWorkerBundle = getPostDetailSuccess.asDriver(onErrorRecover: { _ in fatalError() }) // MARK: 센터, 워커 위치정보 @@ -104,22 +124,6 @@ public class NativePostDetailForWorkerVM: NativePostDetailForWorkerViewModelable } .asDriver(onErrorRecover: { _ in fatalError() }) - // Alert 처리 필요 - alert = getPostDetailFailure - .map { error in - AlertWithCompletionVO( - title: "공고 불러오기 실패", - message: error.message, - buttonInfo: [ - ("닫기", { [weak self] in - self?.coordinator?.coordinatorDidFinish() - }) - ] - ) - } - .asDriver(onErrorJustReturn: .default) - - // MARK: 버튼 처리 backButtonClicked .subscribe(onNext: { [weak self] _ in @@ -129,8 +133,48 @@ public class NativePostDetailForWorkerVM: NativePostDetailForWorkerViewModelable .disposed(by: disposeBag) // 지원하기 버튼 클릭 + // MARK: 지원하기 + let applyRequest: PublishRelay = .init() + self.idleAlertVM = applyButtonClicked + .map { _ in + DefaultIdleAlertVM( + title: "공고에 지원하시겠어요?", + description: "", + acceptButtonLabelText: "지원하기", + cancelButtonLabelText: "취소하기") { + applyRequest.accept(()) + } + } + .asDriver(onErrorDriveWith: .never()) + + // 로딩 시작 + loadingStartObservables.append(applyRequest.map { _ in }) + + let applyRequestResult = applyRequest + .flatMap { [recruitmentPostUseCase] _ in + + // 리스트화면에서는 앱내 지원만 지원합니다. + return recruitmentPostUseCase + .applyToPost(postId: postId, method: .app) + } + .share() + + // 로딩 종료 + loadingEndObservables.append(applyRequestResult.map { _ in }) + + let applyRequestSuccess = applyRequestResult.compactMap { $0.value } + let applyRequestFailure = applyRequestResult.compactMap { $0.error } + + let applyRequestFailureAlert = applyRequestFailure + .map { error in + AlertWithCompletionVO( + title: "지원하기 실패", + message: error.message + ) + } - // 즐겨찾기 버튼 클릭 + // MARK: 즐겨찾기 + // 센터 프로필 조회 버튼클릭 centerCardClicked @@ -141,6 +185,26 @@ public class NativePostDetailForWorkerVM: NativePostDetailForWorkerViewModelable self.coordinator?.showCenterProfileScreen(centerId: centerId) }) .disposed(by: disposeBag) + + + // MARK: Alert + alert = Observable + .merge( + getPostDetailFailureAlert, + applyRequestFailureAlert + ) + .asDriver(onErrorJustReturn: .default) + + + // MARK: 로딩 + showLoading = Observable + .merge(loadingStartObservables) + .asDriver(onErrorDriveWith: .never()) + + dismissLoading = Observable + .merge(loadingEndObservables) + .delay(.milliseconds(500), scheduler: MainScheduler.instance) + .asDriver(onErrorDriveWith: .never()) } // MARK: Test From 5fe549310df19e3710dc1019f9b20daedb141095 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 3 Sep 2024 17:55:06 +0900 Subject: [PATCH 18/21] =?UTF-8?q?[IDLE-000]=20=EC=A7=80=EC=9B=90=ED=95=9C?= =?UTF-8?q?=20=EA=B3=B5=EA=B3=A0/=20=EC=B0=9C=ED=95=9C=20=EA=B3=B5?= =?UTF-8?q?=EA=B3=A0=20=ED=8E=98=EC=9D=B4=EC=A7=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DataSource/API/RcruitmentPostAPI.swift | 8 ++++++-- .../SubVC/WorkerPagablePostBoardVC.swift | 2 ++ .../RecruitmentPost/AppliedPostBoardVM.swift | 20 ++++++++++++------- .../RecruitmentPost/StarredPostBoardVM.swift | 17 ++++++++++------ 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift b/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift index c29476a8..09b61406 100644 --- a/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift +++ b/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift @@ -142,7 +142,9 @@ extension RcruitmentPostAPI: BaseAPI { var parameterEncoding: ParameterEncoding { switch self { - case .getOnGoingNativePostListForWorker: + case .getOnGoingNativePostListForWorker, + .getFavoritePostListForWorker, + .getAppliedPostListForWorker: return URLEncoding.queryString default: return JSONEncoding.default @@ -151,7 +153,9 @@ extension RcruitmentPostAPI: BaseAPI { public var task: Moya.Task { switch self { - case .getOnGoingNativePostListForWorker: + case .getOnGoingNativePostListForWorker, + .getFavoritePostListForWorker, + .getAppliedPostListForWorker: .requestParameters(parameters: bodyParameters ?? [:], encoding: parameterEncoding) case .registerPost(let bodyData): .requestData(bodyData) diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift index eea38451..a77eb7de 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerPagablePostBoardVC.swift @@ -105,6 +105,7 @@ public class WorkerPagablePostBoardVC: BaseViewController { guard let self else { return } self.cellData.accept(cellData) self.postTableView.reloadData() + isPaging = false if isRefreshed { DispatchQueue.main.async { [weak self] in @@ -146,6 +147,7 @@ public class WorkerPagablePostBoardVC: BaseViewController { guard let self else { return } self.cellData.accept(cellData) self.postTableView.reloadData() + isPaging = false if isRefreshed { postTableView.setContentOffset(.zero, animated: false) diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift index ccc0ce59..c6abfd76 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift @@ -53,7 +53,7 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { self?.nextPagingRequest = .initial return recruitmentPostUseCase - .getPostListForWorker( + .getAppliedPostListForWorker( request: .initial, postCount: 10 ) @@ -72,7 +72,7 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { } .flatMap { [recruitmentPostUseCase] request in recruitmentPostUseCase - .getPostListForWorker( + .getAppliedPostListForWorker( request: request, postCount: 10 ) @@ -99,11 +99,17 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { let isRefreshed: Bool = self.nextPagingRequest == .initial - // MARK: 지원 공고의 경우 써드파티에서 불러올 데이터가 없다. - self.nextPagingRequest = .paging( - source: .native, - nextPageId: fetchedData.nextPageId - ) + + if let next = fetchedData.nextPageId { + // 지원 공고의 경우 써드파티에서 불러올 데이터가 없다. + self.nextPagingRequest = .paging( + source: .native, + nextPageId: next + ) + } else { + self.nextPagingRequest = nil + } + // 화면에 표시할 전체리스트 도출 let fetchedPosts = fetchedData.posts diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift index e838c6b2..fc10849e 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift @@ -53,7 +53,7 @@ public class StarredPostBoardVM: WorkerAppliablePostBoardVMable { self?.nextPagingRequest = .initial return recruitmentPostUseCase - .getPostListForWorker( + .getFavoritePostListForWorker( request: .initial, postCount: 10 ) @@ -72,7 +72,7 @@ public class StarredPostBoardVM: WorkerAppliablePostBoardVMable { } .flatMap { [recruitmentPostUseCase] request in recruitmentPostUseCase - .getPostListForWorker( + .getFavoritePostListForWorker( request: request, postCount: 10 ) @@ -101,10 +101,15 @@ public class StarredPostBoardVM: WorkerAppliablePostBoardVMable { // TODO: ‼️ ‼️ 즐겨찾기 공고의 경우 서버에서 아직 워크넷 공고를 처리하는 방법을 정하지 못했음으로 추후에 수정할 예정입니다. - self.nextPagingRequest = .paging( - source: .native, - nextPageId: fetchedData.nextPageId - ) + if let next = fetchedData.nextPageId { + // 지원 공고의 경우 써드파티에서 불러올 데이터가 없다. + self.nextPagingRequest = .paging( + source: .native, + nextPageId: next + ) + } else { + self.nextPagingRequest = nil + } // 화면에 표시할 전체리스트 도출 let fetchedPosts = fetchedData.posts From 591d8a045383e78831080751ad3d58c48e6f579f Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 3 Sep 2024 18:15:33 +0900 Subject: [PATCH 19/21] =?UTF-8?q?[IDLE-000]=20=EC=A7=80=EC=9B=90=ED=95=9C?= =?UTF-8?q?=20=EA=B3=B5=EA=B3=A0=20=EC=A7=80=EC=9B=90=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Entity/VO/Employ/WorkerNativeEmployCardVO.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Projects/Domain/Entity/VO/Employ/WorkerNativeEmployCardVO.swift b/project/Projects/Domain/Entity/VO/Employ/WorkerNativeEmployCardVO.swift index 3e0cecde..cd2c4e34 100644 --- a/project/Projects/Domain/Entity/VO/Employ/WorkerNativeEmployCardVO.swift +++ b/project/Projects/Domain/Entity/VO/Employ/WorkerNativeEmployCardVO.swift @@ -150,8 +150,8 @@ public struct WorkerNativeEmployCardVO { endTime: vo.endTime, paymentType: vo.payType, paymentAmount: vo.payAmount, - applyDate: nil, - isFavorite: false + applyDate: vo.applyTime, + isFavorite: vo.isFavorite ) } } From ed8533e8099286928b22f87214910dac75060314 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 3 Sep 2024 18:21:52 +0900 Subject: [PATCH 20/21] =?UTF-8?q?[IDLE-000]=20=EC=A7=80=EC=9B=90=ED=95=9C?= =?UTF-8?q?=20=EA=B3=B5=EA=B3=A0=20/=20=EC=B0=9C=ED=95=9C=20=EA=B3=B5?= =?UTF-8?q?=EA=B3=A0=20=EA=B3=B5=EA=B3=A0=20=EC=83=81=EC=84=B8=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SubCoordinator/AppliedAndLikedBoardCoordinator.swift | 2 ++ .../Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift | 3 ++- .../Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/AppliedAndLikedBoardCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/AppliedAndLikedBoardCoordinator.swift index 92457d08..d52bc0ad 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/AppliedAndLikedBoardCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/AppliedAndLikedBoardCoordinator.swift @@ -43,9 +43,11 @@ class AppliedAndLikedBoardCoordinator: WorkerRecruitmentBoardCoordinatable { public func start() { let vc = StarredAndAppliedVC() let appliedVM = AppliedPostBoardVM( + coordinator: self, recruitmentPostUseCase: injector.resolve(RecruitmentPostUseCase.self) ) let starredVM = StarredPostBoardVM( + coordinator: self, recruitmentPostUseCase: injector.resolve(RecruitmentPostUseCase.self) ) vc.bind( diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift index c6abfd76..ea9f80ef 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift @@ -38,7 +38,8 @@ public class AppliedPostBoardVM: WorkerPagablePostBoardVMable { /// 가장최신의 데이터를 홀드, 다음 요청시 해당데이터에 새로운 데이터를 더해서 방출 private let currentPostVO: BehaviorRelay<[NativeRecruitmentPostForWorkerVO]> = .init(value: []) - public init(recruitmentPostUseCase: RecruitmentPostUseCase) { + public init(coordinator: WorkerRecruitmentBoardCoordinatable, recruitmentPostUseCase: RecruitmentPostUseCase) { + self.coordinator = coordinator self.recruitmentPostUseCase = recruitmentPostUseCase self.nextPagingRequest = .initial diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift index fc10849e..88e73cd5 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift @@ -38,7 +38,8 @@ public class StarredPostBoardVM: WorkerAppliablePostBoardVMable { /// 가장최신의 데이터를 홀드, 다음 요청시 해당데이터에 새로운 데이터를 더해서 방출 private let currentPostVO: BehaviorRelay<[NativeRecruitmentPostForWorkerVO]> = .init(value: []) - public init(recruitmentPostUseCase: RecruitmentPostUseCase) { + public init(coordinator: WorkerRecruitmentBoardCoordinatable, recruitmentPostUseCase: RecruitmentPostUseCase) { + self.coordinator = coordinator self.recruitmentPostUseCase = recruitmentPostUseCase self.nextPagingRequest = .initial From c244ae6b13cc9539813550c6091db2ed98d1e518 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 3 Sep 2024 19:21:36 +0900 Subject: [PATCH 21/21] =?UTF-8?q?[IDLE-000]=20DSKit=20=EC=98=88=EC=8B=9C?= =?UTF-8?q?=EC=95=B1=20=EC=98=A4=ED=83=88=EC=9E=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DSKit/ExampleApp/Sources/ViewController3.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController3.swift b/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController3.swift index 9cc654d7..9b3b4b50 100644 --- a/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController3.swift +++ b/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController3.swift @@ -26,7 +26,7 @@ class ViewController3: UIViewController { // tableView.estimatedRowHeight = 44.0 tableView.rowHeight = UITableView.automaticDimension - tableView.register(WorkerEmployCardCell.self, forCellReuseIdentifier: String(describing: WorkerEmployCardCell.self)) + tableView.register(WorkerNativeEmployCardCell.self, forCellReuseIdentifier: String(describing: WorkerNativeEmployCardCell.self)) [ tableView ] @@ -51,7 +51,7 @@ extension ViewController3: UITableViewDelegate, UITableViewDataSource { } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: WorkerEmployCardCell.self), for: indexPath) as? WorkerEmployCardCell else { + guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: WorkerNativeEmployCardCell.self), for: indexPath) as? WorkerNativeEmployCardCell else { fatalError("Unable to dequeue WorkerEmployCard") } return cell