From 55629913377baaf459a4d8247d69021fe9edfa7f Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 5 Sep 2024 17:57:49 +0900 Subject: [PATCH 01/17] =?UTF-8?q?[IDLE-000]=20=EC=84=BC=ED=84=B0=20?= =?UTF-8?q?=EA=B3=B5=EA=B3=A0=EC=B9=B4=EB=93=9C=20UI=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Post/Center/CenterEmployCardCell.swift | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift index 6a9dcaae..03f5f4f8 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift @@ -42,10 +42,9 @@ public class CenterEmployCardCell: UITableViewCell { // Row5 lazy var buttonStack: VStack = { - let belowButtonStack = HStack([editPostButton, terminatePostButton,], spacing: 4) let stack = VStack([ checkApplicantsButton, - HStack([belowButtonStack, Spacer()]) + HStack([editPostButton, terminatePostButton, Spacer()], spacing: 4, distribution: .fill) ], spacing: 8, alignment: .fill) return stack }() @@ -88,38 +87,48 @@ public class CenterEmployCardCell: UITableViewCell { disposables = nil } - public override func layoutSubviews() { - super.layoutSubviews() - - contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 0, left: 20, bottom: 8, right: 20)) - } - func setAppearance() { - contentView.backgroundColor = DSColor.gray0.color - contentView.layer.setGrayBorder() + contentView.backgroundColor = .clear } func setLayout() { - contentView.layoutMargins = .init(top: 16, left: 16, bottom: 16, right: 16) + let cellView = UIView() + cellView.layoutMargins = .init(top: 16, left: 16, bottom: 16, right: 16) + cellView.backgroundColor = DSColor.gray0.color + cellView.layer.setGrayBorder() let contentStack = VStack([ cardView, buttonStack ], spacing: 12, alignment: .fill) - + [ contentStack + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + cellView.addSubview($0) + } + + NSLayoutConstraint.activate([ + contentStack.topAnchor.constraint(equalTo: cellView.layoutMarginsGuide.topAnchor), + contentStack.leftAnchor.constraint(equalTo: cellView.layoutMarginsGuide.leftAnchor), + contentStack.rightAnchor.constraint(equalTo: cellView.layoutMarginsGuide.rightAnchor), + contentStack.bottomAnchor.constraint(equalTo: cellView.layoutMarginsGuide.bottomAnchor), + ]) + + [ + cellView ].forEach { $0.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview($0) } NSLayoutConstraint.activate([ - contentStack.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), - contentStack.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor), - contentStack.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor), - contentStack.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), + cellView.topAnchor.constraint(equalTo: contentView.topAnchor), + cellView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20), + cellView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20), + cellView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8), ]) } From b094838702c11c4ef7b3bcf6baad4705e2f84537 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 5 Sep 2024 18:13:48 +0900 Subject: [PATCH 02/17] =?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=EA=B3=B5=EA=B3=A0=EC=B9=B4=EB=93=9C=20UI?= =?UTF-8?q?=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Worker/WorkerNativeEmployCardCell.swift | 24 +++++++++---------- .../WorkConditionDisplayingView.swift | 5 ++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerNativeEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerNativeEmployCardCell.swift index bcf58d48..9d2a86d3 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerNativeEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerNativeEmployCardCell.swift @@ -35,14 +35,14 @@ public class WorkerNativeEmployCardCell: UITableViewCell { var viewModel: WorkerNativeEmployCardViewModelable? private var disposables: [Disposable?]? - public override func layoutSubviews() { - super.layoutSubviews() - - contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 0, left: 20, bottom: 8, right: 20)) - } - // View - let tappableArea: TappableUIView = .init() + let tappableArea: TappableUIView = { + let view = TappableUIView() + view.layer.borderWidth = 1 + view.layer.cornerRadius = 12 + view.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor + return view + }() let cardView = WorkerEmployCard() let applyButton: IdlePrimaryCardButton = { let btn = IdlePrimaryCardButton(level: .large) @@ -65,9 +65,7 @@ public class WorkerNativeEmployCardCell: UITableViewCell { } func setAppearance() { - contentView.layer.borderWidth = 1 - contentView.layer.cornerRadius = 12 - contentView.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor + } func setLayout() { @@ -95,9 +93,9 @@ public class WorkerNativeEmployCardCell: UITableViewCell { mainStack.bottomAnchor.constraint(equalTo: tappableArea.layoutMarginsGuide.bottomAnchor), tappableArea.topAnchor.constraint(equalTo: contentView.topAnchor), - tappableArea.leftAnchor.constraint(equalTo: contentView.leftAnchor), - tappableArea.rightAnchor.constraint(equalTo: contentView.rightAnchor), - tappableArea.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + tappableArea.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20), + tappableArea.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20), + tappableArea.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8), ]) } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/OverView/WorkConditionDisplayingView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/OverView/WorkConditionDisplayingView.swift index 861b5ff8..d95177f7 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/OverView/WorkConditionDisplayingView.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/OverView/WorkConditionDisplayingView.swift @@ -37,8 +37,9 @@ public class WorkConditionDisplayingView: HStack { public init() { super.init( - [keyStack, valueStack, Spacer()], - spacing: 32 + [keyStack, valueStack], + spacing: 32, + distribution: .fill ) setAppearance() setLayout() From 1bd5e344169f5286acb07b1fc5e5772f985a48aa Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 6 Sep 2024 11:05:41 +0900 Subject: [PATCH 03/17] =?UTF-8?q?[IDLE-000]=20=EC=B5=9C=EC=B4=88=20?= =?UTF-8?q?=EC=A0=91=EC=86=8D=EC=8B=9C=20=EB=AC=B4=EC=A1=B0=EA=B1=B4=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=EC=9D=84=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserInfo/DefaultCenterProfileUseCase.swift | 6 +++++- .../UserInfo/DefaultWorkerProfileUseCase.swift | 6 +++++- .../UseCaseInterface/UserInfo/CenterProfileUseCase.swift | 4 ++++ .../UseCaseInterface/UserInfo/WorkerProfileUseCase.swift | 2 ++ .../RecruitmentPost/CenterRecruitmentPostBoardVM.swift | 6 +++++- .../Screen/Common/InitialScreen/InitialScreenVM.swift | 8 ++++---- 6 files changed, 25 insertions(+), 7 deletions(-) diff --git a/project/Projects/Domain/ConcreteUseCase/UserInfo/DefaultCenterProfileUseCase.swift b/project/Projects/Domain/ConcreteUseCase/UserInfo/DefaultCenterProfileUseCase.swift index e7d44276..35ac8da8 100644 --- a/project/Projects/Domain/ConcreteUseCase/UserInfo/DefaultCenterProfileUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/UserInfo/DefaultCenterProfileUseCase.swift @@ -28,7 +28,11 @@ public class DefaultCenterProfileUseCase: CenterProfileUseCase { return .just(.success(cachedProfile)) } - return convert(task: userProfileRepository.getCenterProfile(mode: mode)) + return getFreshProfile(mode: mode) + } + + public func getFreshProfile(mode: Entity.ProfileMode) -> RxSwift.Single> { + convert(task: userProfileRepository.getCenterProfile(mode: mode)) } public func updateProfile(phoneNumber: String?, introduction: String?, imageInfo: ImageUploadInfo?) -> Single> { diff --git a/project/Projects/Domain/ConcreteUseCase/UserInfo/DefaultWorkerProfileUseCase.swift b/project/Projects/Domain/ConcreteUseCase/UserInfo/DefaultWorkerProfileUseCase.swift index 097b7474..7d431d1c 100644 --- a/project/Projects/Domain/ConcreteUseCase/UserInfo/DefaultWorkerProfileUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/UserInfo/DefaultWorkerProfileUseCase.swift @@ -28,7 +28,11 @@ public class DefaultWorkerProfileUseCase: WorkerProfileUseCase { return .just(.success(cachedProfile)) } - return convert(task: userProfileRepository.getWorkerProfile(mode: mode)) + return getFreshProfile(mode: mode) + } + + public func getFreshProfile(mode: Entity.ProfileMode) -> RxSwift.Single> { + convert(task: userProfileRepository.getWorkerProfile(mode: mode)) } public func updateProfile(stateObject: WorkerProfileStateObject, imageInfo: ImageUploadInfo?) -> Single> { diff --git a/project/Projects/Domain/UseCaseInterface/UserInfo/CenterProfileUseCase.swift b/project/Projects/Domain/UseCaseInterface/UserInfo/CenterProfileUseCase.swift index 42cbf4c2..1193d9bc 100644 --- a/project/Projects/Domain/UseCaseInterface/UserInfo/CenterProfileUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/UserInfo/CenterProfileUseCase.swift @@ -20,8 +20,12 @@ public protocol CenterProfileUseCase: UseCaseBase { /// 1. 나의 센터/다른 센터 프로필 정보 조회 /// 6. 특정 센터의 프로필 불러오기 + /// 캐시된 데이터가 있을 경우 해당 데이터를 가져옵니다. func getProfile(mode: ProfileMode) -> Single> + /// 캐쉬되지 않은 정보를 가져옵니다. + func getFreshProfile(mode: ProfileMode) -> Single> + /// 2. 센터 프로필 정보 업데이트(전화번호, 센터소개글) /// 3. 센터 프로필 정보 업데이트(이미지, pre-signed-url) /// 4. 센터 프로필 정보 업데이트(이미지, pre-signed-url-callback) diff --git a/project/Projects/Domain/UseCaseInterface/UserInfo/WorkerProfileUseCase.swift b/project/Projects/Domain/UseCaseInterface/UserInfo/WorkerProfileUseCase.swift index 86c4ee25..f2669f37 100644 --- a/project/Projects/Domain/UseCaseInterface/UserInfo/WorkerProfileUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/UserInfo/WorkerProfileUseCase.swift @@ -21,6 +21,8 @@ public protocol WorkerProfileUseCase: UseCaseBase { /// 5. 특정 요양보호사의 프로필 불러오기 func getProfile(mode: ProfileMode) -> Single> + func getFreshProfile(mode: ProfileMode) -> Single> + /// 2. 나의(요보) 프로필 정보 업데이트(텍스트 데이터) /// 3. 나의(요보) 프로필 정보 업데이트(이미지, pre-signed-url) diff --git a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/CenterRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/CenterRecruitmentPostBoardVM.swift index bb14b3e0..2c4cd58d 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/CenterRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/CenterRecruitmentPostBoardVM.swift @@ -146,7 +146,11 @@ public class CenterRecruitmentPostBoardVM: BaseViewModel, CenterRecruitmentPostB message: error.message ) } - .subscribe(alert) + .subscribe(onNext: { [weak self] alertVO in + guard let self else { return } + + alert.onNext(alertVO) + }) .disposed(by: disposeBag) } diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift index 4200f4db..58c1e5b4 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Common/InitialScreen/InitialScreenVM.swift @@ -73,9 +73,9 @@ public class InitialScreenVM: BaseViewModel { // 센터관리자 확인 printIfDebug("☑️ 센터관리자 정보를 확인합니다.") - // 저장된 센터정보가 없는 경우 + // 센터프로필 조회 및 refresh 확인 let requestCenterInfoResult = centerProfileUseCase - .getProfile(mode: .myProfile) + .getFreshProfile(mode: .myProfile) .asObservable() .share() let success = requestCenterInfoResult.compactMap { $0.value } @@ -118,9 +118,9 @@ public class InitialScreenVM: BaseViewModel { .disposed(by: disposeBag) } else { - // 요양보호사 확인 + // 요양보호사프로필 조회 및 refresh 확인 let requestWorkerInfoResult = workerProfileUseCase - .getProfile(mode: .myProfile) + .getFreshProfile(mode: .myProfile) .asObservable() .share() let success = requestWorkerInfoResult.compactMap { $0.value } From 96f36f4752ff43b5bd4afe0b54497b39b60e3cc2 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 6 Sep 2024 13:00:42 +0900 Subject: [PATCH 04/17] =?UTF-8?q?[IDLE-000]=20WorkerWorknetEmployCard?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WorkerNativeEmployCard.swift} | 129 +---------- .../WorkerNativeEmployCardCell.swift | 2 +- .../NativeCard/WorkerNativeEmployCardRO.swift | 128 +++++++++++ .../WorknetCard/WorkerWorknetEmployCard.swift | 212 ++++++++++++++++++ .../WorkerWorknetEmployCardRO.swift | 37 +++ 5 files changed, 384 insertions(+), 124 deletions(-) rename project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/{WorkerEmployCard.swift => NativeCard/WorkerNativeEmployCard.swift} (65%) rename project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/{ => NativeCard}/WorkerNativeEmployCardCell.swift (99%) create mode 100644 project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardRO.swift create mode 100644 project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift create mode 100644 project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardRO.swift diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCard.swift similarity index 65% rename from project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift rename to project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCard.swift index a55d154a..fdad1bd7 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCard.swift @@ -1,8 +1,8 @@ // -// WorkerNativeEmployCardRO.swift +// WorkerNativeEmployCard.swift // DSKit // -// Created by choijunios on 7/19/24. +// Created by choijunios on 9/6/24. // import UIKit @@ -10,125 +10,7 @@ import RxSwift import RxCocoa import Entity -public class WorkerNativeEmployCardRO { - - let showBiginnerTag: Bool - let titleText: String - let timeDurationForWalkingText: String - let targetInfoText: String - let workDaysText: String - let workTimeText: String - let payText: String - let isFavorite: Bool - - init( - showBiginnerTag: Bool, - titleText: String, - timeDurationForWalkingText: String, - targetInfoText: String, - workDaysText: String, - workTimeText: String, - payText: String, - isFavorite: Bool - ) { - self.showBiginnerTag = showBiginnerTag - self.titleText = titleText - self.timeDurationForWalkingText = timeDurationForWalkingText - self.targetInfoText = targetInfoText - self.workDaysText = workDaysText - self.workTimeText = workTimeText - self.payText = payText - self.isFavorite = isFavorite - } - - public static func create(vo: WorkerNativeEmployCardVO) -> WorkerNativeEmployCardRO { - -// var dayLeftTagText: String? = nil -// var showDayLeftTag: Bool = false -// -// if (0...14).contains(vo.dayLeft) { -// showDayLeftTag = true -// dayLeftTagText = vo.dayLeft == 0 ? "D-Day" : "D-\(vo.dayLeft)" -// } - - let targetInfoText = "\(vo.careGrade.textForCellBtn)등급 \(vo.targetAge)세 \(vo.targetGender.twoLetterKoreanWord)" - - let workDaysText = vo.days.sorted(by: { d1, d2 in - d1.rawValue < d2.rawValue - }).map({ $0.korOneLetterText }).joined(separator: ",") - - let workTimeText = "\(vo.startTime) - \(vo.endTime)" - - var formedPayAmountText = "" - for (index, char) in vo.paymentAmount.reversed().enumerated() { - if (index % 3) == 0, index != 0 { - formedPayAmountText = "," + formedPayAmountText - } - formedPayAmountText = String(char) + formedPayAmountText - } - - let payText = "\(vo.paymentType.korLetterText) \(formedPayAmountText) 원" - - var splittedAddress = vo.title.split(separator: " ") - - if splittedAddress.count >= 3 { - splittedAddress = Array(splittedAddress[0..<3]) - } - let addressTitle = splittedAddress.joined(separator: " ") - - // distance는 미터단위입니다. - let durationText = Self.timeForDistance(meter: vo.distanceFromWorkPlace) - - return .init( - showBiginnerTag: vo.isBeginnerPossible, - titleText: addressTitle, - timeDurationForWalkingText: durationText, - targetInfoText: targetInfoText, - workDaysText: workDaysText, - workTimeText: workTimeText, - payText: payText, - isFavorite: vo.isFavorite - ) - } - - public static let `mock`: WorkerNativeEmployCardRO = .init( - showBiginnerTag: true, - titleText: "사울시 강남동", - timeDurationForWalkingText: "도보 15분 ~ 20분", - targetInfoText: "1등급 54세 여성", - workDaysText: "", - workTimeText: "월, 화, 수", - payText: "시급 5000원", - isFavorite: true - ) - - static func timeForDistance(meter: Int) -> String { - switch meter { - case 0..<200: - return "도보 5분 이내" - case 200..<400: - return "도보 5 ~ 10분" - case 400..<700: - return "도보 10 ~ 15분" - case 700..<1000: - return "도보 15 ~ 20분" - case 1000..<1250: - return "도보 20 ~ 25분" - case 1250..<1500: - return "도보 25 ~ 30분" - case 1500..<1750: - return "도보 30 ~ 35분" - case 1750..<2000: - return "도보 35 ~ 40분" - default: - return "도보 40분 ~" - } - } - -} - - -public class WorkerEmployCard: UIView { +public class WorkerNativeEmployCard: UIView { // View public let starButton: IconWithColorStateButton = { @@ -195,7 +77,9 @@ public class WorkerEmployCard: UIView { public required init?(coder: NSCoder) { fatalError() } - func setAppearance() { } + func setAppearance() { + self.backgroundColor = DSColor.gray0.color + } func setLayout() { @@ -308,7 +192,6 @@ public class WorkerEmployCard: UIView { alignment: .leading ) ] - let mainStack = VStack( stackList, diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerNativeEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardCell.swift similarity index 99% rename from project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerNativeEmployCardCell.swift rename to project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardCell.swift index 9d2a86d3..214dba3d 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerNativeEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardCell.swift @@ -43,7 +43,7 @@ public class WorkerNativeEmployCardCell: UITableViewCell { view.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor return view }() - let cardView = WorkerEmployCard() + let cardView = WorkerNativeEmployCard() let applyButton: IdlePrimaryCardButton = { let btn = IdlePrimaryCardButton(level: .large) btn.label.textString = "지원하기" diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardRO.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardRO.swift new file mode 100644 index 00000000..a0f6bad9 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardRO.swift @@ -0,0 +1,128 @@ +// +// WorkerNativeEmployCardRO.swift +// DSKit +// +// Created by choijunios on 7/19/24. +// + +import UIKit +import RxSwift +import RxCocoa +import Entity + +// MARK: Render object +public class WorkerNativeEmployCardRO { + + let showBiginnerTag: Bool + let titleText: String + let timeDurationForWalkingText: String + let targetInfoText: String + let workDaysText: String + let workTimeText: String + let payText: String + let isFavorite: Bool + + init( + showBiginnerTag: Bool, + titleText: String, + timeDurationForWalkingText: String, + targetInfoText: String, + workDaysText: String, + workTimeText: String, + payText: String, + isFavorite: Bool + ) { + self.showBiginnerTag = showBiginnerTag + self.titleText = titleText + self.timeDurationForWalkingText = timeDurationForWalkingText + self.targetInfoText = targetInfoText + self.workDaysText = workDaysText + self.workTimeText = workTimeText + self.payText = payText + self.isFavorite = isFavorite + } + + public static func create(vo: WorkerNativeEmployCardVO) -> WorkerNativeEmployCardRO { + +// var dayLeftTagText: String? = nil +// var showDayLeftTag: Bool = false +// +// if (0...14).contains(vo.dayLeft) { +// showDayLeftTag = true +// dayLeftTagText = vo.dayLeft == 0 ? "D-Day" : "D-\(vo.dayLeft)" +// } + + let targetInfoText = "\(vo.careGrade.textForCellBtn)등급 \(vo.targetAge)세 \(vo.targetGender.twoLetterKoreanWord)" + + let workDaysText = vo.days.sorted(by: { d1, d2 in + d1.rawValue < d2.rawValue + }).map({ $0.korOneLetterText }).joined(separator: ",") + + let workTimeText = "\(vo.startTime) - \(vo.endTime)" + + var formedPayAmountText = "" + for (index, char) in vo.paymentAmount.reversed().enumerated() { + if (index % 3) == 0, index != 0 { + formedPayAmountText = "," + formedPayAmountText + } + formedPayAmountText = String(char) + formedPayAmountText + } + + let payText = "\(vo.paymentType.korLetterText) \(formedPayAmountText) 원" + + var splittedAddress = vo.title.split(separator: " ") + + if splittedAddress.count >= 3 { + splittedAddress = Array(splittedAddress[0..<3]) + } + let addressTitle = splittedAddress.joined(separator: " ") + + // distance는 미터단위입니다. + let durationText = Self.timeForDistance(meter: vo.distanceFromWorkPlace) + + return .init( + showBiginnerTag: vo.isBeginnerPossible, + titleText: addressTitle, + timeDurationForWalkingText: durationText, + targetInfoText: targetInfoText, + workDaysText: workDaysText, + workTimeText: workTimeText, + payText: payText, + isFavorite: vo.isFavorite + ) + } + + public static let `mock`: WorkerNativeEmployCardRO = .init( + showBiginnerTag: true, + titleText: "사울시 강남동", + timeDurationForWalkingText: "도보 15분 ~ 20분", + targetInfoText: "1등급 54세 여성", + workDaysText: "", + workTimeText: "월, 화, 수", + payText: "시급 5000원", + isFavorite: true + ) + + static func timeForDistance(meter: Int) -> String { + switch meter { + case 0..<200: + return "도보 5분 이내" + case 200..<400: + return "도보 5 ~ 10분" + case 400..<700: + return "도보 10 ~ 15분" + case 700..<1000: + return "도보 15 ~ 20분" + case 1000..<1250: + return "도보 20 ~ 25분" + case 1250..<1500: + return "도보 25 ~ 30분" + case 1500..<1750: + return "도보 30 ~ 35분" + case 1750..<2000: + return "도보 35 ~ 40분" + default: + return "도보 40분 ~" + } + } +} diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift new file mode 100644 index 00000000..ada24d38 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift @@ -0,0 +1,212 @@ +// +// WorkerWorknetEmployCard.swift +// DSKit +// +// Created by choijunios on 9/6/24. +// + +import UIKit +import RxSwift +import RxCocoa +import Entity + +public class WorkerWorknetEmployCard: VStack { + + // View + public let starButton: IconWithColorStateButton = { + let button = IconWithColorStateButton( + representImage: DSKitAsset.Icons.subscribeStar.image, + normalColor: DSKitAsset.Colors.gray200.color, + accentColor: DSKitAsset.Colors.orange300.color + ) + return button + }() + + + // tags + let worknetTag: TagLabel = { + let tag = TagLabel( + text: "워크넷", + typography: .caption, + textColor: hexStringToUIColor(hex: "#2B8BDC"), + backgroundColor: hexStringToUIColor(hex: "#D3EBFF") + ) + return tag + }() + let beginnerTag: TagLabel = { + let tag = TagLabel( + text: "초보가능", + typography: .caption, + textColor: DSKitAsset.Colors.orange500.color, + backgroundColor: DSKitAsset.Colors.orange100.color + ) + return tag + }() + let daysUntilDeadlineTag: TagLabel = { + let tag = TagLabel( + text: "", + typography: .caption, + textColor: DSKitAsset.Colors.orange500.color, + backgroundColor: DSKitAsset.Colors.orange100.color + ) + return tag + }() + + + let titleLabel: IdleLabel = { + let label = IdleLabel(typography: .Subtitle2) + return label + }() + + + let estimatedArrivalTimeLabel: IdleLabel = { + let label = IdleLabel(typography: .Body3) + label.attrTextColor = DSKitAsset.Colors.gray500.color + return label + }() + + + let workTimeLabel: IdleLabel = { + let label = IdleLabel(typography: .Body3) + label.attrTextColor = DSKitAsset.Colors.gray500.color + return label + }() + + + let payLabel: IdleLabel = { + let label = IdleLabel(typography: .Body3) + label.attrTextColor = DSKitAsset.Colors.gray500.color + return label + }() + + public init() { + super.init([], alignment: .fill) + setAppearance() + setLayout() + } + public required init(coder: NSCoder) { fatalError() } + + func setAppearance() { + self.backgroundColor = DSColor.gray0.color + } + + func setLayout() { + + // MARK: Tag & Star + let tagStack = HStack( + [ + worknetTag, + beginnerTag, + daysUntilDeadlineTag + ], + spacing: 4 + ) + + let tagStarStack = HStack( + [ + tagStack, + Spacer(), + starButton + ], + alignment: .center, + distribution: .fill + ) + + // MARK: work days | work time + let timeImage = DSKitAsset.Icons.time.image.toView() + let workTimeStack = HStack( + [ + timeImage, + workTimeLabel + ], + alignment: .center, + distribution: .fill + ) + + // MARK: pay + let payImage = DSKitAsset.Icons.money.image.toView() + let paymentStack = HStack( + [ + payImage, + payLabel + ], + alignment: .center, + distribution: .fill + ) + + NSLayoutConstraint.activate([ + timeImage.widthAnchor.constraint(equalToConstant: 24), + payImage.widthAnchor.constraint(equalToConstant: 24), + ]) + + let viewList = [ + tagStarStack, + Spacer(height: 8), + titleLabel, + Spacer(height: 8), + VStack([ + estimatedArrivalTimeLabel, + Spacer(height: 4), + workTimeStack, + Spacer(height: 2), + paymentStack + ], alignment: .leading) + ] + + viewList.forEach { + self.addArrangedSubview($0) + } + } + + func applyRO(ro: WorkerWorknetEmployCardRO) { + + beginnerTag.isHidden = !ro.showBeginnerTag + daysUntilDeadlineTag.textString = ro.leftDayUnitlDeadlineText + starButton.setState(ro.isStarred ? .accent : .normal) + titleLabel.textString = ro.titleText + estimatedArrivalTimeLabel.textString = ro.timeDurationForWalkingText + workTimeLabel.textString = ro.workTimeInfoText + payLabel.textString = ro.paymentInfoText + } +} + +func hexStringToUIColor(hex: String) -> UIColor { + var cString: String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + + if cString.hasPrefix("#") { + cString.remove(at: cString.startIndex) + } + + if cString.count != 6 { + return UIColor.gray // 기본 색상 + } + + var rgbValue: UInt64 = 0 + Scanner(string: cString).scanHexInt64(&rgbValue) + + return UIColor( + red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, + green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, + blue: CGFloat(rgbValue & 0x0000FF) / 255.0, + alpha: 1.0 + ) +} + +@available(iOS 17.0, *) +#Preview("Preview", traits: .defaultLayout) { + let cardView = WorkerWorknetEmployCard() + + cardView.applyRO( + ro: .init( + showBeginnerTag: true, + leftDayUnitlDeadlineText: "D-10", + titleText: "[수원 매탄동] 방문요양 주 3회 (4등급 여자 어르신)", + timeDurationForWalkingText: "도보 15분~20분", + workTimeInfoText: "주 6일 근무 | (오전) 1시 00분 ~ (오후) 4시 00분 ", + paymentInfoText: "시급 9,860원 이상", + isStarred: true + ) + ) + + return cardView +} diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardRO.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardRO.swift new file mode 100644 index 00000000..801fbcdf --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardRO.swift @@ -0,0 +1,37 @@ +// +// WorkerWorknetEmployCardRO.swift +// DSKit +// +// Created by choijunios on 9/6/24. +// + +import Foundation + +public struct WorkerWorknetEmployCardRO { + + public let showBeginnerTag: Bool + public let leftDayUnitlDeadlineText: String + public let titleText: String + public let timeDurationForWalkingText: String + public let workTimeInfoText: String + public let paymentInfoText: String + public let isStarred: Bool + + public init( + showBeginnerTag: Bool, + leftDayUnitlDeadlineText: String, + titleText: String, + timeDurationForWalkingText: String, + workTimeInfoText: String, + paymentInfoText: String, + isStarred: Bool + ) { + self.showBeginnerTag = showBeginnerTag + self.leftDayUnitlDeadlineText = leftDayUnitlDeadlineText + self.titleText = titleText + self.timeDurationForWalkingText = timeDurationForWalkingText + self.workTimeInfoText = workTimeInfoText + self.paymentInfoText = paymentInfoText + self.isStarred = isStarred + } +} From de66ea7372a2a12daf5194144a467eaabf0eecb3 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 6 Sep 2024 13:17:44 +0900 Subject: [PATCH 05/17] =?UTF-8?q?[IDLE-000]=20WorkerWorknetEmployCardCell?= =?UTF-8?q?=EC=9D=BC=EB=B6=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WorkerNativeEmployCardCell.swift | 22 ++-- .../WorknetCard/WorkerWorknetEmployCard.swift | 4 +- .../WorkerWorknetEmployCardCell.swift | 113 ++++++++++++++++++ 3 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardCell.swift index 214dba3d..9482331d 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardCell.swift @@ -15,10 +15,7 @@ public enum PostAppliedState { case notApplied } -public protocol WorkerNativeEmployCardViewModelable: AnyObject { - - /// '지원하기' 버튼이 눌렸을 때, 공고 id를 전달합니다. - var applyButtonClicked: PublishRelay<(postId: String, postTitle: String)> { get } +public protocol WorkerEmployCardViewModelable: AnyObject { /// 공고상세보기 func showPostDetail(id: String) @@ -27,12 +24,15 @@ public protocol WorkerNativeEmployCardViewModelable: AnyObject { func setPostFavoriteState(isFavoriteRequest: Bool, postId: String, postType: RecruitmentPostType) -> Single } +public protocol AppliableWorkerEmployCardVMable: WorkerEmployCardViewModelable { + /// '지원하기' 버튼이 눌렸을 때, 공고 id를 전달합니다. + var applyButtonClicked: PublishRelay<(postId: String, postTitle: String)> { get } +} + public class WorkerNativeEmployCardCell: UITableViewCell { public static let identifier = String(describing: WorkerNativeEmployCardCell.self) - - var viewModel: WorkerNativeEmployCardViewModelable? private var disposables: [Disposable?]? // View @@ -58,14 +58,12 @@ public class WorkerNativeEmployCardCell: UITableViewCell { public required init?(coder: NSCoder) { fatalError() } public override func prepareForReuse() { - viewModel = nil - disposables?.forEach { $0?.dispose() } disposables = nil } func setAppearance() { - + contentView.backgroundColor = .clear } func setLayout() { @@ -99,7 +97,9 @@ public class WorkerNativeEmployCardCell: UITableViewCell { ]) } - public func bind(postId: String, vo: WorkerNativeEmployCardVO, viewModel: WorkerNativeEmployCardViewModelable) { + public func bind(postId: String, vo: WorkerNativeEmployCardVO, viewModel: WorkerEmployCardViewModelable) { + + guard let appliableVM = viewModel as? AppliableWorkerEmployCardVMable else { return } // 지원 여부 if let appliedDate = vo.applyDate { @@ -142,7 +142,7 @@ public class WorkerNativeEmployCardCell: UITableViewCell { applyButton.rx.tap .map({ _ in (postId, vo.title) }) - .bind(to: viewModel.applyButtonClicked), + .bind(to: appliableVM.applyButtonClicked), favoriteRequestResult .subscribe(onNext: { [starButton] isSuccess in diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift index ada24d38..6bc3150f 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift @@ -97,7 +97,7 @@ public class WorkerWorknetEmployCard: VStack { [ worknetTag, beginnerTag, - daysUntilDeadlineTag + daysUntilDeadlineTag, ], spacing: 4 ) @@ -137,6 +137,8 @@ public class WorkerWorknetEmployCard: VStack { NSLayoutConstraint.activate([ timeImage.widthAnchor.constraint(equalToConstant: 24), payImage.widthAnchor.constraint(equalToConstant: 24), + starButton.widthAnchor.constraint(equalToConstant: 24), + starButton.heightAnchor.constraint(equalTo: starButton.widthAnchor), ]) let viewList = [ diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift new file mode 100644 index 00000000..04bcd1ac --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift @@ -0,0 +1,113 @@ +// +// WorkerWorknetEmployCardCell.swift +// DSKit +// +// Created by choijunios on 9/6/24. +// + +import UIKit +import RxSwift +import RxCocoa +import Entity + +public class WorkerWorknetEmployCardCell: UITableViewCell { + + public static let identifier = String(describing: WorkerNativeEmployCardCell.self) + + private var disposables: [Disposable?]? + + let tappableArea: TappableUIView = { + let view = TappableUIView() + view.layer.borderWidth = 1 + view.layer.cornerRadius = 12 + view.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor + return view + }() + let cardView = WorkerWorknetEmployCard() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setAppearance() + setLayout() + } + public required init?(coder: NSCoder) { fatalError() } + + public override func prepareForReuse() { + disposables?.forEach { $0?.dispose() } + disposables = nil + } + + func setAppearance() { + contentView.backgroundColor = .clear + } + + func setLayout() { + + tappableArea.layoutMargins = .init(top: 16, left: 16, bottom: 16, right: 16) + + cardView.translatesAutoresizingMaskIntoConstraints = false + tappableArea.addSubview(cardView) + + + tappableArea.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(tappableArea) + + + NSLayoutConstraint.activate([ + cardView.topAnchor.constraint(equalTo: tappableArea.layoutMarginsGuide.topAnchor), + cardView.leftAnchor.constraint(equalTo: tappableArea.layoutMarginsGuide.leftAnchor), + cardView.rightAnchor.constraint(equalTo: tappableArea.layoutMarginsGuide.rightAnchor), + cardView.bottomAnchor.constraint(equalTo: tappableArea.layoutMarginsGuide.bottomAnchor), + + tappableArea.topAnchor.constraint(equalTo: contentView.topAnchor), + tappableArea.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20), + tappableArea.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20), + tappableArea.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8), + ]) + } + + public func bind(postId: String, viewModel: WorkerEmployCardViewModelable) { + + // 카드 컨텐츠 바인딩 + + + let starButton = cardView.starButton + + let favoriteRequestResult = starButton + .onTapEvent + .map { state in + // normal인 경우 true / 즐겨찾기 요청 + state == .normal + } + .flatMap { [viewModel] isFavoriteRequest in + viewModel.setPostFavoriteState( + isFavoriteRequest: isFavoriteRequest, + postId: postId, + postType: .native + ) + } + + // input + let disposables: [Disposable?] = [ + + // Input + tappableArea + .rx.tap + .subscribe(onNext: { [weak viewModel] _ in + viewModel?.showPostDetail(id: postId) + }), + + favoriteRequestResult + .subscribe(onNext: { [starButton] isSuccess in + + if isSuccess { + + // 성공시 상태변경 + starButton.toggle() + } + }) + ] + + self.disposables = disposables + } +} From 06495d76970cea70abcd0324ddfd0d1cccdaeb51 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 6 Sep 2024 13:26:37 +0900 Subject: [PATCH 06/17] =?UTF-8?q?[IDLE-000]=20CrawlingPostAPI=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/DataSource/API/BaseAPI.swift | 3 + .../Data/DataSource/API/CrawlingPostAPI.swift | 73 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 project/Projects/Data/DataSource/API/CrawlingPostAPI.swift diff --git a/project/Projects/Data/DataSource/API/BaseAPI.swift b/project/Projects/Data/DataSource/API/BaseAPI.swift index a3d2fe31..6e0e2cb7 100644 --- a/project/Projects/Data/DataSource/API/BaseAPI.swift +++ b/project/Projects/Data/DataSource/API/BaseAPI.swift @@ -13,6 +13,7 @@ public enum APIType { case auth case users case job_postings + case crawling_job_postings case external(url: String) case applys } @@ -36,6 +37,8 @@ public extension BaseAPI { baseStr += "/users" case .job_postings: baseStr += "/job-postings" + case .crawling_job_postings: + baseStr += "/crawling-job-postings" case .applys: baseStr += "/applys" case .external(let url): diff --git a/project/Projects/Data/DataSource/API/CrawlingPostAPI.swift b/project/Projects/Data/DataSource/API/CrawlingPostAPI.swift new file mode 100644 index 00000000..04ae6818 --- /dev/null +++ b/project/Projects/Data/DataSource/API/CrawlingPostAPI.swift @@ -0,0 +1,73 @@ +// +// CrawlingPostAPI.swift +// DataSource +// +// Created by choijunios on 9/6/24. +// + +import Foundation +import Alamofire +import Moya + +public enum CrawlingPostAPI { + + case getPostList(nextPageId: String?, requestCnt: String) + case getDetail(postId: String) +} + +extension CrawlingPostAPI: BaseAPI { + public var apiType: APIType { + .crawling_job_postings + } + + public var path: String { + switch self { + case .getPostList(let nextPageId, let requestCnt): + "" + case .getDetail(let postId): + "/\(postId)" + } + } + + public var method: Moya.Method { + switch self { + case .getPostList(let nextPageId, let requestCnt): + .get + case .getDetail(let postId): + .get + } + } + + var bodyParameters: Parameters? { + var params: Parameters = [:] + switch self { + case .getPostList(let nextPageId, let requestCnt): + if let nextPageId { + params["next"] = nextPageId + } + params["limit"] = requestCnt + default: + break + } + + return params + } + + var parameterEncoding: ParameterEncoding { + switch self { + case .getPostList: + return URLEncoding.queryString + default: + return JSONEncoding.default + } + } + + public var task: Moya.Task { + switch self { + case .getPostList: + .requestParameters(parameters: bodyParameters ?? [:], encoding: parameterEncoding) + default: + .requestPlain + } + } +} From 51b523632429eb33101c78e19da2c1fdb886ea8c Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 6 Sep 2024 20:03:57 +0900 Subject: [PATCH 07/17] =?UTF-8?q?[IDLE-000]=20WorkerWorknetEmployCardCell?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EB=B0=8F=20=EC=A0=81=EC=9A=A9(=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A0=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefaultRecruitmentPostRepository.swift | 35 ++++++++++--- .../Data/DataSource/API/CrawlingPostAPI.swift | 2 +- .../RecuritmentPostListForWorkerDTO.swift | 52 +++++++++++++++++-- .../Data/DataSource/Service/asd.swift | 17 ++++++ .../State/Util/RecruitmentPostType.swift | 6 +-- .../NativeRecruitmentPostForWorkerVO.swift} | 43 +++++++++------ .../RecruitmentPostListForWorkerVO.swift | 25 +++++++++ .../WorknetRecruitmentPostForWorkerVO.swift | 44 ++++++++++++++++ .../RecruitmentPostRepository.swift | 7 +++ .../WorkerWorknetEmployCardCell.swift | 4 +- .../WorkerWorknetEmployCardRO.swift | 47 +++++++++++++++++ .../WorkerRecruitmentPostBoardVC.swift | 52 +++++++++++++++---- .../WorkerRecruitmentPostBoardVM.swift | 24 +++------ 13 files changed, 298 insertions(+), 60 deletions(-) create mode 100644 project/Projects/Data/DataSource/Service/asd.swift rename project/Projects/Domain/Entity/VO/Post/{RecruitmentPostListForWorkerVO.swift => PostList/NativeRecruitmentPostForWorkerVO.swift} (69%) create mode 100644 project/Projects/Domain/Entity/VO/Post/PostList/RecruitmentPostListForWorkerVO.swift create mode 100644 project/Projects/Domain/Entity/VO/Post/PostList/WorknetRecruitmentPostForWorkerVO.swift diff --git a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift index 434021fe..dc2910bf 100644 --- a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift +++ b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift @@ -15,11 +15,13 @@ import Moya public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { private var recruitmentPostService: RecruitmentPostService = .init() + private var crawlingPostService: CrawlingPostService = .init() private var applyService: ApplyService = .init() public init(_ store: KeyValueStore? = nil) { if let store { self.recruitmentPostService = RecruitmentPostService(keyValueStore: store) + self.crawlingPostService = CrawlingPostService(keyValueStore: store) self.applyService = ApplyService(keyValueStore: store) } } @@ -102,13 +104,13 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { } } - public func getNativePostListForWorker(nextPageId: String?, requestCnt: Int = 10) -> RxSwift.Single { + public func getNativePostListForWorker(nextPageId: String?, requestCnt: Int = 10) -> RxSwift.Single { recruitmentPostService.request( api: .getOnGoingNativePostListForWorker(nextPageId: nextPageId, requestCnt: String(requestCnt)), with: .withToken ) - .map(RecruitmentPostListForWorkerDTO.self) + .map(RecruitmentPostListForWorkerDTO.self) .catch({ error in if let moyaError = error as? MoyaError, case .objectMapping(let error, _) = moyaError { #if DEBUG @@ -122,12 +124,12 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { } } - public func getFavoritePostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single { + public func getFavoritePostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single { recruitmentPostService.request( api: .getFavoritePostListForWorker(nextPageId: nextPageId, requestCnt: String(requestCnt)), with: .withToken ) - .map(RecruitmentPostListForWorkerDTO.self) + .map(RecruitmentPostListForWorkerDTO.self) .catch({ error in if let moyaError = error as? MoyaError, case .objectMapping(let error, _) = moyaError { #if DEBUG @@ -141,12 +143,12 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { } } - public func getAppliedPostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single { + public func getAppliedPostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single { recruitmentPostService.request( api: .getAppliedPostListForWorker(nextPageId: nextPageId, requestCnt: String(requestCnt)), with: .withToken ) - .map(RecruitmentPostListForWorkerDTO.self) + .map(RecruitmentPostListForWorkerDTO.self) .catch({ error in if let moyaError = error as? MoyaError, case .objectMapping(let error, _) = moyaError { #if DEBUG @@ -160,6 +162,27 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { } } + public func getWorknetPostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single { + crawlingPostService + .request( + api: .getPostList(nextPageId: nextPageId, requestCnt: 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 applyToPost(postId: String, method: ApplyType) -> Single { applyService .request( diff --git a/project/Projects/Data/DataSource/API/CrawlingPostAPI.swift b/project/Projects/Data/DataSource/API/CrawlingPostAPI.swift index 04ae6818..0dc267c4 100644 --- a/project/Projects/Data/DataSource/API/CrawlingPostAPI.swift +++ b/project/Projects/Data/DataSource/API/CrawlingPostAPI.swift @@ -11,7 +11,7 @@ import Moya public enum CrawlingPostAPI { - case getPostList(nextPageId: String?, requestCnt: String) + case getPostList(nextPageId: String?, requestCnt: Int) case getDetail(postId: String) } diff --git a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift index 87211adf..ed2c3de5 100644 --- a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift +++ b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift @@ -8,9 +8,14 @@ import Foundation import Entity -public struct RecruitmentPostListForWorkerDTO: Codable { +public protocol EntityRepresentable: Codable { + associatedtype Entity + func toEntity() -> Entity +} + +public struct RecruitmentPostListForWorkerDTO: Codable where T.Entity: RecruitmentPostForWorkerRepresentable { - public let items: [RecruitmentPostForWorkerDTO] + public let items: [T] public let next: String? public let total: Int @@ -24,7 +29,43 @@ public struct RecruitmentPostListForWorkerDTO: Codable { } } -public struct RecruitmentPostForWorkerDTO: Codable { +// MARK: Worknet post의 카드 정보 +public struct WorkNetRecruitmentPostForWorkerDTO: EntityRepresentable { + + public let id: String + public let title: String + public let distance: Int + public let workingTime: String + public let workingSchedule: String + public let payInfo: String + public let applyDeadline: String + public let isFavorite: Bool + public let jobPostingType: RecruitmentPostType + + public func toEntity() -> WorknetRecruitmentPostVO { + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + + let deadlineDate = dateFormatter.date(from: self.applyDeadline)! + + return .init( + id: id, + title: title, + distance: distance, + workingTime: workingTime, + workingSchedule: workingSchedule, + payInfo: payInfo, + applyDeadline: deadlineDate, + isFavorite: isFavorite, + postType: jobPostingType + ) + } +} + +// MARK: Native Post의 카드 정보입니다. +public struct NativeRecruitmentPostForWorkerDTO: EntityRepresentable { + public let isExperiencePreferred: Bool public let id: String public let weekdays: [String] @@ -42,6 +83,7 @@ public struct RecruitmentPostForWorkerDTO: Codable { public let distance: Int public let applyTime: String? public let isFavorite: Bool + public let jobPostingType: RecruitmentPostType public func toEntity() -> NativeRecruitmentPostForWorkerVO { @@ -72,7 +114,9 @@ public struct RecruitmentPostForWorkerDTO: Codable { payAmount: String(payAmount), distanceFromWorkPlace: distance, applyTime: applyDate, - isFavorite: isFavorite + isFavorite: isFavorite, + postType: jobPostingType ) } } + diff --git a/project/Projects/Data/DataSource/Service/asd.swift b/project/Projects/Data/DataSource/Service/asd.swift new file mode 100644 index 00000000..325b7f12 --- /dev/null +++ b/project/Projects/Data/DataSource/Service/asd.swift @@ -0,0 +1,17 @@ +// +// CrawlingPostService.swift +// DataSource +// +// Created by choijunios on 9/6/24. +// + +import Foundation + +public class CrawlingPostService: BaseNetworkService { + + public init() { } + + public override init(keyValueStore: KeyValueStore) { + super.init(keyValueStore: keyValueStore) + } +} diff --git a/project/Projects/Domain/Entity/State/Util/RecruitmentPostType.swift b/project/Projects/Domain/Entity/State/Util/RecruitmentPostType.swift index ae4a5737..aedecc6d 100644 --- a/project/Projects/Domain/Entity/State/Util/RecruitmentPostType.swift +++ b/project/Projects/Domain/Entity/State/Util/RecruitmentPostType.swift @@ -7,9 +7,9 @@ import Foundation -public enum RecruitmentPostType { - case native - case workNet +public enum RecruitmentPostType: String, Codable { + case native="CAREMEET" + case workNet="WORKNET" public var upscaleEngWord: String { switch self { diff --git a/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift b/project/Projects/Domain/Entity/VO/Post/PostList/NativeRecruitmentPostForWorkerVO.swift similarity index 69% rename from project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift rename to project/Projects/Domain/Entity/VO/Post/PostList/NativeRecruitmentPostForWorkerVO.swift index 9d366113..513d3cc0 100644 --- a/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift +++ b/project/Projects/Domain/Entity/VO/Post/PostList/NativeRecruitmentPostForWorkerVO.swift @@ -1,5 +1,5 @@ // -// RecruitmentPostListForWorkerVO.swift +// NativeRecruitmentPostForWorkerVO.swift // Entity // // Created by choijunios on 8/16/24. @@ -7,20 +7,10 @@ import Foundation -public struct RecruitmentPostListForWorkerVO { - - public let posts: [NativeRecruitmentPostForWorkerVO] - public let nextPageId: String? - public let fetchedPostCount: Int +public struct NativeRecruitmentPostForWorkerVO: RecruitmentPostForWorkerRepresentable { + + public var postType: RecruitmentPostType - public init(posts: [NativeRecruitmentPostForWorkerVO], nextPageId: String?, fetchedPostCount: Int) { - self.posts = posts - self.nextPageId = nextPageId - self.fetchedPostCount = fetchedPostCount - } -} - -public struct NativeRecruitmentPostForWorkerVO { public let postId: String public let workDays: [WorkDay] @@ -44,7 +34,26 @@ public struct NativeRecruitmentPostForWorkerVO { 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, applyTime: Date?, 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, + applyTime: Date?, + isFavorite: Bool, + postType: RecruitmentPostType + ) { self.postId = postId self.workDays = workDays self.startTime = startTime @@ -62,6 +71,7 @@ public struct NativeRecruitmentPostForWorkerVO { self.distanceFromWorkPlace = distanceFromWorkPlace self.applyTime = applyTime self.isFavorite = isFavorite + self.postType = postType } public static let mock = NativeRecruitmentPostForWorkerVO( @@ -81,6 +91,7 @@ public struct NativeRecruitmentPostForWorkerVO { payAmount: "15000", distanceFromWorkPlace: 2500, applyTime: Date(), - isFavorite: true + isFavorite: true, + postType: .native ) } diff --git a/project/Projects/Domain/Entity/VO/Post/PostList/RecruitmentPostListForWorkerVO.swift b/project/Projects/Domain/Entity/VO/Post/PostList/RecruitmentPostListForWorkerVO.swift new file mode 100644 index 00000000..f02632c1 --- /dev/null +++ b/project/Projects/Domain/Entity/VO/Post/PostList/RecruitmentPostListForWorkerVO.swift @@ -0,0 +1,25 @@ +// +// RecruitmentPostListForWorkerVO.swift +// Entity +// +// Created by choijunios on 9/6/24. +// + +import Foundation + +public struct RecruitmentPostListForWorkerVO { + + public let posts: [RecruitmentPostForWorkerRepresentable] + public let nextPageId: String? + public let fetchedPostCount: Int + + public init(posts: [RecruitmentPostForWorkerRepresentable], nextPageId: String?, fetchedPostCount: Int) { + self.posts = posts + self.nextPageId = nextPageId + self.fetchedPostCount = fetchedPostCount + } +} + +public protocol RecruitmentPostForWorkerRepresentable { + var postType: RecruitmentPostType { get } +} diff --git a/project/Projects/Domain/Entity/VO/Post/PostList/WorknetRecruitmentPostForWorkerVO.swift b/project/Projects/Domain/Entity/VO/Post/PostList/WorknetRecruitmentPostForWorkerVO.swift new file mode 100644 index 00000000..fd6c0562 --- /dev/null +++ b/project/Projects/Domain/Entity/VO/Post/PostList/WorknetRecruitmentPostForWorkerVO.swift @@ -0,0 +1,44 @@ +// +// WorknetRecruitmentPostVO.swift +// Entity +// +// Created by choijunios on 9/6/24. +// + +import Foundation + +public struct WorknetRecruitmentPostVO: RecruitmentPostForWorkerRepresentable { + + public let postType: RecruitmentPostType + + public let id: String + public let title: String + public let distance: Int + public let workingTime: String + public let workingSchedule: String + public let payInfo: String + public let applyDeadline: Date + public let isFavorite: Bool + + public init( + id: String, + title: String, + distance: Int, + workingTime: String, + workingSchedule: String, + payInfo: String, + applyDeadline: Date, + isFavorite: Bool, + postType: RecruitmentPostType + ) { + self.id = id + self.title = title + self.distance = distance + self.workingTime = workingTime + self.workingSchedule = workingSchedule + self.payInfo = payInfo + self.applyDeadline = applyDeadline + self.isFavorite = isFavorite + self.postType = postType + } +} diff --git a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift index e45eb8a9..748f972b 100644 --- a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift +++ b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift @@ -44,6 +44,8 @@ public protocol RecruitmentPostRepository: RepositoryBase { /// 요양보호사 공고의 상세정보를 조회합니다. func getPostDetailForWorker(id: String) -> Single + // MARK: Native post + /// 요양보호사가 확인하는 케어밋 자체 공고정보를 가져옵니다. func getNativePostListForWorker(nextPageId: String?, requestCnt: Int) -> Single @@ -53,6 +55,11 @@ public protocol RecruitmentPostRepository: RepositoryBase { /// 요양보호사가 확인하는 케어밋 자체 공고정보를 가져옵니다. func getAppliedPostListForWorker(nextPageId: String?, requestCnt: Int) -> Single + // MARK: Worknet Post + + /// 요양보호사가 확인하는 워크넷 공고정보를 가져옵니다. + func getWorknetPostListForWorker(nextPageId: String?, requestCnt: Int) -> Single + /// 요양보호사가 인앱 공고에 지원합니다. func applyToPost(postId: String, method: ApplyType) -> Single diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift index 04bcd1ac..02752611 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift @@ -66,10 +66,10 @@ public class WorkerWorknetEmployCardCell: UITableViewCell { ]) } - public func bind(postId: String, viewModel: WorkerEmployCardViewModelable) { + public func bind(postId: String, ro: WorkerWorknetEmployCardRO, viewModel: WorkerEmployCardViewModelable) { // 카드 컨텐츠 바인딩 - + cardView.applyRO(ro: ro) let starButton = cardView.starButton diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardRO.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardRO.swift index 801fbcdf..9d0c54f9 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardRO.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardRO.swift @@ -6,6 +6,7 @@ // import Foundation +import Entity public struct WorkerWorknetEmployCardRO { @@ -34,4 +35,50 @@ public struct WorkerWorknetEmployCardRO { self.paymentInfoText = paymentInfoText self.isStarred = isStarred } + + public static func create(vo: WorknetRecruitmentPostVO) -> WorkerWorknetEmployCardRO { + + var leftDayUnitlDeadlineText: String = "타임존 다름" + if let dateDiff = Calendar.current.dateComponents([.day], from: .now, to: vo.applyDeadline).day { + leftDayUnitlDeadlineText = "D-\(dateDiff)" + if dateDiff == 0 { + leftDayUnitlDeadlineText = "D-Day" + } + } + + let durationText = Self.timeForDistance(meter: vo.distance) + + return .init( + showBeginnerTag: false, + leftDayUnitlDeadlineText: leftDayUnitlDeadlineText, + titleText: vo.title, + timeDurationForWalkingText: durationText, + workTimeInfoText: "\(vo.workingSchedule) | \(vo.workingTime)", + paymentInfoText: vo.payInfo, + isStarred: vo.isFavorite + ) + } + + static func timeForDistance(meter: Int) -> String { + switch meter { + case 0..<200: + return "도보 5분 이내" + case 200..<400: + return "도보 5 ~ 10분" + case 400..<700: + return "도보 10 ~ 15분" + case 700..<1000: + return "도보 15 ~ 20분" + case 1000..<1250: + return "도보 20 ~ 25분" + case 1250..<1500: + return "도보 25 ~ 30분" + case 1500..<1750: + return "도보 30 ~ 35분" + case 1750..<2000: + return "도보 35 ~ 40분" + default: + return "도보 40분 ~" + } + } } 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 338ebc6b..56a202b7 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,8 @@ import DSKit public class WorkerRecruitmentPostBoardVC: BaseViewController { - typealias Cell = WorkerNativeEmployCardCell + typealias NativeCell = WorkerNativeEmployCardCell + typealias WorknetCell = WorkerWorknetEmployCardCell // View fileprivate let topContainer: WorkerMainTopContainer = { @@ -25,7 +26,8 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { let postTableView: UITableView = { let tableView = UITableView() tableView.rowHeight = UITableView.automaticDimension - tableView.register(Cell.self, forCellReuseIdentifier: Cell.identifier) + tableView.register(NativeCell.self, forCellReuseIdentifier: NativeCell.identifier) + tableView.register(WorknetCell.self, forCellReuseIdentifier: NativeCell.identifier) return tableView }() let tableHeader = BoardSortigHeaderView() @@ -34,7 +36,7 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { var isPaging = true // Observable - let cellData: BehaviorRelay<[PostBoardCellData]> = .init(value: []) + var postData: [RecruitmentPostForWorkerRepresentable] = [] let requestNextPage: PublishRelay = .init() public init() { @@ -63,9 +65,9 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { viewModel .postBoardData? - .drive(onNext: { [weak self] (isRefreshed: Bool, cellData) in + .drive(onNext: { [weak self] (isRefreshed: Bool, postData) in guard let self else { return } - self.cellData.accept(cellData) + self.postData = postData postTableView.reloadData() isPaging = false @@ -143,20 +145,48 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { extension WorkerRecruitmentPostBoardVC: UITableViewDataSource, UITableViewDelegate { public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - cellData.value.count + postData.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell - cell.selectionStyle = .none + var cell: UITableViewCell! - let cellData = cellData.value[indexPath.row] + let postData = postData[indexPath.row] - if let vm = viewModel as? WorkerNativeEmployCardViewModelable { - cell.bind(postId: cellData.postId, vo: cellData.cardVO, viewModel: vm) + switch postData.postType { + case .native: + + // Cell + let nativeCell = tableView.dequeueReusableCell(withIdentifier: NativeCell.identifier) as! NativeCell + + let postVO = postData as! NativeRecruitmentPostForWorkerVO + let vm = viewModel as! WorkerEmployCardViewModelable + let cellVO: WorkerNativeEmployCardVO = .create(vo: postVO) + nativeCell.bind( + postId: postVO.postId, + vo: cellVO, + viewModel: vm + ) + cell = nativeCell + case .workNet: + + // Cell + let workNetCell = tableView.dequeueReusableCell(withIdentifier: WorknetCell.identifier) as! WorknetCell + + let postVO = postData as! WorknetRecruitmentPostVO + let vm = viewModel as! WorkerEmployCardViewModelable + let cellRO = WorkerWorknetEmployCardRO.create(vo: postVO) + workNetCell.bind( + postId: postVO.id, + ro: cellRO, + viewModel: vm + ) + cell = workNetCell } + cell.selectionStyle = .none + return cell } } 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 d3ed925d..fc26e537 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,13 +14,10 @@ import Entity import DSKit import UseCaseInterface -public struct PostBoardCellData { - let postId: String - let cardVO: WorkerNativeEmployCardVO -} +public typealias BoardRefreshResult = (isRefreshed: Bool, postData: [RecruitmentPostForWorkerRepresentable]) /// 페이징 보드 -public protocol WorkerPagablePostBoardVMable: BaseViewModel, WorkerNativeEmployCardViewModelable { +public protocol WorkerPagablePostBoardVMable: BaseViewModel, AppliableWorkerEmployCardVMable { var coordinator: WorkerRecruitmentBoardCoordinatable? { get } @@ -33,7 +30,7 @@ public protocol WorkerPagablePostBoardVMable: BaseViewModel, WorkerNativeEmployC var requestInitialPageRequest: PublishRelay { get } /// 페이지요청에 대한 결과를 전달합니다. - var postBoardData: Driver<(isRefreshed: Bool, cellData: [PostBoardCellData])>? { get } + var postBoardData: Driver? { get } } /// 페이징 + 지원하기 @@ -55,7 +52,7 @@ public protocol WorkerRecruitmentPostBoardVMable: WorkerAppliablePostBoardVMable public class WorkerRecruitmentPostBoardVM: BaseViewModel, WorkerRecruitmentPostBoardVMable { // Output - public var postBoardData: Driver<(isRefreshed: Bool, cellData: [PostBoardCellData])>? + public var postBoardData: Driver<(isRefreshed: Bool, postData: [RecruitmentPostForWorkerRepresentable])>? public var workerLocationTitleText: Driver? public var idleAlertVM: RxCocoa.Driver? @@ -75,7 +72,7 @@ public class WorkerRecruitmentPostBoardVM: BaseViewModel, WorkerRecruitmentPostB /// 값이 nil이라면 요청을 보내지 않습니다. var nextPagingRequest: PostPagingRequestForWorker? = .initial /// 가장최신의 데이터를 홀드, 다음 요청시 해당데이터에 새로운 데이터를 더해서 방출 - private let currentPostVO: BehaviorRelay<[NativeRecruitmentPostForWorkerVO]> = .init(value: []) + private let currentPostVO: BehaviorRelay<[RecruitmentPostForWorkerRepresentable]> = .init(value: []) // Observable let dispostBag = DisposeBag() @@ -208,7 +205,7 @@ public class WorkerRecruitmentPostBoardVM: BaseViewModel, WorkerRecruitmentPostB currentPostVO, requestPostListSuccess ) - .compactMap { [weak self] (prevPostList, fetchedData) -> (Bool, [PostBoardCellData])? in + .compactMap { [weak self] (prevPostList, fetchedData) -> BoardRefreshResult? in guard let self else { return nil } @@ -254,14 +251,7 @@ public class WorkerRecruitmentPostBoardVM: BaseViewModel, WorkerRecruitmentPostB // 최근값 업데이트 self.currentPostVO.accept(mergedPosts) - // cellData생성 - let cellData: [PostBoardCellData] = mergedPosts.map { postVO in - - let cardVO: WorkerNativeEmployCardVO = .create(vo: postVO) - return .init(postId: postVO.postId, cardVO: cardVO) - } - - return (isRefreshed, cellData) + return (isRefreshed, mergedPosts) } .asDriver(onErrorDriveWith: .never()) From c768472922fbda74ea90364bb2811ac829cbe984 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 6 Sep 2024 20:34:41 +0900 Subject: [PATCH 08/17] =?UTF-8?q?[IDLE-000]=20WorkerWorknetEmployCardCell?= =?UTF-8?q?=20=EB=AA=A8=ED=82=B9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefualtRecruitmentPostUseCase.swift | 10 ++-- .../WorknetCard/WorkerWorknetEmployCard.swift | 6 ++ .../WorkerWorknetEmployCardCell.swift | 2 +- .../View/NativePostDetailForWorkerVC.swift | 4 +- .../DetailVC/PostDetailForCenterVC.swift | 4 +- .../Overview/PostOverviewVC.swift | 4 +- .../SubVC/WorkerPagablePostBoardVC.swift | 58 ++++++++++++++----- .../WorkerRecruitmentPostBoardVC.swift | 14 ++--- .../RecruitmentPost/AppliedPostBoardVM.swift | 16 ++--- .../RecruitmentPost/StarredPostBoardVM.swift | 16 ++--- 10 files changed, 77 insertions(+), 57 deletions(-) diff --git a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift index f02a905f..90e7018c 100644 --- a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift @@ -111,8 +111,10 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { requestCnt: postCount ) case .thirdParty: - // TODO: ‼️ ‼️워크넷 가져오기 미구현 - fatalError() + stream = repository.getWorknetPostListForWorker( + nextPageId: nextPageId, + requestCnt: postCount + ) } } @@ -163,9 +165,7 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { requestCnt: postCount ) case .thirdParty: - - // TODO: ‼️ ‼️워크넷 가져오기 미구현 - return .just(.failure(.notImplemented)) + return .just(.failure(.undefinedError)) } } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift index 6bc3150f..768620f4 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift @@ -55,6 +55,7 @@ public class WorkerWorknetEmployCard: VStack { let titleLabel: IdleLabel = { let label = IdleLabel(typography: .Subtitle2) + label.numberOfLines = 0 return label }() @@ -62,6 +63,7 @@ public class WorkerWorknetEmployCard: VStack { let estimatedArrivalTimeLabel: IdleLabel = { let label = IdleLabel(typography: .Body3) label.attrTextColor = DSKitAsset.Colors.gray500.color + label.numberOfLines = 0 return label }() @@ -69,6 +71,7 @@ public class WorkerWorknetEmployCard: VStack { let workTimeLabel: IdleLabel = { let label = IdleLabel(typography: .Body3) label.attrTextColor = DSKitAsset.Colors.gray500.color + label.numberOfLines = 0 return label }() @@ -76,6 +79,7 @@ public class WorkerWorknetEmployCard: VStack { let payLabel: IdleLabel = { let label = IdleLabel(typography: .Body3) label.attrTextColor = DSKitAsset.Colors.gray500.color + label.numberOfLines = 0 return label }() @@ -136,7 +140,9 @@ public class WorkerWorknetEmployCard: VStack { NSLayoutConstraint.activate([ timeImage.widthAnchor.constraint(equalToConstant: 24), + timeImage.heightAnchor.constraint(equalTo: timeImage.widthAnchor), payImage.widthAnchor.constraint(equalToConstant: 24), + payImage.heightAnchor.constraint(equalTo: payImage.widthAnchor), starButton.widthAnchor.constraint(equalToConstant: 24), starButton.heightAnchor.constraint(equalTo: starButton.widthAnchor), ]) diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift index 02752611..4d3d315a 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift @@ -12,7 +12,7 @@ import Entity public class WorkerWorknetEmployCardCell: UITableViewCell { - public static let identifier = String(describing: WorkerNativeEmployCardCell.self) + public static let identifier = String(describing: WorkerWorknetEmployCardCell.self) private var disposables: [Disposable?]? 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 1aeca239..c941697a 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 @@ -253,8 +253,8 @@ public class NativePostDetailForWorkerVC: BaseViewController { public class PostDetailForWorkerContentView: UIView { /// 구인공고 카드 - let cardView: WorkerEmployCard = { - let view = WorkerEmployCard() + let cardView: WorkerNativeEmployCard = { + let view = WorkerNativeEmployCard() view.setToPostAppearance() return view }() diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/DetailVC/PostDetailForCenterVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/DetailVC/PostDetailForCenterVC.swift index 7d214be1..f2e066d4 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/DetailVC/PostDetailForCenterVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/DetailVC/PostDetailForCenterVC.swift @@ -30,8 +30,8 @@ public class PostDetailForCenterVC: BaseViewController { return bar }() - let sampleCard: WorkerEmployCard = { - let card = WorkerEmployCard() + let sampleCard: WorkerNativeEmployCard = { + let card = WorkerNativeEmployCard() card.starButton.isHidden = true return card }() 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 8d5e3185..ff32ca00 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 @@ -76,8 +76,8 @@ public class PostOverviewVC: BaseViewController { return label }() - let sampleCard: WorkerEmployCard = { - let card = WorkerEmployCard() + let sampleCard: WorkerNativeEmployCard = { + let card = WorkerNativeEmployCard() card.starButton.isUserInteractionEnabled = false return card }() 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 984800fa..c0cd96bb 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,13 +15,15 @@ import DSKit public class WorkerPagablePostBoardVC: BaseViewController { - typealias Cell = WorkerNativeEmployCardCell + typealias NativeCell = WorkerNativeEmployCardCell + typealias WorknetCell = WorkerWorknetEmployCardCell // View let postTableView: UITableView = { let tableView = UITableView() tableView.rowHeight = UITableView.automaticDimension - tableView.register(Cell.self, forCellReuseIdentifier: Cell.identifier) + tableView.register(NativeCell.self, forCellReuseIdentifier: NativeCell.identifier) + tableView.register(WorknetCell.self, forCellReuseIdentifier: WorknetCell.identifier) return tableView }() @@ -31,7 +33,7 @@ public class WorkerPagablePostBoardVC: BaseViewController { var isPaging = true // Observable - let cellData: BehaviorRelay<[PostBoardCellData]> = .init(value: []) + var postData: [RecruitmentPostForWorkerRepresentable] = [] let requestNextPage: PublishRelay = .init() public init() { @@ -94,9 +96,9 @@ public class WorkerPagablePostBoardVC: BaseViewController { // Output viewModel .postBoardData? - .drive(onNext: { [weak self] (isRefreshed, cellData) in + .drive(onNext: { [weak self] (isRefreshed, postData) in guard let self else { return } - self.cellData.accept(cellData) + self.postData = postData self.postTableView.reloadData() isPaging = false @@ -128,7 +130,7 @@ public class WorkerPagablePostBoardVC: BaseViewController { .postBoardData? .drive(onNext: { [weak self] (isRefreshed, cellData) in guard let self else { return } - self.cellData.accept(cellData) + self.postData = postData self.postTableView.reloadData() isPaging = false @@ -153,20 +155,48 @@ public class WorkerPagablePostBoardVC: BaseViewController { extension WorkerPagablePostBoardVC: UITableViewDataSource, UITableViewDelegate { public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - cellData.value.count + postData.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell - cell.selectionStyle = .none - - let cellData = cellData.value[indexPath.row] - - if let vm = viewModel as? WorkerNativeEmployCardViewModelable { - cell.bind(postId: cellData.postId, vo: cellData.cardVO, viewModel: vm) + var cell: UITableViewCell! + + let postData = postData[indexPath.row] + + switch postData.postType { + case .native: + + // Cell + let nativeCell = tableView.dequeueReusableCell(withIdentifier: NativeCell.identifier) as! NativeCell + + let postVO = postData as! NativeRecruitmentPostForWorkerVO + let vm = viewModel as! WorkerEmployCardViewModelable + let cellVO: WorkerNativeEmployCardVO = .create(vo: postVO) + nativeCell.bind( + postId: postVO.postId, + vo: cellVO, + viewModel: vm + ) + cell = nativeCell + case .workNet: + + // Cell + let workNetCell = tableView.dequeueReusableCell(withIdentifier: WorknetCell.identifier) as! WorknetCell + + let postVO = postData as! WorknetRecruitmentPostVO + let vm = viewModel as! WorkerEmployCardViewModelable + let cellRO = WorkerWorknetEmployCardRO.create(vo: postVO) + workNetCell.bind( + postId: postVO.id, + ro: cellRO, + viewModel: vm + ) + cell = workNetCell } + cell.selectionStyle = .none + 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 56a202b7..ee8ae4e9 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 @@ -23,13 +23,7 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { let container = WorkerMainTopContainer(innerViews: []) return container }() - let postTableView: UITableView = { - let tableView = UITableView() - tableView.rowHeight = UITableView.automaticDimension - tableView.register(NativeCell.self, forCellReuseIdentifier: NativeCell.identifier) - tableView.register(WorknetCell.self, forCellReuseIdentifier: NativeCell.identifier) - return tableView - }() + let postTableView = UITableView() let tableHeader = BoardSortigHeaderView() // Paging @@ -102,6 +96,12 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { } private func setTableView() { + + postTableView.rowHeight = UITableView.automaticDimension + postTableView.estimatedRowHeight = 218 + postTableView.register(NativeCell.self, forCellReuseIdentifier: NativeCell.identifier) + postTableView.register(WorknetCell.self, forCellReuseIdentifier: WorknetCell.identifier) + postTableView.dataSource = self postTableView.delegate = self postTableView.separatorStyle = .none 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 aa49e317..230ceb3b 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: BaseViewModel, WorkerPagablePostBoardVMable { public var applyButtonClicked: RxRelay.PublishRelay<(postId: String, postTitle: String)> = .init() // Output - public var postBoardData: RxCocoa.Driver<(isRefreshed: Bool, cellData: [PostBoardCellData])>? + public var postBoardData: RxCocoa.Driver? // Init public weak var coordinator: WorkerRecruitmentBoardCoordinatable? @@ -33,7 +33,7 @@ public class AppliedPostBoardVM: BaseViewModel, WorkerPagablePostBoardVMable { /// 값이 nil이라면 요청을 보내지 않습니다. var nextPagingRequest: PostPagingRequestForWorker? /// 가장최신의 데이터를 홀드, 다음 요청시 해당데이터에 새로운 데이터를 더해서 방출 - private let currentPostVO: BehaviorRelay<[NativeRecruitmentPostForWorkerVO]> = .init(value: []) + private let currentPostVO: BehaviorRelay<[RecruitmentPostForWorkerRepresentable]> = .init(value: []) public init(coordinator: WorkerRecruitmentBoardCoordinatable, recruitmentPostUseCase: RecruitmentPostUseCase) { self.coordinator = coordinator @@ -93,7 +93,7 @@ public class AppliedPostBoardVM: BaseViewModel, WorkerPagablePostBoardVMable { currentPostVO, requestPostListSuccess ) - .compactMap { [weak self] (prevPostList, fetchedData) -> (Bool, [PostBoardCellData])? in + .compactMap { [weak self] (prevPostList, fetchedData) -> BoardRefreshResult? in guard let self else { return nil } @@ -119,15 +119,7 @@ public class AppliedPostBoardVM: BaseViewModel, WorkerPagablePostBoardVMable { // 최근값 업데이트 self.currentPostVO.accept(mergedPosts) - // cellData 생성 - let cellData: [PostBoardCellData] = mergedPosts.map { vo in - - let cardVO: WorkerNativeEmployCardVO = .create(vo: vo) - - return .init(postId: vo.postId, cardVO: cardVO) - } - - return (isRefreshed, cellData) + return (isRefreshed, mergedPosts) } .asDriver(onErrorDriveWith: .never()) 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 6af08e59..ff3546d2 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: BaseViewModel, WorkerAppliablePostBoardVMable { public var applyButtonClicked: RxRelay.PublishRelay<(postId: String, postTitle: String)> = .init() // Output - public var postBoardData: RxCocoa.Driver<(isRefreshed: Bool, cellData: [PostBoardCellData])>? + public var postBoardData: RxCocoa.Driver? public var idleAlertVM: RxCocoa.Driver? // Init @@ -33,7 +33,7 @@ public class StarredPostBoardVM: BaseViewModel, WorkerAppliablePostBoardVMable { /// 값이 nil이라면 요청을 보내지 않습니다. var nextPagingRequest: PostPagingRequestForWorker? /// 가장최신의 데이터를 홀드, 다음 요청시 해당데이터에 새로운 데이터를 더해서 방출 - private let currentPostVO: BehaviorRelay<[NativeRecruitmentPostForWorkerVO]> = .init(value: []) + private let currentPostVO: BehaviorRelay<[RecruitmentPostForWorkerRepresentable]> = .init(value: []) public init(coordinator: WorkerRecruitmentBoardCoordinatable, recruitmentPostUseCase: RecruitmentPostUseCase) { self.coordinator = coordinator @@ -93,7 +93,7 @@ public class StarredPostBoardVM: BaseViewModel, WorkerAppliablePostBoardVMable { currentPostVO, requestPostListSuccess ) - .compactMap { [weak self] (prevPostList, fetchedData) -> (Bool, [PostBoardCellData])? in + .compactMap { [weak self] (prevPostList, fetchedData) -> BoardRefreshResult? in guard let self else { return nil } @@ -119,15 +119,7 @@ public class StarredPostBoardVM: BaseViewModel, WorkerAppliablePostBoardVMable { // 최근값 업데이트 self.currentPostVO.accept(mergedPosts) - // cellData 생성 - let cellData: [PostBoardCellData] = mergedPosts.map { vo in - - let cardVO: WorkerNativeEmployCardVO = .create(vo: vo) - - return .init(postId: vo.postId, cardVO: cardVO) - } - - return (isRefreshed, cellData) + return (isRefreshed, mergedPosts) } .asDriver(onErrorDriveWith: .never()) From cb245af56c0ca5de1293e71837add114a9d709af Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 6 Sep 2024 20:36:05 +0900 Subject: [PATCH 09/17] =?UTF-8?q?[IDLE-000]=20estimatedCellHeight=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SubVC/WorkerPagablePostBoardVC.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 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 c0cd96bb..cb07e86e 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 @@ -19,13 +19,7 @@ public class WorkerPagablePostBoardVC: BaseViewController { typealias WorknetCell = WorkerWorknetEmployCardCell // View - let postTableView: UITableView = { - let tableView = UITableView() - tableView.rowHeight = UITableView.automaticDimension - tableView.register(NativeCell.self, forCellReuseIdentifier: NativeCell.identifier) - tableView.register(WorknetCell.self, forCellReuseIdentifier: WorknetCell.identifier) - return tableView - }() + let postTableView = UITableView() let tableHeader = BoardSortigHeaderView() @@ -51,6 +45,12 @@ public class WorkerPagablePostBoardVC: BaseViewController { } private func setTableView() { + + postTableView.rowHeight = UITableView.automaticDimension + postTableView.estimatedRowHeight = 218 + postTableView.register(NativeCell.self, forCellReuseIdentifier: NativeCell.identifier) + postTableView.register(WorknetCell.self, forCellReuseIdentifier: WorknetCell.identifier) + postTableView.dataSource = self postTableView.delegate = self postTableView.separatorStyle = .none From 8ca71c2b3dc22c75ba1d41e2155a38913497b37c Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 6 Sep 2024 21:00:55 +0900 Subject: [PATCH 10/17] =?UTF-8?q?[IDLE-000]=20=EC=9B=8C=ED=81=AC=EB=84=B7?= =?UTF-8?q?=20=EA=B3=B5=EA=B3=A0=20=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?UseCase/Repository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefaultRecruitmentPostRepository.swift | 14 ++-- ...t => NativeRecruitmentPostDetailDTO.swift} | 4 +- .../WorknetRecruitmentPostDetailDTO.swift | 65 ++++++++++++++++ .../Util/Extension/Single+Extension.swift | 8 ++ .../DefualtRecruitmentPostUseCase.swift | 8 +- .../WorknetRecruitmentPostDetailVO.swift | 76 +++++++++++++++++++ .../NativeRecruitmentPostForWorkerVO.swift | 0 .../RecruitmentPostListForWorkerVO.swift | 0 .../WorknetRecruitmentPostForWorkerVO.swift | 0 .../RecruitmentPostRepository.swift | 7 +- .../RecruitmentPostUseCase.swift | 5 +- 11 files changed, 175 insertions(+), 12 deletions(-) rename project/Projects/Data/DataSource/DTO/RecruitmentPost/{RecruitmentPostDetailForWorkerDTO.swift => NativeRecruitmentPostDetailDTO.swift} (97%) create mode 100644 project/Projects/Data/DataSource/DTO/RecruitmentPost/WorknetRecruitmentPostDetailDTO.swift create mode 100644 project/Projects/Domain/Entity/VO/Post/Detail/WorknetRecruitmentPostDetailVO.swift rename project/Projects/Domain/Entity/VO/Post/{PostList => List}/NativeRecruitmentPostForWorkerVO.swift (100%) rename project/Projects/Domain/Entity/VO/Post/{PostList => List}/RecruitmentPostListForWorkerVO.swift (100%) rename project/Projects/Domain/Entity/VO/Post/{PostList => List}/WorknetRecruitmentPostForWorkerVO.swift (100%) diff --git a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift index dc2910bf..f76bcfd8 100644 --- a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift +++ b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift @@ -93,17 +93,21 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { } // MARK: Worker - public func getPostDetailForWorker(id: String) -> RxSwift.Single { + public func getNativePostDetailForWorker(id: String) -> RxSwift.Single { recruitmentPostService.request( api: .postDetail(id: id, userType: .worker), with: .withToken ) - .map(RecruitmentPostDTO.self) - .map { dto in - dto.toEntity() - } + .mapToEntity(NativeRecruitmentPostDetailDTO.self) } + public func getWorknetPostDetailForWorker(id: String) -> RxSwift.Single { + crawlingPostService + .request(api: .getDetail(postId: id), with: .withToken) + .mapToEntity(WorknetRecruitmentPostDetailDTO.self) + } + + public func getNativePostListForWorker(nextPageId: String?, requestCnt: Int = 10) -> RxSwift.Single { recruitmentPostService.request( diff --git a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift b/project/Projects/Data/DataSource/DTO/RecruitmentPost/NativeRecruitmentPostDetailDTO.swift similarity index 97% rename from project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift rename to project/Projects/Data/DataSource/DTO/RecruitmentPost/NativeRecruitmentPostDetailDTO.swift index da0b1247..aaa6f6bc 100644 --- a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift +++ b/project/Projects/Data/DataSource/DTO/RecruitmentPost/NativeRecruitmentPostDetailDTO.swift @@ -1,5 +1,5 @@ // -// RecruitmentPostDetailForWorkerDTO.swift +// NativeRecruitmentPostDetailDTO.swift // NetworkDataSource // // Created by choijunios on 8/15/24. @@ -8,7 +8,7 @@ import Foundation import Entity -public struct RecruitmentPostDTO: Codable { +public struct NativeRecruitmentPostDetailDTO: EntityRepresentable { public let id: String public let longitude: String diff --git a/project/Projects/Data/DataSource/DTO/RecruitmentPost/WorknetRecruitmentPostDetailDTO.swift b/project/Projects/Data/DataSource/DTO/RecruitmentPost/WorknetRecruitmentPostDetailDTO.swift new file mode 100644 index 00000000..26aea2d0 --- /dev/null +++ b/project/Projects/Data/DataSource/DTO/RecruitmentPost/WorknetRecruitmentPostDetailDTO.swift @@ -0,0 +1,65 @@ +// +// WorknetRecruitmentPostDetailDTO.swift +// DataSource +// +// Created by choijunios on 9/6/24. +// + +import Foundation +import Entity + +public struct WorknetRecruitmentPostDetailDTO: EntityRepresentable { + + let id: String + let title: String + let content: String + let clientAddress: String + let longitude: String + let latitude: String + let distance: Int + let createdAt: String + let payInfo: String + let workingTime: String + let workingSchedule: String + let applyDeadline: String + let recruitmentProcess: String + let applyMethod: String + let requiredDocumentation: String + let centerName: String + let centerAddress: String + let jobPostingUrl: String + let jobPostingType: RecruitmentPostType + let isFavorite: Bool + + public func toEntity() -> WorknetRecruitmentPostDetailVO { + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + + let createdAtDate = dateFormatter.date(from: createdAt) ?? Date() + let applyDeadlineDate = dateFormatter.date(from: applyDeadline) ?? Date() + + return .init( + id: id, + title: title, + content: content, + clientAddress: clientAddress, + longitude: longitude, + latitude: latitude, + distance: distance, + createdAt: createdAtDate, + payInfo: payInfo, + workingTime: workingTime, + workingSchedule: workingSchedule, + applyDeadline: applyDeadlineDate, + recruitmentProcess: recruitmentProcess, + applyMethod: applyMethod, + requiredDocumentation: requiredDocumentation, + centerName: centerName, + centerAddress: centerAddress, + jobPostingUrl: jobPostingUrl, + jobPostingType: jobPostingType, + isFavorite: isFavorite + ) + } +} diff --git a/project/Projects/Data/DataSource/Util/Extension/Single+Extension.swift b/project/Projects/Data/DataSource/Util/Extension/Single+Extension.swift index 98393f93..34030e83 100644 --- a/project/Projects/Data/DataSource/Util/Extension/Single+Extension.swift +++ b/project/Projects/Data/DataSource/Util/Extension/Single+Extension.swift @@ -12,4 +12,12 @@ public extension PrimitiveSequence where Trait == SingleTrait, Element == Respon func mapToVoid() -> Single { flatMap { _ in .just(()) } } + + func mapToEntity(_ type: T.Type) -> Single { + map(T.self) + .map { dto in + dto.toEntity() + } + + } } diff --git a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift index 90e7018c..d293a6d8 100644 --- a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift @@ -53,8 +53,12 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { convert(task: repository.getPostDetailForCenter(id: id)) } - public func getPostDetailForWorker(id: String) -> RxSwift.Single> { - convert(task: repository.getPostDetailForWorker(id: id)) + public func getNativePostDetailForWorker(id: String) -> RxSwift.Single> { + convert(task: repository.getNativePostDetailForWorker(id: id)) + } + + public func getWorknetPostDetailForWorker(id: String) -> RxSwift.Single> { + convert(task: repository.getWorknetPostDetailForWorker(id: id)) } public func getOngoingPosts() -> RxSwift.Single> { diff --git a/project/Projects/Domain/Entity/VO/Post/Detail/WorknetRecruitmentPostDetailVO.swift b/project/Projects/Domain/Entity/VO/Post/Detail/WorknetRecruitmentPostDetailVO.swift new file mode 100644 index 00000000..326403df --- /dev/null +++ b/project/Projects/Domain/Entity/VO/Post/Detail/WorknetRecruitmentPostDetailVO.swift @@ -0,0 +1,76 @@ +// +// WorknetRecruitmentPostDetailVO.swift +// Entity +// +// Created by choijunios on 9/6/24. +// + +import Foundation + +public struct WorknetRecruitmentPostDetailVO: Decodable { + + public let id: String + public let title: String + public let content: String + public let clientAddress: String + public let longitude: String + public let latitude: String + public let distance: Int + public let createdAt: Date + public let payInfo: String + public let workingTime: String + public let workingSchedule: String + public let applyDeadline: Date + public let recruitmentProcess: String + public let applyMethod: String + public let requiredDocumentation: String + public let centerName: String + public let centerAddress: String + public let jobPostingUrl: String + public let jobPostingType: RecruitmentPostType + public let isFavorite: Bool + + public init( + id: String, + title: String, + content: String, + clientAddress: String, + longitude: String, + latitude: String, + distance: Int, + createdAt: Date, + payInfo: String, + workingTime: String, + workingSchedule: String, + applyDeadline: Date, + recruitmentProcess: String, + applyMethod: String, + requiredDocumentation: String, + centerName: String, + centerAddress: String, + jobPostingUrl: String, + jobPostingType: RecruitmentPostType, + isFavorite: Bool + ) { + self.id = id + self.title = title + self.content = content + self.clientAddress = clientAddress + self.longitude = longitude + self.latitude = latitude + self.distance = distance + self.createdAt = createdAt + self.payInfo = payInfo + self.workingTime = workingTime + self.workingSchedule = workingSchedule + self.applyDeadline = applyDeadline + self.recruitmentProcess = recruitmentProcess + self.applyMethod = applyMethod + self.requiredDocumentation = requiredDocumentation + self.centerName = centerName + self.centerAddress = centerAddress + self.jobPostingUrl = jobPostingUrl + self.jobPostingType = jobPostingType + self.isFavorite = isFavorite + } +} diff --git a/project/Projects/Domain/Entity/VO/Post/PostList/NativeRecruitmentPostForWorkerVO.swift b/project/Projects/Domain/Entity/VO/Post/List/NativeRecruitmentPostForWorkerVO.swift similarity index 100% rename from project/Projects/Domain/Entity/VO/Post/PostList/NativeRecruitmentPostForWorkerVO.swift rename to project/Projects/Domain/Entity/VO/Post/List/NativeRecruitmentPostForWorkerVO.swift diff --git a/project/Projects/Domain/Entity/VO/Post/PostList/RecruitmentPostListForWorkerVO.swift b/project/Projects/Domain/Entity/VO/Post/List/RecruitmentPostListForWorkerVO.swift similarity index 100% rename from project/Projects/Domain/Entity/VO/Post/PostList/RecruitmentPostListForWorkerVO.swift rename to project/Projects/Domain/Entity/VO/Post/List/RecruitmentPostListForWorkerVO.swift diff --git a/project/Projects/Domain/Entity/VO/Post/PostList/WorknetRecruitmentPostForWorkerVO.swift b/project/Projects/Domain/Entity/VO/Post/List/WorknetRecruitmentPostForWorkerVO.swift similarity index 100% rename from project/Projects/Domain/Entity/VO/Post/PostList/WorknetRecruitmentPostForWorkerVO.swift rename to project/Projects/Domain/Entity/VO/Post/List/WorknetRecruitmentPostForWorkerVO.swift diff --git a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift index 748f972b..bee72ff8 100644 --- a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift +++ b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift @@ -41,8 +41,11 @@ public protocol RecruitmentPostRepository: RepositoryBase { func removePost(id: String) -> Single // MARK: Worker - /// 요양보호사 공고의 상세정보를 조회합니다. - func getPostDetailForWorker(id: String) -> Single + /// 요양보호사 앱내 공고의 상세정보를 조회합니다. + func getNativePostDetailForWorker(id: String) -> Single + + /// 요양보호사 워크넷 공고의 상세정보를 조회합니다. + func getWorknetPostDetailForWorker(id: String) -> Single // MARK: Native post diff --git a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift index 837fb648..3d4c778b 100644 --- a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift @@ -47,7 +47,10 @@ public protocol RecruitmentPostUseCase: UseCaseBase { /// - 공고상세정보(센터와 달리 고객 이름 배제) /// - 근무지 위치(위경도) /// - 센터정보(센터 id, 이름, 도로명 주소) - func getPostDetailForWorker(id: String) -> Single> + func getNativePostDetailForWorker(id: String) -> Single> + + /// 워크넷 공고 상세정보를 반환합니다. + func getWorknetPostDetailForWorker(id: String) -> Single> /// 요양보호사가 메인화면에 사용할 공고리스트를 호출합니다. func getPostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> Single> From 8fc259298c37080e54f1843cf7f82577aeebbb0e Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 6 Sep 2024 22:26:12 +0900 Subject: [PATCH 11/17] =?UTF-8?q?[IDLE-000]=20=EC=9B=8C=ED=81=AC=EB=84=B7?= =?UTF-8?q?=20=EA=B3=B5=EA=B3=A0=20=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?WorknetPostDetailForWorkerVC=EA=B5=AC=ED=98=84(=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A0=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Profile/Center/CenterInfoCardView.swift | 20 +- .../NativePostDetailForWorkerVC.swift | 2 +- .../WorknetPostDetailForWorkerVC.swift | 241 ++++++++++++++++++ 3 files changed, 253 insertions(+), 10 deletions(-) rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/{ => Postdetail}/NativePostDetailForWorkerVC.swift (99%) create mode 100644 project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Center/CenterInfoCardView.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Center/CenterInfoCardView.swift index e1186f0b..8f4f6fe4 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Center/CenterInfoCardView.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Center/CenterInfoCardView.swift @@ -26,6 +26,17 @@ public class CenterInfoCardView: TappableUIView { return label }() + public let locationImageView: UIImageView = { + let iamgeView = DSKitAsset.Icons.location.image.toView() + iamgeView.tintColor = DSColor.gray400.color + return iamgeView + }() + public let chevronLeftImage: UIImageView = { + let view = DSKitAsset.Icons.chevronRight.image.toView() + view.tintColor = DSKitAsset.Colors.gray200.color + return view + }() + // Observable private let disposeBag = DisposeBag() @@ -45,17 +56,8 @@ public class CenterInfoCardView: TappableUIView { } - public let chevronLeftImage: UIImageView = { - let view = DSKitAsset.Icons.chevronRight.image.toView() - view.tintColor = DSKitAsset.Colors.gray200.color - return view - }() - private func setLayout() { - let locationImageView = DSKitAsset.Icons.location.image.toView() - locationImageView.tintColor = DSColor.gray400.color - let locationStack = HStack( [ locationImageView, 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/Postdetail/NativePostDetailForWorkerVC.swift similarity index 99% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/NativePostDetailForWorkerVC.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/NativePostDetailForWorkerVC.swift index c941697a..dfd8de35 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/Postdetail/NativePostDetailForWorkerVC.swift @@ -260,7 +260,7 @@ public class PostDetailForWorkerContentView: UIView { }() /// 지도뷰 - public let workPlaceAndWorkerLocationView = WorkPlaceAndWorkerLocationView() + let workPlaceAndWorkerLocationView = WorkPlaceAndWorkerLocationView() /// 공고 상세정보들 let workConditionView = WorkConditionDisplayingView() diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift new file mode 100644 index 00000000..6bc1153b --- /dev/null +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift @@ -0,0 +1,241 @@ +// +// WorknetPostDetailForWorkerVC.swift +// BaseFeature +// +// Created by choijunios on 9/6/24. +// + +import UIKit +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit + +public class WorknetPostDetailForWorkerVC: BaseViewController { + + // Init + + // View + let navigationBar: IdleNavigationBar = { + let bar = IdleNavigationBar(innerViews: []) + bar.titleLabel.textString = "공고 정보" + return bar + }() + + // 구인공고 카드 + let cardView: WorkerWorknetEmployCard = { + let view = WorkerWorknetEmployCard() + return view + }() + + // 지도뷰 + let workPlaceAndWorkerLocationView = WorkPlaceAndWorkerLocationView() + + public init() { + + super.init(nibName: nil, bundle: nil) + + setAppearance() + setLayout() + } + + // 모집 요강 + let recruitmentDetailTextView: MultiLineTextField = { + let field = MultiLineTextField(typography: .Body3) + return field + }() + + // 근무 조건 + let workShapeLabel: IdleLabel = { + let label = IdleLabel(typography: .Body2) + return label + }() + let workTimeLabel: IdleLabel = { + let label = IdleLabel(typography: .Body2) + return label + }() + let paymentConditionLabel: IdleLabel = { + let label = IdleLabel(typography: .Body2) + return label + }() + let workAddressLabel: IdleLabel = { + let label = IdleLabel(typography: .Body2) + return label + }() + + + // 전형방법 + let applyDeadlineLabel: IdleLabel = { + let label = IdleLabel(typography: .Body2) + return label + }() + let applyMethodLabel: IdleLabel = { + let label = IdleLabel(typography: .Body2) + return label + }() + let submitMethodLabel: IdleLabel = { + let label = IdleLabel(typography: .Body2) + return label + }() + let submitDocsLabel: IdleLabel = { + let label = IdleLabel(typography: .Body2) + return label + }() + + + // 기관 정보 + let centerInfoCard: CenterInfoCardView = { + let view = CenterInfoCardView() + view.isUserInteractionEnabled = false + view.chevronLeftImage.isHidden = true + return view + }() + + // 워크넷 링크 + let worknetLinkCard: CenterInfoCardView = { + let view = CenterInfoCardView() + view.locationImageView.isHidden = true + return view + }() + + public required init?(coder: NSCoder) { fatalError() } + + func setAppearance() { + view.backgroundColor = DSColor.gray0.color + } + + func setLayout() { + + // 모집요강 + + + // 근무조건 + let workConditionComponentStackList = [ + ("근무 형태", workShapeLabel), + ("근무 시간", workTimeLabel), + ("임김 조건", paymentConditionLabel), + ("근무 주소", workAddressLabel), + ].map { (keyText, valueLabel) in + + let keyLabel = IdleLabel(typography: .Body2) + keyLabel.attrTextColor = DSColor.gray300.color + keyLabel.textString = keyText + keyLabel.textAlignment = .left + + return HStack([keyLabel, valueLabel], spacing: 32) + } + + let workConditionStack: VStack = .init(workConditionComponentStackList, spacing: 8, alignment: .leading) + + // 전형방법 + let applyMethodComponentStackList = [ + ("접수 마감일", applyDeadlineLabel), + ("전형 방법", applyMethodLabel), + ("접수 방법", submitMethodLabel), + ("제출 서류", submitDocsLabel), + ].map { (keyText, valueLabel) in + + let keyLabel = IdleLabel(typography: .Body2) + keyLabel.attrTextColor = DSColor.gray300.color + keyLabel.textString = keyText + let labelWidth = keyLabel.intrinsicContentSize.width + let spacing = 114 - labelWidth + + return HStack([keyLabel, valueLabel], spacing: spacing) + } + + let applyMethodComponentStack: VStack = .init(applyMethodComponentStackList, spacing: 8, alignment: .leading) + + let scrollView = UIScrollView() + let scvContentGuide = scrollView.contentLayoutGuide + let scvFrameGuide = scrollView.frameLayoutGuide + + let viewList = [ + + cardView, + + workPlaceAndWorkerLocationView, + + VStack([ + makeTitleLabel(text: "모집요강"), + recruitmentDetailTextView + ], spacing: 20, alignment: .fill), + + VStack([ + makeTitleLabel(text: "근무 조건"), + workConditionStack + ], spacing: 20, alignment: .fill), + + VStack([ + makeTitleLabel(text: "전형 방법"), + applyMethodComponentStack + ], spacing: 20, alignment: .fill), + + VStack([ + makeTitleLabel(text: "기관 정보"), + centerInfoCard + ], spacing: 20, alignment: .fill), + + VStack([ + makeTitleLabel(text: "워크넷 링크"), + worknetLinkCard + ], spacing: 20, alignment: .fill), + + ].map { containerView in + + let backgroundView = UIView() + backgroundView.backgroundColor = DSColor.gray0.color + backgroundView.layoutMargins = .init(top: 24, left: 20, bottom: 24, right: 20) + backgroundView.addSubview(containerView) + NSLayoutConstraint.activate([ + containerView.topAnchor.constraint(equalTo: backgroundView.layoutMarginsGuide.topAnchor), + containerView.leftAnchor.constraint(equalTo: backgroundView.layoutMarginsGuide.leftAnchor), + containerView.rightAnchor.constraint(equalTo: backgroundView.layoutMarginsGuide.rightAnchor), + containerView.bottomAnchor.constraint(equalTo: backgroundView.layoutMarginsGuide.bottomAnchor), + ]) + return backgroundView + } + + let scvContentStack = VStack(viewList, spacing: 8) + scvContentStack.backgroundColor = DSColor.gray050.color + + scrollView.addSubview(scvContentStack) + + NSLayoutConstraint.activate([ + scvContentStack.topAnchor.constraint(equalTo: scvContentGuide.topAnchor), + scvContentStack.leftAnchor.constraint(equalTo: scvContentGuide.leftAnchor), + scvContentStack.rightAnchor.constraint(equalTo: scvContentGuide.rightAnchor), + scvContentStack.bottomAnchor.constraint(equalTo: scvContentGuide.bottomAnchor), + + scvContentStack.widthAnchor.constraint(equalTo: scvFrameGuide.widthAnchor) + ]) + + // MARK: view + [ + navigationBar, + scrollView + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + view.addSubview($0) + } + + NSLayoutConstraint.activate([ + navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + navigationBar.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), + navigationBar.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor), + + scrollView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), + scrollView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), + scrollView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor), + scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + ]) + } + + func makeTitleLabel(text: String) -> IdleLabel { + let label = IdleLabel(typography: .Subtitle1) + label.textAlignment = .left + label.textString = text + return label + } +} From cd8d928e3ea8715c00fca42d54b3022b5cc5212b Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Sat, 7 Sep 2024 01:04:12 +0900 Subject: [PATCH 12/17] =?UTF-8?q?[IDLE-000]=20=EC=9B=8C=ED=81=AC=EB=84=B7?= =?UTF-8?q?=20=EA=B3=B5=EA=B3=A0=20=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?WorknetPostDetailForWorkerVC=EA=B5=AC=ED=98=84(=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=99=84=EB=A3=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AppliedAndLikedBoardCoordinator.swift | 4 +- .../WorkerRecruitmentBoardCoordinator.swift | 4 +- .../Domain/Entity/VO/AlertContentVO.swift | 4 +- .../WorkerNativeEmployCardCell.swift | 4 +- .../WorknetCard/WorkerWorknetEmployCard.swift | 2 +- .../WorkerWorknetEmployCardCell.swift | 2 +- .../WorkerWorknetEmployCardRO.swift | 23 ++ .../Profile/Center/CenterInfoCardView.swift | 1 + .../PostDetailForWorkerCoodinator.swift | 37 ++- .../WorknetPostDetailForWorkerVC.swift | 142 +++++++++- .../View/WorkPlaceAndWorkerLocationView.swift | 8 +- .../NativePostDetailForWorkerVM.swift | 45 +++- .../WorknetPostDetailForWorkerVM.swift | 245 ++++++++++++++++++ .../Base/BaseViewController.swift | 3 + .../WorkerRecruitmentPostBoardVM.swift | 4 +- .../WorkerRecruitmentBoardCoordinatable.swift | 3 +- 16 files changed, 495 insertions(+), 36 deletions(-) create mode 100644 project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/WorknetPostDetailForWorkerVM.swift 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 d52bc0ad..351617ba 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/AppliedAndLikedBoardCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/AppliedAndLikedBoardCoordinator.swift @@ -7,6 +7,7 @@ import UIKit import WorkerFeature +import Entity import BaseFeature import CenterFeature import PresentationCore @@ -65,9 +66,10 @@ class AppliedAndLikedBoardCoordinator: WorkerRecruitmentBoardCoordinatable { } extension AppliedAndLikedBoardCoordinator { - public func showPostDetail(postId: String) { + public func showPostDetail(postType: RecruitmentPostType, postId: String) { let coodinator = PostDetailForWorkerCoodinator( dependency: .init( + postType: postType, postId: postId, parent: self, navigationController: navigationController, diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerRecruitmentBoardCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerRecruitmentBoardCoordinator.swift index 662d11bb..8bcf2be6 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerRecruitmentBoardCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerRecruitmentBoardCoordinator.swift @@ -9,6 +9,7 @@ import UIKit import WorkerFeature import BaseFeature import CenterFeature +import Entity import PresentationCore import UseCaseInterface @@ -54,9 +55,10 @@ class WorkerRecruitmentBoardCoordinator: WorkerRecruitmentBoardCoordinatable { } extension WorkerRecruitmentBoardCoordinator { - public func showPostDetail(postId: String) { + public func showPostDetail(postType: RecruitmentPostType, postId: String) { let coodinator = PostDetailForWorkerCoodinator( dependency: .init( + postType: postType, postId: postId, parent: self, navigationController: navigationController, diff --git a/project/Projects/Domain/Entity/VO/AlertContentVO.swift b/project/Projects/Domain/Entity/VO/AlertContentVO.swift index c2eb64d1..ee5d4ee4 100644 --- a/project/Projects/Domain/Entity/VO/AlertContentVO.swift +++ b/project/Projects/Domain/Entity/VO/AlertContentVO.swift @@ -11,10 +11,12 @@ public struct DefaultAlertContentVO { public let title: String public let message: String + public let onDismiss: (() -> ())? - public init(title: String, message: String) { + public init(title: String, message: String, onDismiss: (() -> ())? = nil) { self.title = title self.message = message + self.onDismiss = onDismiss } public static let `default` = DefaultAlertContentVO( diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardCell.swift index 9482331d..f2386f57 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/NativeCard/WorkerNativeEmployCardCell.swift @@ -18,7 +18,7 @@ public enum PostAppliedState { public protocol WorkerEmployCardViewModelable: AnyObject { /// 공고상세보기 - func showPostDetail(id: String) + func showPostDetail(postType: RecruitmentPostType, id: String) /// 즐겨찾기 버튼 클릭 func setPostFavoriteState(isFavoriteRequest: Bool, postId: String, postType: RecruitmentPostType) -> Single @@ -137,7 +137,7 @@ public class WorkerNativeEmployCardCell: UITableViewCell { tappableArea .rx.tap .subscribe(onNext: { [weak viewModel] _ in - viewModel?.showPostDetail(id: postId) + viewModel?.showPostDetail(postType: .native, id: postId) }), applyButton.rx.tap diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift index 768620f4..75c0d8dd 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCard.swift @@ -166,7 +166,7 @@ public class WorkerWorknetEmployCard: VStack { } } - func applyRO(ro: WorkerWorknetEmployCardRO) { + public func applyRO(ro: WorkerWorknetEmployCardRO) { beginnerTag.isHidden = !ro.showBeginnerTag daysUntilDeadlineTag.textString = ro.leftDayUnitlDeadlineText diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift index 4d3d315a..bdbd6f89 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardCell.swift @@ -94,7 +94,7 @@ public class WorkerWorknetEmployCardCell: UITableViewCell { tappableArea .rx.tap .subscribe(onNext: { [weak viewModel] _ in - viewModel?.showPostDetail(id: postId) + viewModel?.showPostDetail(postType: .workNet, id: postId) }), favoriteRequestResult diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardRO.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardRO.swift index 9d0c54f9..2717a78a 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardRO.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorknetCard/WorkerWorknetEmployCardRO.swift @@ -36,6 +36,29 @@ public struct WorkerWorknetEmployCardRO { self.isStarred = isStarred } + public static func create(vo: WorknetRecruitmentPostDetailVO) -> WorkerWorknetEmployCardRO { + + var leftDayUnitlDeadlineText: String = "타임존 다름" + if let dateDiff = Calendar.current.dateComponents([.day], from: .now, to: vo.applyDeadline).day { + leftDayUnitlDeadlineText = "D-\(dateDiff)" + if dateDiff == 0 { + leftDayUnitlDeadlineText = "D-Day" + } + } + + let durationText = Self.timeForDistance(meter: vo.distance) + + return .init( + showBeginnerTag: false, + leftDayUnitlDeadlineText: leftDayUnitlDeadlineText, + titleText: vo.title, + timeDurationForWalkingText: durationText, + workTimeInfoText: "\(vo.workingSchedule) | \(vo.workingTime)", + paymentInfoText: vo.payInfo, + isStarred: vo.isFavorite + ) + } + public static func create(vo: WorknetRecruitmentPostVO) -> WorkerWorknetEmployCardRO { var leftDayUnitlDeadlineText: String = "타임존 다름" diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Center/CenterInfoCardView.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Center/CenterInfoCardView.swift index 8f4f6fe4..7cdb9975 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Center/CenterInfoCardView.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Center/CenterInfoCardView.swift @@ -18,6 +18,7 @@ public class CenterInfoCardView: TappableUIView { // View let nameLabel: IdleLabel = { let label = IdleLabel(typography: .Subtitle3) + label.numberOfLines = 0 return label }() let locationLabel: IdleLabel = { 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 843d9a28..cf86b423 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 @@ -13,12 +13,14 @@ import Entity public class PostDetailForWorkerCoodinator: ChildCoordinator { public struct Dependency { + let postType: RecruitmentPostType let postId: String weak var parent: WorkerRecruitmentBoardCoordinatable? let navigationController: UINavigationController let recruitmentPostUseCase: RecruitmentPostUseCase - public init(postId: String, parent: WorkerRecruitmentBoardCoordinatable? = nil, navigationController: UINavigationController, recruitmentPostUseCase: RecruitmentPostUseCase) { + public init(postType: RecruitmentPostType, postId: String, parent: WorkerRecruitmentBoardCoordinatable? = nil, navigationController: UINavigationController, recruitmentPostUseCase: RecruitmentPostUseCase) { + self.postType = postType self.postId = postId self.parent = parent self.navigationController = navigationController @@ -29,6 +31,7 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { public weak var viewControllerRef: UIViewController? public weak var parent: WorkerRecruitmentBoardCoordinatable? + let postType: RecruitmentPostType let postId: String public let navigationController: UINavigationController let recruitmentPostUseCase: RecruitmentPostUseCase @@ -36,6 +39,7 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { public init( dependency: Dependency ) { + self.postType = dependency.postType self.postId = dependency.postId self.parent = dependency.parent self.navigationController = dependency.navigationController @@ -47,13 +51,30 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { } public func start() { - let vc = NativePostDetailForWorkerVC() - let vm = NativePostDetailForWorkerVM( - postId: postId, - coordinator: self, - recruitmentPostUseCase: recruitmentPostUseCase - ) - vc.bind(viewModel: vm) + + var vc: UIViewController! + + switch postType { + case .native: + let nativeDetailVC = NativePostDetailForWorkerVC() + let vm = NativePostDetailForWorkerVM( + postId: postId, + coordinator: self, + recruitmentPostUseCase: recruitmentPostUseCase + ) + nativeDetailVC.bind(viewModel: vm) + vc = nativeDetailVC + case .workNet: + let worknetDetailVC = WorknetPostDetailForWorkerVC() + let vm = WorknetPostDetailForWorkerVM( + postId: postId, + coordinator: self, + recruitmentPostUseCase: recruitmentPostUseCase + ) + worknetDetailVC.bind(viewModel: vm) + vc = worknetDetailVC + } + viewControllerRef = vc navigationController.pushViewController(vc, animated: true) } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift index 6bc1153b..99f1da4b 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift @@ -29,9 +29,12 @@ public class WorknetPostDetailForWorkerVC: BaseViewController { return view }() - // 지도뷰 + // 근무 장소(타이틀/설명1/걸어서~/지도) let workPlaceAndWorkerLocationView = WorkPlaceAndWorkerLocationView() + // 워크넷 링크 + var workNetPostLink: URL? + public init() { super.init(nibName: nil, bundle: nil) @@ -43,24 +46,30 @@ public class WorknetPostDetailForWorkerVC: BaseViewController { // 모집 요강 let recruitmentDetailTextView: MultiLineTextField = { let field = MultiLineTextField(typography: .Body3) + field.isScrollEnabled = false + field.isUserInteractionEnabled = false return field }() // 근무 조건 let workShapeLabel: IdleLabel = { let label = IdleLabel(typography: .Body2) + label.numberOfLines = 0 return label }() let workTimeLabel: IdleLabel = { let label = IdleLabel(typography: .Body2) + label.numberOfLines = 0 return label }() let paymentConditionLabel: IdleLabel = { let label = IdleLabel(typography: .Body2) + label.numberOfLines = 0 return label }() let workAddressLabel: IdleLabel = { let label = IdleLabel(typography: .Body2) + label.numberOfLines = 0 return label }() @@ -68,18 +77,22 @@ public class WorknetPostDetailForWorkerVC: BaseViewController { // 전형방법 let applyDeadlineLabel: IdleLabel = { let label = IdleLabel(typography: .Body2) + label.numberOfLines = 0 return label }() let applyMethodLabel: IdleLabel = { let label = IdleLabel(typography: .Body2) + label.numberOfLines = 0 return label }() let submitMethodLabel: IdleLabel = { let label = IdleLabel(typography: .Body2) + label.numberOfLines = 0 return label }() let submitDocsLabel: IdleLabel = { let label = IdleLabel(typography: .Body2) + label.numberOfLines = 0 return label }() @@ -101,15 +114,119 @@ public class WorknetPostDetailForWorkerVC: BaseViewController { public required init?(coder: NSCoder) { fatalError() } + public func bind(viewModel: WorknetPostDetailForWorkerViewModelable) { + + super.bind(viewModel: viewModel) + + // Output + viewModel + .postDetail? + .drive(onNext: { + [weak self] detailVO in + + guard let self else { return } + + // card + let cardRO: WorkerWorknetEmployCardRO = .create(vo: detailVO) + cardView.applyRO(ro: cardRO) + + // 모집요강 + recruitmentDetailTextView.textString = detailVO.content + recruitmentDetailTextView.sizeToFit() + + // 근무조건 + workShapeLabel.textString = detailVO.workingSchedule + workTimeLabel.textString = detailVO.workingTime + paymentConditionLabel.textString = detailVO.payInfo + workAddressLabel.textString = detailVO.clientAddress + + // 전형방법 + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + let applyDeadlineText = dateFormatter.string(from: detailVO.applyDeadline) + applyDeadlineLabel.textString = applyDeadlineText + applyMethodLabel.textString = detailVO.applyMethod + submitMethodLabel.textString = detailVO.recruitmentProcess + submitDocsLabel.textString = detailVO.requiredDocumentation + + // 기관정보 + centerInfoCard.bind( + nameText: detailVO.centerName, + locationText: detailVO.centerAddress + ) + + // 워크넷 링크 + worknetLinkCard.bind( + nameText: detailVO.title, + locationText: detailVO.jobPostingUrl + ) + workNetPostLink = URL(string: detailVO.jobPostingUrl) + }) + .disposed(by: disposeBag) + + viewModel + .starButtonRequestResult? + .drive(onNext: { [weak self] isSuccess in + + guard let self else { return } + + if isSuccess { + cardView.starButton.toggle() + } + }) + .disposed(by: disposeBag) + + // 위치정보 + if let locationInfo = viewModel.locationInfo?.asObservable().share() { + + locationInfo + .subscribe(onNext: { + [weak self] info in + // 위치정보 전달 + self?.workPlaceAndWorkerLocationView.bind(locationRO: info) + }) + .disposed(by: disposeBag) + + // 지도화면 클릭시 + workPlaceAndWorkerLocationView.mapViewBackGround + .rx.tap + .withLatestFrom(locationInfo) + .subscribe { [weak self] locationInfo in + let fullMapVC = WorkPlaceAndWorkerLocationFullVC() + fullMapVC.bind(locationRO: locationInfo) + self?.navigationController?.pushViewController(fullMapVC, animated: true) + } + .disposed(by: disposeBag) + } + + // Input + self.rx.viewWillAppear + .map({ _ in }) + .bind(to: viewModel.requestRefresh) + .disposed(by: disposeBag) + + cardView.starButton + .onTapEvent + .map { state in + // normal인 경우 true / 즐겨찾기 요청 + state == .normal + } + .bind(to: viewModel.starButtonClicked) + .disposed(by: disposeBag) + + // 뒤로가기 버튼 + navigationBar.backButton + .rx.tap + .bind(to: viewModel.backButtonClicked) + .disposed(by: disposeBag) + } + func setAppearance() { view.backgroundColor = DSColor.gray0.color } func setLayout() { - // 모집요강 - - // 근무조건 let workConditionComponentStackList = [ ("근무 형태", workShapeLabel), @@ -123,7 +240,7 @@ public class WorknetPostDetailForWorkerVC: BaseViewController { keyLabel.textString = keyText keyLabel.textAlignment = .left - return HStack([keyLabel, valueLabel], spacing: 32) + return HStack([keyLabel, valueLabel], spacing: 32, alignment: .top) } let workConditionStack: VStack = .init(workConditionComponentStackList, spacing: 8, alignment: .leading) @@ -142,7 +259,7 @@ public class WorknetPostDetailForWorkerVC: BaseViewController { let labelWidth = keyLabel.intrinsicContentSize.width let spacing = 114 - labelWidth - return HStack([keyLabel, valueLabel], spacing: spacing) + return HStack([keyLabel, valueLabel], spacing: spacing, alignment: .top) } let applyMethodComponentStack: VStack = .init(applyMethodComponentStackList, spacing: 8, alignment: .leading) @@ -187,7 +304,10 @@ public class WorknetPostDetailForWorkerVC: BaseViewController { let backgroundView = UIView() backgroundView.backgroundColor = DSColor.gray0.color backgroundView.layoutMargins = .init(top: 24, left: 20, bottom: 24, right: 20) + + containerView.translatesAutoresizingMaskIntoConstraints = false backgroundView.addSubview(containerView) + NSLayoutConstraint.activate([ containerView.topAnchor.constraint(equalTo: backgroundView.layoutMarginsGuide.topAnchor), containerView.leftAnchor.constraint(equalTo: backgroundView.layoutMarginsGuide.leftAnchor), @@ -197,9 +317,10 @@ public class WorknetPostDetailForWorkerVC: BaseViewController { return backgroundView } - let scvContentStack = VStack(viewList, spacing: 8) + let scvContentStack = VStack(viewList, spacing: 8, alignment: .fill) scvContentStack.backgroundColor = DSColor.gray050.color + scvContentStack.translatesAutoresizingMaskIntoConstraints = false scrollView.addSubview(scvContentStack) NSLayoutConstraint.activate([ @@ -228,10 +349,15 @@ public class WorknetPostDetailForWorkerVC: BaseViewController { scrollView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), scrollView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), scrollView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor), - scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) } + func setObservable() { + + //TODO: 워크넷 딥링크 + } + func makeTitleLabel(text: String) -> IdleLabel { let label = IdleLabel(typography: .Subtitle1) label.textAlignment = .left 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 bbc6d96f..3f581e3e 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 @@ -17,7 +17,7 @@ public struct WorkPlaceAndWorkerLocationMapRO { let workPlaceRoadAddress: String let homeToworkPlaceText: NSMutableAttributedString - let distanceToWorkPlaceText: String + let estimatedArrivalTimeText: String let workPlaceLocation: LocationInformation let workerLocation: LocationInformation? @@ -33,7 +33,7 @@ public class WorkPlaceAndWorkerLocationView: VStack { return label }() - let distanceLabel: IdleLabel = { + let estimatedArrivalTimeTextLabel: IdleLabel = { let label = IdleLabel(typography: .Subtitle2) label.textString = "" label.textAlignment = .left @@ -73,7 +73,7 @@ public class WorkPlaceAndWorkerLocationView: VStack { private func setLayout() { let walkingImage = DSKitAsset.Icons.walkingHuman.image.toView() - let timeCostStack = HStack([walkingImage, distanceLabel], spacing: 6, alignment: .center) + let timeCostStack = HStack([walkingImage, estimatedArrivalTimeTextLabel], spacing: 6, alignment: .center) let labelStack = VStack([walkToLocationLabel, timeCostStack], spacing: 4, alignment: .leading) @@ -107,7 +107,7 @@ public class WorkPlaceAndWorkerLocationView: VStack { public func bind(locationRO: WorkPlaceAndWorkerLocationMapRO) { walkToLocationLabel.attributedText = locationRO.homeToworkPlaceText - distanceLabel.textString = locationRO.distanceToWorkPlaceText + estimatedArrivalTimeTextLabel.textString = locationRO.estimatedArrivalTimeText mapView.bind( locationRO: locationRO, 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 6d093724..87c2ba8b 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 @@ -72,7 +72,7 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork let getPostDetailResult = viewWillAppear .flatMap { [recruitmentPostUseCase] _ in recruitmentPostUseCase - .getPostDetailForWorker(id: postId) + .getNativePostDetailForWorker(id: postId) } .share() @@ -109,10 +109,12 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork let range = NSRange(text.range(of: roadAddress)!, in: text) attrText.addAttribute(.font, value: roadTextFont, range: range) + let estimatedArrivalTimeText = self?.timeForDistance(meter: bundle.distanceToWorkPlace) + return WorkPlaceAndWorkerLocationMapRO( workPlaceRoadAddress: roadAddress, homeToworkPlaceText: attrText, - distanceToWorkPlaceText: "\(bundle.distanceToWorkPlace)m", + estimatedArrivalTimeText: estimatedArrivalTimeText ?? "", workPlaceLocation: workPlaceLocation, workerLocation: workerLocation ) @@ -197,20 +199,28 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork getPostDetailFailureAlert, applyRequestFailureAlert ) - .subscribe(self.alert) + .subscribe(onNext: { [weak self] alertVO in + guard let self else { return } + alert.onNext(alertVO) + }) .disposed(by: disposeBag) - // MARK: 로딩 Observable .merge(loadingStartObservables) - .subscribe(showLoading) + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + showLoading.onNext(()) + }) .disposed(by: disposeBag) Observable .merge(loadingEndObservables) .delay(.milliseconds(500), scheduler: MainScheduler.instance) - .subscribe(dismissLoading) + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + dismissLoading.onNext(()) + }) .disposed(by: disposeBag) } @@ -288,4 +298,27 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork } } } + + func timeForDistance(meter: Int) -> String { + switch meter { + case 0..<200: + return "도보 5분 이내" + case 200..<400: + return "도보 5 ~ 10분" + case 400..<700: + return "도보 10 ~ 15분" + case 700..<1000: + return "도보 15 ~ 20분" + case 1000..<1250: + return "도보 20 ~ 25분" + case 1250..<1500: + return "도보 25 ~ 30분" + case 1500..<1750: + return "도보 30 ~ 35분" + case 1750..<2000: + return "도보 35 ~ 40분" + default: + return "도보 40분 ~" + } + } } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/WorknetPostDetailForWorkerVM.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/WorknetPostDetailForWorkerVM.swift new file mode 100644 index 00000000..a34a544c --- /dev/null +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/WorknetPostDetailForWorkerVM.swift @@ -0,0 +1,245 @@ +// +// WorknetPostDetailForWorkerVM.swift +// BaseFeature +// +// Created by choijunios on 9/6/24. +// + +import UIKit +import RxCocoa +import RxSwift +import Entity +import PresentationCore +import UseCaseInterface +import DSKit + +public protocol WorknetPostDetailForWorkerViewModelable: BaseViewModel { + + // Output + var postDetail: Driver? { get } + var locationInfo: Driver? { get } + var starButtonRequestResult: Driver? { get } + + // Input + var requestRefresh: PublishRelay { get } + var backButtonClicked: PublishRelay { get } + var starButtonClicked: PublishRelay { get } +} + +public class WorknetPostDetailForWorkerVM: BaseViewModel, WorknetPostDetailForWorkerViewModelable { + + public weak var coordinator: PostDetailForWorkerCoodinator? + + // Init + private let postId: String + private let recruitmentPostUseCase: RecruitmentPostUseCase + + // Ouput + public var postDetail: RxCocoa.Driver? + public var locationInfo: RxCocoa.Driver? + public var starButtonRequestResult: Driver? + + // Input + public var requestRefresh: RxRelay.PublishRelay = .init() + public var backButtonClicked: RxRelay.PublishRelay = .init() + public var starButtonClicked: RxRelay.PublishRelay = .init() + + + public init( + postId: String, + coordinator: PostDetailForWorkerCoodinator?, + recruitmentPostUseCase: RecruitmentPostUseCase + ) + { + self.postId = postId + self.coordinator = coordinator + self.recruitmentPostUseCase = recruitmentPostUseCase + + super.init() + + let getPostDetailResult = requestRefresh + .flatMap { [recruitmentPostUseCase] _ in + recruitmentPostUseCase + .getWorknetPostDetailForWorker(id: postId) + } + .share() + + let getPostDetailSuccess = getPostDetailResult.compactMap { $0.value } + let getPostDetailFailure = getPostDetailResult.compactMap { $0.error } + + let getPostDetailFailureAlert = getPostDetailFailure + .map { [weak self] error in + DefaultAlertContentVO( + title: "공고 불러오기 실패", + message: error.message + ) { [weak self] in + self?.coordinator?.coordinatorDidFinish() + } + } + + postDetail = getPostDetailSuccess + .asDriver(onErrorDriveWith: .never()) + + // MARK: 센터, 워커 위치정보 + locationInfo = getPostDetailSuccess + .map { [weak self] postVO in + // 요양보호사 위치 가져오기 + let workerLocation = self?.getWorkerLocation() + + let workPlaceLocation: LocationInformation = .init( + longitude: Double(postVO.longitude) ?? 0.0, + latitude: Double(postVO.latitude) ?? 0.0 + ) + + let roadAddress = postVO.clientAddress + let text = "거주지에서 \(roadAddress) 까지" + var normalAttr = Typography.Body2.attributes + normalAttr[.foregroundColor] = DSKitAsset.Colors.gray500.color + + let attrText = NSMutableAttributedString(string: text, attributes: normalAttr) + + let roadTextFont = Typography.Subtitle3.attributes[.font]! + + let range = NSRange(text.range(of: roadAddress)!, in: text) + attrText.addAttribute(.font, value: roadTextFont, range: range) + + let estimatedArrivalTimeText = self?.timeForDistance(meter: postVO.distance) + + return WorkPlaceAndWorkerLocationMapRO( + workPlaceRoadAddress: roadAddress, + homeToworkPlaceText: attrText, + estimatedArrivalTimeText: estimatedArrivalTimeText ?? "", + workPlaceLocation: workPlaceLocation, + workerLocation: workerLocation + ) + } + .asDriver(onErrorRecover: { _ in fatalError() }) + + // MARK: 버튼 처리 + backButtonClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + self.coordinator?.coordinatorDidFinish() + }) + .disposed(by: disposeBag) + + // MARK: 즐겨찾기 + starButtonRequestResult = starButtonClicked + .flatMap { [weak self] isFavoriteRequest in + self?.setPostFavoriteState( + isFavoriteRequest: isFavoriteRequest, + postId: postId, + postType: .native + ) ?? .never() + } + .asDriver(onErrorJustReturn: false) + + + // MARK: Alert + Observable + .merge(getPostDetailFailureAlert) + .subscribe(onNext: { [weak self] alertVO in + guard let self else { return } + alert.onNext(alertVO) + }) + .disposed(by: disposeBag) + } + + // MARK: Test + func getWorkerLocation() -> LocationInformation { + return .init( + longitude: 127.046425, + latitude: 37.504588 + ) + } + + public func setPostFavoriteState(isFavoriteRequest: Bool, postId: String, postType: Entity.RecruitmentPostType) -> RxSwift.Single { + + return Single.create { [weak self] observer in + + guard let self else { return Disposables.create { } } + + let observable: Single>! + + // 로딩화면 시작 + self.showLoading.onNext(()) + + if isFavoriteRequest { + + observable = recruitmentPostUseCase + .addFavoritePost( + postId: postId, + type: postType + ) + + } else { + + observable = recruitmentPostUseCase + .removeFavoritePost(postId: postId) + } + + let reuslt = observable + .asObservable() + .map({ [weak self] result in + + // 로딩화면 종료 + self?.dismissLoading.onNext(()) + + return result + }) + .share() + + let success = reuslt.compactMap { $0.value } + let failure = reuslt.compactMap { $0.error } + + let failureAlertDisposable = failure.map { error in + DefaultAlertContentVO( + title: "즐겨찾기 오류", + message: error.message + ) + } + .asObservable() + .subscribe(onNext: { [weak self] alertVO in + guard let self else { return } + alert.onNext(alertVO) + }) + + + let disposable = Observable + .merge( + success.map { _ in true }.asObservable(), + failure.map { _ in false }.asObservable() + ) + .asSingle() + .subscribe(observer) + + return Disposables.create { + disposable.dispose() + failureAlertDisposable.dispose() + } + } + } + + func timeForDistance(meter: Int) -> String { + switch meter { + case 0..<200: + return "도보 5분 이내" + case 200..<400: + return "도보 5 ~ 10분" + case 400..<700: + return "도보 10 ~ 15분" + case 700..<1000: + return "도보 15 ~ 20분" + case 1000..<1250: + return "도보 20 ~ 25분" + case 1250..<1500: + return "도보 25 ~ 30분" + case 1500..<1750: + return "도보 30 ~ 35분" + case 1750..<2000: + return "도보 35 ~ 40분" + default: + return "도보 40분 ~" + } + } +} 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 4d6d3455..1822595a 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 @@ -70,6 +70,9 @@ public extension BaseViewController { ) let close = UIAlertAction(title: "닫기", style: .default) { [weak self] _ in self?.alert(vo: vo) + + // dismiss + vo.onDismiss?() } alert.addAction(close) present(alert, animated: true, completion: nil) 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 fc26e537..146f3e8e 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 @@ -302,8 +302,8 @@ public class WorkerRecruitmentPostBoardVM: BaseViewModel, WorkerRecruitmentPostB // MARK: WorkerPagablePostBoardVMable + Extension extension WorkerPagablePostBoardVMable { - public func showPostDetail(id: String) { - coordinator?.showPostDetail(postId: id) + public func showPostDetail(postType: RecruitmentPostType, id: String) { + coordinator?.showPostDetail(postType: postType, postId: id) } public func setPostFavoriteState(isFavoriteRequest: Bool, postId: String, postType: Entity.RecruitmentPostType) -> RxSwift.Single { diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Worker/WorkerRecruitmentBoardCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Worker/WorkerRecruitmentBoardCoordinatable.swift index 721df7cd..bfd1a779 100644 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Worker/WorkerRecruitmentBoardCoordinatable.swift +++ b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Worker/WorkerRecruitmentBoardCoordinatable.swift @@ -6,10 +6,11 @@ // import Foundation +import Entity public protocol WorkerRecruitmentBoardCoordinatable: ParentCoordinator { /// 요양보호사가 볼 수 있는 공고 상세정보를 표시합니다. - func showPostDetail(postId: String) + func showPostDetail(postType: RecruitmentPostType, postId: String) /// 센터 프로필을 표시합니다. func showCenterProfile(centerId: String) From c319c08af735a11f868276de3a58169fe803a761 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Sat, 7 Sep 2024 01:29:16 +0900 Subject: [PATCH 13/17] =?UTF-8?q?[IDLE-000]=20=EA=B3=B5=EA=B3=A0=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=9A=94=EC=96=91=EB=B3=B4=ED=98=B8=EC=82=AC=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AppliedAndLikedBoardCoordinator.swift | 3 +- .../WorkerRecruitmentBoardCoordinator.swift | 3 +- .../NativeRecruitmentPostDetailDTO.swift | 4 +-- .../DTO/UserInfo/WorkerProfileDTO.swift | 7 ++-- .../Entity/VO/UserInfo/WorkerProfileVO.swift | 14 +++++--- .../PostDetailForWorkerCoodinator.swift | 16 +++++++-- .../NativePostDetailForWorkerVM.swift | 34 ++++++++++++------- 7 files changed, 57 insertions(+), 24 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 351617ba..f7fb2469 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/AppliedAndLikedBoardCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/AppliedAndLikedBoardCoordinator.swift @@ -73,7 +73,8 @@ extension AppliedAndLikedBoardCoordinator { postId: postId, parent: self, navigationController: navigationController, - recruitmentPostUseCase: injector.resolve(RecruitmentPostUseCase.self) + recruitmentPostUseCase: injector.resolve(RecruitmentPostUseCase.self), + workerProfileUseCase: injector.resolve(WorkerProfileUseCase.self) ) ) addChildCoordinator(coodinator) diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerRecruitmentBoardCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerRecruitmentBoardCoordinator.swift index 8bcf2be6..b4006799 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerRecruitmentBoardCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerRecruitmentBoardCoordinator.swift @@ -62,7 +62,8 @@ extension WorkerRecruitmentBoardCoordinator { postId: postId, parent: self, navigationController: navigationController, - recruitmentPostUseCase: injector.resolve(RecruitmentPostUseCase.self) + recruitmentPostUseCase: injector.resolve(RecruitmentPostUseCase.self), + workerProfileUseCase: injector.resolve(WorkerProfileUseCase.self) ) ) addChildCoordinator(coodinator) diff --git a/project/Projects/Data/DataSource/DTO/RecruitmentPost/NativeRecruitmentPostDetailDTO.swift b/project/Projects/Data/DataSource/DTO/RecruitmentPost/NativeRecruitmentPostDetailDTO.swift index aaa6f6bc..b83d80d9 100644 --- a/project/Projects/Data/DataSource/DTO/RecruitmentPost/NativeRecruitmentPostDetailDTO.swift +++ b/project/Projects/Data/DataSource/DTO/RecruitmentPost/NativeRecruitmentPostDetailDTO.swift @@ -106,8 +106,8 @@ public struct NativeRecruitmentPostDetailDTO: EntityRepresentable { ) let jobLocation: LocationInformation = .init( - longitude: Double(longitude)!, - latitude: Double(latitude)! + longitude: Double(longitude) ?? 0.0, + latitude: Double(latitude) ?? 0.0 ) // MARK: Apply date diff --git a/project/Projects/Data/DataSource/DTO/UserInfo/WorkerProfileDTO.swift b/project/Projects/Data/DataSource/DTO/UserInfo/WorkerProfileDTO.swift index 8072ab90..2669b62f 100644 --- a/project/Projects/Data/DataSource/DTO/UserInfo/WorkerProfileDTO.swift +++ b/project/Projects/Data/DataSource/DTO/UserInfo/WorkerProfileDTO.swift @@ -20,7 +20,8 @@ public struct CarerProfileDTO: Codable { let speciality: String? let profileImageUrl: String? let jobSearchStatus: String - + let longitude: String + let latitude: String public func toVO() -> WorkerProfileVO { @@ -37,7 +38,9 @@ public struct CarerProfileDTO: Codable { jibunAddress: lotNumberAddress ), introductionText: introduce ?? "", - specialty: speciality ?? "" + specialty: speciality ?? "", + longitude: Double(longitude)!, + latitude: Double(latitude)! ) } } diff --git a/project/Projects/Domain/Entity/VO/UserInfo/WorkerProfileVO.swift b/project/Projects/Domain/Entity/VO/UserInfo/WorkerProfileVO.swift index f1947225..51c3ccee 100644 --- a/project/Projects/Domain/Entity/VO/UserInfo/WorkerProfileVO.swift +++ b/project/Projects/Domain/Entity/VO/UserInfo/WorkerProfileVO.swift @@ -10,8 +10,8 @@ import Foundation public struct WorkerProfileVO: Codable { public let profileImageURL: String? - - + public let longitude: Double + public let latitude: Double public let nameText: String public let phoneNumber: String public let isLookingForJob: Bool @@ -32,7 +32,9 @@ public struct WorkerProfileVO: Codable { expYear: Int?, address: AddressInformation, introductionText: String, - specialty: String + specialty: String, + longitude: Double, + latitude: Double ) { self.profileImageURL = profileImageURL self.nameText = nameText @@ -44,6 +46,8 @@ public struct WorkerProfileVO: Codable { self.address = address self.introductionText = introductionText self.specialty = specialty + self.latitude = latitude + self.longitude = longitude } } @@ -61,6 +65,8 @@ public extension WorkerProfileVO { jibunAddress: "" ), introductionText: "", - specialty: "" + specialty: "", + longitude: 0.0, + latitude: 0.0 ) } 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 cf86b423..74245e50 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 @@ -18,13 +18,22 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { weak var parent: WorkerRecruitmentBoardCoordinatable? let navigationController: UINavigationController let recruitmentPostUseCase: RecruitmentPostUseCase + let workerProfileUseCase: WorkerProfileUseCase - public init(postType: RecruitmentPostType, postId: String, parent: WorkerRecruitmentBoardCoordinatable? = nil, navigationController: UINavigationController, recruitmentPostUseCase: RecruitmentPostUseCase) { + public init( + postType: RecruitmentPostType, + postId: String, + parent: WorkerRecruitmentBoardCoordinatable? = nil, + navigationController: UINavigationController, + recruitmentPostUseCase: RecruitmentPostUseCase, + workerProfileUseCase: WorkerProfileUseCase + ) { self.postType = postType self.postId = postId self.parent = parent self.navigationController = navigationController self.recruitmentPostUseCase = recruitmentPostUseCase + self.workerProfileUseCase = workerProfileUseCase } } @@ -35,6 +44,7 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { let postId: String public let navigationController: UINavigationController let recruitmentPostUseCase: RecruitmentPostUseCase + let workerProfileUseCase: WorkerProfileUseCase public init( dependency: Dependency @@ -44,6 +54,7 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { self.parent = dependency.parent self.navigationController = dependency.navigationController self.recruitmentPostUseCase = dependency.recruitmentPostUseCase + self.workerProfileUseCase = dependency.workerProfileUseCase } deinit { @@ -60,7 +71,8 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { let vm = NativePostDetailForWorkerVM( postId: postId, coordinator: self, - recruitmentPostUseCase: recruitmentPostUseCase + recruitmentPostUseCase: recruitmentPostUseCase, + workerProfileUseCase: workerProfileUseCase ) nativeDetailVC.bind(viewModel: vm) vc = nativeDetailVC 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 87c2ba8b..a2f4781e 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 @@ -37,6 +37,7 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork // Init private let postId: String private let recruitmentPostUseCase: RecruitmentPostUseCase + private let workerProfileUseCase: WorkerProfileUseCase // Ouput public var postForWorkerBundle: RxCocoa.Driver? @@ -55,12 +56,14 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork public init( postId: String, coordinator: PostDetailForWorkerCoodinator?, - recruitmentPostUseCase: RecruitmentPostUseCase + recruitmentPostUseCase: RecruitmentPostUseCase, + workerProfileUseCase: WorkerProfileUseCase ) { self.postId = postId self.coordinator = coordinator self.recruitmentPostUseCase = recruitmentPostUseCase + self.workerProfileUseCase = workerProfileUseCase super.init() @@ -90,10 +93,25 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork postForWorkerBundle = getPostDetailSuccess.asDriver(onErrorRecover: { _ in fatalError() }) // MARK: 센터, 워커 위치정보 - locationInfo = getPostDetailSuccess - .map { [weak self] bundle in + let requestWorkerLocationResult = viewWillAppear + .flatMap({ [workerProfileUseCase] _ in + workerProfileUseCase + .getProfile(mode: .myProfile) + }) + + let requestWorkerLocationSuccess = requestWorkerLocationResult.compactMap { $0.value } + let requestWorkerLocationFailure = requestWorkerLocationResult.compactMap { $0.error } + + locationInfo = Observable + .zip(getPostDetailSuccess, requestWorkerLocationSuccess) + .map { + [weak self] bundle, profile in + // 요양보호사 위치 가져오기 - let workerLocation = self?.getWorkerLocation() + let workerLocation: LocationInformation = .init( + longitude: profile.longitude, + latitude: profile.latitude + ) let workPlaceLocation = bundle.jobLocation @@ -223,14 +241,6 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork }) .disposed(by: disposeBag) } - - // MARK: Test - func getWorkerLocation() -> LocationInformation { - return .init( - longitude: 127.046425, - latitude: 37.504588 - ) - } public func setPostFavoriteState(isFavoriteRequest: Bool, postId: String, postType: Entity.RecruitmentPostType) -> RxSwift.Single { From 1671469bdb20289ac0677e6a7aed1b937bda5462 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Sat, 7 Sep 2024 01:38:28 +0900 Subject: [PATCH 14/17] =?UTF-8?q?[IDLE-000]=20=EC=9B=8C=ED=81=AC=EB=84=B7?= =?UTF-8?q?=20=EA=B3=B5=EA=B3=A0=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PostDetailForWorkerCoodinator.swift | 3 +- .../WorknetPostDetailForWorkerVC.swift | 18 +++++++++- .../NativePostDetailForWorkerVM.swift | 27 ++++++++++----- .../WorknetPostDetailForWorkerVM.swift | 33 ++++++++++++------- 4 files changed, 58 insertions(+), 23 deletions(-) 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 74245e50..204d0555 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 @@ -81,7 +81,8 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { let vm = WorknetPostDetailForWorkerVM( postId: postId, coordinator: self, - recruitmentPostUseCase: recruitmentPostUseCase + recruitmentPostUseCase: recruitmentPostUseCase, + workerProfileUseCase: workerProfileUseCase ) worknetDetailVC.bind(viewModel: vm) vc = worknetDetailVC diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift index 99f1da4b..765ac4a6 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift @@ -41,6 +41,7 @@ public class WorknetPostDetailForWorkerVC: BaseViewController { setAppearance() setLayout() + setObservable() } // 모집 요강 @@ -355,7 +356,22 @@ public class WorknetPostDetailForWorkerVC: BaseViewController { func setObservable() { - //TODO: 워크넷 딥링크 + worknetLinkCard + .rx.tap + .subscribe(onNext: { [weak self] in + guard let self, let url = self.workNetPostLink else { return } + + _ = openDeepLink(url: url) + }) + .disposed(by: disposeBag) + } + + private func openDeepLink(url: URL) -> Bool { + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + return true + } + return false } func makeTitleLabel(text: String) -> IdleLabel { 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 a2f4781e..110e354d 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 @@ -82,14 +82,6 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork let getPostDetailSuccess = getPostDetailResult.compactMap { $0.value } let getPostDetailFailure = getPostDetailResult.compactMap { $0.error } - let getPostDetailFailureAlert = getPostDetailFailure - .map { error in - DefaultAlertContentVO( - title: "공고 불러오기 실패", - message: error.message - ) - } - postForWorkerBundle = getPostDetailSuccess.asDriver(onErrorRecover: { _ in fatalError() }) // MARK: 센터, 워커 위치정보 @@ -187,6 +179,22 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork message: error.message ) } + + let getPostDetailFailureAlert = getPostDetailFailure + .map { error in + DefaultAlertContentVO( + title: "공고 불러오기 실패", + message: error.message + ) + } + + let requestWorkerLocationFailureAlert = requestWorkerLocationFailure + .map { error in + DefaultAlertContentVO( + title: "요양보호사 위치정보 확인 실패", + message: error.message + ) + } // MARK: 즐겨찾기 starButtonRequestResult = starButtonClicked @@ -215,7 +223,8 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork Observable .merge( getPostDetailFailureAlert, - applyRequestFailureAlert + applyRequestFailureAlert, + requestWorkerLocationFailureAlert ) .subscribe(onNext: { [weak self] alertVO in guard let self else { return } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/WorknetPostDetailForWorkerVM.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/WorknetPostDetailForWorkerVM.swift index a34a544c..349f53a0 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/WorknetPostDetailForWorkerVM.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/WorknetPostDetailForWorkerVM.swift @@ -33,6 +33,7 @@ public class WorknetPostDetailForWorkerVM: BaseViewModel, WorknetPostDetailForWo // Init private let postId: String private let recruitmentPostUseCase: RecruitmentPostUseCase + private let workerProfileUseCase: WorkerProfileUseCase // Ouput public var postDetail: RxCocoa.Driver? @@ -48,12 +49,14 @@ public class WorknetPostDetailForWorkerVM: BaseViewModel, WorknetPostDetailForWo public init( postId: String, coordinator: PostDetailForWorkerCoodinator?, - recruitmentPostUseCase: RecruitmentPostUseCase + recruitmentPostUseCase: RecruitmentPostUseCase, + workerProfileUseCase: WorkerProfileUseCase ) { self.postId = postId self.coordinator = coordinator self.recruitmentPostUseCase = recruitmentPostUseCase + self.workerProfileUseCase = workerProfileUseCase super.init() @@ -81,10 +84,24 @@ public class WorknetPostDetailForWorkerVM: BaseViewModel, WorknetPostDetailForWo .asDriver(onErrorDriveWith: .never()) // MARK: 센터, 워커 위치정보 - locationInfo = getPostDetailSuccess - .map { [weak self] postVO in + let requestWorkerLocationResult = requestRefresh + .flatMap({ [workerProfileUseCase] _ in + workerProfileUseCase + .getProfile(mode: .myProfile) + }) + + let requestWorkerLocationSuccess = requestWorkerLocationResult.compactMap { $0.value } + let requestWorkerLocationFailure = requestWorkerLocationResult.compactMap { $0.error } + + locationInfo = Observable + .zip(getPostDetailSuccess, requestWorkerLocationSuccess) + .map { [weak self] postVO, profile in + // 요양보호사 위치 가져오기 - let workerLocation = self?.getWorkerLocation() + let workerLocation: LocationInformation = .init( + longitude: profile.longitude, + latitude: profile.latitude + ) let workPlaceLocation: LocationInformation = .init( longitude: Double(postVO.longitude) ?? 0.0, @@ -144,14 +161,6 @@ public class WorknetPostDetailForWorkerVM: BaseViewModel, WorknetPostDetailForWo }) .disposed(by: disposeBag) } - - // MARK: Test - func getWorkerLocation() -> LocationInformation { - return .init( - longitude: 127.046425, - latitude: 37.504588 - ) - } public func setPostFavoriteState(isFavoriteRequest: Bool, postId: String, postType: Entity.RecruitmentPostType) -> RxSwift.Single { From a8358d2e7dfd7274dd28f01c12ddaaf481802484 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Sat, 7 Sep 2024 10:46:48 +0900 Subject: [PATCH 15/17] =?UTF-8?q?[IDLE-000]=20=EC=9B=8C=ED=81=AC=EB=84=B7?= =?UTF-8?q?=20=EA=B3=B5=EA=B3=A0=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=EB=B6=88=EA=B0=80=EC=8B=9C=20alert=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Postdetail/WorknetPostDetailForWorkerVC.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift index 765ac4a6..4a3166e2 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/WorknetPostDetailForWorkerVC.swift @@ -361,7 +361,11 @@ public class WorknetPostDetailForWorkerVC: BaseViewController { .subscribe(onNext: { [weak self] in guard let self, let url = self.workNetPostLink else { return } - _ = openDeepLink(url: url) + let isSafaiOpended = openDeepLink(url: url) + + if !isSafaiOpended { + self.showAlert(vo: .init(title: "공고 확인 실패", message: "처리과정에서 문제가 발생했습니다.")) + } }) .disposed(by: disposeBag) } From 6c99d9a5ad7cd6ded1cba07ae1e9515debde8dc7 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Sat, 7 Sep 2024 11:51:48 +0900 Subject: [PATCH 16/17] =?UTF-8?q?[IDLE-000]=20=EC=95=B1=EB=82=B4/=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=EB=84=B7=20=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0?= =?UTF-8?q?=ED=95=9C=20=EA=B3=B5=EA=B3=A0=20API=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefaultRecruitmentPostRepository.swift | 65 ++------- .../Data/DataSource/API/CrawlingPostAPI.swift | 11 +- .../DataSource/API/RcruitmentPostAPI.swift | 20 ++- .../RecuritmentPostListForWorkerDTO.swift | 34 ++++- .../Util/Extension/Single+Extension.swift | 14 +- .../DefualtRecruitmentPostUseCase.swift | 28 ++-- .../NativeRecruitmentPostForWorkerVO.swift | 10 +- .../List/RecruitmentPostListForWorkerVO.swift | 2 +- .../WorknetRecruitmentPostForWorkerVO.swift | 8 +- .../RecruitmentPostRepository.swift | 7 +- .../RecruitmentPostUseCase.swift | 2 +- .../RecruitmentPost/StarredPostBoardVM.swift | 136 ++++++------------ 12 files changed, 145 insertions(+), 192 deletions(-) diff --git a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift index f76bcfd8..6625c5e4 100644 --- a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift +++ b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift @@ -114,37 +114,23 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { api: .getOnGoingNativePostListForWorker(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() - } + .mapToEntity(RecruitmentPostListForWorkerDTO.self) } - public func getFavoritePostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single { + public func getNativeFavoritePostListForWorker() -> RxSwift.Single<[RecruitmentPostForWorkerRepresentable]> { recruitmentPostService.request( - api: .getFavoritePostListForWorker(nextPageId: nextPageId, requestCnt: String(requestCnt)), + api: .getNativeFavoritePost, 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() - } + .mapToEntity(FavoriteRecruitmentPostListForWorkerDTO.self) + } + + public func getWorknetFavoritePostListForWorker() -> RxSwift.Single<[RecruitmentPostForWorkerRepresentable]> { + crawlingPostService.request( + api: .getWorknetFavoritePost, + with: .withToken + ) + .mapToEntity(FavoriteRecruitmentPostListForWorkerDTO.self) } public func getAppliedPostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single { @@ -152,18 +138,7 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { 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) - }) - .map { dto in - dto.toEntity() - } + .mapToEntity(RecruitmentPostListForWorkerDTO.self) } public func getWorknetPostListForWorker(nextPageId: String?, requestCnt: Int) -> RxSwift.Single { @@ -172,19 +147,7 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { api: .getPostList(nextPageId: nextPageId, requestCnt: 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() - } - + .mapToEntity(RecruitmentPostListForWorkerDTO.self) } public func applyToPost(postId: String, method: ApplyType) -> Single { diff --git a/project/Projects/Data/DataSource/API/CrawlingPostAPI.swift b/project/Projects/Data/DataSource/API/CrawlingPostAPI.swift index 0dc267c4..c5d01545 100644 --- a/project/Projects/Data/DataSource/API/CrawlingPostAPI.swift +++ b/project/Projects/Data/DataSource/API/CrawlingPostAPI.swift @@ -13,6 +13,7 @@ public enum CrawlingPostAPI { case getPostList(nextPageId: String?, requestCnt: Int) case getDetail(postId: String) + case getWorknetFavoritePost } extension CrawlingPostAPI: BaseAPI { @@ -22,18 +23,22 @@ extension CrawlingPostAPI: BaseAPI { public var path: String { switch self { - case .getPostList(let nextPageId, let requestCnt): + case .getPostList: "" case .getDetail(let postId): "/\(postId)" + case .getWorknetFavoritePost: + "/my/favorites" } } public var method: Moya.Method { switch self { - case .getPostList(let nextPageId, let requestCnt): + case .getPostList: .get - case .getDetail(let postId): + case .getDetail: + .get + case .getWorknetFavoritePost: .get } } diff --git a/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift b/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift index b5d6164e..48cf8c5e 100644 --- a/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift +++ b/project/Projects/Data/DataSource/API/RcruitmentPostAPI.swift @@ -31,8 +31,10 @@ public enum RcruitmentPostAPI { // Worker case getOnGoingNativePostListForWorker(nextPageId: String?, requestCnt: String) - case getFavoritePostListForWorker(nextPageId: String?, requestCnt: String) case getAppliedPostListForWorker(nextPageId: String?, requestCnt: String) + + // Favorite posts + case getNativeFavoritePost case addFavoritePost(id: String, jobPostingType: RecruitmentPostType) case removeFavoritePost(id: String) } @@ -73,11 +75,12 @@ extension RcruitmentPostAPI: BaseAPI { case .getOnGoingNativePostListForWorker: "" - case .getFavoritePostListForWorker: - "/my/favorites" case .getAppliedPostListForWorker: "/carer/my/applied" + + case .getNativeFavoritePost: + "/my/favorites" case .addFavoritePost(let id, _): "/\(id)/favorites" case .removeFavoritePost(let id): @@ -115,11 +118,11 @@ extension RcruitmentPostAPI: BaseAPI { case .getOnGoingNativePostListForWorker: .get - case .getFavoritePostListForWorker: - .get case .getAppliedPostListForWorker: .get + case .getNativeFavoritePost: + .get case .addFavoritePost: .post case .removeFavoritePost: @@ -135,11 +138,6 @@ 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 @@ -156,7 +154,6 @@ extension RcruitmentPostAPI: BaseAPI { var parameterEncoding: ParameterEncoding { switch self { case .getOnGoingNativePostListForWorker, - .getFavoritePostListForWorker, .getAppliedPostListForWorker: return URLEncoding.queryString default: @@ -167,7 +164,6 @@ extension RcruitmentPostAPI: BaseAPI { public var task: Moya.Task { switch self { case .getOnGoingNativePostListForWorker, - .getFavoritePostListForWorker, .getAppliedPostListForWorker: .requestParameters(parameters: bodyParameters ?? [:], encoding: parameterEncoding) case .registerPost(let bodyData): diff --git a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift index ed2c3de5..b34ef0c7 100644 --- a/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift +++ b/project/Projects/Data/DataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift @@ -13,7 +13,7 @@ public protocol EntityRepresentable: Codable { func toEntity() -> Entity } -public struct RecruitmentPostListForWorkerDTO: Codable where T.Entity: RecruitmentPostForWorkerRepresentable { +public struct RecruitmentPostListForWorkerDTO: EntityRepresentable where T.Entity: RecruitmentPostForWorkerRepresentable { public let items: [T] public let next: String? @@ -29,6 +29,18 @@ public struct RecruitmentPostListForWorkerDTO: Codable w } } +public struct FavoriteRecruitmentPostListForWorkerDTO: EntityRepresentable where T.Entity: RecruitmentPostForWorkerRepresentable { + + public let favoriteJobPostings: [T] + + public func toEntity() -> [RecruitmentPostForWorkerRepresentable] { + + favoriteJobPostings.map { dto in + dto.toEntity() + } + } +} + // MARK: Worknet post의 카드 정보 public struct WorkNetRecruitmentPostForWorkerDTO: EntityRepresentable { @@ -41,6 +53,7 @@ public struct WorkNetRecruitmentPostForWorkerDTO: EntityRepresentable { public let applyDeadline: String public let isFavorite: Bool public let jobPostingType: RecruitmentPostType + public let createdAt: String? public func toEntity() -> WorknetRecruitmentPostVO { @@ -49,6 +62,12 @@ public struct WorkNetRecruitmentPostForWorkerDTO: EntityRepresentable { let deadlineDate = dateFormatter.date(from: self.applyDeadline)! + let iso8601Formatter = ISO8601DateFormatter() + var createdDate: Date? + if let createdAt { + createdDate = iso8601Formatter.date(from: createdAt) + } + return .init( id: id, title: title, @@ -58,7 +77,8 @@ public struct WorkNetRecruitmentPostForWorkerDTO: EntityRepresentable { payInfo: payInfo, applyDeadline: deadlineDate, isFavorite: isFavorite, - postType: jobPostingType + postType: jobPostingType, + beFavoritedTime: createdDate ) } } @@ -84,6 +104,7 @@ public struct NativeRecruitmentPostForWorkerDTO: EntityRepresentable { public let applyTime: String? public let isFavorite: Bool public let jobPostingType: RecruitmentPostType + public let createdAt: String? public func toEntity() -> NativeRecruitmentPostForWorkerVO { @@ -97,6 +118,12 @@ public struct NativeRecruitmentPostForWorkerDTO: EntityRepresentable { let deadlineDate = self.applyDeadline != nil ? dateFormatter.date(from: self.applyDeadline!) : nil let applyDate = self.applyTime != nil ? dateFormatter.date(from: self.applyDeadline!) : nil + let iso8601Formatter = ISO8601DateFormatter() + var createdDate: Date? + if let createdAt { + createdDate = iso8601Formatter.date(from: createdAt) + } + return .init( postId: id, workDays: workDayList, @@ -115,7 +142,8 @@ public struct NativeRecruitmentPostForWorkerDTO: EntityRepresentable { distanceFromWorkPlace: distance, applyTime: applyDate, isFavorite: isFavorite, - postType: jobPostingType + postType: jobPostingType, + beFavoritedTime: createdDate ) } } diff --git a/project/Projects/Data/DataSource/Util/Extension/Single+Extension.swift b/project/Projects/Data/DataSource/Util/Extension/Single+Extension.swift index 34030e83..252f8d9b 100644 --- a/project/Projects/Data/DataSource/Util/Extension/Single+Extension.swift +++ b/project/Projects/Data/DataSource/Util/Extension/Single+Extension.swift @@ -15,9 +15,17 @@ public extension PrimitiveSequence where Trait == SingleTrait, Element == Respon func mapToEntity(_ type: T.Type) -> Single { map(T.self) - .map { dto in - dto.toEntity() - } + .catch({ error in + if let moyaError = error as? MoyaError, case .objectMapping(let error, _) = moyaError { + #if DEBUG + print("[디코딩에러] \(String(describing: T.self))") + #endif + } + return .error(error) + }) + .map { dto in + dto.toEntity() + } } } diff --git a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift index d293a6d8..58969877 100644 --- a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift @@ -125,30 +125,18 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { return convert(task: stream) } - public func getFavoritePostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> RxSwift.Single> { + public func getFavoritePostListForWorker() -> RxSwift.Single> { - let stream: Single! + let nativeList = repository.getNativeFavoritePostListForWorker() + let worknetList = repository.getWorknetFavoritePostListForWorker() - 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() + let task = Single + .zip(nativeList, worknetList) + .map { (native, worknet) in + native + worknet } - } - return convert(task: stream) + return convert(task: task) } public func getAppliedPostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> RxSwift.Single> { diff --git a/project/Projects/Domain/Entity/VO/Post/List/NativeRecruitmentPostForWorkerVO.swift b/project/Projects/Domain/Entity/VO/Post/List/NativeRecruitmentPostForWorkerVO.swift index 513d3cc0..b771c191 100644 --- a/project/Projects/Domain/Entity/VO/Post/List/NativeRecruitmentPostForWorkerVO.swift +++ b/project/Projects/Domain/Entity/VO/Post/List/NativeRecruitmentPostForWorkerVO.swift @@ -9,7 +9,9 @@ import Foundation public struct NativeRecruitmentPostForWorkerVO: RecruitmentPostForWorkerRepresentable { + // protocol required public var postType: RecruitmentPostType + public var beFavoritedTime: Date? public let postId: String @@ -34,6 +36,7 @@ public struct NativeRecruitmentPostForWorkerVO: RecruitmentPostForWorkerRepresen public let applyTime: Date? public let isFavorite: Bool + public init( postId: String, workDays: [WorkDay], @@ -52,7 +55,8 @@ public struct NativeRecruitmentPostForWorkerVO: RecruitmentPostForWorkerRepresen distanceFromWorkPlace: Int, applyTime: Date?, isFavorite: Bool, - postType: RecruitmentPostType + postType: RecruitmentPostType, + beFavoritedTime: Date? ) { self.postId = postId self.workDays = workDays @@ -72,6 +76,7 @@ public struct NativeRecruitmentPostForWorkerVO: RecruitmentPostForWorkerRepresen self.applyTime = applyTime self.isFavorite = isFavorite self.postType = postType + self.beFavoritedTime = beFavoritedTime } public static let mock = NativeRecruitmentPostForWorkerVO( @@ -92,6 +97,7 @@ public struct NativeRecruitmentPostForWorkerVO: RecruitmentPostForWorkerRepresen distanceFromWorkPlace: 2500, applyTime: Date(), isFavorite: true, - postType: .native + postType: .native, + beFavoritedTime: Calendar.current.date(byAdding: .day, value: 7, to: Date()) ) } diff --git a/project/Projects/Domain/Entity/VO/Post/List/RecruitmentPostListForWorkerVO.swift b/project/Projects/Domain/Entity/VO/Post/List/RecruitmentPostListForWorkerVO.swift index f02632c1..87186311 100644 --- a/project/Projects/Domain/Entity/VO/Post/List/RecruitmentPostListForWorkerVO.swift +++ b/project/Projects/Domain/Entity/VO/Post/List/RecruitmentPostListForWorkerVO.swift @@ -19,7 +19,7 @@ public struct RecruitmentPostListForWorkerVO { self.fetchedPostCount = fetchedPostCount } } - public protocol RecruitmentPostForWorkerRepresentable { var postType: RecruitmentPostType { get } + var beFavoritedTime: Date? { get } } diff --git a/project/Projects/Domain/Entity/VO/Post/List/WorknetRecruitmentPostForWorkerVO.swift b/project/Projects/Domain/Entity/VO/Post/List/WorknetRecruitmentPostForWorkerVO.swift index fd6c0562..2a69386f 100644 --- a/project/Projects/Domain/Entity/VO/Post/List/WorknetRecruitmentPostForWorkerVO.swift +++ b/project/Projects/Domain/Entity/VO/Post/List/WorknetRecruitmentPostForWorkerVO.swift @@ -9,7 +9,9 @@ import Foundation public struct WorknetRecruitmentPostVO: RecruitmentPostForWorkerRepresentable { - public let postType: RecruitmentPostType + // protocol required + public var postType: RecruitmentPostType + public var beFavoritedTime: Date? public let id: String public let title: String @@ -29,7 +31,8 @@ public struct WorknetRecruitmentPostVO: RecruitmentPostForWorkerRepresentable { payInfo: String, applyDeadline: Date, isFavorite: Bool, - postType: RecruitmentPostType + postType: RecruitmentPostType, + beFavoritedTime: Date? = nil ) { self.id = id self.title = title @@ -40,5 +43,6 @@ public struct WorknetRecruitmentPostVO: RecruitmentPostForWorkerRepresentable { self.applyDeadline = applyDeadline self.isFavorite = isFavorite self.postType = postType + self.beFavoritedTime = beFavoritedTime } } diff --git a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift index bee72ff8..e920fd7c 100644 --- a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift +++ b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift @@ -52,8 +52,11 @@ public protocol RecruitmentPostRepository: RepositoryBase { /// 요양보호사가 확인하는 케어밋 자체 공고정보를 가져옵니다. func getNativePostListForWorker(nextPageId: String?, requestCnt: Int) -> Single - /// 요양보호사가 확인하는 케어밋 자체 공고정보를 가져옵니다. - func getFavoritePostListForWorker(nextPageId: String?, requestCnt: Int) -> Single + /// 요양보호사가 즐겨찾는 케어밋 자체 공고정보를 가져옵니다. + func getNativeFavoritePostListForWorker() -> Single<[RecruitmentPostForWorkerRepresentable]> + + /// 요양보호사가 즐겨찾는 워크넷 공고정보를 가져옵니다. + func getWorknetFavoritePostListForWorker() -> Single<[RecruitmentPostForWorkerRepresentable]> /// 요양보호사가 확인하는 케어밋 자체 공고정보를 가져옵니다. 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 3d4c778b..a1689a7a 100644 --- a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift @@ -56,7 +56,7 @@ public protocol RecruitmentPostUseCase: UseCaseBase { func getPostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> Single> /// 요양보호사가 즐겨찾기한 공고리스트를 호출합니다. - func getFavoritePostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> Single> + func getFavoritePostListForWorker() -> Single> /// 요양보호사가 지원한 공고리스트를 호출합니다. func getAppliedPostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> Single> 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 ff3546d2..756f7041 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift @@ -29,102 +29,53 @@ public class StarredPostBoardVM: BaseViewModel, WorkerAppliablePostBoardVMable { public weak var coordinator: WorkerRecruitmentBoardCoordinatable? public let recruitmentPostUseCase: RecruitmentPostUseCase - // Paging - /// 값이 nil이라면 요청을 보내지 않습니다. - var nextPagingRequest: PostPagingRequestForWorker? - /// 가장최신의 데이터를 홀드, 다음 요청시 해당데이터에 새로운 데이터를 더해서 방출 - private let currentPostVO: BehaviorRelay<[RecruitmentPostForWorkerRepresentable]> = .init(value: []) - public init(coordinator: WorkerRecruitmentBoardCoordinatable, recruitmentPostUseCase: RecruitmentPostUseCase) { self.coordinator = coordinator self.recruitmentPostUseCase = recruitmentPostUseCase - self.nextPagingRequest = .initial super.init() - var loadingStartObservables: [Observable] = [] - var loadingEndObservables: [Observable] = [] - // MARK: 공고리스트 처음부터 요청하기 - let initialRequest = requestInitialPageRequest - .flatMap { [weak self, recruitmentPostUseCase] request in + let initialRequestResult = requestInitialPageRequest + .flatMap { [weak self, recruitmentPostUseCase] _ in - self?.currentPostVO.accept([]) - self?.nextPagingRequest = .initial + self?.showLoading.onNext(()) return recruitmentPostUseCase - .getFavoritePostListForWorker( - request: .initial, - postCount: 10 - ) + .getFavoritePostListForWorker() } .share() - // 로딩 시작 - loadingStartObservables.append(initialRequest.map { _ in }) - - // MARK: 공고리스트 페이징 요청 - let pagingRequest = requestNextPage - .compactMap { [weak self] _ in - // 요청이 없는 경우 요청을 보내지 않는다. - // ThirdPatry에서도 불러올 데이터가 없는 경우입니다. - self?.nextPagingRequest - } - .flatMap { [recruitmentPostUseCase] request in - recruitmentPostUseCase - .getFavoritePostListForWorker( - request: request, - postCount: 10 - ) - } - - let postPageReqeustResult = Observable - .merge(initialRequest, pagingRequest) - .share() - - // 로딩 종료 - loadingEndObservables.append(postPageReqeustResult.map { _ in }) + initialRequestResult + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + dismissLoading.onNext(()) + }) + .disposed(by: disposeBag) - let requestPostListSuccess = postPageReqeustResult.compactMap { $0.value } - let requestPostListFailure = postPageReqeustResult.compactMap { $0.error } + let initialRequestSuccess = initialRequestResult.compactMap { $0.value } + let initialRequestFailure = initialRequestResult.compactMap { $0.error } - postBoardData = Observable - .zip( - currentPostVO, - requestPostListSuccess - ) - .compactMap { [weak self] (prevPostList, fetchedData) -> BoardRefreshResult? in - - guard let self else { return nil } - - let isRefreshed: Bool = self.nextPagingRequest == .initial - - // TODO: ‼️ ‼️ 즐겨찾기 공고의 경우 서버에서 아직 워크넷 공고를 처리하는 방법을 정하지 못했음으로 추후에 수정할 예정입니다. + postBoardData = initialRequestSuccess + .map({ list in - if let next = fetchedData.nextPageId { - // 지원 공고의 경우 써드파티에서 불러올 데이터가 없다. - self.nextPagingRequest = .paging( - source: .native, - nextPageId: next - ) - } else { - self.nextPagingRequest = nil + let sortedList = list.sorted { lhs, rhs in + guard let lhsDate = lhs.beFavoritedTime, let rhsDate = rhs.beFavoritedTime else { + return false + } + + // 최신값을 배열의 앞쪽(화면의 상단)에 노출 + + return lhsDate > rhsDate } - // 화면에 표시할 전체리스트 도출 - let fetchedPosts = fetchedData.posts - var mergedPosts = currentPostVO.value - mergedPosts.append(contentsOf: fetchedPosts) - - // 최근값 업데이트 - self.currentPostVO.accept(mergedPosts) - - return (isRefreshed, mergedPosts) - } + return (true, sortedList) + }) .asDriver(onErrorDriveWith: .never()) // MARK: 지원하기 let applyRequest: PublishRelay = .init() + self.idleAlertVM = applyButtonClicked .map { (postId: String, postTitle: String) in DefaultIdleAlertVM( @@ -136,20 +87,18 @@ public class StarredPostBoardVM: BaseViewModel, WorkerAppliablePostBoardVMable { } } .asDriver(onErrorDriveWith: .never()) - - // 로딩 시작 - loadingStartObservables.append(applyRequest.map { _ in }) let applyRequestResult = applyRequest - .flatMap { [recruitmentPostUseCase] postId in + .flatMap { [weak self, recruitmentPostUseCase] postId in + + self?.showLoading.onNext(()) + // 리스트화면에서는 앱내 지원만 지원합니다. - recruitmentPostUseCase + return recruitmentPostUseCase .applyToPost(postId: postId, method: .app) } .share() - // 로딩 종료 - loadingEndObservables.append(applyRequestResult.map { _ in }) let applyRequestFailure = applyRequestResult.compactMap { $0.error } @@ -161,7 +110,7 @@ public class StarredPostBoardVM: BaseViewModel, WorkerAppliablePostBoardVMable { ) } - let requestPostListFailureAlert = requestPostListFailure + let requestPostListFailureAlert = initialRequestFailure .map { error in DefaultAlertContentVO( title: "즐겨찾기한 공고 불러오기 오류", @@ -171,19 +120,22 @@ public class StarredPostBoardVM: BaseViewModel, WorkerAppliablePostBoardVMable { Observable .merge(applyRequestFailureAlert, requestPostListFailureAlert) - .subscribe(self.alert) - .disposed(by: disposeBag) - - // MARK: 로딩 - Observable - .merge(loadingStartObservables) - .subscribe(self.showLoading) + .subscribe(onNext: { [weak self] alertVO in + guard let self else { return } + alert.onNext(alertVO) + }) .disposed(by: disposeBag) + // MARK: 로딩 종료 Observable - .merge(loadingEndObservables) - .delay(.milliseconds(300), scheduler: MainScheduler.instance) - .subscribe(self.dismissLoading) + .merge( + initialRequestResult.map({ _ in }), + applyRequestResult.map({ _ in }) + ) + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + dismissLoading.onNext(()) + }) .disposed(by: disposeBag) } } From e093cb750b506e9a1952d2d1a95eba4a493902d6 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Sat, 7 Sep 2024 12:06:14 +0900 Subject: [PATCH 17/17] =?UTF-8?q?[IDLE-000]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Projects/Data/ConcretesTests/SaveUserInfoDataTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/project/Projects/Data/ConcretesTests/SaveUserInfoDataTests.swift b/project/Projects/Data/ConcretesTests/SaveUserInfoDataTests.swift index 7802a010..1244698d 100644 --- a/project/Projects/Data/ConcretesTests/SaveUserInfoDataTests.swift +++ b/project/Projects/Data/ConcretesTests/SaveUserInfoDataTests.swift @@ -91,7 +91,9 @@ class SaveUserInfoDataTests: XCTestCase { expYear: nil, address: .init(roadAddress: "test", jibunAddress: "test"), introductionText: "test", - specialty: "test" + specialty: "test", + longitude: 0.0, + latitude: 0.0 ) )