From ac5669a19e96eb393526183d15a20723cfae0f72 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 15 Aug 2024 12:40:50 +0900 Subject: [PATCH 01/21] =?UTF-8?q?[IDLE-313]=20=EC=9A=94=EC=96=91=EB=B3=B4?= =?UTF-8?q?=ED=98=B8=EC=82=AC=20=EB=A9=94=EC=9D=B8=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=BD=94=EB=94=94=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecruitmentBoardCoordinator.swift | 31 ---------- .../WorkerRecruitmentBoardCoordinator.swift | 57 +++++++++++++++++++ 2 files changed, 57 insertions(+), 31 deletions(-) delete mode 100644 project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/RecruitmentBoardCoordinator.swift create mode 100644 project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/RecruitmentBoardCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/RecruitmentBoardCoordinator.swift deleted file mode 100644 index b16bee40..00000000 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/RecruitmentBoardCoordinator.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// RecruitmentBoardCoordinator.swift -// RootFeature -// -// Created by choijunios on 7/25/24. -// - -import UIKit -import PresentationCore - -public class RecruitmentBoardCoordinator: ChildCoordinator { - - public weak var viewControllerRef: UIViewController? - - public var navigationController: UINavigationController - - public init(navigationController: UINavigationController) { - self.navigationController = navigationController - } - - public func start() { - let vc = RecruitmentBoardVC() - - navigationController.pushViewController(vc, animated: false) - } - - public func coordinatorDidFinish() { - - } -} - diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift new file mode 100644 index 00000000..51ec16f0 --- /dev/null +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift @@ -0,0 +1,57 @@ +// +// WorkerRecruitmentBoardCoordinator.swift +// RootFeature +// +// Created by choijunios on 7/25/24. +// + +import UIKit +import PresentationCore + +public protocol WorkerRecruitmentBoardCoordinatable: ParentCoordinator { + /// 요양보호사가 볼 수 있는 공고 상세정보를 표시합니다. + func showPostDetail(postId: String) + + /// 센터 프로필을 표시합니다. + func showCenterProfile(centerId: String) +} + +public class WorkerRecruitmentBoardCoordinator: WorkerRecruitmentBoardCoordinatable { + + public struct Dependency { + let navigationController: UINavigationController + } + + public var childCoordinators: [any PresentationCore.Coordinator] = [] + + public weak var viewControllerRef: UIViewController? + + public var navigationController: UINavigationController + + weak var parent: ParentCoordinator? + + public init(depedency: Dependency) { + self.navigationController = depedency.navigationController + } + + public func start() { + let vc = RecruitmentBoardVC() + + navigationController.pushViewController(vc, animated: false) + } + + public func coordinatorDidFinish() { + popViewController() + parent?.removeChildCoordinator(self) + } +} + +extension WorkerRecruitmentBoardCoordinator { + public func showPostDetail(postId: String) { + + } + public func showCenterProfile(centerId: String) { + + } +} + From 15c1269fe0a7a515764a064d875fe20e85af8e73 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 15 Aug 2024 13:32:28 +0900 Subject: [PATCH 02/21] =?UTF-8?q?[IDLE-163]=20WorkerEmployCardRO=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 요양보호사 메인화면에 표시되는 Cell은 해당 오브젝트를 통해 내용이 보여지게됩니다. --- .../Entity/VO/Employ/WorkerEmployCardVO.swift | 22 ++--- .../Card/Post/Worker/WorkerEmployCard.swift | 90 ++++++++++++++----- .../Post/Worker/WorkerEmployCardCell.swift | 87 +++++++++++++++--- .../Edit/CustomerInformationContentView.swift | 2 +- .../Worker/View/ RecruitmentBoardVC.swift | 2 +- .../WorkerRecruitmentPostBoardVC.swift | 22 +++++ 6 files changed, 174 insertions(+), 51 deletions(-) create mode 100644 project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift diff --git a/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift b/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift index 9c5d3f45..ebd37509 100644 --- a/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift +++ b/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift @@ -12,23 +12,21 @@ public struct WorkerEmployCardVO { public let dayLeft: Int public let isBeginnerPossible: Bool public let title: String - public let timeTakenForWalk: String public let targetAge: Int - public let targetLevel: Int + public let careGrade: CareGrade public let targetGender: Gender public let days: [WorkDay] public let startTime: String public let endTime: String public let paymentType: PaymentType - public let paymentAmount: String + public let paymentAmount: Int - public init(dayLeft: Int, isBeginnerPossible: Bool, title: String, timeTakenForWalk: String, targetAge: Int, targetLevel: Int, targetGender: Gender, days: [WorkDay], startTime: String, endTime: String, paymentType: PaymentType, paymentAmount: String) { + public init(dayLeft: Int, isBeginnerPossible: Bool, title: String, targetAge: Int, careGrade: CareGrade, targetGender: Gender, days: [WorkDay], startTime: String, endTime: String, paymentType: PaymentType, paymentAmount: Int) { self.dayLeft = dayLeft self.isBeginnerPossible = isBeginnerPossible self.title = title - self.timeTakenForWalk = timeTakenForWalk self.targetAge = targetAge - self.targetLevel = targetLevel + self.careGrade = careGrade self.targetGender = targetGender self.days = days self.startTime = startTime @@ -44,29 +42,27 @@ public extension WorkerEmployCardVO { dayLeft: 10, isBeginnerPossible: true, title: "서울특별시 강남구 신사동", - timeTakenForWalk: "도보 15분~20분", targetAge: 78, - targetLevel: 1, + careGrade: .four, targetGender: .female, days: WorkDay.allCases, startTime: "09:00", endTime: "15:00", - paymentType: .monthly, - paymentAmount: "12,500" + paymentType: .hourly, + paymentAmount: 12500 ) static let `default` = WorkerEmployCardVO( dayLeft: 0, isBeginnerPossible: true, title: "기본값", - timeTakenForWalk: "기본값", targetAge: 10, - targetLevel: 1, + careGrade: .one, targetGender: .notDetermined, days: [], startTime: "00:00", endTime: "00:00", paymentType: .hourly, - paymentAmount: "12,500" + paymentAmount: 0 ) } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift index 1d3bbb58..a2eedb9d 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift @@ -10,6 +10,61 @@ import RxSwift import RxCocoa import Entity +public class WorkerEmployCardRO { + + let showBiginnerTag: Bool + let showDayLeftTag: Bool + let dayLeftTagText: String? + let titleText: String + let timeTakenForWalkText: String + let tagetInfoText: String + let workDaysText: String + let workTimeText: String + let payText: String + + init(showBiginnerTag: Bool, showDayLeftTag: Bool, dayLeftTagText: String?, titleText: String, timeTakenForWalkText: String, tagetInfoText: String, workDaysText: String, workTimeText: String, payText: String) { + self.showBiginnerTag = showBiginnerTag + self.showDayLeftTag = showDayLeftTag + self.dayLeftTagText = dayLeftTagText + self.titleText = titleText + self.timeTakenForWalkText = timeTakenForWalkText + self.tagetInfoText = tagetInfoText + self.workDaysText = workDaysText + self.workTimeText = workTimeText + self.payText = payText + } + + public static func create(vo: WorkerEmployCardVO) -> WorkerEmployCardRO { + + 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.map({ $0.korOneLetterText }).joined(separator: ",") + let workTimeText = "\(vo.startTime) - \(vo.endTime)" + let payText = "\(vo.paymentType.korLetterText) \(vo.paymentAmount) 원" + + return .init( + showBiginnerTag: !vo.isBeginnerPossible, + showDayLeftTag: showDayLeftTag, + dayLeftTagText: dayLeftTagText, + titleText: vo.title, + timeTakenForWalkText: "걸어서 n분(미구현)", + tagetInfoText: targetInfoText, + workDaysText: workDaysText, + workTimeText: workTimeText, + payText: payText + ) + } +} + + public class WorkerEmployCard: UIView { // View @@ -68,7 +123,7 @@ public class WorkerEmployCard: UIView { return label }() - let payPerHourLabel: IdleLabel = { + let payLabel: IdleLabel = { let label = IdleLabel(typography: .Body3) label.attrTextColor = DSKitAsset.Colors.gray500.color return label @@ -166,7 +221,7 @@ public class WorkerEmployCard: UIView { payView.backgroundColor = .clear [ payImage, - payPerHourLabel + payLabel ] .forEach { $0.translatesAutoresizingMaskIntoConstraints = false @@ -180,10 +235,10 @@ public class WorkerEmployCard: UIView { payView.leadingAnchor.constraint(equalTo: payImage.leadingAnchor), payView.bottomAnchor.constraint(equalTo: payImage.bottomAnchor), - payPerHourLabel.leadingAnchor.constraint(equalTo: payImage.trailingAnchor, constant: 2), - payPerHourLabel.centerYAnchor.constraint(equalTo: payImage.centerYAnchor), + payLabel.leadingAnchor.constraint(equalTo: payImage.trailingAnchor, constant: 2), + payLabel.centerYAnchor.constraint(equalTo: payImage.centerYAnchor), - payView.trailingAnchor.constraint(equalTo: payPerHourLabel.trailingAnchor), + payView.trailingAnchor.constraint(equalTo: payLabel.trailingAnchor), ]) @@ -224,23 +279,14 @@ public class WorkerEmployCard: UIView { ]) } - public func bind(vo: WorkerEmployCardVO) { + public func bind(ro: WorkerEmployCardRO) { - beginnerTag.isHidden = !vo.isBeginnerPossible - if vo.dayLeft <= 0 { - if vo.dayLeft == 0 { - dayLeftTag.textString = "D-Day" - } else { - dayLeftTag.isHidden = true - } - } else { - dayLeftTag.textString = "D-\(vo.dayLeft)" - } - titleLabel.textString = vo.title - timeTakenForWalkLabel.textString = vo.timeTakenForWalk - serviceTargetInfoLabel.textString = "\(vo.targetLevel)등급 \(vo.targetAge)세 \(vo.targetGender.twoLetterKoreanWord)" - workDaysLabel.textString = vo.days.map({ $0.korOneLetterText }).joined(separator: ",") - workTimeLabel.textString = "\(vo.startTime) - \(vo.endTime)" - payPerHourLabel.textString = "\(vo.paymentType.korLetterText) \(vo.paymentAmount) 원" + beginnerTag.isHidden = !ro.showBiginnerTag + titleLabel.textString = ro.titleText + timeTakenForWalkLabel.textString = ro.timeTakenForWalkText + serviceTargetInfoLabel.textString = ro.tagetInfoText + workDaysLabel.textString = ro.workDaysText + workTimeLabel.textString = ro.workTimeText + payLabel.textString = ro.payText } } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift index 4805f6ed..7a679fde 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift @@ -10,24 +10,44 @@ import RxSwift import RxCocoa import Entity +/// WorkerEmployCardCell에서 사용됩니다. +public struct ApplicationInfo { + let isApplied: Bool + let applicationDateText: String +} + +public protocol WorkerEmployCardViewModelable { + + // Output + var renderObject: Driver? { get } + var applicationInformation: Driver? { get } + + // Input + var cardClicked: PublishRelay { get } + var applyButtonClicked: PublishRelay { get } + + /// true일 경우 즐겨 찾기에 등록됩니다. + var starButtonClicked: PublishRelay { get } +} + public class WorkerEmployCardCell: UITableViewCell { public static let identifier = String(describing: WorkerEmployCardCell.self) - let tappableArea: TappableUIView = .init() - let cardView = WorkerEmployCard() + var viewModel: WorkerEmployCardViewModelable? + private var disposables: [Disposable?]? + - let applyButton: TextButtonType1 = { - - let btn = TextButtonType1( - labelText: "지원하기" - ) + // View + let tappableArea: TappableUIView = .init() + let cardView = WorkerEmployCard() + let applyButton: IdlePrimaryCardButton = { + let btn = IdlePrimaryCardButton(level: .large) + btn.label.textString = "" return btn }() - private var touchDispoable: Disposable? - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setAppearance() @@ -36,8 +56,10 @@ public class WorkerEmployCardCell: UITableViewCell { public required init?(coder: NSCoder) { fatalError() } public override func prepareForReuse() { - touchDispoable?.dispose() - touchDispoable = nil + viewModel = nil + + disposables?.forEach { $0?.dispose() } + disposables = nil } func setAppearance() { @@ -77,10 +99,47 @@ public class WorkerEmployCardCell: UITableViewCell { ]) } - public func bind(vo: WorkerEmployCardVO) { + public func bind(viewModel: WorkerEmployCardViewModelable) { + + self.viewModel = viewModel - // tap설정 + // input + let disposables: [Disposable?] = [ + // Output + viewModel + .applicationInformation? + .drive(onNext: { [weak self] info in + guard let self else { return } + if info.isApplied { + applyButton.setEnabled(false) + applyButton.label.textString = "지원완료 \(info.applicationDateText)" + } else { + applyButton.setEnabled(true) + applyButton.label.textString = "지원하기" + } + }), + viewModel + .renderObject? + .drive(onNext: { [cardView] ro in + cardView.bind(ro: ro) + }), + + // Input + tappableArea + .rx.tap + .bind(to: viewModel.cardClicked), + + applyButton + .rx.tap + .bind(to: viewModel.applyButtonClicked), + + cardView + .starButton + .eventPublisher + .map { $0 == .accent } + .bind(to: viewModel.starButtonClicked), + ] - cardView.bind(vo: vo) + self.disposables = disposables } } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Edit/CustomerInformationContentView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Edit/CustomerInformationContentView.swift index cff54e8b..0eb8a1a1 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Edit/CustomerInformationContentView.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Edit/CustomerInformationContentView.swift @@ -249,7 +249,7 @@ public class CustomerInformationContentView: UIView { birthYearField.textString = stateFromVM.birthYear // 몸무게 - weightField.textField.textString = stateFromVM.weight.emptyDefault("-") + weightField.textField.textString = stateFromVM.weight.emptyDefault("") // 요양등급 if let state = stateFromVM.careGrade { diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/View/ RecruitmentBoardVC.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/View/ RecruitmentBoardVC.swift index 7444898a..be5270f5 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/View/ RecruitmentBoardVC.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/View/ RecruitmentBoardVC.swift @@ -1,5 +1,5 @@ // -// RecruitmentBoardVC.swift +// RecruitmentBoardVC.swift // RootFeature // // Created by choijunios on 7/25/24. diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift new file mode 100644 index 00000000..d1746ee2 --- /dev/null +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift @@ -0,0 +1,22 @@ +// +// WorkerRecruitmentPostBoardVC.swift +// WorkerFeature +// +// Created by choijunios on 8/15/24. +// + +import UIKit +import BaseFeature +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit + + +public class CenterRecruitmentPostBoardVC: BaseViewController { + typealias Cell = WorkerEmployCardCell + + +} + From 779f5b69158aef1645fa87201693bb1782e6de2b Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 15 Aug 2024 14:04:42 +0900 Subject: [PATCH 03/21] =?UTF-8?q?[IDLE-163]=20WorkerRecruitmentPostBoardVC?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../location.imageset/Contents.json | 15 ++ .../location.imageset/location.svg | 3 + .../location_small.imageset/Contents.json | 23 -- .../location_image 1.png | Bin 1166 -> 0 bytes .../location_image 2.png | Bin 1166 -> 0 bytes .../location_image.png | Bin 1166 -> 0 bytes .../Board/BoardSortigHeaderView.swift | 44 ++++ .../Card/Post/Center/CenterEmployCard.swift | 2 - .../Board/Post/SubVC/OnGoingPostVC.swift | 37 ---- .../WorkerRecruitmentPostBoardVC.swift | 198 +++++++++++++++++- 10 files changed, 259 insertions(+), 63 deletions(-) create mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location.imageset/Contents.json create mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location.imageset/location.svg delete mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/Contents.json delete mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/location_image 1.png delete mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/location_image 2.png delete mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/location_image.png create mode 100644 project/Projects/Presentation/DSKit/Sources/CommonUI/Board/BoardSortigHeaderView.swift diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location.imageset/Contents.json new file mode 100644 index 00000000..96141147 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "location.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location.imageset/location.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location.imageset/location.svg new file mode 100644 index 00000000..2b5e0de1 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location.imageset/location.svg @@ -0,0 +1,3 @@ + + + diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/Contents.json deleted file mode 100644 index f0863b1f..00000000 --- a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "location_image.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "location_image 1.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "location_image 2.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/location_image 1.png b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/location_small.imageset/location_image 1.png deleted file mode 100644 index 734c54305f01a68f3e018afeb2b3c60b118c0120..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1166 zcmV;91abR`P)L8u?CvZ z7^k<#FaCT&KgUYpxX?IkG_aSvijUy|rvfEOXFMOnVgx)ZMKPnV6}C54;w~KGOrZ8w zXHjW%aBU^^AgdyAQ&&P+7l>U+=h&%XF?T}l*HsZx+pcF-pk%8vRRFuRzf8aeWKB_o zmQ9hY2~>)s(ZhXRYB(pf(YKP7xDN|h5s2NYC`cSzmh(3!Q3KO9EJ<7-j#R}1961!{ z9Co);5-EvKAV>sZ^cdEhi&K6uCd$tiM%02z*UIq?9*H#VN9{O+5TX{u;T)c!WKz_M z#4KnOtmhI*D-yFHUDUgCit8?ol%mk&`f9BBhr;$Wy&rQeh8j@rH4h=iAVi%^St+%r zPW4iHBl2A2G<~wlJ^HpCtqs4h!UFwXh5#Zic99ewnaoj8S-QI1sxo$Mv(q!Z0c?>H zd69^VofYO`T{ZMl^|4q|=Pi)R`7aXHW^>P58s;!B`^1}_5T%52YKbd5O@$Oxp(%%6MG zFvn<)F7$T@AR-WDu0|Uu=QPPir&)D8jh#2ngAA|fj&<(OSdI*a6cKi~ej7Hftns3< z*>8AJBP0+vsVI@lOy+R^hP#fdWF5DzOIB8=GpV>t5$Z0=)natOM_1M~nZsY43e#OG zB{Fi;{f0*WjC$Bqq)zZYRh%Cv8bkB9A%vJfQH~GP(R-n5H0gPNp}XEJ)U`?1(B+Sd zF?adfmlA^AjI2oP&eg@Vj2VV#9>i9pYGlb$aQ{d}I)xw&2$E$7Kby02zv$&0JV5(d zoV=FnM>AH=#olbb@6yr>a0t6ry0Tokg;EtOrABn7mX%PVR)x~FPIU|mSP`hqiyC-d zx2J5v`MyIuF2p^BfSAr6cD{tXA{fe5X5X#q=3UB&|RGA{xa8 zk$pbs&$Q;0ah5#{Ix5;|$M0Yn>jE){y@PCK_UR-!g-lO}64Lc{{1Ogu)-aQbY`g}E zBXZ_d1!n?DM7AL8u?CvZ z7^k<#FaCT&KgUYpxX?IkG_aSvijUy|rvfEOXFMOnVgx)ZMKPnV6}C54;w~KGOrZ8w zXHjW%aBU^^AgdyAQ&&P+7l>U+=h&%XF?T}l*HsZx+pcF-pk%8vRRFuRzf8aeWKB_o zmQ9hY2~>)s(ZhXRYB(pf(YKP7xDN|h5s2NYC`cSzmh(3!Q3KO9EJ<7-j#R}1961!{ z9Co);5-EvKAV>sZ^cdEhi&K6uCd$tiM%02z*UIq?9*H#VN9{O+5TX{u;T)c!WKz_M z#4KnOtmhI*D-yFHUDUgCit8?ol%mk&`f9BBhr;$Wy&rQeh8j@rH4h=iAVi%^St+%r zPW4iHBl2A2G<~wlJ^HpCtqs4h!UFwXh5#Zic99ewnaoj8S-QI1sxo$Mv(q!Z0c?>H zd69^VofYO`T{ZMl^|4q|=Pi)R`7aXHW^>P58s;!B`^1}_5T%52YKbd5O@$Oxp(%%6MG zFvn<)F7$T@AR-WDu0|Uu=QPPir&)D8jh#2ngAA|fj&<(OSdI*a6cKi~ej7Hftns3< z*>8AJBP0+vsVI@lOy+R^hP#fdWF5DzOIB8=GpV>t5$Z0=)natOM_1M~nZsY43e#OG zB{Fi;{f0*WjC$Bqq)zZYRh%Cv8bkB9A%vJfQH~GP(R-n5H0gPNp}XEJ)U`?1(B+Sd zF?adfmlA^AjI2oP&eg@Vj2VV#9>i9pYGlb$aQ{d}I)xw&2$E$7Kby02zv$&0JV5(d zoV=FnM>AH=#olbb@6yr>a0t6ry0Tokg;EtOrABn7mX%PVR)x~FPIU|mSP`hqiyC-d zx2J5v`MyIuF2p^BfSAr6cD{tXA{fe5X5X#q=3UB&|RGA{xa8 zk$pbs&$Q;0ah5#{Ix5;|$M0Yn>jE){y@PCK_UR-!g-lO}64Lc{{1Ogu)-aQbY`g}E zBXZ_d1!n?DM7AL8u?CvZ z7^k<#FaCT&KgUYpxX?IkG_aSvijUy|rvfEOXFMOnVgx)ZMKPnV6}C54;w~KGOrZ8w zXHjW%aBU^^AgdyAQ&&P+7l>U+=h&%XF?T}l*HsZx+pcF-pk%8vRRFuRzf8aeWKB_o zmQ9hY2~>)s(ZhXRYB(pf(YKP7xDN|h5s2NYC`cSzmh(3!Q3KO9EJ<7-j#R}1961!{ z9Co);5-EvKAV>sZ^cdEhi&K6uCd$tiM%02z*UIq?9*H#VN9{O+5TX{u;T)c!WKz_M z#4KnOtmhI*D-yFHUDUgCit8?ol%mk&`f9BBhr;$Wy&rQeh8j@rH4h=iAVi%^St+%r zPW4iHBl2A2G<~wlJ^HpCtqs4h!UFwXh5#Zic99ewnaoj8S-QI1sxo$Mv(q!Z0c?>H zd69^VofYO`T{ZMl^|4q|=Pi)R`7aXHW^>P58s;!B`^1}_5T%52YKbd5O@$Oxp(%%6MG zFvn<)F7$T@AR-WDu0|Uu=QPPir&)D8jh#2ngAA|fj&<(OSdI*a6cKi~ej7Hftns3< z*>8AJBP0+vsVI@lOy+R^hP#fdWF5DzOIB8=GpV>t5$Z0=)natOM_1M~nZsY43e#OG zB{Fi;{f0*WjC$Bqq)zZYRh%Cv8bkB9A%vJfQH~GP(R-n5H0gPNp}XEJ)U`?1(B+Sd zF?adfmlA^AjI2oP&eg@Vj2VV#9>i9pYGlb$aQ{d}I)xw&2$E$7Kby02zv$&0JV5(d zoV=FnM>AH=#olbb@6yr>a0t6ry0Tokg;EtOrABn7mX%PVR)x~FPIU|mSP`hqiyC-d zx2J5v`MyIuF2p^BfSAr6cD{tXA{fe5X5X#q=3UB&|RGA{xa8 zk$pbs&$Q;0ah5#{Ix5;|$M0Yn>jE){y@PCK_UR-!g-lO}64Lc{{1Ogu)-aQbY`g}E zBXZ_d1!n?DM7A CenterEmployCardRO { .init( startDay: vo.startDay, @@ -187,7 +186,6 @@ fileprivate class TextVM: CenterEmployCardViewModelable { var terminatePostBtnClicked: RxRelay.PublishRelay = .init() init() { - renderObject = publishObect.asDriver(onErrorJustReturn: .mock) } } diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/OnGoingPostVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/OnGoingPostVC.swift index 1f8bcce9..70709db1 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/OnGoingPostVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/OnGoingPostVC.swift @@ -89,7 +89,6 @@ public class OnGoingPostVC: BaseViewController { postTableView.rightAnchor.constraint(equalTo: view.rightAnchor), postTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) - } private func setObservable() { @@ -138,39 +137,3 @@ extension OnGoingPostVC: UITableViewDataSource, UITableViewDelegate { return cell } } - -class BoardSortigHeaderView: UIView { - - let sortingTypeButton: ImageTextButton = { - let button = ImageTextButton( - iconImage: DSKitAsset.Icons.chevronDown.image, - position: .postfix - ) - button.label.textString = "정렬 기준" - button.label.attrTextColor = DSKitAsset.Colors.gray300.color - return button - }() - - init() { - super.init(frame: .zero) - setLayout() - } - - required init?(coder: NSCoder) { fatalError() } - - func setLayout() { - - [ - sortingTypeButton - ].forEach { - $0.translatesAutoresizingMaskIntoConstraints = false - self.addSubview($0) - } - - NSLayoutConstraint.activate([ - sortingTypeButton.topAnchor.constraint(equalTo: self.topAnchor, constant: 24), - sortingTypeButton.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -24), - sortingTypeButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -12), - ]) - } -} diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift index d1746ee2..4d5b0392 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift @@ -13,10 +13,206 @@ import RxSwift import Entity import DSKit +public protocol WorkerRecruitmentPostBoardVMable: DefaultAlertOutputable { + + var ongoingPostCardVO: Driver<[WorkerEmployCardVO]>? { get } + var requestOngoingPost: PublishRelay { get } + + func createCellVM(vo: WorkerEmployCardVO) -> WorkerEmployCardViewModelable +} -public class CenterRecruitmentPostBoardVC: BaseViewController { +public class WorkerRecruitmentPostBoardVC: BaseViewController { typealias Cell = WorkerEmployCardCell + var viewModel: WorkerRecruitmentPostBoardVMable? + + // View + let topContainer: WorkerMainTopContainer = { + let container = WorkerMainTopContainer(innerViews: []) + return container + }() + let postTableView: UITableView = { + let tableView = UITableView() + tableView.rowHeight = UITableView.automaticDimension + tableView.register(Cell.self, forCellReuseIdentifier: Cell.identifier) + return tableView + }() + + let tableHeader = BoardSortigHeaderView() + + let ongoingPostCardVO: BehaviorRelay<[WorkerEmployCardVO]> = .init(value: []) + + // Observable + private let disposeBag = DisposeBag() + + public init() { + super.init(nibName: nil, bundle: nil) + } + + public required init?(coder: NSCoder) { fatalError() } + + public override func viewDidLoad() { + super.viewDidLoad() + setTableView() + setLayout() + } + + public func bind(viewModel: WorkerRecruitmentPostBoardVMable) { + // Output + viewModel + .ongoingPostCardVO? + .drive(ongoingPostCardVO) + .disposed(by: disposeBag) + + // Input + rx.viewWillAppear + .map { _ in () } + .bind(to: viewModel.requestOngoingPost) + .disposed(by: disposeBag) + } + + private func setTableView() { + postTableView.dataSource = self + postTableView.delegate = self + postTableView.separatorStyle = .none + postTableView.delaysContentTouches = false + + postTableView.tableHeaderView = tableHeader + + tableHeader.frame = .init(origin: .zero, size: .init( + width: view.bounds.width, + height: 60) + ) + } + + private func setLayout() { + + [ + topContainer, + postTableView + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + view.addSubview($0) + } + + NSLayoutConstraint.activate([ + topContainer.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + topContainer.leftAnchor.constraint(equalTo: view.leftAnchor), + topContainer.rightAnchor.constraint(equalTo: view.rightAnchor), + + postTableView.topAnchor.constraint(equalTo: topContainer.bottomAnchor), + postTableView.leftAnchor.constraint(equalTo: view.leftAnchor), + postTableView.rightAnchor.constraint(equalTo: view.rightAnchor), + postTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } + + private func setObservable() { + + } +} + +extension WorkerRecruitmentPostBoardVC: UITableViewDataSource, UITableViewDelegate { + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + ongoingPostCardVO.value.count + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell + cell.selectionStyle = .none + + if let viewModel = self.viewModel { + let vo = ongoingPostCardVO.value[indexPath.row] + let vm = viewModel.createCellVM(vo: vo) + cell.bind(viewModel: vm) + } + + return cell + } } +// MARK: Top Container +class WorkerMainTopContainer: UIView { + + // Init parameters + + // View + + lazy var locationLabel: IdleLabel = { + + let label = IdleLabel(typography: .Heading1) + label.textAlignment = .left + return label + }() + + let locationImage: UIImageView = { + let imageView = UIImageView() + imageView.image = DSIcon.location.image + imageView.tintColor = DSColor.gray700.color + return imageView + }() + + private let disposeBag = DisposeBag() + + init( + titleText: String = "", + innerViews: [UIView] + ) { + super.init(frame: .zero) + + self.locationLabel.textString = titleText + + setApearance() + setAutoLayout(innerViews: innerViews) + } + + public required init(coder: NSCoder) { fatalError() } + + func setApearance() { + + } + + private func setAutoLayout(innerViews: [UIView]) { + + self.layoutMargins = .init( + top: 20.43, + left: 20, + bottom: 8, + right: 20 + ) + + let mainStack = HStack( + [ + [ + locationImage, + Spacer(width: 4), + locationLabel, + Spacer(), + ], + innerViews + ].flatMap { $0 }, + alignment: .center, + distribution: .fill + ) + + [ + mainStack + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + self.addSubview($0) + } + + NSLayoutConstraint.activate([ + locationImage.widthAnchor.constraint(equalToConstant: 32), + locationImage.heightAnchor.constraint(equalTo: locationImage.widthAnchor), + + mainStack.leftAnchor.constraint(equalTo: self.layoutMarginsGuide.leftAnchor), + mainStack.rightAnchor.constraint(equalTo: self.layoutMarginsGuide.rightAnchor), + mainStack.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor), + mainStack.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor), + ]) + + } +} From 6f33ed7e63d137e16eebcc68c87a76f5b781489b Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 15 Aug 2024 16:07:04 +0900 Subject: [PATCH 04/21] =?UTF-8?q?[IDLE-163]=20=EC=9A=94=EC=96=91=EB=B3=B4?= =?UTF-8?q?=ED=98=B8=EC=82=AC=20=EC=A7=84=ED=96=89=EC=A4=91=EC=9D=B8=20?= =?UTF-8?q?=EA=B3=B5=EA=B3=A0=ED=99=94=EB=A9=B4=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Main/Worker/WorkerMainCoordinator.swift | 15 +- .../Entity/VO/Employ/WorkerEmployCardVO.swift | 88 +++++++++++- .../Card/Post/Worker/WorkerEmployCard.swift | 33 +++-- .../Post/Worker/WorkerEmployCardCell.swift | 10 ++ .../Profile/Center/CenterInfoCardView.swift | 2 +- .../ProfileButton/CenterProfileButton.swift | 2 +- .../Component/ImageView/IdleImageView.swift | 2 +- .../RecruitmentPost/Detail/PostDetailVC.swift | 2 +- .../DetailVC/PostDetailForCenterVC.swift | 2 +- .../Overview/PostOverviewVC.swift | 2 +- .../PostDetailForCenterVM.swift | 66 +-------- .../RegisterRecruitmentPostVM.swift | 67 ++------- .../WorkerRecruitmentBoardCoordinator.swift | 30 ++-- .../WorkerRecruitmentPostBoardVC.swift | 15 +- .../WorkerRecruitmentPostBoardVM.swift | 135 ++++++++++++++++++ .../CenterPostBoardCoordinatable.swift | 0 .../CheckApplicantCoordinatable.swift | 0 .../RecruitmentManagementCoordinatable.swift | 0 ...RegisterRecruitmentPostCoordinatable.swift | 0 .../WorkerRecruitmentBoardCoordinatable.swift | 16 +++ 20 files changed, 330 insertions(+), 157 deletions(-) create mode 100644 project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/WorkerRecruitmentPostBoardVM.swift rename project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/{ => Center}/CenterPostBoardCoordinatable.swift (100%) rename project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/{ => Center}/CheckApplicantCoordinatable.swift (100%) rename project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/{ => Center}/RecruitmentManagementCoordinatable.swift (100%) rename project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/{ => Center}/RegisterRecruitmentPostCoordinatable.swift (100%) create mode 100644 project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Worker/WorkerRecruitmentBoardCoordinatable.swift diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift index 8f1578fb..7420b00c 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift @@ -9,6 +9,7 @@ import UIKit import DSKit import PresentationCore import RootFeature +import UseCaseInterface class WorkerMainCoordinator: ParentCoordinator { @@ -54,7 +55,7 @@ class WorkerMainCoordinator: ParentCoordinator { func createNavForTab(page: IdleWorkerMainPage) -> UINavigationController { let tabNavController = UINavigationController() - tabNavController.setNavigationBarHidden(false, animated: false) + tabNavController.setNavigationBarHidden(true, animated: false) startTabCoordinator( page: page, @@ -66,19 +67,23 @@ class WorkerMainCoordinator: ParentCoordinator { // #2. 생성한 컨트롤러를 각 탭별 Coordinator에 전달 func startTabCoordinator(page: IdleWorkerMainPage, navigationController: UINavigationController) { - var coordinator: ChildCoordinator! + var coordinator: Coordinator! switch page { case .home: - coordinator = RecruitmentBoardCoordinator( - navigationController: navigationController + coordinator = WorkerRecruitmentBoardCoordinator( + depedency: .init( + navigationController: navigationController, + centerProfileUseCase: injector.resolve(CenterProfileUseCase.self), + recruitmentPostUseCase: injector.resolve(RecruitmentPostUseCase.self) + ) ) case .preferredPost: coordinator = ApplyManagementCoordinator( navigationController: navigationController ) case .setting: - coordinator = RecruitmentBoardCoordinator( + coordinator = ApplyManagementCoordinator( navigationController: navigationController ) } diff --git a/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift b/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift index ebd37509..51a64330 100644 --- a/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift +++ b/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift @@ -9,8 +9,10 @@ import Foundation public struct WorkerEmployCardVO { + public let postId: String public let dayLeft: Int public let isBeginnerPossible: Bool + public let timeTakenForWalk: String public let title: String public let targetAge: Int public let careGrade: CareGrade @@ -21,9 +23,11 @@ public struct WorkerEmployCardVO { public let paymentType: PaymentType public let paymentAmount: Int - public init(dayLeft: Int, isBeginnerPossible: Bool, title: String, targetAge: Int, careGrade: CareGrade, targetGender: Gender, days: [WorkDay], startTime: String, endTime: String, paymentType: PaymentType, paymentAmount: Int) { + public init(postId: String, dayLeft: Int, isBeginnerPossible: Bool, timeTakenForWalk: String, title: String, targetAge: Int, careGrade: CareGrade, targetGender: Gender, days: [WorkDay], startTime: String, endTime: String, paymentType: PaymentType, paymentAmount: Int) { + self.postId = postId self.dayLeft = dayLeft self.isBeginnerPossible = isBeginnerPossible + self.timeTakenForWalk = timeTakenForWalk self.title = title self.targetAge = targetAge self.careGrade = careGrade @@ -34,13 +38,93 @@ public struct WorkerEmployCardVO { self.paymentType = paymentType self.paymentAmount = paymentAmount } + + public static func create( + postId: String, + workTimeAndPay: WorkTimeAndPayStateObject, + customerRequirement: CustomerRequirementStateObject, + customerInformation: CustomerInformationStateObject, + applicationDetail: ApplicationDetailStateObject, + addressInfo: AddressInputStateObject + ) -> WorkerEmployCardVO { + + // 남은 일수 + var leftDay: Int? = nil + let calendar = Calendar.current + let currentDate = Date() + + if applicationDetail.applyDeadlineType == .specificDate, let deadlineDate = applicationDetail.deadlineDate { + + let component = calendar.dateComponents([.day], from: currentDate, to: deadlineDate) + leftDay = component.day + } + + // 초보가능 여부 + let isBeginnerPossible = applicationDetail.experiencePreferenceType == .beginnerPossible + + // 제목(=도로명주소) + let title = addressInfo.addressInfo?.roadAddress.emptyDefault("위치정보 표기 오류") ?? "" + + // 도보시간 + let timeTakenForWalk = "도보 n분" + + // 생년 + let birthYear = Int(customerInformation.birthYear) ?? 1950 + let currentYear = calendar.component(.year, from: currentDate) + let targetAge = currentYear - birthYear + 1 + + // 요양등급 + let careGrade: CareGrade = customerInformation.careGrade ?? .one + + // 성별 + let targetGender = customerInformation.gender + + // 근무 요일 + let days = workTimeAndPay.selectedDays.filter { (_, value) in + value + }.map { (key, _) in key } + + // 근무 시작, 종료시간 + let startTime = workTimeAndPay.workStartTime?.convertToStringForButton() ?? "00:00" + let workEndTime = workTimeAndPay.workEndTime?.convertToStringForButton() ?? "00:00" + + // 급여타입및 양 + let paymentType = workTimeAndPay.paymentType ?? .hourly + let paymentAmount = Int(workTimeAndPay.paymentAmount) ?? 0 + + return WorkerEmployCardVO( + postId: postId, + dayLeft: leftDay ?? 0, + isBeginnerPossible: isBeginnerPossible, + timeTakenForWalk: timeTakenForWalk, + title: title, + targetAge: targetAge, + careGrade: careGrade, + targetGender: targetGender ?? .notDetermined, + days: days, + startTime: startTime, + endTime: workEndTime, + paymentType: paymentType, + paymentAmount: paymentAmount + ) + } +} + + +fileprivate extension String { + + func emptyDefault(_ str: String) -> String { + self.isEmpty ? str : self + } } public extension WorkerEmployCardVO { static let mock = WorkerEmployCardVO( + postId: "00-00000-00000", dayLeft: 10, isBeginnerPossible: true, + timeTakenForWalk: "도보 15분", title: "서울특별시 강남구 신사동", targetAge: 78, careGrade: .four, @@ -53,8 +137,10 @@ public extension WorkerEmployCardVO { ) static let `default` = WorkerEmployCardVO( + postId: "00-00000-00000", dayLeft: 0, isBeginnerPossible: true, + timeTakenForWalk: "도보 15분", title: "기본값", targetAge: 10, careGrade: .one, diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift index a2eedb9d..f8796d50 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift @@ -17,18 +17,18 @@ public class WorkerEmployCardRO { let dayLeftTagText: String? let titleText: String let timeTakenForWalkText: String - let tagetInfoText: String + let targetInfoText: String let workDaysText: String let workTimeText: String let payText: String - init(showBiginnerTag: Bool, showDayLeftTag: Bool, dayLeftTagText: String?, titleText: String, timeTakenForWalkText: String, tagetInfoText: String, workDaysText: String, workTimeText: String, payText: String) { + init(showBiginnerTag: Bool, showDayLeftTag: Bool, dayLeftTagText: String?, titleText: String, timeTakenForWalkText: String, targetInfoText: String, workDaysText: String, workTimeText: String, payText: String) { self.showBiginnerTag = showBiginnerTag self.showDayLeftTag = showDayLeftTag self.dayLeftTagText = dayLeftTagText self.titleText = titleText self.timeTakenForWalkText = timeTakenForWalkText - self.tagetInfoText = tagetInfoText + self.targetInfoText = targetInfoText self.workDaysText = workDaysText self.workTimeText = workTimeText self.payText = payText @@ -46,22 +46,37 @@ public class WorkerEmployCardRO { let targetInfoText = "\(vo.careGrade.textForCellBtn)등급 \(vo.targetAge)세 \(vo.targetGender.twoLetterKoreanWord)" - let workDaysText = vo.days.map({ $0.korOneLetterText }).joined(separator: ",") + let workDaysText = vo.days.sorted(by: { d1, d2 in + d1.rawValue < d2.rawValue + }).map({ $0.korOneLetterText }).joined(separator: ",") + let workTimeText = "\(vo.startTime) - \(vo.endTime)" let payText = "\(vo.paymentType.korLetterText) \(vo.paymentAmount) 원" return .init( - showBiginnerTag: !vo.isBeginnerPossible, + showBiginnerTag: vo.isBeginnerPossible, showDayLeftTag: showDayLeftTag, dayLeftTagText: dayLeftTagText, titleText: vo.title, - timeTakenForWalkText: "걸어서 n분(미구현)", - tagetInfoText: targetInfoText, + timeTakenForWalkText: vo.timeTakenForWalk, + targetInfoText: targetInfoText, workDaysText: workDaysText, workTimeText: workTimeText, payText: payText ) } + + public static let `mock`: WorkerEmployCardRO = .init( + showBiginnerTag: true, + showDayLeftTag: true, + dayLeftTagText: "D-14", + titleText: "사울시 강남동", + timeTakenForWalkText: "도보 5분", + targetInfoText: "1등급 54세 여성", + workDaysText: "", + workTimeText: "월, 화, 수", + payText: "시급 5000원" + ) } @@ -282,9 +297,11 @@ public class WorkerEmployCard: UIView { public func bind(ro: WorkerEmployCardRO) { beginnerTag.isHidden = !ro.showBiginnerTag + dayLeftTag.isHidden = !ro.showDayLeftTag + dayLeftTag.textString = ro.dayLeftTagText ?? "" titleLabel.textString = ro.titleText timeTakenForWalkLabel.textString = ro.timeTakenForWalkText - serviceTargetInfoLabel.textString = ro.tagetInfoText + serviceTargetInfoLabel.textString = ro.targetInfoText workDaysLabel.textString = ro.workDaysText workTimeLabel.textString = ro.workTimeText payLabel.textString = ro.payText diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift index 7a679fde..d071a738 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift @@ -14,6 +14,11 @@ import Entity public struct ApplicationInfo { let isApplied: Bool let applicationDateText: String + + public static let mock: ApplicationInfo = .init( + isApplied: true, + applicationDateText: "2024. 10. 22" + ) } public protocol WorkerEmployCardViewModelable { @@ -38,6 +43,11 @@ public class WorkerEmployCardCell: UITableViewCell { var viewModel: WorkerEmployCardViewModelable? 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() 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 ea9b61d7..7c517350 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 @@ -47,7 +47,7 @@ public class CenterInfoCardView: TappableUIView { private func setLayout() { - let locationImageView = DSKitAsset.Icons.locationSmall.image.toView() + let locationImageView = DSKitAsset.Icons.location.image.toView() let locationStack = HStack( [ locationImageView, diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/ProfileButton/CenterProfileButton.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/ProfileButton/CenterProfileButton.swift index 9578dbd7..4c4fca1a 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/ProfileButton/CenterProfileButton.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/ProfileButton/CenterProfileButton.swift @@ -65,7 +65,7 @@ public class CenterProfileButton: TappableUIView { private func setLayout() { - let locImage = DSKitAsset.Icons.locationSmall.image.toView() + let locImage = DSKitAsset.Icons.location.image.toView() let locationStack = HStack( [ locImage, diff --git a/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift b/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift index afac6eae..83ce7b81 100644 --- a/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift +++ b/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift @@ -16,7 +16,7 @@ public extension UIImageView { }() static let locationMark: UIImageView = { - let view = UIImageView(image: DSKitAsset.Icons.locationSmall.image) + let view = UIImageView(image: DSKitAsset.Icons.location.image) view.contentMode = .scaleAspectFit return view }() diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailVC.swift index 1f77f7b0..955f0fd8 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailVC.swift @@ -240,7 +240,7 @@ public class PostDetailContentView: UIView { public func bind() { - cardView.bind(vo: .mock) + cardView.bind(ro: .mock) workLocationView.bind() centerInfoCard.bind(nameText: "세얼간이 센터", locationText: "아남타워 7층") } 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 fc5ce1aa..5f610202 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 @@ -292,7 +292,7 @@ public class PostDetailForCenterVC: BaseViewController { viewModel .workerEmployCardVO? .drive(onNext: { [sampleCard] vo in - sampleCard.bind(vo: vo) + sampleCard.bind(ro: .create(vo: vo)) }) .disposed(by: disposeBag) } 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 b92f47d1..9b341542 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 @@ -342,7 +342,7 @@ public class PostOverviewVC: BaseViewController { viewModel .workerEmployCardVO? .drive(onNext: { [sampleCard] vo in - sampleCard.bind(vo: vo) + sampleCard.bind(ro: .create(vo: vo)) }) .disposed(by: disposeBag) diff --git a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/PostDetailForCenterVM.swift b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/PostDetailForCenterVM.swift index ac2dd707..651da0d9 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/PostDetailForCenterVM.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/PostDetailForCenterVM.swift @@ -121,65 +121,13 @@ public class PostDetailForCenterVM: PostDetailViewModelable { fetched_applicationDetail.accept(bundle.applicationDetail) fetched_addressInfo.accept(bundle.addressInfo) - // 남은 일수 - var leftDay: Int? = nil - let calendar = Calendar.current - let currentDate = Date() - - if fetched_applicationDetail.value.applyDeadlineType == .specificDate, let deadlineDate = fetched_applicationDetail.value.deadlineDate { - - let component = calendar.dateComponents([.day], from: currentDate, to: deadlineDate) - leftDay = component.day - } - - // 초보가능 여부 - let isBeginnerPossible = fetched_applicationDetail.value.experiencePreferenceType == .beginnerPossible - - // 제목(=도로명주소) - let title = fetched_addressInfo.value.addressInfo?.roadAddress.emptyDefault("위치정보 표기 오류") ?? "" - - // 도보시간 - let timeTakenForWalk = "도보 n분" - - // 생년 - let birthYear = Int(fetched_customerInformation.value.birthYear) ?? 1970 - let currentYear = calendar.component(.year, from: currentDate) - let targetAge = currentYear - birthYear + 1 - - // 요양등급 - let targetLavel: Int = (fetched_customerInformation.value.careGrade?.rawValue ?? 0)+1 - - // 성별 - let targetGender = fetched_customerInformation.value.gender - - // 근무 요일 - let days = fetched_workTimeAndPay.value.selectedDays.filter { (_, value) in - value - }.map { (key, _) in - key - } - - // 근무 시작, 종료시간 - let startTime = fetched_workTimeAndPay.value.workStartTime?.convertToStringForButton() ?? "00:00" - let workEndTime = fetched_workTimeAndPay.value.workEndTime?.convertToStringForButton() ?? "00:00" - - // 급여타입및 양 - let paymentType = fetched_workTimeAndPay.value.paymentType ?? .hourly - let paymentAmount = fetched_workTimeAndPay.value.paymentAmount - - return WorkerEmployCardVO( - dayLeft: leftDay ?? 0, - isBeginnerPossible: isBeginnerPossible, - title: title, - timeTakenForWalk: timeTakenForWalk, - targetAge: targetAge, - targetLevel: targetLavel, - targetGender: targetGender ?? .notDetermined, - days: days, - startTime: startTime, - endTime: workEndTime, - paymentType: paymentType, - paymentAmount: paymentAmount + return WorkerEmployCardVO.create( + postId: id, + workTimeAndPay: fetched_workTimeAndPay.value, + customerRequirement: fetched_customerRequirement.value, + customerInformation: fetched_customerInformation.value, + applicationDetail: fetched_applicationDetail.value, + addressInfo: fetched_addressInfo.value ) } .asDriver(onErrorJustReturn: .default) diff --git a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift index 03d4f992..a1554f74 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift @@ -389,69 +389,18 @@ public class RegisterRecruitmentPostVM: RegisterRecruitmentPostViewModelable { .create { [ editing_workTimeAndPay, editing_customerInformation, + editing_customerRequirement, editing_applicationDetail, editing_addressInfo ] emitter in - // 남은 일수 - var leftDay: Int? = nil - let calendar = Calendar.current - let currentDate = Date() - - if editing_applicationDetail.value.applyDeadlineType == .specificDate, let deadlineDate = editing_applicationDetail.value.deadlineDate { - - let component = calendar.dateComponents([.day], from: currentDate, to: deadlineDate) - leftDay = component.day - } - - // 초보가능 여부 - let isBeginnerPossible = editing_applicationDetail.value.experiencePreferenceType == .beginnerPossible - - // 제목(=도로명주소) - let title = editing_addressInfo.value.addressInfo?.roadAddress ?? "위치정보 표기 오류" - - // 도보시간 - let timeTakenForWalk = "도보 n분" - - // 생년 - let birthYear = Int(editing_customerInformation.value.birthYear) ?? 1970 - let currentYear = calendar.component(.year, from: currentDate) - let targetAge = currentYear - birthYear + 1 - - // 요양등급 - let targetLavel: Int = (editing_customerInformation.value.careGrade?.rawValue ?? 0)+1 - - // 성별 - let targetGender = editing_customerInformation.value.gender - - // 근무 요일 - let days = editing_workTimeAndPay.value.selectedDays.filter { (_, value) in - value - }.map { (key, _) in - key - } - - // 근무 시작, 종료시간 - let startTime = editing_workTimeAndPay.value.workStartTime?.convertToStringForButton() ?? "00:00" - let workEndTime = editing_workTimeAndPay.value.workEndTime?.convertToStringForButton() ?? "00:00" - - // 급여타입및 양 - let paymentType = editing_workTimeAndPay.value.paymentType ?? .hourly - let paymentAmount = editing_workTimeAndPay.value.paymentAmount - - let vo = WorkerEmployCardVO( - dayLeft: leftDay ?? 0, - isBeginnerPossible: isBeginnerPossible, - title: title, - timeTakenForWalk: timeTakenForWalk, - targetAge: targetAge, - targetLevel: targetLavel, - targetGender: targetGender ?? .notDetermined, - days: days, - startTime: startTime, - endTime: workEndTime, - paymentType: paymentType, - paymentAmount: paymentAmount + let vo = WorkerEmployCardVO.create( + postId: "", + workTimeAndPay: editing_workTimeAndPay.value, + customerRequirement: editing_customerRequirement.value, + customerInformation: editing_customerInformation.value, + applicationDetail: editing_applicationDetail.value, + addressInfo: editing_addressInfo.value ) emitter.onNext(vo) diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift index 51ec16f0..2bf60d04 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift @@ -6,20 +6,22 @@ // import UIKit +import WorkerFeature import PresentationCore - -public protocol WorkerRecruitmentBoardCoordinatable: ParentCoordinator { - /// 요양보호사가 볼 수 있는 공고 상세정보를 표시합니다. - func showPostDetail(postId: String) - - /// 센터 프로필을 표시합니다. - func showCenterProfile(centerId: String) -} +import UseCaseInterface public class WorkerRecruitmentBoardCoordinator: WorkerRecruitmentBoardCoordinatable { public struct Dependency { let navigationController: UINavigationController + let centerProfileUseCase: CenterProfileUseCase + let recruitmentPostUseCase: RecruitmentPostUseCase + + public init(navigationController: UINavigationController, centerProfileUseCase: CenterProfileUseCase, recruitmentPostUseCase: RecruitmentPostUseCase) { + self.navigationController = navigationController + self.centerProfileUseCase = centerProfileUseCase + self.recruitmentPostUseCase = recruitmentPostUseCase + } } public var childCoordinators: [any PresentationCore.Coordinator] = [] @@ -30,13 +32,21 @@ public class WorkerRecruitmentBoardCoordinator: WorkerRecruitmentBoardCoordinata weak var parent: ParentCoordinator? + let centerProfileUseCase: CenterProfileUseCase + let recruitmentPostUseCase: RecruitmentPostUseCase + public init(depedency: Dependency) { self.navigationController = depedency.navigationController + self.centerProfileUseCase = depedency.centerProfileUseCase + self.recruitmentPostUseCase = depedency.recruitmentPostUseCase } public func start() { - let vc = RecruitmentBoardVC() - + let vc = WorkerRecruitmentPostBoardVC() + let vm = WorkerRecruitmentPostBoardVM( + coordinator: self + ) + vc.bind(viewModel: vm) navigationController.pushViewController(vc, animated: false) } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift index 4d5b0392..4475065d 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift @@ -13,14 +13,6 @@ import RxSwift import Entity import DSKit -public protocol WorkerRecruitmentPostBoardVMable: DefaultAlertOutputable { - - var ongoingPostCardVO: Driver<[WorkerEmployCardVO]>? { get } - var requestOngoingPost: PublishRelay { get } - - func createCellVM(vo: WorkerEmployCardVO) -> WorkerEmployCardViewModelable -} - public class WorkerRecruitmentPostBoardVC: BaseViewController { typealias Cell = WorkerEmployCardCell @@ -58,6 +50,11 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { } public func bind(viewModel: WorkerRecruitmentPostBoardVMable) { + + self.viewModel = viewModel + + topContainer.locationLabel.textString = "서울시 영등포구(미구현)" + // Output viewModel .ongoingPostCardVO? @@ -67,7 +64,7 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { // Input rx.viewWillAppear .map { _ in () } - .bind(to: viewModel.requestOngoingPost) + .bind(to: viewModel.viewWillAppear) .disposed(by: disposeBag) } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/WorkerRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/WorkerRecruitmentPostBoardVM.swift new file mode 100644 index 00000000..413419ce --- /dev/null +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/WorkerRecruitmentPostBoardVM.swift @@ -0,0 +1,135 @@ +// +// WorkerRecruitmentPostBoardVM.swift +// WorkerFeature +// +// Created by choijunios on 8/15/24. +// + +import UIKit +import BaseFeature +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit + +public protocol WorkerRecruitmentPostBoardVMable: DefaultAlertOutputable { + + var ongoingPostCardVO: Driver<[WorkerEmployCardVO]>? { get } + var viewWillAppear: PublishRelay { get } + var locationTtitleText: PublishRelay { get } + + func createCellVM(vo: WorkerEmployCardVO) -> WorkerEmployCardViewModelable +} + + +public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { + + weak var coordinator: WorkerRecruitmentBoardCoordinatable? + + public var ongoingPostCardVO: RxCocoa.Driver<[Entity.WorkerEmployCardVO]>? + public var alert: RxCocoa.Driver? + + public var viewWillAppear: RxRelay.PublishRelay = .init() + public var locationTtitleText: RxRelay.PublishRelay = .init() + + public init(coordinator: WorkerRecruitmentBoardCoordinatable) { + + self.coordinator = coordinator + + let requestOngoingPostResult = viewWillAppear + .flatMap { [unowned self] _ in + publishOngoingPostMocks() + } + .share() + + let requestOngoingPostSuccess = requestOngoingPostResult.compactMap { $0.value } + let requestOngoingPostFailure = requestOngoingPostResult.compactMap { $0.error } + + ongoingPostCardVO = requestOngoingPostSuccess.asDriver(onErrorJustReturn: []) + + alert = requestOngoingPostFailure + .map { error in + DefaultAlertContentVO( + title: "시스템 오류", + message: error.message + ) + } + .asDriver(onErrorJustReturn: .default) + } + + public func createCellVM(vo: Entity.WorkerEmployCardVO) -> any DSKit.WorkerEmployCardViewModelable { + WorkerEmployCardVM( + vo: vo, + coordinator: coordinator + ) + } + + func publishOngoingPostMocks() -> Single> { + return .just(.success((0...10).map { _ in WorkerEmployCardVO.mock })) + } +} + +public class WorkerEmployCardVM: WorkerEmployCardViewModelable { + + weak var coordinator: WorkerRecruitmentBoardCoordinatable? + + // Init + let postId: String + + public var renderObject: RxCocoa.Driver? + public var applicationInformation: RxCocoa.Driver? + + public var cardClicked: RxRelay.PublishRelay = .init() + public var applyButtonClicked: RxRelay.PublishRelay = .init() + public var starButtonClicked: RxRelay.PublishRelay = .init() + + let disposeBag = DisposeBag() + + public init + ( + vo: WorkerEmployCardVO, + coordinator: WorkerRecruitmentBoardCoordinatable? = nil + ) + { + self.postId = vo.postId + self.coordinator = coordinator + + // MARK: 지원여부 + let applicationInformation: BehaviorRelay = .init(value: .mock) + self.applicationInformation = applicationInformation.asDriver() + + // MARK: Card RenderObject + let workerEmployCardRO: BehaviorRelay = .init(value: .mock) + renderObject = workerEmployCardRO.asDriver(onErrorJustReturn: .mock) + + workerEmployCardRO.accept(WorkerEmployCardRO.create(vo: vo)) + + // MARK: 버튼 처리 + applyButtonClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + + // 지원하기 버튼 눌림 + }) + .disposed(by: disposeBag) + + cardClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + + coordinator?.showPostDetail( + postId: postId + ) + }) + .disposed(by: disposeBag) + + starButtonClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + + // 즐겨찾기 버튼눌림 + }) + .disposed(by: disposeBag) + } +} diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CenterPostBoardCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/CenterPostBoardCoordinatable.swift similarity index 100% rename from project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CenterPostBoardCoordinatable.swift rename to project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/CenterPostBoardCoordinatable.swift diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CheckApplicantCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/CheckApplicantCoordinatable.swift similarity index 100% rename from project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CheckApplicantCoordinatable.swift rename to project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/CheckApplicantCoordinatable.swift diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/RecruitmentManagementCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/RecruitmentManagementCoordinatable.swift similarity index 100% rename from project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/RecruitmentManagementCoordinatable.swift rename to project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/RecruitmentManagementCoordinatable.swift diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/RegisterRecruitmentPostCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/RegisterRecruitmentPostCoordinatable.swift similarity index 100% rename from project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/RegisterRecruitmentPostCoordinatable.swift rename to project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Center/RegisterRecruitmentPostCoordinatable.swift 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 new file mode 100644 index 00000000..721df7cd --- /dev/null +++ b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Worker/WorkerRecruitmentBoardCoordinatable.swift @@ -0,0 +1,16 @@ +// +// WorkerRecruitmentBoardCoordinatable.swift +// PresentationCore +// +// Created by choijunios on 8/15/24. +// + +import Foundation + +public protocol WorkerRecruitmentBoardCoordinatable: ParentCoordinator { + /// 요양보호사가 볼 수 있는 공고 상세정보를 표시합니다. + func showPostDetail(postId: String) + + /// 센터 프로필을 표시합니다. + func showCenterProfile(centerId: String) +} From 6e1eca8c7bc1b9d8ebdc0978e8c22b70ec06ceb0 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 15 Aug 2024 16:11:02 +0900 Subject: [PATCH 05/21] =?UTF-8?q?[IDLE-000]=20caption2=20=ED=83=80?= =?UTF-8?q?=EC=9D=B4=ED=8F=AC=20=EA=B7=B8=EB=9E=98=ED=94=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DSKit/Sources/Component/Typography/Typograpy.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/project/Projects/Presentation/DSKit/Sources/Component/Typography/Typograpy.swift b/project/Projects/Presentation/DSKit/Sources/Component/Typography/Typograpy.swift index 2d0a02bc..9daf8d98 100644 --- a/project/Projects/Presentation/DSKit/Sources/Component/Typography/Typograpy.swift +++ b/project/Projects/Presentation/DSKit/Sources/Component/Typography/Typograpy.swift @@ -32,6 +32,7 @@ public enum Typography { case Body3 case caption + case caption2 var lineHeight: CGFloat { switch self { @@ -59,6 +60,8 @@ public enum Typography { 20 case .caption: 18.6 + case .caption2: + 18.6 } } @@ -149,6 +152,13 @@ public enum Typography { letterSpacing: -0.2, color: DSKitAsset.Colors.gray900.color ) + case .caption2: + Self.createAttribute( + weight: .Semibold, + size: 12, + letterSpacing: -0.2, + color: DSKitAsset.Colors.gray900.color + ) } } From ef358a0f97219a47b9e309e481025200fc813442 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 15 Aug 2024 16:14:35 +0900 Subject: [PATCH 06/21] =?UTF-8?q?[IDLE-000]=20=ED=83=AD=EB=B0=94=EC=95=84?= =?UTF-8?q?=EC=9D=B4=ED=85=9C=20=EB=9D=BC=EB=B2=A8=20=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=83=89=EC=83=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonUI/TabBar/TabBarContainer/IdleTabBarItem.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/TabBarContainer/IdleTabBarItem.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/TabBarContainer/IdleTabBarItem.swift index 8ebb488a..e03a4ad7 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/TabBarContainer/IdleTabBarItem.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/TabBarContainer/IdleTabBarItem.swift @@ -23,15 +23,15 @@ public class IdleTabBarItem: TappableUIView { // idle let idleIconColor: UIColor = DSColor.gray300.color - + let idleTextColor: UIColor = DSColor.gray300.color // accent let accentIconColor: UIColor = DSColor.gray700.color + let accentTextColor: UIColor = DSColor.gray700.color // View let label: IdleLabel = { let label = IdleLabel(typography: .caption) - label.attrTextColor = DSColor.gray700.color return label }() let imageView: UIImageView = { @@ -54,6 +54,7 @@ public class IdleTabBarItem: TappableUIView { private func setAppearance() { imageView.tintColor = idleIconColor + label.attrTextColor = idleTextColor } private func setLayout() { @@ -86,10 +87,12 @@ public class IdleTabBarItem: TappableUIView { private func setToIdle() { imageView.tintColor = idleIconColor + label.attrTextColor = idleTextColor } private func setToAccent() { imageView.tintColor = accentIconColor + label.attrTextColor = accentTextColor } } From 98448c2420495df6246b72e220ff603b8181aeeb Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 15 Aug 2024 17:23:50 +0900 Subject: [PATCH 07/21] =?UTF-8?q?[IDLE-163]=20=EC=9A=94=EC=96=91=EB=B3=B4?= =?UTF-8?q?=ED=98=B8=EC=82=AC=EA=B0=80=20=ED=99=95=EC=9D=B8=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8A=94=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20DTO=EB=B0=8F=20=EB=A0=88?= =?UTF-8?q?=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefaultRecruitmentPostRepository.swift | 13 ++ .../RecruitmentPost/RecruitmentPostDTO.swift | 16 +-- .../RecruitmentPostDetailForWorkerDTO.swift | 119 ++++++++++++++++++ .../RecruitmentPostForWorkerBundle.swift | 65 ++++++++++ .../RecruitmentPostRepository.swift | 3 + ...ilVC.swift => PostDetailForWorkerVC.swift} | 15 ++- 6 files changed, 219 insertions(+), 12 deletions(-) create mode 100644 project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift create mode 100644 project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentPostForWorkerBundle.swift rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/{PostDetailVC.swift => PostDetailForWorkerVC.swift} (94%) diff --git a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift index 090e05e4..42a74022 100644 --- a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift +++ b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift @@ -21,6 +21,7 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { } } + // MARK: Center public func registerPost(bundle: RegisterRecruitmentPostBundle) -> RxSwift.Single { let encodedData = try! JSONEncoder().encode(bundle.toDTO()) @@ -47,6 +48,18 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { with: .withToken ).map { _ in () } } + + // MARK: Worker + public func getPostDetailForWorker(id: String) -> RxSwift.Single { + service.request( + api: .postDetail(id: id, userType: .worker), + with: .withToken + ) + .map(RecruitmentPostDTO.self) + .map { dto in + <#code#> + } + } } fileprivate extension RegisterRecruitmentPostBundle { diff --git a/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDTO.swift b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDTO.swift index fd106d6c..4bf5b4ee 100644 --- a/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDTO.swift +++ b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDTO.swift @@ -184,7 +184,7 @@ public struct RecruitmentPostFetchDTO: Codable { } } -fileprivate extension ApplyType { +extension ApplyType { static func toEntity(text: String) -> ApplyType { switch text { case "CALLING": @@ -200,7 +200,7 @@ fileprivate extension ApplyType { } } -fileprivate extension ApplyDeadlineType { +extension ApplyDeadlineType { static func toEntity(text: String) -> ApplyDeadlineType { switch text { case "UNLIMITED": @@ -214,7 +214,7 @@ fileprivate extension ApplyDeadlineType { } } -fileprivate extension DailySupportType { +extension DailySupportType { static func toEntity(text: String) -> DailySupportType { switch text { case "CLEANING": @@ -234,7 +234,7 @@ fileprivate extension DailySupportType { } } -fileprivate extension Gender { +extension Gender { static func toEntity(text: String) -> Gender { switch text { case "MAN": @@ -248,7 +248,7 @@ fileprivate extension Gender { } } -fileprivate extension PaymentType { +extension PaymentType { static func toEntity(text: String) -> PaymentType { switch text { @@ -265,7 +265,7 @@ fileprivate extension PaymentType { } } -fileprivate extension IdleDateComponent { +extension IdleDateComponent { static func toEntity(text: String) -> IdleDateComponent { let timeArr = text.split(separator: ":") @@ -283,7 +283,7 @@ fileprivate extension IdleDateComponent { } } -fileprivate extension CognitionDegree { +extension CognitionDegree { static func toEntity(text: String) -> CognitionDegree { switch text { case "NORMAL": @@ -299,7 +299,7 @@ fileprivate extension CognitionDegree { } } -fileprivate extension WorkDay { +extension WorkDay { static func toEntity(text: String) -> WorkDay { diff --git a/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift new file mode 100644 index 00000000..17231782 --- /dev/null +++ b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift @@ -0,0 +1,119 @@ +// +// RecruitmentPostDetailForWorkerDTO.swift +// NetworkDataSource +// +// Created by choijunios on 8/15/24. +// + +import Foundation +import Entity + +public struct RecruitmentPostDTO: Codable { + public let id: String + + public let longitude: String + public let latitude: String + + public let centerId: String + public let centerName: String + public let centerRoadNameAddress: String + + public let isMealAssistance: Bool + public let isBowelAssistance: Bool + public let isWalkingAssistance: Bool + public let isExperiencePreferred: Bool + + public let weekdays: [String] + public let startTime: String + public let endTime: String + public let payType: String + public let payAmount: Int + public let roadNameAddress: String + public let lotNumberAddress: String + public let gender: String + public let age: Int + public let weight: Int? + public let careLevel: Int + public let mentalStatus: String + public let disease: String? + public let lifeAssistance: [String]? + public let extraRequirement: String? + public let applyMethod: [String] + public let applyDeadlineType: String + public let applyDeadline: String? + + func toEntity() -> RecruitmentPostForWorkerBundle { + + let workTimeAndPay: WorkTimeAndPayStateObject = .init() + weekdays.forEach({ dayText in + let entity = WorkDay.toEntity(text: dayText) + workTimeAndPay.selectedDays[entity] = true + }) + workTimeAndPay.workStartTime = IdleDateComponent.toEntity(text: startTime) + workTimeAndPay.workEndTime = IdleDateComponent.toEntity(text: endTime) + workTimeAndPay.paymentType = PaymentType.toEntity(text: payType) + workTimeAndPay.paymentAmount = String(payAmount) + + let addressInfo: AddressInputStateObject = .init() + addressInfo.addressInfo = .init( + roadAddress: roadNameAddress, + jibunAddress: lotNumberAddress + ) + + let customerInfo: CustomerInformationStateObject = .init() + customerInfo.gender = Gender.toEntity(text: gender) + + let currentYear = Calendar.current.component(.year, from: Date()) + customerInfo.birthYear = String(currentYear - age) + customerInfo.weight = (weight == nil) ? String(weight!) : "" + customerInfo.careGrade = CareGrade(rawValue: careLevel-1)! + + customerInfo.cognitionState = CognitionDegree.toEntity(text: mentalStatus) + customerInfo.deceaseDescription = disease ?? "" + + let customerRequirement: CustomerRequirementStateObject = .init() + customerRequirement.mealSupportNeeded = isMealAssistance + customerRequirement.toiletSupportNeeded = isBowelAssistance + customerRequirement.movingSupportNeeded = isWalkingAssistance + customerRequirement.additionalRequirement = extraRequirement ?? "" + lifeAssistance?.forEach({ str in + let entity = DailySupportType.toEntity(text: str) + customerRequirement.dailySupportTypeNeeds[entity] = true + }) + + let applicationDetail: ApplicationDetailStateObject = .init() + applicationDetail.experiencePreferenceType = isExperiencePreferred ? .experiencedFirst : .beginnerPossible + applyMethod.forEach { type in + let entity = ApplyType.toEntity(text: type) + applicationDetail.applyType[entity] = true + } + applicationDetail.applyDeadlineType = ApplyDeadlineType.toEntity(text: applyDeadlineType) + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + applicationDetail.deadlineDate = applyDeadline != nil ? dateFormatter.date(from: applyDeadline!) : nil + + // MARK: CenterInfo + let centerInfo: RecruitmentPostForWorkerBundle.CenterInfo = .init( + centerId: centerId, + centerName: centerName, + centerRoadAddress: centerRoadNameAddress + ) + + let jobLocation: LocationInformation = .init( + longitude: longitude, + latitude: latitude + ) + + return .init( + workTimeAndPay: workTimeAndPay, + customerRequirement: customerRequirement, + customerInformation: customerInfo, + applicationDetail: applicationDetail, + addressInfo: addressInfo, + centerInfo: centerInfo, + jobLocation: jobLocation + ) + } +} + diff --git a/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentPostForWorkerBundle.swift b/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentPostForWorkerBundle.swift new file mode 100644 index 00000000..dd96e117 --- /dev/null +++ b/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentPostForWorkerBundle.swift @@ -0,0 +1,65 @@ +// +// RecruitmentPostForWorkerBundle.swift +// Entity +// +// Created by choijunios on 8/15/24. +// + +import Foundation + +/// 위도경도 좌표를 담고 있습니다. +public struct LocationInformation { + public let longitude: String + public let latitude: String + + public init(longitude: String, latitude: String) { + self.longitude = longitude + self.latitude = latitude + } +} + +public class RecruitmentPostForWorkerBundle { + + public struct CenterInfo { + public let centerId: String + public let centerName: String + public let centerRoadAddress: String + + public init(centerId: String, centerName: String, centerRoadAddress: String) { + self.centerId = centerId + self.centerName = centerName + self.centerRoadAddress = centerRoadAddress + } + } + + public let workTimeAndPay: WorkTimeAndPayStateObject + public let customerRequirement: CustomerRequirementStateObject + public let customerInformation: CustomerInformationStateObject + public let applicationDetail: ApplicationDetailStateObject + public let addressInfo: AddressInputStateObject + + /// 센터정보 + public let centerInfo: CenterInfo + + /// 근무지 위치정보 + public let jobLocation: LocationInformation + + + public init( + workTimeAndPay: WorkTimeAndPayStateObject, + customerRequirement: CustomerRequirementStateObject, + customerInformation: CustomerInformationStateObject, + applicationDetail: ApplicationDetailStateObject, + addressInfo: AddressInputStateObject, + centerInfo: CenterInfo, + jobLocation: LocationInformation + ) { + self.workTimeAndPay = workTimeAndPay + self.customerRequirement = customerRequirement + self.customerInformation = customerInformation + self.applicationDetail = applicationDetail + self.addressInfo = addressInfo + self.centerInfo = centerInfo + self.jobLocation = jobLocation + } +} diff --git a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift index 0e6f0ab8..98bc1e4b 100644 --- a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift +++ b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift @@ -17,4 +17,7 @@ public protocol RecruitmentPostRepository: RepositoryBase { func getPostDetailForCenter(id: String) -> Single func editPostDetail(id: String, bundle: RegisterRecruitmentPostBundle) -> Single + + // MARK: Worker + func getPostDetailForWorker(id: String) -> Single } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailForWorkerVC.swift similarity index 94% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailVC.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailForWorkerVC.swift index 955f0fd8..c24e9359 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailForWorkerVC.swift @@ -1,5 +1,5 @@ // -// PostDetailVc.swift +// PostDetailForWorkerVC.swift // BaseFeature // // Created by choijunios on 8/7/24. @@ -12,7 +12,8 @@ import RxSwift import Entity import DSKit -public class PostDetailVC: BaseViewController { +/// 센토도 요양보호사가 보는 공고화면을 볼 수 있기 때문에 해당뷰를 BaseFeature에 구현하였습니다. +public class PostDetailForWorkerVC: BaseViewController { // Init @@ -22,7 +23,7 @@ public class PostDetailVC: BaseViewController { return bar }() - let contentView = PostDetailContentView() + let contentView = PostDetailForWorkerContentView() // 하단 버튼 let csButton: IdleSecondaryButton = { @@ -132,14 +133,20 @@ public class PostDetailVC: BaseViewController { } // MARK: PostDetailContentView -public class PostDetailContentView: UIView { +public class PostDetailForWorkerContentView: UIView { + /// 구인공고 카드 let cardView: WorkerEmployCard = .init() + /// 지도뷰 let workLocationView = WorkLocationView() + + /// 공고 상세정보들 let workConditionView = WorkConditionDisplayingView() let customerInfoView = CustomerInformationDisplayingView() let applicationDetailView = ApplicationDetailDisplayingView() + + /// 센터 프로필로 이동하는 카드및 센터정보 표시 let centerInfoCard = CenterInfoCardView() public init() { From 825d6279306d79837c2042b39a6c86fa13820bed Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Thu, 15 Aug 2024 23:30:09 +0900 Subject: [PATCH 08/21] =?UTF-8?q?[IDLE-163]=20PostDetailForWorkerVC?= =?UTF-8?q?=EB=B0=94=EC=9D=B8=EB=93=9C=20=ED=95=A8=EC=88=98=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefualtRecruitmentPostUseCase.swift | 4 + .../RecruitmentPostUseCase.swift | 12 ++ .../Detail/PostDetailForWorkerVC.swift | 120 +++++++++++++++--- .../ApplicationDetailDisplayingView.swift | 55 ++++---- .../CustomerInformationDisplayingView.swift | 78 +++++++----- .../WorkConditionDisplayingView.swift | 54 +++++--- 6 files changed, 231 insertions(+), 92 deletions(-) diff --git a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift index 5c95aa67..588c1519 100644 --- a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift @@ -53,4 +53,8 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { public func getPostDetailForCenter(id: String) -> RxSwift.Single> { convert(task: repository.getPostDetailForCenter(id: id)) } + + public func getPostDetailForWorker(id: String) -> RxSwift.Single> { + convert(task: repository.getPostDetailForWorker(id: id)) + } } diff --git a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift index 737b1a79..d62dec8c 100644 --- a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift @@ -10,6 +10,8 @@ import Entity import RxSwift public protocol RecruitmentPostUseCase: UseCaseBase { + + // MARK: Center /// 센터측이 공고를 등록하는 액션입니다. func registerRecruitmentPost(inputs: RegisterRecruitmentPostBundle) -> Single> @@ -19,4 +21,14 @@ public protocol RecruitmentPostUseCase: UseCaseBase { /// 센터측이 공고를 조회하는 액션입니다. func getPostDetailForCenter(id: String) -> Single> + + + // MARK: Worker + + /// 요양보호사가 공고상세를 확인하는 경우에 호출합니다. + /// - 반환값 + /// - 공고상세정보(센터와 달리 고객 이름 배제) + /// - 근무지 위치(위경도) + /// - 센터정보(센터 id, 이름, 도로명 주소) + func getPostDetailForWorker(id: String) -> Single> } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailForWorkerVC.swift index c24e9359..c9ab34a9 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailForWorkerVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailForWorkerVC.swift @@ -12,14 +12,37 @@ import RxSwift import Entity import DSKit +public struct WorkAndWorkerLocationMapRO { + + let workLocation: LocationInformation + let workerLocation: LocationInformation +} + +public protocol PostDetailForWorkerViewModelable { + + // Output + var postForWorkerBundle: Driver? { get } + var locationInfo: Driver? { get } + + // Input + var backButtonClicked: PublishRelay { get } + var applyButtonClicked: PublishRelay { get } + var startButtonClicked: PublishRelay { get } + var centerCardClicked: PublishRelay { get } + var viewWillAppear: PublishRelay { get } +} + /// 센토도 요양보호사가 보는 공고화면을 볼 수 있기 때문에 해당뷰를 BaseFeature에 구현하였습니다. public class PostDetailForWorkerVC: BaseViewController { + var viewModel: PostDetailForWorkerViewModelable? + // Init // View - let navigationBar: NavigationBarType1 = { - let bar = NavigationBarType1(navigationTitle: "공고 정보") + let navigationBar: IdleNavigationBar = { + let bar = IdleNavigationBar(innerViews: []) + bar.titleLabel.textString = "공고 정보" return bar }() @@ -94,8 +117,8 @@ public class PostDetailForWorkerVC: BaseViewController { } NSLayoutConstraint.activate([ - navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 21), - navigationBar.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 12), + 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), @@ -121,14 +144,86 @@ public class PostDetailForWorkerVC: BaseViewController { .disposed(by: disposeBag) } - public func bind() { + public func bind(viewModel: PostDetailForWorkerViewModelable) { + + self.viewModel = viewModel - // back button + // Output + viewModel + .postForWorkerBundle? + .drive(onNext: { + [weak self] bundle in + guard let self else { return } + + // 근무 조건 + contentView.workConditionView.bind( + workTimeAndPayStateObject: bundle.workTimeAndPay, + addressInputStateObject: bundle.addressInfo + ) + + // 고객 정보 + contentView.customerInfoView.bind( + customerInformationStateObject: bundle.customerInformation, + customerRequirementStateObject: bundle.customerRequirement + ) + + // 추가 지원 정보 + contentView.applicationDetailView.bind( + applicationDetailStateObject: bundle.applicationDetail + ) + + // 센터 정보 카드 + let centerInfo = bundle.centerInfo + contentView.centerInfoCard.bind( + nameText: centerInfo.centerName, + locationText: centerInfo.centerRoadAddress + ) + + }) + .disposed(by: disposeBag) - // Content view - contentView.bind() - // button + viewModel + .locationInfo? + .drive(onNext: { bundle in + + // 위치정보 전달 + + }) + .disposed(by: disposeBag) + + // Input + + // viewWillAppear + self.rx.viewWillAppear + .map({ _ in }) + .bind(to: viewModel.viewWillAppear) + .disposed(by: disposeBag) + + // 지원하기 + applyButton + .rx.tap + .bind(to: viewModel.applyButtonClicked) + .disposed(by: disposeBag) + + // 즐겨 찾기 버튼 + contentView.cardView.starButton + .eventPublisher + .map { state in return state == .accent } + .bind(to: viewModel.startButtonClicked) + .disposed(by: disposeBag) + + // 센터 프로필 보기 버튼 + contentView.centerInfoCard + .rx.tap + .bind(to: viewModel.applyButtonClicked) + .disposed(by: disposeBag) + + // 뒤로가기 버튼 + navigationBar.backButton + .rx.tap + .bind(to: viewModel.backButtonClicked) + .disposed(by: disposeBag) } } @@ -244,11 +339,4 @@ public class PostDetailForWorkerContentView: UIView { } - - public func bind() { - - cardView.bind(ro: .mock) - workLocationView.bind() - centerInfoCard.bind(nameText: "세얼간이 센터", locationText: "아남타워 7층") - } } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/ApplicationDetailDisplayingView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/ApplicationDetailDisplayingView.swift index 19811692..5f1311c7 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/ApplicationDetailDisplayingView.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/ApplicationDetailDisplayingView.swift @@ -105,6 +105,36 @@ public class ApplicationDetailDisplayingView: HStack { public extension ApplicationDetailDisplayingView { + private func applyObject(_ object: ApplicationDetailStateObject) { + expPreferenceLabel.textString = object.experiencePreferenceType?.korTextForBtn ?? "오류" + + applTypeLabel.textString = object.applyType.compactMap({ (key, value) -> String? in + value ? key.twoLetterKorTextForDisplay : nil + }).joined(separator: ", ") + + if let type = object.applyDeadlineType { + if type == .specificDate { + if let date = object.deadlineDate { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy. MM. dd" + let deadLineText = dateFormatter.string(from: date) + deadlineLabel.textString = deadLineText + } else { + deadlineLabel.textString = "오류" + } + } else { + deadlineLabel.textString = type.korTextForBtn + } + + } else { + deadlineLabel.textString = "오류" + } + } + + func bind(applicationDetailStateObject: ApplicationDetailStateObject) { + applyObject(applicationDetailStateObject) + } + /// ViewModelType: ApplicationDetailContentVMable func bind(viewModel: ApplicationDetailDisplayingVMable) { @@ -112,30 +142,7 @@ public extension ApplicationDetailDisplayingView { .casting_applicationDetail .drive(onNext: { [weak self] object in guard let self else { return } - - expPreferenceLabel.textString = object.experiencePreferenceType?.korTextForBtn ?? "오류" - - applTypeLabel.textString = object.applyType.compactMap({ (key, value) -> String? in - value ? key.twoLetterKorTextForDisplay : nil - }).joined(separator: ", ") - - if let type = object.applyDeadlineType { - if type == .specificDate { - if let date = object.deadlineDate { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy. MM. dd" - let deadLineText = dateFormatter.string(from: date) - deadlineLabel.textString = deadLineText - } else { - deadlineLabel.textString = "오류" - } - } else { - deadlineLabel.textString = type.korTextForBtn - } - - } else { - deadlineLabel.textString = "오류" - } + applyObject(object) }) .disposed(by: disposeBag) } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/CustomerInformationDisplayingView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/CustomerInformationDisplayingView.swift index 64c86890..a59cec74 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/CustomerInformationDisplayingView.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/CustomerInformationDisplayingView.swift @@ -202,32 +202,57 @@ public class CustomerInformationDisplayingView: VStack { public extension CustomerInformationDisplayingView { + private func applyObject(_ object: CustomerInformationStateObject) { + nameLabel.textString = object.name + genderLabel.textString = object.gender?.twoLetterKoreanWord ?? "오류" + birthYearLabel.textString = object.birthYear + weightLabel.textString = object.weight + + if let careGrade = object.careGrade { + let text: String = careGrade.textForCellBtn + "등급" + careGradeLabel.textString = text + } else { + careGradeLabel.textString = "오류" + } + + if let cognitionState = object.cognitionState { + cognitionStateLabel.textString = cognitionState.korTextForCellBtn + } else { + cognitionStateLabel.textString = "오류" + } + + deceaseLabel.textString = object.deceaseDescription.isEmpty ? "-" : object.deceaseDescription + } + private func applyObject(_ object: CustomerRequirementStateObject) { + + mealSupportLabel.textString = object.mealSupportNeeded == true ? "필요" : "불필요" + toiletSupportLabel.textString = object.toiletSupportNeeded == true ? "필요" : "불필요" + movingSupportLabel.textString = object.movingSupportNeeded == true ? "필요" : "불필요" + + let dailySupportText = object.dailySupportTypeNeeds.compactMap { (day, isActive) -> String? in + return isActive ? day.korLetterTextForBtn : nil + }.joined(separator: ", ") + + dailySupportLabel.textString = dailySupportText + + additionalTextLabel.textString = object.additionalRequirement + } + + func bind( + customerInformationStateObject: CustomerInformationStateObject, + customerRequirementStateObject: CustomerRequirementStateObject + ) { + applyObject(customerInformationStateObject) + applyObject(customerRequirementStateObject) + } + func bind(viewModel: CustomerInformationDisplayingVMable) { viewModel .casting_customerInformation .drive(onNext: { [weak self] object in guard let self else { return } - - nameLabel.textString = object.name - genderLabel.textString = object.gender?.twoLetterKoreanWord ?? "오류" - birthYearLabel.textString = object.birthYear - weightLabel.textString = object.weight - - if let careGrade = object.careGrade { - let text: String = careGrade.textForCellBtn + "등급" - careGradeLabel.textString = text - } else { - careGradeLabel.textString = "오류" - } - - if let cognitionState = object.cognitionState { - cognitionStateLabel.textString = cognitionState.korTextForCellBtn - } else { - cognitionStateLabel.textString = "오류" - } - - deceaseLabel.textString = object.deceaseDescription.isEmpty ? "-" : object.deceaseDescription + applyObject(object) }) .disposed(by: disposeBag) @@ -235,18 +260,7 @@ public extension CustomerInformationDisplayingView { .casting_customerRequirement .drive(onNext: { [weak self] object in guard let self else { return } - - mealSupportLabel.textString = object.mealSupportNeeded == true ? "필요" : "불필요" - toiletSupportLabel.textString = object.toiletSupportNeeded == true ? "필요" : "불필요" - movingSupportLabel.textString = object.movingSupportNeeded == true ? "필요" : "불필요" - - let dailySupportText = object.dailySupportTypeNeeds.compactMap { (day, isActive) -> String? in - return isActive ? day.korLetterTextForBtn : nil - }.joined(separator: ", ") - - dailySupportLabel.textString = dailySupportText - - additionalTextLabel.textString = object.additionalRequirement + applyObject(object) }) .disposed(by: disposeBag) } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/WorkConditionDisplayingView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/WorkConditionDisplayingView.swift index 5569d4cc..d4f35283 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/WorkConditionDisplayingView.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/WorkConditionDisplayingView.swift @@ -108,30 +108,45 @@ public class WorkConditionDisplayingView: HStack { public extension WorkConditionDisplayingView { + private func applyObject(_ object: WorkTimeAndPayStateObject) { + let daysText = object.selectedDays.compactMap { (day, isActive) -> String? in + return isActive ? day.korOneLetterText : nil + }.joined(separator: ", ") + + workDaysLabel.textString = daysText + + let workTimeText = [ + object.workStartTime?.convertToStringForButton() ?? "00:00", + object.workEndTime?.convertToStringForButton() ?? "00:00" + ].joined(separator: " - ") + + workTimeLabel.textString = workTimeText + + let paymentTypeText = object.paymentType?.korLetterText ?? "오류" + let paymentAmountText = object.paymentAmount + + workPaymentLabel.textString = "\(paymentTypeText) \(paymentAmountText)원" + } + + private func applyObject(_ object: AddressInputStateObject) { + workLocationLabel.textString = object.addressInfo?.roadAddress ?? "오류" + } + + func bind( + workTimeAndPayStateObject: WorkTimeAndPayStateObject, + addressInputStateObject: AddressInputStateObject + ) { + applyObject(workTimeAndPayStateObject) + applyObject(addressInputStateObject) + } + func bind(viewModel: WorkConditionDisplayingVMable) { viewModel .casting_workTimeAndPay .drive(onNext: { [weak self] object in guard let self else { return } - - let daysText = object.selectedDays.compactMap { (day, isActive) -> String? in - return isActive ? day.korOneLetterText : nil - }.joined(separator: ", ") - - workDaysLabel.textString = daysText - - let workTimeText = [ - object.workStartTime?.convertToStringForButton() ?? "00:00", - object.workEndTime?.convertToStringForButton() ?? "00:00" - ].joined(separator: " - ") - - workTimeLabel.textString = workTimeText - - let paymentTypeText = object.paymentType?.korLetterText ?? "오류" - let paymentAmountText = object.paymentAmount - - workPaymentLabel.textString = "\(paymentTypeText) \(paymentAmountText)원" + applyObject(object) }) .disposed(by: disposeBag) @@ -139,8 +154,7 @@ public extension WorkConditionDisplayingView { .casting_addressInput .drive(onNext: { [weak self] object in guard let self else { return } - - workLocationLabel.textString = object.addressInfo?.roadAddress ?? "오류" + applyObject(object) }) .disposed(by: disposeBag) } From 8a1e65314e91e7ef3217d58dc2c3dd23d92dd20d Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 16 Aug 2024 00:19:35 +0900 Subject: [PATCH 09/21] =?UTF-8?q?[IDLE-163]=20=EA=B3=B5=EA=B3=A0=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=9D=B4=EB=8F=99=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefaultRecruitmentPostRepository.swift | 2 +- .../RecruitmentPostDetailForWorkerDTO.swift | 2 +- .../DefualtRecruitmentPostUseCase.swift | 1 - .../Entity/VO/Employ/WorkerEmployCardVO.swift | 9 +- .../Edit/AddressContentView.swift | 0 .../ApplicationDetailViewContentView.swift | 0 .../Edit/CustomerInformationContentView.swift | 0 .../Edit/CustomerRequirementContentView.swift | 0 .../Edit/WorkTimeAndPayContentView.swift | 0 .../ApplicationDetailDisplayingView.swift | 0 .../CustomerInformationDisplayingView.swift | 0 .../WorkConditionDisplayingView.swift | 0 .../Detail/Coordinator/Coordinator.swift | 72 ++++++++++++++ .../Detail/View}/PostDetailForWorkerVC.swift | 27 +++--- .../Detail/View}/SelectCSTypeVC.swift | 0 .../Detail/View}/WorkLocationView.swift | 0 .../ViewModel/PostDetailForWorkerVM.swift | 93 +++++++++++++++++++ .../PostDetailForCenterVM.swift | 1 - .../RegisterRecruitmentPostVM.swift | 1 - .../WorkerRecruitmentBoardCoordinator.swift | 12 ++- .../WorkerRecruitmentPostBoardVC.swift | 7 +- .../WorkerRecruitmentPostBoardVM.swift | 8 +- 22 files changed, 204 insertions(+), 31 deletions(-) rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/{ => Center}/Edit/AddressContentView.swift (100%) rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/{ => Center}/Edit/ApplicationDetailViewContentView.swift (100%) rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/{ => Center}/Edit/CustomerInformationContentView.swift (100%) rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/{ => Center}/Edit/CustomerRequirementContentView.swift (100%) rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/{ => Center}/Edit/WorkTimeAndPayContentView.swift (100%) rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/{ => Center}/OverView/ApplicationDetailDisplayingView.swift (100%) rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/{ => Center}/OverView/CustomerInformationDisplayingView.swift (100%) rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/{ => Center}/OverView/WorkConditionDisplayingView.swift (100%) create mode 100644 project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/Coordinator.swift rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/{Detail => Worker/Detail/View}/PostDetailForWorkerVC.swift (94%) rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/{Detail => Worker/Detail/View}/SelectCSTypeVC.swift (100%) rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/{Detail => Worker/Detail/View}/WorkLocationView.swift (100%) create mode 100644 project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift diff --git a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift index 42a74022..4beb068f 100644 --- a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift +++ b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift @@ -57,7 +57,7 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { ) .map(RecruitmentPostDTO.self) .map { dto in - <#code#> + dto.toEntity() } } } diff --git a/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift index 17231782..ae51621e 100644 --- a/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift +++ b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift @@ -42,7 +42,7 @@ public struct RecruitmentPostDTO: Codable { public let applyDeadlineType: String public let applyDeadline: String? - func toEntity() -> RecruitmentPostForWorkerBundle { + public func toEntity() -> RecruitmentPostForWorkerBundle { let workTimeAndPay: WorkTimeAndPayStateObject = .init() weekdays.forEach({ dayText in diff --git a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift index 588c1519..65685f1a 100644 --- a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift @@ -49,7 +49,6 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { ) } - public func getPostDetailForCenter(id: String) -> RxSwift.Single> { convert(task: repository.getPostDetailForCenter(id: id)) } diff --git a/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift b/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift index 51a64330..0268a123 100644 --- a/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift +++ b/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift @@ -9,7 +9,6 @@ import Foundation public struct WorkerEmployCardVO { - public let postId: String public let dayLeft: Int public let isBeginnerPossible: Bool public let timeTakenForWalk: String @@ -23,8 +22,8 @@ public struct WorkerEmployCardVO { public let paymentType: PaymentType public let paymentAmount: Int - public init(postId: String, dayLeft: Int, isBeginnerPossible: Bool, timeTakenForWalk: String, title: String, targetAge: Int, careGrade: CareGrade, targetGender: Gender, days: [WorkDay], startTime: String, endTime: String, paymentType: PaymentType, paymentAmount: Int) { - self.postId = postId + public init(dayLeft: Int, isBeginnerPossible: Bool, timeTakenForWalk: String, title: String, targetAge: Int, careGrade: CareGrade, targetGender: Gender, days: [WorkDay], startTime: String, endTime: String, paymentType: PaymentType, paymentAmount: Int) { + self.dayLeft = dayLeft self.isBeginnerPossible = isBeginnerPossible self.timeTakenForWalk = timeTakenForWalk @@ -40,7 +39,6 @@ public struct WorkerEmployCardVO { } public static func create( - postId: String, workTimeAndPay: WorkTimeAndPayStateObject, customerRequirement: CustomerRequirementStateObject, customerInformation: CustomerInformationStateObject, @@ -93,7 +91,6 @@ public struct WorkerEmployCardVO { let paymentAmount = Int(workTimeAndPay.paymentAmount) ?? 0 return WorkerEmployCardVO( - postId: postId, dayLeft: leftDay ?? 0, isBeginnerPossible: isBeginnerPossible, timeTakenForWalk: timeTakenForWalk, @@ -121,7 +118,6 @@ fileprivate extension String { public extension WorkerEmployCardVO { static let mock = WorkerEmployCardVO( - postId: "00-00000-00000", dayLeft: 10, isBeginnerPossible: true, timeTakenForWalk: "도보 15분", @@ -137,7 +133,6 @@ public extension WorkerEmployCardVO { ) static let `default` = WorkerEmployCardVO( - postId: "00-00000-00000", dayLeft: 0, isBeginnerPossible: true, timeTakenForWalk: "도보 15분", diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Edit/AddressContentView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/Edit/AddressContentView.swift similarity index 100% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Edit/AddressContentView.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/Edit/AddressContentView.swift diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Edit/ApplicationDetailViewContentView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/Edit/ApplicationDetailViewContentView.swift similarity index 100% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Edit/ApplicationDetailViewContentView.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/Edit/ApplicationDetailViewContentView.swift diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Edit/CustomerInformationContentView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/Edit/CustomerInformationContentView.swift similarity index 100% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Edit/CustomerInformationContentView.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/Edit/CustomerInformationContentView.swift diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Edit/CustomerRequirementContentView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/Edit/CustomerRequirementContentView.swift similarity index 100% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Edit/CustomerRequirementContentView.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/Edit/CustomerRequirementContentView.swift diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Edit/WorkTimeAndPayContentView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/Edit/WorkTimeAndPayContentView.swift similarity index 100% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Edit/WorkTimeAndPayContentView.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/Edit/WorkTimeAndPayContentView.swift diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/ApplicationDetailDisplayingView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/OverView/ApplicationDetailDisplayingView.swift similarity index 100% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/ApplicationDetailDisplayingView.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/OverView/ApplicationDetailDisplayingView.swift diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/CustomerInformationDisplayingView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/OverView/CustomerInformationDisplayingView.swift similarity index 100% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/CustomerInformationDisplayingView.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/OverView/CustomerInformationDisplayingView.swift diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/WorkConditionDisplayingView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/OverView/WorkConditionDisplayingView.swift similarity index 100% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/OverView/WorkConditionDisplayingView.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Center/OverView/WorkConditionDisplayingView.swift diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/Coordinator.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/Coordinator.swift new file mode 100644 index 00000000..ee198bc6 --- /dev/null +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/Coordinator.swift @@ -0,0 +1,72 @@ +// +// Coordinator.swift +// BaseFeature +// +// Created by choijunios on 8/15/24. +// + +import UIKit +import PresentationCore +import UseCaseInterface +import Entity + +public class PostDetailForWorkerCoodinator: ChildCoordinator { + + public struct Dependency { + let postId: String + weak var parent: WorkerRecruitmentBoardCoordinatable? + let navigationController: UINavigationController + let recruitmentPostUseCase: RecruitmentPostUseCase + + public init(postId: String, parent: WorkerRecruitmentBoardCoordinatable? = nil, navigationController: UINavigationController, recruitmentPostUseCase: RecruitmentPostUseCase) { + self.postId = postId + self.parent = parent + self.navigationController = navigationController + self.recruitmentPostUseCase = recruitmentPostUseCase + } + } + + public weak var viewControllerRef: UIViewController? + public weak var parent: WorkerRecruitmentBoardCoordinatable? + + let postId: String + public let navigationController: UINavigationController + let recruitmentPostUseCase: RecruitmentPostUseCase + + public init( + dependency: Dependency + ) { + self.postId = dependency.postId + self.parent = dependency.parent + self.navigationController = dependency.navigationController + self.recruitmentPostUseCase = dependency.recruitmentPostUseCase + } + + deinit { + printIfDebug("\(String(describing: PostDetailForWorkerCoodinator.self))") + } + + public func start() { + let vc = PostDetailForWorkerVC() + let vm = PostDetailForWorkerVM( + postId: postId, + coordinator: self, + recruitmentPostUseCase: recruitmentPostUseCase + ) + vc.bind(viewModel: vm) + viewControllerRef = vc + navigationController.pushViewController(vc, animated: true) + } + + public func coordinatorDidFinish() { + popViewController() + parent?.removeChildCoordinator(self) + } +} + +extension PostDetailForWorkerCoodinator { + + func showCenterProfileScreen(centerId: String) { + parent?.showCenterProfile(centerId: centerId) + } +} diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift similarity index 94% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailForWorkerVC.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift index c9ab34a9..e72d243d 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/PostDetailForWorkerVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift @@ -17,20 +17,6 @@ public struct WorkAndWorkerLocationMapRO { let workLocation: LocationInformation let workerLocation: LocationInformation } - -public protocol PostDetailForWorkerViewModelable { - - // Output - var postForWorkerBundle: Driver? { get } - var locationInfo: Driver? { get } - - // Input - var backButtonClicked: PublishRelay { get } - var applyButtonClicked: PublishRelay { get } - var startButtonClicked: PublishRelay { get } - var centerCardClicked: PublishRelay { get } - var viewWillAppear: PublishRelay { get } -} /// 센토도 요양보호사가 보는 공고화면을 볼 수 있기 때문에 해당뷰를 BaseFeature에 구현하였습니다. public class PostDetailForWorkerVC: BaseViewController { @@ -155,6 +141,19 @@ public class PostDetailForWorkerVC: BaseViewController { [weak self] bundle in guard let self else { return } + // 상단 구인공고 카드 + contentView.cardView.bind( + ro: WorkerEmployCardRO.create( + vo: .create( + workTimeAndPay: bundle.workTimeAndPay, + customerRequirement: bundle.customerRequirement, + customerInformation: bundle.customerInformation, + applicationDetail: bundle.applicationDetail, + addressInfo: bundle.addressInfo + ) + ) + ) + // 근무 조건 contentView.workConditionView.bind( workTimeAndPayStateObject: bundle.workTimeAndPay, diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/SelectCSTypeVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/SelectCSTypeVC.swift similarity index 100% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/SelectCSTypeVC.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/SelectCSTypeVC.swift diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/WorkLocationView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkLocationView.swift similarity index 100% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Detail/WorkLocationView.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkLocationView.swift diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift new file mode 100644 index 00000000..495385ef --- /dev/null +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift @@ -0,0 +1,93 @@ +// +// asd.swift +// BaseFeature +// +// Created by choijunios on 8/15/24. +// + +import UIKit +import RxCocoa +import RxSwift +import Entity +import PresentationCore +import UseCaseInterface + +public protocol PostDetailForWorkerViewModelable: DefaultAlertOutputable { + + // Output + var postForWorkerBundle: Driver? { get } + var locationInfo: Driver? { get } + + // Input + var viewWillAppear: PublishRelay { get } + + var backButtonClicked: PublishRelay { get } + var applyButtonClicked: PublishRelay { get } + var startButtonClicked: PublishRelay { get } + var centerCardClicked: PublishRelay { get } +} + +public class PostDetailForWorkerVM: PostDetailForWorkerViewModelable { + + public weak var coordinator: PostDetailForWorkerCoodinator? + + // Init + private let postId: String + private let recruitmentPostUseCase: RecruitmentPostUseCase + + + public var postForWorkerBundle: RxCocoa.Driver? + public var locationInfo: RxCocoa.Driver? + + public var alert: RxCocoa.Driver? + + + + public var backButtonClicked: RxRelay.PublishRelay = .init() + public var applyButtonClicked: RxRelay.PublishRelay = .init() + public var startButtonClicked: RxRelay.PublishRelay = .init() + public var centerCardClicked: RxRelay.PublishRelay = .init() + public var viewWillAppear: RxRelay.PublishRelay = .init() + + private let disposeBag = DisposeBag() + + public init( + postId: String, + coordinator: PostDetailForWorkerCoodinator?, + recruitmentPostUseCase: RecruitmentPostUseCase + ) + { + self.postId = postId + self.coordinator = coordinator + self.recruitmentPostUseCase = recruitmentPostUseCase + + let getPostDetailResult = viewWillAppear + .flatMap { [recruitmentPostUseCase] _ in + recruitmentPostUseCase + .getPostDetailForWorker(id: postId) + } + .share() + + let getPostDetailSuccess = getPostDetailResult.compactMap { $0.value } + let getPostDetailFailure = getPostDetailResult.compactMap { $0.error } + + postForWorkerBundle = getPostDetailSuccess.asDriver(onErrorRecover: { _ in fatalError() }) + + // Alert 처리 필요 + + // MARK: 버튼 처리 + backButtonClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + coordinator?.coordinatorDidFinish() + }) + .disposed(by: disposeBag) + + // 지원하기 버튼 클릭 + + // 즐겨찾기 버튼 클릭 + + // 센터 프로필 조회 버튼클릭 + + } +} diff --git a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/PostDetailForCenterVM.swift b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/PostDetailForCenterVM.swift index 651da0d9..cb488146 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/PostDetailForCenterVM.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/PostDetailForCenterVM.swift @@ -122,7 +122,6 @@ public class PostDetailForCenterVM: PostDetailViewModelable { fetched_addressInfo.accept(bundle.addressInfo) return WorkerEmployCardVO.create( - postId: id, workTimeAndPay: fetched_workTimeAndPay.value, customerRequirement: fetched_customerRequirement.value, customerInformation: fetched_customerInformation.value, diff --git a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift index a1554f74..b64439ec 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/RegisterRecruitmentPostVM.swift @@ -395,7 +395,6 @@ public class RegisterRecruitmentPostVM: RegisterRecruitmentPostViewModelable { ] emitter in let vo = WorkerEmployCardVO.create( - postId: "", workTimeAndPay: editing_workTimeAndPay.value, customerRequirement: editing_customerRequirement.value, customerInformation: editing_customerInformation.value, diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift index 2bf60d04..aab6f831 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift @@ -7,6 +7,7 @@ import UIKit import WorkerFeature +import BaseFeature import PresentationCore import UseCaseInterface @@ -58,7 +59,16 @@ public class WorkerRecruitmentBoardCoordinator: WorkerRecruitmentBoardCoordinata extension WorkerRecruitmentBoardCoordinator { public func showPostDetail(postId: String) { - + let coodinator = PostDetailForWorkerCoodinator( + dependency: .init( + postId: postId, + parent: self, + navigationController: navigationController, + recruitmentPostUseCase: recruitmentPostUseCase + ) + ) + addChildCoordinator(coodinator) + coodinator.start() } public func showCenterProfile(centerId: String) { diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift index 4475065d..6f9b009f 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift @@ -120,9 +120,14 @@ extension WorkerRecruitmentPostBoardVC: UITableViewDataSource, UITableViewDelega let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell cell.selectionStyle = .none + // MARK: TODO: PostId를 가져오기 + if let viewModel = self.viewModel { let vo = ongoingPostCardVO.value[indexPath.row] - let vm = viewModel.createCellVM(vo: vo) + let vm = viewModel.createCellVM( + postId: "00-00000-00000", + vo: vo + ) cell.bind(viewModel: vm) } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/WorkerRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/WorkerRecruitmentPostBoardVM.swift index 413419ce..4b29064a 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/WorkerRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/WorkerRecruitmentPostBoardVM.swift @@ -19,7 +19,7 @@ public protocol WorkerRecruitmentPostBoardVMable: DefaultAlertOutputable { var viewWillAppear: PublishRelay { get } var locationTtitleText: PublishRelay { get } - func createCellVM(vo: WorkerEmployCardVO) -> WorkerEmployCardViewModelable + func createCellVM(postId: String, vo: WorkerEmployCardVO) -> WorkerEmployCardViewModelable } @@ -58,8 +58,9 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { .asDriver(onErrorJustReturn: .default) } - public func createCellVM(vo: Entity.WorkerEmployCardVO) -> any DSKit.WorkerEmployCardViewModelable { + public func createCellVM(postId: String, vo: Entity.WorkerEmployCardVO) -> any DSKit.WorkerEmployCardViewModelable { WorkerEmployCardVM( + postId: postId, vo: vo, coordinator: coordinator ) @@ -88,11 +89,12 @@ public class WorkerEmployCardVM: WorkerEmployCardViewModelable { public init ( + postId: String, vo: WorkerEmployCardVO, coordinator: WorkerRecruitmentBoardCoordinatable? = nil ) { - self.postId = vo.postId + self.postId = postId self.coordinator = coordinator // MARK: 지원여부 From 7cc1f485e05b949b2ac78674e332a0df09c38119 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 16 Aug 2024 09:56:07 +0900 Subject: [PATCH 10/21] =?UTF-8?q?[IDLE-163]=20=EA=B3=B5=EA=B3=A0=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=ED=99=94=EB=A9=B4=20=EA=B3=B5=EA=B3=A0?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=ED=83=80=EC=9D=B4=ED=8F=AC=20=EA=B7=B8?= =?UTF-8?q?=EB=9E=98=ED=94=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonUI/Card/Post/Worker/WorkerEmployCard.swift | 9 +++++++++ .../Card/Profile/Center/CenterInfoCardView.swift | 1 + .../Worker/Detail/View/PostDetailForWorkerVC.swift | 6 +++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift index f8796d50..2d617d00 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift @@ -294,6 +294,15 @@ public class WorkerEmployCard: UIView { ]) } + public func setToPostAppearance() { + titleLabel.typography = .Subtitle1 + timeTakenForWalkLabel.isHidden = true + serviceTargetInfoLabel.typography = .Body3 + workDaysLabel.typography = .Body2 + workTimeLabel.typography = .Body2 + payLabel.typography = .Body2 + } + public func bind(ro: WorkerEmployCardRO) { beginnerTag.isHidden = !ro.showBiginnerTag 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 7c517350..1f6422f5 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 @@ -48,6 +48,7 @@ public class CenterInfoCardView: TappableUIView { 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/PostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift index e72d243d..4d45dd9b 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift @@ -230,7 +230,11 @@ public class PostDetailForWorkerVC: BaseViewController { public class PostDetailForWorkerContentView: UIView { /// 구인공고 카드 - let cardView: WorkerEmployCard = .init() + let cardView: WorkerEmployCard = { + let view = WorkerEmployCard() + view.setToPostAppearance() + return view + }() /// 지도뷰 let workLocationView = WorkLocationView() From 1545a6b4ecfe24f6c461451dd8e4bb941ac3010a Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 16 Aug 2024 10:29:07 +0900 Subject: [PATCH 11/21] =?UTF-8?q?[IDLE-163]=20=EA=B3=B5=EA=B3=A0=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=84=BC=ED=84=B0=ED=94=84=EB=A1=9C=ED=95=84=20=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EA=B5=AC=ED=98=84=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CenterProfileRegisterCoordinator.swift | 8 ++-- ...ft => PostDetailForWorkerCoodinator.swift} | 0 .../Detail/View/PostDetailForWorkerVC.swift | 2 +- .../ViewModel/PostDetailForWorkerVM.swift | 11 +++++- .../CenterProfileCoordinator.swift | 38 +++++++++++-------- .../Profile/CenterProfileViewController.swift | 3 +- .../WorkerRecruitmentBoardCoordinator.swift | 12 +++++- 7 files changed, 50 insertions(+), 24 deletions(-) rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/{Coordinator.swift => PostDetailForWorkerCoodinator.swift} (100%) diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Center /OtherCoordinator/CenterProfileRegisterCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Center /OtherCoordinator/CenterProfileRegisterCoordinator.swift index eb2538f4..d10f28d4 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Center /OtherCoordinator/CenterProfileRegisterCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Center /OtherCoordinator/CenterProfileRegisterCoordinator.swift @@ -64,9 +64,11 @@ extension CenterProfileRegisterCoordinator { func showMyCenterProfile() { let coordinator = CenterProfileCoordinator( - mode: .myProfile, - profileUseCase: injector.resolve(CenterProfileUseCase.self), - navigationController: navigationController + dependency: .init( + mode: .myProfile, + profileUseCase: injector.resolve(CenterProfileUseCase.self), + navigationController: navigationController + ) ) addChildCoordinator(coordinator) coordinator.parent = self diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/Coordinator.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/PostDetailForWorkerCoodinator.swift similarity index 100% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/Coordinator.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/PostDetailForWorkerCoodinator.swift diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift index 4d45dd9b..a1a36d63 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift @@ -215,7 +215,7 @@ public class PostDetailForWorkerVC: BaseViewController { // 센터 프로필 보기 버튼 contentView.centerInfoCard .rx.tap - .bind(to: viewModel.applyButtonClicked) + .bind(to: viewModel.centerCardClicked) .disposed(by: disposeBag) // 뒤로가기 버튼 diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift index 495385ef..d5430b30 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift @@ -79,7 +79,7 @@ public class PostDetailForWorkerVM: PostDetailForWorkerViewModelable { backButtonClicked .subscribe(onNext: { [weak self] _ in guard let self else { return } - coordinator?.coordinatorDidFinish() + self.coordinator?.coordinatorDidFinish() }) .disposed(by: disposeBag) @@ -88,6 +88,13 @@ public class PostDetailForWorkerVM: PostDetailForWorkerViewModelable { // 즐겨찾기 버튼 클릭 // 센터 프로필 조회 버튼클릭 - + centerCardClicked + .withLatestFrom(getPostDetailSuccess) + .subscribe(onNext: { [weak self] bundle in + guard let self else { return } + let centerId = bundle.centerInfo.centerId + self.coordinator?.showCenterProfileScreen(centerId: centerId) + }) + .disposed(by: disposeBag) } } diff --git a/project/Projects/Presentation/Feature/Center/Sources/Coordinator/RegisterCenterInfo/CenterProfileCoordinator.swift b/project/Projects/Presentation/Feature/Center/Sources/Coordinator/RegisterCenterInfo/CenterProfileCoordinator.swift index 0d3c814a..28e0754e 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/Coordinator/RegisterCenterInfo/CenterProfileCoordinator.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/Coordinator/RegisterCenterInfo/CenterProfileCoordinator.swift @@ -12,36 +12,42 @@ import Entity /// 내센터, 다른 센터를 모두 불러올 수 있습니다. public class CenterProfileCoordinator: ChildCoordinator { + + public struct Dependency { + let mode: ProfileMode + let profileUseCase: CenterProfileUseCase + let navigationController: UINavigationController + + public init(mode: ProfileMode, profileUseCase: CenterProfileUseCase, navigationController: UINavigationController) { + self.mode = mode + self.profileUseCase = profileUseCase + self.navigationController = navigationController + } + } public weak var viewControllerRef: UIViewController? - public weak var parent: CenterProfileRegisterCoordinatable? + public weak var parent: ParentCoordinator? public let navigationController: UINavigationController + let mode: ProfileMode + let profileUseCase: CenterProfileUseCase - public let viewModel: any CenterProfileViewModelable - - public init( - mode: ProfileMode, - profileUseCase: CenterProfileUseCase, - navigationController: UINavigationController - ) { - self.viewModel = CenterProfileViewModel(mode: mode, useCase: profileUseCase) - self.navigationController = navigationController + public init(dependency: Dependency) { + self.mode = dependency.mode + self.profileUseCase = dependency.profileUseCase + self.navigationController = dependency.navigationController } public func start() { let vc = CenterProfileViewController(coordinator: self) - vc.bind(viewModel: viewModel) + let vm = CenterProfileViewModel(mode: mode, useCase: profileUseCase) + vc.bind(viewModel: vm) self.viewControllerRef = vc navigationController.pushViewController(vc, animated: true) } public func coordinatorDidFinish() { - parent?.removeChildCoordinator(self) - } - - func closeViewController() { popViewController() - coordinatorDidFinish() + parent?.removeChildCoordinator(self) } } diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift b/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift index 0497c0f5..93969031 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift @@ -191,6 +191,7 @@ public class CenterProfileViewController: BaseViewController { ]) let locationIcon = UIImageView.locationMark + locationIcon.tintColor = DSColor.gray700.color let centerLocationStack = HStack( [ @@ -326,7 +327,7 @@ public class CenterProfileViewController: BaseViewController { navigationBar .eventPublisher .subscribe { [weak coordinator] _ in - coordinator?.closeViewController() + coordinator?.coordinatorDidFinish() } .disposed(by: disposeBag) } diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift index aab6f831..ea9dfd2e 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift @@ -8,6 +8,7 @@ import UIKit import WorkerFeature import BaseFeature +import CenterFeature import PresentationCore import UseCaseInterface @@ -71,7 +72,16 @@ extension WorkerRecruitmentBoardCoordinator { coodinator.start() } public func showCenterProfile(centerId: String) { - + let coordinator = CenterProfileCoordinator( + dependency: .init( + mode: .otherProfile(id: centerId), + profileUseCase: centerProfileUseCase, + navigationController: navigationController + ) + ) + addChildCoordinator(coordinator) + coordinator.parent = self + coordinator.start() } } From c85936ed90af6fe2d897670b32719c87bf40fea1 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 16 Aug 2024 11:16:26 +0900 Subject: [PATCH 12/21] =?UTF-8?q?[IDLE-163]=20=EA=B3=B5=EA=B3=A0=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=ED=99=94=EB=A9=B4=20=EA=B3=B5=EA=B3=A0?= =?UTF-8?q?=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20=EC=8B=A4=ED=8C=A8?= =?UTF-8?q?=EC=8B=9C=20alert=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 실패시 화면을 탈출합니다. --- .../Domain/Entity/VO/AlertContentVO.swift | 29 +++++++++++++++++++ .../Detail/View/PostDetailForWorkerVC.swift | 7 +++++ .../ViewModel/PostDetailForWorkerVM.swift | 20 +++++++++++-- .../Base/BaseViewController.swift | 12 ++++++++ 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/project/Projects/Domain/Entity/VO/AlertContentVO.swift b/project/Projects/Domain/Entity/VO/AlertContentVO.swift index 7a4d34fe..d174b0d5 100644 --- a/project/Projects/Domain/Entity/VO/AlertContentVO.swift +++ b/project/Projects/Domain/Entity/VO/AlertContentVO.swift @@ -22,3 +22,32 @@ public struct DefaultAlertContentVO { message: "동작을 수행하지 못했습니다." ) } + +public struct AlertWithCompletionVO { + + public typealias AlertCompletion = () -> () + + public let title: String + public let message: String + public let buttonInfo: [(String, AlertCompletion?)] + + public init( + title: String, + message: String, + buttonInfo: [( + String, + AlertCompletion? + )] = [ + ("닫기", nil) + ] + ) { + self.title = title + self.message = message + self.buttonInfo = buttonInfo + } + + public static let `default` = AlertWithCompletionVO( + title: "오류", + message: "동작을 수행하지 못했습니다." + ) +} diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift index a1a36d63..1e685b0f 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift @@ -191,6 +191,13 @@ public class PostDetailForWorkerVC: BaseViewController { }) .disposed(by: disposeBag) + viewModel + .alert? + .drive(onNext: { [weak self] alertVO in + self?.showAlert(vo: alertVO) + }) + .disposed(by: disposeBag) + // Input // viewWillAppear diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift index d5430b30..95fbc119 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift @@ -12,11 +12,12 @@ import Entity import PresentationCore import UseCaseInterface -public protocol PostDetailForWorkerViewModelable: DefaultAlertOutputable { +public protocol PostDetailForWorkerViewModelable { // Output var postForWorkerBundle: Driver? { get } var locationInfo: Driver? { get } + var alert: Driver? { get } // Input var viewWillAppear: PublishRelay { get } @@ -39,8 +40,7 @@ public class PostDetailForWorkerVM: PostDetailForWorkerViewModelable { public var postForWorkerBundle: RxCocoa.Driver? public var locationInfo: RxCocoa.Driver? - public var alert: RxCocoa.Driver? - + public var alert: RxCocoa.Driver? public var backButtonClicked: RxRelay.PublishRelay = .init() @@ -74,6 +74,20 @@ public class PostDetailForWorkerVM: PostDetailForWorkerViewModelable { postForWorkerBundle = getPostDetailSuccess.asDriver(onErrorRecover: { _ in fatalError() }) // Alert 처리 필요 + alert = getPostDetailFailure + .map { error in + AlertWithCompletionVO( + title: "공고 불러오기 실패", + message: error.message, + buttonInfo: [ + ("닫기", { [weak self] in + self?.coordinator?.coordinatorDidFinish() + }) + ] + ) + } + .asDriver(onErrorJustReturn: .default) + // MARK: 버튼 처리 backButtonClicked 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 10508e43..9e4810ca 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 @@ -21,4 +21,16 @@ public extension BaseViewController { alret.addAction(close) present(alret, animated: true, completion: nil) } + + func showAlert(vo: AlertWithCompletionVO) { + let alret = UIAlertController(title: vo.title, message: vo.message, preferredStyle: .alert) + + vo.buttonInfo.forEach { (buttonTitle: String, completion: AlertWithCompletionVO.AlertCompletion?) in + let button = UIAlertAction(title: buttonTitle, style: .default) { _ in + completion?() + } + alret.addAction(button) + } + present(alret, animated: true, completion: nil) + } } From e7572823ae7021932f0283df6e9ece4f27e6704a Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 16 Aug 2024 11:31:31 +0900 Subject: [PATCH 13/21] =?UTF-8?q?[IDLE-163]=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EB=B6=88=EB=9F=AC=EC=98=AC=20?= =?UTF-8?q?=EC=88=98=20=EC=97=86=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=ED=83=88=EC=B6=9C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 실패시 화면을 탈출합니다. --- .../CenterProfileCoordinator.swift | 8 ++- .../Profile/CenterProfileViewController.swift | 48 +++---------- .../Profile/CenterProfileViewModel.swift | 72 ++++++++++++++++--- 3 files changed, 78 insertions(+), 50 deletions(-) diff --git a/project/Projects/Presentation/Feature/Center/Sources/Coordinator/RegisterCenterInfo/CenterProfileCoordinator.swift b/project/Projects/Presentation/Feature/Center/Sources/Coordinator/RegisterCenterInfo/CenterProfileCoordinator.swift index 28e0754e..71bbc62c 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/Coordinator/RegisterCenterInfo/CenterProfileCoordinator.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/Coordinator/RegisterCenterInfo/CenterProfileCoordinator.swift @@ -39,8 +39,12 @@ public class CenterProfileCoordinator: ChildCoordinator { } public func start() { - let vc = CenterProfileViewController(coordinator: self) - let vm = CenterProfileViewModel(mode: mode, useCase: profileUseCase) + let vc = CenterProfileViewController() + let vm = CenterProfileViewModel( + mode: mode, + coordinator: self, + useCase: profileUseCase + ) vc.bind(viewModel: vm) self.viewControllerRef = vc navigationController.pushViewController(vc, animated: true) diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift b/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift index 93969031..f1ab026f 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/Profile/CenterProfileViewController.swift @@ -13,40 +13,10 @@ import DSKit import Entity import BaseFeature -public protocol CenterProfileViewModelable where Input: CenterProfileInputable, Output: CenterProfileOutputable { - associatedtype Input - associatedtype Output - var input: Input { get } - var output: Output? { get } - - var profileMode: ProfileMode { get } -} - -public protocol CenterProfileInputable { - var readyToFetch: PublishRelay { get } - var editingButtonPressed: PublishRelay { get } - var editingFinishButtonPressed: PublishRelay { get } - var editingPhoneNumber: BehaviorRelay { get } - var editingInstruction: BehaviorRelay { get } - var selectedImage: PublishRelay { get } -} - -public protocol CenterProfileOutputable: DefaultAlertOutputable { - var centerName: Driver { get } - var centerLocation: Driver { get } - var centerPhoneNumber: Driver { get } - var centerIntroduction: Driver { get } - var displayingImage: Driver { get } - var isEditingMode: Driver { get } - var editingValidation: Driver { get } -} - public class CenterProfileViewController: BaseViewController { var viewModel: (any CenterProfileViewModelable)? - weak var coordinator: CenterProfileCoordinator? - let navigationBar: NavigationBarType1 = { let bar = NavigationBarType1(navigationTitle: "내 센터 정보") return bar @@ -151,9 +121,7 @@ public class CenterProfileViewController: BaseViewController { private let disposeBag = DisposeBag() - public init(coordinator: CenterProfileCoordinator) { - - self.coordinator = coordinator + public init() { super.init(nibName: nil, bundle: nil) @@ -323,13 +291,7 @@ public class CenterProfileViewController: BaseViewController { } private func setObservable() { - - navigationBar - .eventPublisher - .subscribe { [weak coordinator] _ in - coordinator?.coordinatorDidFinish() - } - .disposed(by: disposeBag) + } public func bind(viewModel: any CenterProfileViewModelable) { @@ -375,6 +337,11 @@ public class CenterProfileViewController: BaseViewController { .disposed(by: disposeBag) } + navigationBar + .eventPublisher + .bind(to: input.exitButtonClicked) + .disposed(by: disposeBag) + // output guard let output = viewModel.output else { fatalError() } @@ -456,6 +423,7 @@ public class CenterProfileViewController: BaseViewController { output .alert? .drive { [weak self] vo in + print("!!") self?.showAlert(vo: vo) } .disposed(by: disposeBag) diff --git a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift index 992b92b1..625841fb 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/Profile/CenterProfileViewModel.swift @@ -12,15 +12,49 @@ import RxCocoa import PresentationCore import UseCaseInterface -public struct ChangeCenterInformation { +struct ChangeCenterInformation { let phoneNumber: String? let introduction: String? let image: UIImage? } +public protocol CenterProfileViewModelable where Input: CenterProfileInputable, Output: CenterProfileOutputable { + associatedtype Input + associatedtype Output + var input: Input { get } + var output: Output? { get } + + var profileMode: ProfileMode { get } +} + +public protocol CenterProfileInputable { + var readyToFetch: PublishRelay { get } + var editingButtonPressed: PublishRelay { get } + var editingFinishButtonPressed: PublishRelay { get } + var editingPhoneNumber: BehaviorRelay { get } + var editingInstruction: BehaviorRelay { get } + var selectedImage: PublishRelay { get } + + var exitButtonClicked: PublishRelay { get } +} + +public protocol CenterProfileOutputable { + var centerName: Driver { get } + var centerLocation: Driver { get } + var centerPhoneNumber: Driver { get } + var centerIntroduction: Driver { get } + var displayingImage: Driver { get } + var isEditingMode: Driver { get } + var editingValidation: Driver { get } + + var alert: Driver? { get } +} + + public class CenterProfileViewModel: CenterProfileViewModelable { let profileUseCase: CenterProfileUseCase + weak var coordinator: CenterProfileCoordinator? public var input: Input public var output: Output? = nil @@ -45,9 +79,16 @@ public class CenterProfileViewModel: CenterProfileViewModelable { ) } - public init(mode: ProfileMode, useCase: CenterProfileUseCase) { + let disposeBag = DisposeBag() + + public init( + mode: ProfileMode, + coordinator: CenterProfileCoordinator, + useCase: CenterProfileUseCase) + { self.profileMode = mode + self.coordinator = coordinator self.profileUseCase = useCase self.input = Input() @@ -66,7 +107,15 @@ public class CenterProfileViewModel: CenterProfileViewModelable { let profileRequestFailure = profileRequestResult .compactMap { $0.error } .map { error in - DefaultAlertContentVO(title: "프로필 정보 불러오기 실패", message: error.message) + AlertWithCompletionVO( + title: "프로필 정보 불러오기 실패", + message: error.message, + buttonInfo: [ + ("닫기", { [weak self] in + self?.coordinator?.coordinatorDidFinish() + }) + ] + ) } let centerNameDriver = profileRequestSuccess @@ -122,7 +171,7 @@ public class CenterProfileViewModel: CenterProfileViewModelable { let imageValidationFailure = imageValidationResult .filter { $0 == nil } .map { _ in - DefaultAlertContentVO( + AlertWithCompletionVO( title: "이미지 선택 오류", message: "지원하지 않는 이미지 형식입니다." ) @@ -177,7 +226,7 @@ public class CenterProfileViewModel: CenterProfileViewModelable { .compactMap({ $0.error }) .map({ error in // 변경 실패 Alert - return DefaultAlertContentVO( + return AlertWithCompletionVO( title: "변경 실패", message: "변경 싪패 이유" ) @@ -218,6 +267,13 @@ public class CenterProfileViewModel: CenterProfileViewModelable { imageValidationFailure ) .asDriver(onErrorJustReturn: .default) + + // MARK: Exit Button + input.exitButtonClicked + .subscribe(onNext: { [weak self] _ in + self?.coordinator?.coordinatorDidFinish() + }) + .disposed(by: disposeBag) self.output = .init( centerName: centerNameDriver, @@ -245,7 +301,6 @@ public class CenterProfileViewModel: CenterProfileViewModelable { public extension CenterProfileViewModel { class Input: CenterProfileInputable { - // ViewController에서 받아오는 데이터 public var readyToFetch: PublishRelay = .init() public var editingButtonPressed: PublishRelay = .init() @@ -253,6 +308,7 @@ public extension CenterProfileViewModel { public var editingPhoneNumber: BehaviorRelay = .init(value: "") public var editingInstruction: BehaviorRelay = .init(value: "") public var selectedImage: PublishRelay = .init() + public var exitButtonClicked: RxRelay.PublishRelay = .init() } class Output: CenterProfileOutputable { @@ -269,9 +325,9 @@ public extension CenterProfileViewModel { // 요구사항 X public var editingValidation: Driver - public var alert: Driver? + public var alert: Driver? - init(centerName: Driver, centerLocation: Driver, centerPhoneNumber: Driver, centerIntroduction: Driver, displayingImage: Driver, isEditingMode: Driver, editingValidation: Driver, alert: Driver) { + init(centerName: Driver, centerLocation: Driver, centerPhoneNumber: Driver, centerIntroduction: Driver, displayingImage: Driver, isEditingMode: Driver, editingValidation: Driver, alert: Driver) { self.centerName = centerName self.centerLocation = centerLocation self.centerPhoneNumber = centerPhoneNumber From 5d31ee5aff04089935fbb20fbf44ac0b18f224a8 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 16 Aug 2024 12:14:46 +0900 Subject: [PATCH 14/21] =?UTF-8?q?[IDLE-163]=20=EC=A7=80=EB=8F=84=EB=B7=B0?= =?UTF-8?q?=20=EC=98=B5=EC=85=98=20=EC=84=A4=EC=A0=95=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 마커, 오버레이, 줌레벨, 제스처 금지 옵션을 설정했습니다. --- .../RecruitmentPostForWorkerBundle.swift | 6 +- .../workPlaceMarker.imageset/Contents.json | 12 +++ .../workPlaceMarker.svg | 6 ++ .../workerMarker.imageset/Contents.json | 12 +++ .../workerMarker.imageset/workerMarker.svg | 11 +++ .../Detail/View/PostDetailForWorkerVC.swift | 8 +- ...t => WorkPlaceAndWorkerLocationView.swift} | 94 +++++++++++++++---- 7 files changed, 124 insertions(+), 25 deletions(-) create mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workPlaceMarker.imageset/Contents.json create mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workPlaceMarker.imageset/workPlaceMarker.svg create mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerMarker.imageset/Contents.json create mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerMarker.imageset/workerMarker.svg rename project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/{WorkLocationView.swift => WorkPlaceAndWorkerLocationView.swift} (51%) diff --git a/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentPostForWorkerBundle.swift b/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentPostForWorkerBundle.swift index dd96e117..96b533df 100644 --- a/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentPostForWorkerBundle.swift +++ b/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentPostForWorkerBundle.swift @@ -9,10 +9,10 @@ import Foundation /// 위도경도 좌표를 담고 있습니다. public struct LocationInformation { - public let longitude: String - public let latitude: String + public let longitude: Double + public let latitude: Double - public init(longitude: String, latitude: String) { + public init(longitude: Double, latitude: Double) { self.longitude = longitude self.latitude = latitude } diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workPlaceMarker.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workPlaceMarker.imageset/Contents.json new file mode 100644 index 00000000..3d93cb04 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workPlaceMarker.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "workPlaceMarker.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workPlaceMarker.imageset/workPlaceMarker.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workPlaceMarker.imageset/workPlaceMarker.svg new file mode 100644 index 00000000..7a63d971 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workPlaceMarker.imageset/workPlaceMarker.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerMarker.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerMarker.imageset/Contents.json new file mode 100644 index 00000000..8a7d330d --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerMarker.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "workerMarker.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerMarker.imageset/workerMarker.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerMarker.imageset/workerMarker.svg new file mode 100644 index 00000000..73c96b2d --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerMarker.imageset/workerMarker.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift index 1e685b0f..a1ddb757 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift @@ -12,9 +12,9 @@ import RxSwift import Entity import DSKit -public struct WorkAndWorkerLocationMapRO { +public struct WorkPlaceAndWorkerLocationMapRO { - let workLocation: LocationInformation + let workPlaceLocation: LocationInformation let workerLocation: LocationInformation } @@ -244,7 +244,7 @@ public class PostDetailForWorkerContentView: UIView { }() /// 지도뷰 - let workLocationView = WorkLocationView() + let workPlaceAndWorkerLocationView = WorkPlaceAndWorkerLocationView() /// 공고 상세정보들 let workConditionView = WorkConditionDisplayingView() @@ -271,7 +271,7 @@ public class PostDetailForWorkerContentView: UIView { func setLayout() { let titleViewData: [(title: String, view: UIView)] = [ - ("근무 장소", workLocationView), + ("근무 장소", workPlaceAndWorkerLocationView), ("근무 조건", workConditionView), ("고객 정보", customerInfoView), ("추가 지원 정보", applicationDetailView), diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkLocationView.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkPlaceAndWorkerLocationView.swift similarity index 51% rename from project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkLocationView.swift rename to project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkPlaceAndWorkerLocationView.swift index c951fd38..03e8aec3 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkLocationView.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkPlaceAndWorkerLocationView.swift @@ -1,5 +1,5 @@ // -// WorkLocationView.swift +// WorkPlaceAndWorkerLocationView.swift // BaseFeature // // Created by choijunios on 8/7/24. @@ -13,7 +13,7 @@ import Entity import DSKit import NMapsMap -public class WorkLocationView: VStack { +public class WorkPlaceAndWorkerLocationView: VStack { // Init @@ -32,7 +32,6 @@ public class WorkLocationView: VStack { }() let mapView: NMFNaverMapView = { - let view = NMFNaverMapView(frame: .zero) view.layer.cornerRadius = 8 view.clipsToBounds = true @@ -91,27 +90,86 @@ public class WorkLocationView: VStack { private func configureMapAppearance() { - mapView.mapView.touchDelegate = self - - let initialCoordinate = NMGLatLng(lat: 37.5666102, lng: 126.9783881) - let cameraPosition = NMFCameraPosition(initialCoordinate, zoom: 15.0) - - mapView.mapView.moveCamera(NMFCameraUpdate(position: cameraPosition)) - mapView.showZoomControls = false } private func setObservable() { } - public func bind() { - configureMapAppearance() - } -} - -extension WorkLocationView: NMFMapViewTouchDelegate { - public func mapView(_ mapView: NMFMapView, didTapMap latlng: NMGLatLng, point: CGPoint) { + public func bind(locationRO: WorkPlaceAndWorkerLocationMapRO) { - printIfDebug("\(latlng.lat), \(latlng.lng)") + // 마커 설정 + let workPlacePos: NMGLatLng = .init( + lat: locationRO.workPlaceLocation.latitude, + lng: locationRO.workPlaceLocation.longitude + ) + let workerPos: NMGLatLng = .init( + lat: locationRO.workerLocation.latitude, + lng: locationRO.workerLocation.longitude + ) + + let workPlaceMarker = NMFMarker( + position: workPlacePos, + iconImage: .init(image: DSIcon.workPlaceMarker.image) + ) + let workerMarker = NMFMarker( + position: workerPos, + iconImage: .init(image: DSIcon.workerMarker.image) + ) + [ + workPlaceMarker, + workerMarker + ].forEach { marker in + marker.mapView = self.mapView.mapView + marker.globalZIndex = 40001 + } + // 근무지가 우선 표시도되도록 + workPlaceMarker.zIndex = 1 + workerMarker.zIndex = 0 + + // 경로선 + let pathOverlay = NMFPath() + pathOverlay.path = NMGLineString(points: [ + workPlacePos, + workerPos + ]) + pathOverlay.width = 3 + pathOverlay.outlineWidth = 0 + pathOverlay.progress = 0 + pathOverlay.color = DSColor.orange400.color + + // 카메라 이동 + let camerUpdate = NMFCameraUpdate( + fit: .init( + latLngs: [ + workPlacePos, + workerPos, + ] + ), + paddingInsets: .init( + top: 42, + left: 59, + bottom: 44, + right: 59 + ) + ) + self.mapView.mapView.moveCamera(camerUpdate) + // 지도 Config + let map = mapView.mapView + map.mapType = .basic + map.symbolScale = 2 + + // - 제스처 끄기 + map.isScrollGestureEnabled = false + map.isZoomGestureEnabled = false + map.isTiltGestureEnabled = false + map.isRotateGestureEnabled = false + map.isStopGestureEnabled = false + + // 지도 뷰 Config + mapView.showCompass = false + mapView.showScaleBar = false + mapView.showZoomControls = false + mapView.showLocationButton = false } } From a8d9a3626fe33ed795fad8506cc76e61ff543588 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 16 Aug 2024 16:10:01 +0900 Subject: [PATCH 15/21] =?UTF-8?q?[IDLE-163]=20=ED=92=80=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=B0=20=EC=A7=80=EB=8F=84=EB=B7=B0=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=EB=B0=8F=20=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecruitmentPostDetailForWorkerDTO.swift | 9 +- .../RecruitmentPostForWorkerBundle.swift | 5 +- .../Detail/View/PostDetailForWorkerVC.swift | 38 ++-- .../WorkPlaceAndWorkerLocationFullVC.swift | 104 ++++++++++ .../View/WorkPlaceAndWorkerLocationView.swift | 193 +++++++++++------- .../ViewModel/PostDetailForWorkerVM.swift | 43 +++- .../DetailVC/PostDetailForCenterVC.swift | 5 +- 7 files changed, 300 insertions(+), 97 deletions(-) create mode 100644 project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkPlaceAndWorkerLocationFullVC.swift diff --git a/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift index ae51621e..c5cc517e 100644 --- a/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift +++ b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift @@ -17,6 +17,8 @@ public struct RecruitmentPostDTO: Codable { public let centerId: String public let centerName: String public let centerRoadNameAddress: String + + public let distance: Int public let isMealAssistance: Bool public let isBowelAssistance: Bool @@ -101,8 +103,8 @@ public struct RecruitmentPostDTO: Codable { ) let jobLocation: LocationInformation = .init( - longitude: longitude, - latitude: latitude + longitude: Double(longitude)!, + latitude: Double(latitude)! ) return .init( @@ -112,7 +114,8 @@ public struct RecruitmentPostDTO: Codable { applicationDetail: applicationDetail, addressInfo: addressInfo, centerInfo: centerInfo, - jobLocation: jobLocation + jobLocation: jobLocation, + distanceToWorkPlace: distance ) } } diff --git a/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentPostForWorkerBundle.swift b/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentPostForWorkerBundle.swift index 96b533df..eb60c940 100644 --- a/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentPostForWorkerBundle.swift +++ b/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentPostForWorkerBundle.swift @@ -42,6 +42,7 @@ public class RecruitmentPostForWorkerBundle { public let centerInfo: CenterInfo /// 근무지 위치정보 + public let distanceToWorkPlace: Int public let jobLocation: LocationInformation @@ -52,7 +53,8 @@ public class RecruitmentPostForWorkerBundle { applicationDetail: ApplicationDetailStateObject, addressInfo: AddressInputStateObject, centerInfo: CenterInfo, - jobLocation: LocationInformation + jobLocation: LocationInformation, + distanceToWorkPlace: Int ) { self.workTimeAndPay = workTimeAndPay self.customerRequirement = customerRequirement @@ -61,5 +63,6 @@ public class RecruitmentPostForWorkerBundle { self.addressInfo = addressInfo self.centerInfo = centerInfo self.jobLocation = jobLocation + self.distanceToWorkPlace = distanceToWorkPlace } } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift index a1ddb757..187c09a2 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/PostDetailForWorkerVC.swift @@ -12,12 +12,6 @@ import RxSwift import Entity import DSKit -public struct WorkPlaceAndWorkerLocationMapRO { - - let workPlaceLocation: LocationInformation - let workerLocation: LocationInformation -} - /// 센토도 요양보호사가 보는 공고화면을 볼 수 있기 때문에 해당뷰를 BaseFeature에 구현하였습니다. public class PostDetailForWorkerVC: BaseViewController { @@ -181,15 +175,27 @@ public class PostDetailForWorkerVC: BaseViewController { }) .disposed(by: disposeBag) - - viewModel - .locationInfo? - .drive(onNext: { bundle in - - // 위치정보 전달 - - }) - .disposed(by: disposeBag) + if let locationInfo = viewModel.locationInfo?.asObservable().share() { + + locationInfo + .subscribe(onNext: { + [weak self] info in + // 위치정보 전달 + self?.contentView.workPlaceAndWorkerLocationView.bind(locationRO: info) + }) + .disposed(by: disposeBag) + + // 지도화면 클릭시 + contentView.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) + } viewModel .alert? @@ -244,7 +250,7 @@ public class PostDetailForWorkerContentView: UIView { }() /// 지도뷰 - let workPlaceAndWorkerLocationView = WorkPlaceAndWorkerLocationView() + public let workPlaceAndWorkerLocationView = WorkPlaceAndWorkerLocationView() /// 공고 상세정보들 let workConditionView = WorkConditionDisplayingView() diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkPlaceAndWorkerLocationFullVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkPlaceAndWorkerLocationFullVC.swift new file mode 100644 index 00000000..32026fa3 --- /dev/null +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/WorkPlaceAndWorkerLocationFullVC.swift @@ -0,0 +1,104 @@ +// +// WorkPlaceAndWorkerLocationFullVC.swift +// BaseFeature +// +// Created by choijunios on 8/16/24. +// + +import UIKit +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit +import NMapsMap + +public class WorkPlaceAndWorkerLocationFullVC: BaseViewController { + + // Init + + // View + let navigationBar: IdleNavigationBar = { + let bar = IdleNavigationBar( + titleText: "", + innerViews: [] + ) + return bar + }() + + let mapView: NMFNaverMapView = { + let view = NMFNaverMapView(frame: .zero) + view.backgroundColor = DSColor.gray050.color + view.layer.cornerRadius = 8 + view.clipsToBounds = true + return view + }() + + // Observable + private let disposeBag = DisposeBag() + + public init() { + super.init(nibName: nil, bundle: nil) + } + + public required init?(coder: NSCoder) { fatalError() } + + public override func viewDidLoad() { + setAppearance() + setLayout() + setObservable() + } + + private func setAppearance() { + view.backgroundColor = DSColor.gray0.color + } + + private func setLayout() { + [ + navigationBar, + mapView, + ].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), + + mapView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), + mapView.leftAnchor.constraint(equalTo: view.leftAnchor), + mapView.rightAnchor.constraint(equalTo: view.rightAnchor), + mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } + + private func setObservable() { + navigationBar.backButton.rx.tap + .subscribe(onNext: { [weak self] _ in + self?.navigationController?.popViewController(animated: true) + }) + .disposed(by: disposeBag) + } + + public func bind(locationRO: WorkPlaceAndWorkerLocationMapRO) { + + navigationBar.titleLabel.textString = locationRO.workPlaceRoadAddress + + mapView.bind( + locationRO: locationRO, + paddingInsets: .init( + top: 0, + left: 71, + bottom: 0, + right: 71 + ) + ) + + // 지도 뷰 Config + mapView.showLocationButton = true + mapView.showZoomControls = false + } +} + 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 03e8aec3..23e74079 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 @@ -13,6 +13,16 @@ import Entity import DSKit import NMapsMap +public struct WorkPlaceAndWorkerLocationMapRO { + + let workPlaceRoadAddress: String + let homeToworkPlaceText: NSMutableAttributedString + let distanceToWorkPlaceText: String + + let workPlaceLocation: LocationInformation + let workerLocation: LocationInformation? +} + public class WorkPlaceAndWorkerLocationView: VStack { // Init @@ -20,21 +30,27 @@ public class WorkPlaceAndWorkerLocationView: VStack { // View let walkToLocationLabel: UILabel = { let label = UILabel() - label.text = "거주지에서--" return label }() - let timeCostByWalkLabel: IdleLabel = { + let distanceLabel: IdleLabel = { let label = IdleLabel(typography: .Subtitle2) - label.textString = "걸어서 ~ 소요" + label.textString = "" label.textAlignment = .left return label }() + public let mapViewBackGround: TappableUIView = { + let view = TappableUIView() + view.backgroundColor = DSColor.gray050.color + return view + }() let mapView: NMFNaverMapView = { let view = NMFNaverMapView(frame: .zero) + view.backgroundColor = DSColor.gray050.color view.layer.cornerRadius = 8 view.clipsToBounds = true + view.isUserInteractionEnabled = false return view }() @@ -45,6 +61,7 @@ public class WorkPlaceAndWorkerLocationView: VStack { super.init([], spacing: 16, alignment: .fill) setAppearance() setLayout() + setObservable() } public required init(coder: NSCoder) { fatalError() } @@ -56,120 +73,148 @@ public class WorkPlaceAndWorkerLocationView: VStack { private func setLayout() { let walkingImage = DSKitAsset.Icons.walkingHuman.image.toView() - let timeCostStack = HStack([walkingImage, timeCostByWalkLabel], spacing: 6, alignment: .center) + let timeCostStack = HStack([walkingImage, distanceLabel], spacing: 6, alignment: .center) let labelStack = VStack([walkToLocationLabel, timeCostStack], spacing: 4, alignment: .leading) + mapViewBackGround.addSubview(mapView) + mapView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + mapView.topAnchor.constraint(equalTo: mapViewBackGround.topAnchor), + mapView.leftAnchor.constraint(equalTo: mapViewBackGround.leftAnchor), + mapView.rightAnchor.constraint(equalTo: mapViewBackGround.rightAnchor), + mapView.bottomAnchor.constraint(equalTo: mapViewBackGround.bottomAnchor), + ]) + [ labelStack, - mapView + mapViewBackGround ].forEach { $0.translatesAutoresizingMaskIntoConstraints = false self.addArrangedSubview($0) } NSLayoutConstraint.activate([ - mapView.heightAnchor.constraint(equalToConstant: 224), + mapViewBackGround.heightAnchor.constraint(equalToConstant: 224), ]) } - private func setLocationLabel(roadAddress: String) { - 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) + private func setObservable() { - walkToLocationLabel.attributedText = attrText } - private func configureMapAppearance() { + public func bind(locationRO: WorkPlaceAndWorkerLocationMapRO) { - } - - private func setObservable() { + walkToLocationLabel.attributedText = locationRO.homeToworkPlaceText + distanceLabel.textString = locationRO.distanceToWorkPlaceText + mapView.bind( + locationRO: locationRO, + paddingInsets: .init( + top: 42, + left: 59, + bottom: 44, + right: 59 + ) + ) + + // - 제스처 끄기 + mapView.mapView.isScrollGestureEnabled = false + mapView.mapView.isZoomGestureEnabled = false + mapView.mapView.isTiltGestureEnabled = false + mapView.mapView.isRotateGestureEnabled = false + mapView.mapView.isStopGestureEnabled = false + + // 지도 뷰 Config + mapView.showCompass = false + mapView.showScaleBar = false + mapView.showZoomControls = false + mapView.showLocationButton = false } +} + + +extension NMFNaverMapView { - public func bind(locationRO: WorkPlaceAndWorkerLocationMapRO) { - + func bind( + locationRO: WorkPlaceAndWorkerLocationMapRO, + paddingInsets: UIEdgeInsets + ) { // 마커 설정 let workPlacePos: NMGLatLng = .init( lat: locationRO.workPlaceLocation.latitude, lng: locationRO.workPlaceLocation.longitude ) - let workerPos: NMGLatLng = .init( - lat: locationRO.workerLocation.latitude, - lng: locationRO.workerLocation.longitude - ) + + var posArr = [workPlacePos] + + var workerPos: NMGLatLng? + + if let workerLocation = locationRO.workerLocation { + workerPos = .init( + lat: workerLocation.latitude, + lng: workerLocation.longitude + ) + posArr.append(workerPos!) + } let workPlaceMarker = NMFMarker( position: workPlacePos, iconImage: .init(image: DSIcon.workPlaceMarker.image) ) - let workerMarker = NMFMarker( - position: workerPos, - iconImage: .init(image: DSIcon.workerMarker.image) - ) - [ - workPlaceMarker, - workerMarker - ].forEach { marker in - marker.mapView = self.mapView.mapView + workPlaceMarker.width = 41 + workPlaceMarker.height = 56 + + var markerArr = [workPlaceMarker] + + var workerMarker: NMFMarker? + + if let workerPos { + workerMarker = .init( + position: workerPos, + iconImage: .init(image: DSIcon.workerMarker.image) + ) + workerMarker?.width = 33 + workerMarker?.height = 44 + + markerArr.append(workerMarker!) + } + + + markerArr.forEach { marker in + marker.mapView = self.mapView marker.globalZIndex = 40001 + marker.anchor = .init(x: 0.5, y: 1) } - // 근무지가 우선 표시도되도록 - workPlaceMarker.zIndex = 1 - workerMarker.zIndex = 0 // 경로선 - let pathOverlay = NMFPath() - pathOverlay.path = NMGLineString(points: [ - workPlacePos, - workerPos - ]) - pathOverlay.width = 3 - pathOverlay.outlineWidth = 0 - pathOverlay.progress = 0 - pathOverlay.color = DSColor.orange400.color + if posArr.count == 2 { + let pathOverlay = NMFPath() + pathOverlay.path = .init(points: posArr) + pathOverlay.width = 3 + pathOverlay.outlineWidth = 0 + pathOverlay.color = DSColor.orange400.color + pathOverlay.mapView = self.mapView + pathOverlay.globalZIndex = 40001 + pathOverlay.zIndex = 0 + } + // 근무지가 우선 표시도되도록 + workPlaceMarker.zIndex = 2 + workerMarker?.zIndex = 1 + // 카메라 이동 let camerUpdate = NMFCameraUpdate( fit: .init( - latLngs: [ - workPlacePos, - workerPos, - ] + latLngs: posArr ), - paddingInsets: .init( - top: 42, - left: 59, - bottom: 44, - right: 59 - ) + paddingInsets: paddingInsets ) - self.mapView.mapView.moveCamera(camerUpdate) + self.mapView.moveCamera(camerUpdate) // 지도 Config - let map = mapView.mapView + let map = self.mapView map.mapType = .basic map.symbolScale = 2 - - // - 제스처 끄기 - map.isScrollGestureEnabled = false - map.isZoomGestureEnabled = false - map.isTiltGestureEnabled = false - map.isRotateGestureEnabled = false - map.isStopGestureEnabled = false - - // 지도 뷰 Config - mapView.showCompass = false - mapView.showScaleBar = false - mapView.showZoomControls = false - mapView.showLocationButton = false } } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift index 95fbc119..ee16d246 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/PostDetailForWorkerVM.swift @@ -11,12 +11,13 @@ import RxSwift import Entity import PresentationCore import UseCaseInterface +import DSKit public protocol PostDetailForWorkerViewModelable { // Output var postForWorkerBundle: Driver? { get } - var locationInfo: Driver? { get } + var locationInfo: Driver? { get } var alert: Driver? { get } // Input @@ -38,7 +39,7 @@ public class PostDetailForWorkerVM: PostDetailForWorkerViewModelable { public var postForWorkerBundle: RxCocoa.Driver? - public var locationInfo: RxCocoa.Driver? + public var locationInfo: RxCocoa.Driver? public var alert: RxCocoa.Driver? @@ -73,6 +74,36 @@ public class PostDetailForWorkerVM: PostDetailForWorkerViewModelable { postForWorkerBundle = getPostDetailSuccess.asDriver(onErrorRecover: { _ in fatalError() }) + // MARK: 센터, 워커 위치정보 + locationInfo = getPostDetailSuccess + .map { [weak self] bundle in + // 요양보호사 위치 가져오기 + let workerLocation = self?.getWorkerLocation() + + let workPlaceLocation = bundle.jobLocation + + let roadAddress = bundle.addressInfo.addressInfo?.roadAddress ?? "근무지 위치" + 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) + + return WorkPlaceAndWorkerLocationMapRO( + workPlaceRoadAddress: roadAddress, + homeToworkPlaceText: attrText, + distanceToWorkPlaceText: "\(bundle.distanceToWorkPlace)m", + workPlaceLocation: workPlaceLocation, + workerLocation: workerLocation + ) + } + .asDriver(onErrorRecover: { _ in fatalError() }) + // Alert 처리 필요 alert = getPostDetailFailure .map { error in @@ -111,4 +142,12 @@ public class PostDetailForWorkerVM: PostDetailForWorkerViewModelable { }) .disposed(by: disposeBag) } + + // MARK: Test + func getWorkerLocation() -> LocationInformation { + return .init( + longitude: 127.046425, + latitude: 37.504588 + ) + } } 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 5f610202..bd4283e4 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 @@ -241,7 +241,10 @@ public class PostDetailForCenterVC: BaseViewController { } private func setObservable() { - + // 지도뷰 풀스크린 + // 재사용률이 떨어져 ViewController에 직접 삽입합니다. + let fullMapVC = WorkPlaceAndWorkerLocationFullVC() + navigationController?.pushViewController(fullMapVC, animated: true) } public func bind(viewModel: PostDetailViewModelable) { From 6d71a301952f00ef2713d251c8264d545f4b100e Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Fri, 16 Aug 2024 18:52:58 +0900 Subject: [PATCH 16/21] =?UTF-8?q?[IDLE-163]=20StarredAndAppliedVC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 페이징 작업을 완료한 이후 해당 작업을 이어나갈 예정입니다. --- .../Main/Worker/WorkerMainCoordinator.swift | 16 +- .../ApplyManagementCoordinator.swift | 30 --- .../WorkerRecruitmentBoardCoordinator.swift | 0 .../{ => MainBoard}/ RecruitmentBoardVC.swift | 0 .../{ => MainBoard}/ApplyManagementVC.swift | 0 .../LikedAndApplied/AppliedBoardVC.swift | 43 ++++ .../LikedAndApplied/StarredAndAppliedVC.swift | 192 ++++++++++++++++++ .../LikedAndApplied/StarredBoardVC.swift | 131 ++++++++++++ .../WorkerRecruitmentPostBoardVC.swift | 5 +- 9 files changed, 381 insertions(+), 36 deletions(-) delete mode 100644 project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/ApplyManagementCoordinator.swift rename project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/{ => MainBoard}/WorkerRecruitmentBoardCoordinator.swift (100%) rename project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/View/{ => MainBoard}/ RecruitmentBoardVC.swift (100%) rename project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/View/{ => MainBoard}/ApplyManagementVC.swift (100%) create mode 100644 project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/AppliedBoardVC.swift create mode 100644 project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift create mode 100644 project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredBoardVC.swift diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift index 7420b00c..bea236eb 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift @@ -79,12 +79,20 @@ class WorkerMainCoordinator: ParentCoordinator { ) ) case .preferredPost: - coordinator = ApplyManagementCoordinator( - navigationController: navigationController + coordinator = WorkerRecruitmentBoardCoordinator( + depedency: .init( + navigationController: navigationController, + centerProfileUseCase: injector.resolve(CenterProfileUseCase.self), + recruitmentPostUseCase: injector.resolve(RecruitmentPostUseCase.self) + ) ) case .setting: - coordinator = ApplyManagementCoordinator( - navigationController: navigationController + coordinator = WorkerRecruitmentBoardCoordinator( + depedency: .init( + navigationController: navigationController, + centerProfileUseCase: injector.resolve(CenterProfileUseCase.self), + recruitmentPostUseCase: injector.resolve(RecruitmentPostUseCase.self) + ) ) } diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/ApplyManagementCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/ApplyManagementCoordinator.swift deleted file mode 100644 index de8d7645..00000000 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/ApplyManagementCoordinator.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// ApplyManagementCoordinator.swift -// RootFeature -// -// Created by choijunios on 7/25/24. -// - -import UIKit -import PresentationCore - -public class ApplyManagementCoordinator: ChildCoordinator { - - public weak var viewControllerRef: UIViewController? - - public var navigationController: UINavigationController - - public init(navigationController: UINavigationController) { - self.navigationController = navigationController - } - - public func start() { - let vc = ApplyManagementVC() - - navigationController.pushViewController(vc, animated: false) - } - - public func coordinatorDidFinish() { - - } -} diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/MainBoard/WorkerRecruitmentBoardCoordinator.swift similarity index 100% rename from project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/WorkerRecruitmentBoardCoordinator.swift rename to project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/MainBoard/WorkerRecruitmentBoardCoordinator.swift diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/View/ RecruitmentBoardVC.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/View/MainBoard/ RecruitmentBoardVC.swift similarity index 100% rename from project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/View/ RecruitmentBoardVC.swift rename to project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/View/MainBoard/ RecruitmentBoardVC.swift diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/View/ApplyManagementVC.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/View/MainBoard/ApplyManagementVC.swift similarity index 100% rename from project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/View/ApplyManagementVC.swift rename to project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/View/MainBoard/ApplyManagementVC.swift diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/AppliedBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/AppliedBoardVC.swift new file mode 100644 index 00000000..d7c6c05d --- /dev/null +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/AppliedBoardVC.swift @@ -0,0 +1,43 @@ +// +// AppliedBoardVC.swift +// WorkerFeature +// +// Created by choijunios on 8/16/24. +// + +import UIKit +import BaseFeature +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit + +public class AppliedBoardVC: BaseViewController { + + // Init + + // View + + // Observable + private let disposeBag = DisposeBag() + + public init() { + super.init(nibName: nil, bundle: nil) + } + + public required init?(coder: NSCoder) { fatalError() } + + private func setAppearance() { + + } + + private func setLayout() { + + } + + private func setObservable() { + + } +} + diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift new file mode 100644 index 00000000..03d68bd8 --- /dev/null +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift @@ -0,0 +1,192 @@ +// +// StarredAndAppliedVC.swift +// WorkerFeature +// +// Created by choijunios on 8/16/24. +// + +import UIKit +import BaseFeature +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit +import CenterFeature + +public protocol AppliedPostViewModelable { + + +} + +public protocol StarredPostViewModelable { + + +} + +public protocol StarredAndAppliedVVMable: AppliedPostViewModelable & StarredPostViewModelable { + var alert: Driver? { get } +} + +public class StarredAndAppliedVC: BaseViewController { + enum TabBarState: Int, CaseIterable { + case applied = 0 + case starred = 1 + + var titleText: String { + switch self { + case .applied: + "지원한 공고" + case .starred: + "찜한 공고" + } + } + } + struct TabBarItem: IdleTabItem { + var id: TabBarState + var tabLabelText: String + + init(id: TabBarState) { + self.id = id + self.tabLabelText = id.titleText + } + } + + var viewModel: StarredAndAppliedVVMable? + + private var currentState: TabBarState = .applied + private let viewControllerDict: [TabBarState: UIViewController] = [ + .applied : UIViewController(), + .starred : UIViewController() + ] + + // Init + + // View + let titleLabel: IdleLabel = { + let label = IdleLabel(typography: .Heading1) + label.textString = "공고 관리" + return label + }() + + lazy var tabBar: IdleTabControlBar = .init( + items: TabBarState.allCases.map { TabBarItem(id: $0) }, + initialItem: .init(id: currentState) + )! + + // Observable + private let disposeBag = DisposeBag() + + public init() { + super.init(nibName: nil, bundle: nil) + } + + public required init?(coder: NSCoder) { fatalError() } + + public override func viewDidLoad() { + setAppearance() + setLayout() + setObservable() + + addViewControllerAndSetLayout(vc: viewControllerDict[currentState]!) + } + + private func setAppearance() { + view.backgroundColor = DSKitAsset.Colors.gray0.color + } + + private func setLayout() { + [ + titleLabel, + tabBar, + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + view.addSubview($0) + } + + NSLayoutConstraint.activate([ + titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 21), + titleLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20), + + tabBar.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8), + tabBar.leftAnchor.constraint(equalTo: view.leftAnchor), + tabBar.rightAnchor.constraint(equalTo: view.rightAnchor), + ]) + } + + private func setObservable() { + tabBar + .statePublisher + .subscribe(onNext: { [weak self] item in + self?.showViewController(state: item.id) + }) + .disposed(by: disposeBag) + } + + private func showViewController(state: TabBarState) { + + if currentState == state { return } + + // 탭바터치 정지 + tabBar.isUserInteractionEnabled = false + + /// viewWillAppear이후에 호출 + let prevViewController = viewControllerDict[currentState] + let vc = viewControllerDict[state]! + + let prevIndex = currentState.rawValue + let currentIndex = state.rawValue + + addViewControllerAndSetLayout(vc: vc) + + vc.view.transform = .init(translationX: view.bounds.width * (prevIndex < currentIndex ? 1 : -1), y: 0) + + UIView.animate(withDuration: 0.2) { [weak self] in + + guard let self else { return } + + vc.view.transform = .identity + prevViewController?.view.transform = .init(translationX: (prevIndex < currentIndex ? -1 : 1) * view.bounds.width, y: 0) + + } completion: { [weak self] _ in + + prevViewController?.view.removeFromSuperview() + + prevViewController?.willMove(toParent: nil) + prevViewController?.removeFromParent() + + self?.currentState = state + self?.tabBar.isUserInteractionEnabled = true + } + } + + private func addViewControllerAndSetLayout(vc: UIViewController) { + addChild(vc) + view.addSubview(vc.view) + vc.didMove(toParent: self) + vc.view.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + vc.view.topAnchor.constraint(equalTo: tabBar.bottomAnchor), + vc.view.leftAnchor.constraint(equalTo: view.leftAnchor), + vc.view.rightAnchor.constraint(equalTo: view.rightAnchor), + vc.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + } + + public func bind(viewModel: StarredAndAppliedVVMable) { + + self.viewModel = viewModel + +// (viewControllerDict[.applied] as? AppliedBoardVC)?.bind(viewModel: viewModel) +// (viewControllerDict[.closedPost] as? StarredBoardVC)?.bind(viewModel: viewModel) +// +// viewModel +// .alert? +// .drive(onNext: { [weak self] alertVO in +// self?.showAlert(vo: alertVO) +// }) +// .disposed(by: disposeBag) + } +} + diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredBoardVC.swift new file mode 100644 index 00000000..630b0b51 --- /dev/null +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredBoardVC.swift @@ -0,0 +1,131 @@ +// +// StarredBoardVC.swift +// WorkerFeature +// +// Created by choijunios on 8/16/24. +// + +import UIKit +import BaseFeature +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit + +public class StarredBoardVC: BaseViewController { + + typealias Cell = WorkerEmployCardCell + +// var viewModel: StarredPostViewModelable? + + // View + let postTableView: UITableView = { + let tableView = UITableView() + tableView.rowHeight = UITableView.automaticDimension + tableView.register(Cell.self, forCellReuseIdentifier: Cell.identifier) + return tableView + }() + + let tableHeader = BoardSortigHeaderView() + + let starredPostCardVO: BehaviorRelay<[WorkerEmployCardVO]> = .init(value: [.mock]) + + // Observable + private let disposeBag = DisposeBag() + + public init() { + super.init(nibName: nil, bundle: nil) + } + + public required init?(coder: NSCoder) { fatalError() } + + public override func viewDidLoad() { + super.viewDidLoad() + setAppearance() + setLayout() + setObservable() + setTableView() + } + + private func setTableView() { + postTableView.dataSource = self + postTableView.delegate = self + postTableView.separatorStyle = .none + postTableView.delaysContentTouches = false + + postTableView.tableHeaderView = tableHeader + + tableHeader.frame = .init(origin: .zero, size: .init( + width: view.bounds.width, + height: 60) + ) + } + + private func setAppearance() { + + } + + private func setLayout() { + + [ + postTableView + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + view.addSubview($0) + } + + NSLayoutConstraint.activate([ + postTableView.topAnchor.constraint(equalTo: view.topAnchor), + postTableView.leftAnchor.constraint(equalTo: view.leftAnchor), + postTableView.rightAnchor.constraint(equalTo: view.rightAnchor), + postTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } + + private func setObservable() { + + } + + public func bind(viewModel: StarredPostViewModelable) { + +// self.viewModel = viewModel +// +// // Output +// viewModel +// .starredPostCardVO? +// .drive(onNext: { [weak self] vos in +// guard let self else { return } +// starredPostCardVO.accept(vos) +// postTableView.reloadData() +// }) +// .disposed(by: disposeBag) +// +// // Input +// rx.viewWillAppear +// .map { _ in } +// .bind(to: viewModel.requestAppliedPost) +// .disposed(by: disposeBag) + } +} + +extension StarredBoardVC: UITableViewDataSource, UITableViewDelegate { + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + starredPostCardVO.value.count + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell + cell.selectionStyle = .none +// +// if let viewModel = self.viewModel { +// let vo = starredPostCardVO.value[indexPath.row] +// let vm = viewModel.createCellVM(vo: vo) +// cell.bind(viewModel: vm) +// } + + return cell + } +} diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift index 6f9b009f..a32d1225 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift @@ -13,13 +13,14 @@ import RxSwift import Entity import DSKit + public class WorkerRecruitmentPostBoardVC: BaseViewController { typealias Cell = WorkerEmployCardCell var viewModel: WorkerRecruitmentPostBoardVMable? // View - let topContainer: WorkerMainTopContainer = { + fileprivate let topContainer: WorkerMainTopContainer = { let container = WorkerMainTopContainer(innerViews: []) return container }() @@ -136,7 +137,7 @@ extension WorkerRecruitmentPostBoardVC: UITableViewDataSource, UITableViewDelega } // MARK: Top Container -class WorkerMainTopContainer: UIView { +fileprivate class WorkerMainTopContainer: UIView { // Init parameters From d0c88b386180e5f122981292071bedc478db7bb4 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Sat, 17 Aug 2024 00:16:28 +0900 Subject: [PATCH 17/21] =?UTF-8?q?[IDLE-163]=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=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefaultRecruitmentPostRepository.swift | 16 ++++ .../API/RcruitmentPostAPI.swift | 24 ++++++ .../API/UserInformationAPI.swift | 4 +- .../RecruitmentPost/RecruitmentPostDTO.swift | 14 +++- .../RecruitmentPostDetailForWorkerDTO.swift | 11 ++- .../RecuritmentPostListForWorkerDTO.swift | 77 ++++++++++++++++++ .../RecruitmentPostForWorkerBundle.swift | 0 .../Post/RecruitmentPostListForWorkerVO.swift | 78 +++++++++++++++++++ .../Post}/RegisterRecruitmentPostBundle.swift | 0 .../RecruitmentPostRepository.swift | 7 ++ 10 files changed, 226 insertions(+), 5 deletions(-) create mode 100644 project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift rename project/Projects/Domain/Entity/{State/RecruitmentPost => VO/Post}/RecruitmentPostForWorkerBundle.swift (100%) create mode 100644 project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift rename project/Projects/Domain/Entity/{State/RecruitmentPost => VO/Post}/RegisterRecruitmentPostBundle.swift (100%) diff --git a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift index 4beb068f..16764bc0 100644 --- a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift +++ b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift @@ -60,6 +60,22 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { dto.toEntity() } } + + public func getPostListForWorker(nextPageId: String?, requestCnt: Int = 10) -> RxSwift.Single { + + service + .request( + api: .postList( + nextPageId: nextPageId, + requestCnt: requestCnt + ), + with: .withToken + ) + .map(RecruitmentPostListForWorkerDTO.self) + .map { dto in + dto.toEntity() + } + } } fileprivate extension RegisterRecruitmentPostBundle { diff --git a/project/Projects/Data/NetworkDataSource/API/RcruitmentPostAPI.swift b/project/Projects/Data/NetworkDataSource/API/RcruitmentPostAPI.swift index 13b7d1e4..fed11993 100644 --- a/project/Projects/Data/NetworkDataSource/API/RcruitmentPostAPI.swift +++ b/project/Projects/Data/NetworkDataSource/API/RcruitmentPostAPI.swift @@ -7,6 +7,7 @@ import Moya import Foundation +import Alamofire import Entity public enum RcruitmentPostAPI { @@ -19,6 +20,9 @@ public enum RcruitmentPostAPI { case editPost(id: String, postData: Data) case removePost(id: String) case closePost(id: String) + + // Worker + case postList(nextPageId: String?, requestCnt: Int) } extension RcruitmentPostAPI: BaseAPI { @@ -39,6 +43,8 @@ extension RcruitmentPostAPI: BaseAPI { "/\(id)" case .closePost(let id): "/\(id)/end" + case .postList: + "" } } @@ -54,7 +60,23 @@ extension RcruitmentPostAPI: BaseAPI { .delete case .closePost: .patch + case .postList: + .get + } + } + + var bodyParameters: Parameters? { + var params: Parameters = [:] + switch self { + case .postList(let nextPageId, let requestCnt): + if let nextPageId { + params["next"] = nextPageId + } + params["limit"] = requestCnt + default: + break } + return params } var parameterEncoding: ParameterEncoding { @@ -66,6 +88,8 @@ extension RcruitmentPostAPI: BaseAPI { public var task: Moya.Task { switch self { + case .postList: + .requestParameters(parameters: bodyParameters ?? [:], encoding: parameterEncoding) case .registerPost(let bodyData): .requestData(bodyData) case .editPost(_, let editData): diff --git a/project/Projects/Data/NetworkDataSource/API/UserInformationAPI.swift b/project/Projects/Data/NetworkDataSource/API/UserInformationAPI.swift index 22fa8838..8a914db2 100644 --- a/project/Projects/Data/NetworkDataSource/API/UserInformationAPI.swift +++ b/project/Projects/Data/NetworkDataSource/API/UserInformationAPI.swift @@ -90,9 +90,9 @@ extension UserInformationAPI: BaseAPI { .post case .getMyWorkerProfile: .get - case .getOtherWorkerProfile(id: let id): + case .getOtherWorkerProfile: .get - case .updateWorkerProfile(data: let data): + case .updateWorkerProfile: .patch } } diff --git a/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDTO.swift b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDTO.swift index 4bf5b4ee..7c17a650 100644 --- a/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDTO.swift +++ b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDTO.swift @@ -89,7 +89,7 @@ public struct RecruitmentPostFetchDTO: Codable { age: Int, weight: Int?, careLevel: Int, - mentalStatus: String, + mentalStatus: String, ㅇ disease: String?, lifeAssistance: [String]?, extraRequirement: String?, @@ -132,7 +132,17 @@ public struct RecruitmentPostFetchDTO: Codable { workTimeAndPay.workStartTime = IdleDateComponent.toEntity(text: startTime) workTimeAndPay.workEndTime = IdleDateComponent.toEntity(text: endTime) workTimeAndPay.paymentType = PaymentType.toEntity(text: payType) - workTimeAndPay.paymentAmount = String(payAmount) + + let payAmount = String(payAmount) + var formedPayAmount = "" + for (index, char) in payAmount.reversed().enumerated() { + if (index % 3) == 0, index != 0 { + formedPayAmount += "," + } + formedPayAmount += String(char) + } + + workTimeAndPay.paymentAmount = formedPayAmount let addressInfo: AddressInputStateObject = .init() addressInfo.addressInfo = .init( diff --git a/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift index c5cc517e..164df625 100644 --- a/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift +++ b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecruitmentPostDetailForWorkerDTO.swift @@ -54,7 +54,16 @@ public struct RecruitmentPostDTO: Codable { workTimeAndPay.workStartTime = IdleDateComponent.toEntity(text: startTime) workTimeAndPay.workEndTime = IdleDateComponent.toEntity(text: endTime) workTimeAndPay.paymentType = PaymentType.toEntity(text: payType) - workTimeAndPay.paymentAmount = String(payAmount) + + let payAmount = String(payAmount) + var formedPayAmount = "" + for (index, char) in payAmount.reversed().enumerated() { + if (index % 3) == 0, index != 0 { + formedPayAmount += "," + } + formedPayAmount += String(char) + } + workTimeAndPay.paymentAmount = formedPayAmount let addressInfo: AddressInputStateObject = .init() addressInfo.addressInfo = .init( diff --git a/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift new file mode 100644 index 00000000..deabd7db --- /dev/null +++ b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift @@ -0,0 +1,77 @@ +// +// RecuritmentPostListForWorkerDTO.swift +// NetworkDataSource +// +// Created by choijunios on 8/16/24. +// + +import Foundation +import Entity + +public struct RecruitmentPostListForWorkerDTO: Codable { + + public let items: [RecruitmentPostForWorkerDTO] + public let next: String? + public let fetchedPostCount: Int + + public func toEntity() -> RecruitmentPostListForWorkerVO { + + return .init( + posts: items.map { $0.toEntity() }, + nextPageId: next, + fetchedPostCount: fetchedPostCount + ) + } +} + +public struct RecruitmentPostForWorkerDTO: Codable { + public let isExperiencePreferred: Bool + public let id: String + public let weekdays: [String] + public let startTime: String + public let endTime: String + public let payType: String + public let payAmount: Int + public let roadNameAddress: String + public let lotNumberAddress: String + public let gender: String + public let age: Int + public let careLevel: Int + public let applyDeadlineType: String + public let applyDeadline: String? + public let distance: Int + + public func toEntity() -> RecruitmentPostForWorkerVO { + + let weekDayList = weekdays.map({ dayText in + WorkDay.toEntity(text: dayText) + }) + + let payAmount = String(payAmount) + var formedPayAmount = "" + for (index, char) in payAmount.reversed().enumerated() { + if (index % 3) == 0, index != 0 { + formedPayAmount += "," + } + formedPayAmount += String(char) + } + + return .init( + postId: id, + weekdays: weekDayList, + startTime: startTime, + endTime: endTime, + roadNameAddress: roadNameAddress, + lotNumberAddress: lotNumberAddress, + gender: Gender.toEntity(text: gender), + age: age, + careLevel: CareGrade(rawValue: careLevel-1)!, + isExperiencePreferred: isExperiencePreferred, + applyDeadlineType: ApplyDeadlineType.toEntity(text: applyDeadlineType), + applyDeadline: applyDeadline, + payType: PaymentType.toEntity(text: payType), + payAmount: formedPayAmount, + distance: distance + ) + } +} diff --git a/project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentPostForWorkerBundle.swift b/project/Projects/Domain/Entity/VO/Post/RecruitmentPostForWorkerBundle.swift similarity index 100% rename from project/Projects/Domain/Entity/State/RecruitmentPost/RecruitmentPostForWorkerBundle.swift rename to project/Projects/Domain/Entity/VO/Post/RecruitmentPostForWorkerBundle.swift diff --git a/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift b/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift new file mode 100644 index 00000000..ff389139 --- /dev/null +++ b/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift @@ -0,0 +1,78 @@ +// +// RecruitmentPostListForWorkerVO.swift +// Entity +// +// Created by choijunios on 8/16/24. +// + +import Foundation + +public class RecruitmentPostListForWorkerVO { + + public let posts: [RecruitmentPostForWorkerVO] + public let nextPageId: String? + public let fetchedPostCount: Int + + public init(posts: [RecruitmentPostForWorkerVO], nextPageId: String?, fetchedPostCount: Int) { + self.posts = posts + self.nextPageId = nextPageId + self.fetchedPostCount = fetchedPostCount + } +} + +public class RecruitmentPostForWorkerVO { + public let postId: String + + public let weekdays: [WorkDay] + public let startTime: String + public let endTime: String + + public let roadNameAddress: String + public let lotNumberAddress: String + + public let gender: Gender + public let age: Int + public let careLevel: CareGrade + + public let isExperiencePreferred: Bool + public let applyDeadlineType: ApplyDeadlineType + public let applyDeadline: String? + public let payType: PaymentType + public let payAmount: String + + public let distance: Int + + public init( + postId: String, + weekdays: [WorkDay], + startTime: String, + endTime: String, + roadNameAddress: String, + lotNumberAddress: String, + gender: Gender, + age: Int, + careLevel: CareGrade, + isExperiencePreferred: Bool, + applyDeadlineType: ApplyDeadlineType, + applyDeadline: String?, + payType: PaymentType, + payAmount: String, + distance: Int + ) { + self.postId = postId + self.weekdays = weekdays + self.startTime = startTime + self.endTime = endTime + self.roadNameAddress = roadNameAddress + self.lotNumberAddress = lotNumberAddress + self.gender = gender + self.age = age + self.careLevel = careLevel + self.isExperiencePreferred = isExperiencePreferred + self.applyDeadlineType = applyDeadlineType + self.applyDeadline = applyDeadline + self.payType = payType + self.payAmount = payAmount + self.distance = distance + } +} diff --git a/project/Projects/Domain/Entity/State/RecruitmentPost/RegisterRecruitmentPostBundle.swift b/project/Projects/Domain/Entity/VO/Post/RegisterRecruitmentPostBundle.swift similarity index 100% rename from project/Projects/Domain/Entity/State/RecruitmentPost/RegisterRecruitmentPostBundle.swift rename to project/Projects/Domain/Entity/VO/Post/RegisterRecruitmentPostBundle.swift diff --git a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift index 98bc1e4b..d56cd76b 100644 --- a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift +++ b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift @@ -12,12 +12,19 @@ import Entity public protocol RecruitmentPostRepository: RepositoryBase { // MARK: Center + /// 공고를 등록합니다. func registerPost(bundle: RegisterRecruitmentPostBundle) -> Single + /// 센터측에서 등록한 공고의 상세내역을 확인합니다. func getPostDetailForCenter(id: String) -> Single + /// 센터가 등록한 공고의 상세정보를 수정합니다. func editPostDetail(id: String, bundle: RegisterRecruitmentPostBundle) -> Single // MARK: Worker + /// 요양보호사 공고의 상세정보를 조회합니다. func getPostDetailForWorker(id: String) -> Single + + /// 요양보호사 메인화면에 표시될 유효한 공고를 조회합니다. + func getPostListForWorker(nextPageId: String?, requestCnt: Int) -> Single } From dd15ef98aa2cfc54498328565632ef9afbfac6dc Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Sat, 17 Aug 2024 14:16:03 +0900 Subject: [PATCH 18/21] =?UTF-8?q?[IDLE-163]=20=EC=9A=94=EC=96=91=EB=B3=B4?= =?UTF-8?q?=ED=98=B8=EC=82=AC=20=EB=A9=94=EC=9D=B8=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=95=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Main/Worker/WorkerMainCoordinator.swift | 16 +- .../DefaultRecruitmentPostRepository.swift | 27 +-- .../Data/NetworkDataSource/API/AuthAPI.swift | 2 +- .../API/RcruitmentPostAPI.swift | 12 +- .../RecuritmentPostListForWorkerDTO.swift | 25 ++- .../DefualtRecruitmentPostUseCase.swift | 18 ++ .../Paging/PostPagingRequestForWorker.swift | 13 ++ .../Entity/VO/Employ/WorkerEmployCardVO.swift | 68 ++++++-- .../Post/RecruitmentPostListForWorkerVO.swift | 28 +-- .../RecruitmentPostRepository.swift | 4 +- .../RecruitmentPostUseCase.swift | 3 + .../Card/Post/Worker/WorkerEmployCard.swift | 18 +- .../WorkerRecruitmentBoardCoordinator.swift | 4 +- .../WorkerRecruitmentPostBoardVC.swift | 72 +++++--- .../WorkerRecruitmentPostBoardVM.swift | 160 ++++++++++++++---- 15 files changed, 344 insertions(+), 126 deletions(-) create mode 100644 project/Projects/Domain/Entity/State/RecruitmentPost/Paging/PostPagingRequestForWorker.swift diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift index bea236eb..a7fb847d 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift @@ -79,20 +79,12 @@ class WorkerMainCoordinator: ParentCoordinator { ) ) case .preferredPost: - coordinator = WorkerRecruitmentBoardCoordinator( - depedency: .init( - navigationController: navigationController, - centerProfileUseCase: injector.resolve(CenterProfileUseCase.self), - recruitmentPostUseCase: injector.resolve(RecruitmentPostUseCase.self) - ) + coordinator = SettingCoordinator( + navigationController: navigationController ) case .setting: - coordinator = WorkerRecruitmentBoardCoordinator( - depedency: .init( - navigationController: navigationController, - centerProfileUseCase: injector.resolve(CenterProfileUseCase.self), - recruitmentPostUseCase: injector.resolve(RecruitmentPostUseCase.self) - ) + coordinator = SettingCoordinator( + navigationController: navigationController ) } diff --git a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift index 16764bc0..c616a412 100644 --- a/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift +++ b/project/Projects/Data/ConcreteRepository/RecruitmentPost/DefaultRecruitmentPostRepository.swift @@ -10,6 +10,7 @@ import RxSwift import Entity import NetworkDataSource import Foundation +import Moya public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { @@ -61,20 +62,22 @@ public class DefaultRecruitmentPostRepository: RecruitmentPostRepository { } } - public func getPostListForWorker(nextPageId: String?, requestCnt: Int = 10) -> RxSwift.Single { + public func getNativePostListForWorker(nextPageId: String?, requestCnt: Int = 10) -> RxSwift.Single { - service - .request( - api: .postList( - nextPageId: nextPageId, - requestCnt: requestCnt - ), - with: .withToken - ) - .map(RecruitmentPostListForWorkerDTO.self) - .map { dto in - dto.toEntity() + service.request( + api: .nativePostList(nextPageId: nextPageId, requestCnt: String(requestCnt)), + with: .withToken + ) + .map(RecruitmentPostListForWorkerDTO.self) + .catch({ error in + if let moyaError = error as? MoyaError, case .objectMapping(let error, let response) = moyaError { + print(error.localizedDescription) } + return .error(error) + }) + .map { dto in + dto.toEntity() + } } } diff --git a/project/Projects/Data/NetworkDataSource/API/AuthAPI.swift b/project/Projects/Data/NetworkDataSource/API/AuthAPI.swift index 6051ba67..cf891f1b 100644 --- a/project/Projects/Data/NetworkDataSource/API/AuthAPI.swift +++ b/project/Projects/Data/NetworkDataSource/API/AuthAPI.swift @@ -29,7 +29,7 @@ public enum AuthAPI { extension AuthAPI: BaseAPI { - public var apiType: APIType { .auth} + public var apiType: APIType { .auth } public var method: Moya.Method { diff --git a/project/Projects/Data/NetworkDataSource/API/RcruitmentPostAPI.swift b/project/Projects/Data/NetworkDataSource/API/RcruitmentPostAPI.swift index fed11993..3a1169d3 100644 --- a/project/Projects/Data/NetworkDataSource/API/RcruitmentPostAPI.swift +++ b/project/Projects/Data/NetworkDataSource/API/RcruitmentPostAPI.swift @@ -22,7 +22,7 @@ public enum RcruitmentPostAPI { case closePost(id: String) // Worker - case postList(nextPageId: String?, requestCnt: Int) + case nativePostList(nextPageId: String?, requestCnt: String) } extension RcruitmentPostAPI: BaseAPI { @@ -43,7 +43,7 @@ extension RcruitmentPostAPI: BaseAPI { "/\(id)" case .closePost(let id): "/\(id)/end" - case .postList: + case .nativePostList: "" } } @@ -60,7 +60,7 @@ extension RcruitmentPostAPI: BaseAPI { .delete case .closePost: .patch - case .postList: + case .nativePostList: .get } } @@ -68,7 +68,7 @@ extension RcruitmentPostAPI: BaseAPI { var bodyParameters: Parameters? { var params: Parameters = [:] switch self { - case .postList(let nextPageId, let requestCnt): + case .nativePostList(let nextPageId, let requestCnt): if let nextPageId { params["next"] = nextPageId } @@ -81,6 +81,8 @@ extension RcruitmentPostAPI: BaseAPI { var parameterEncoding: ParameterEncoding { switch self { + case .nativePostList: + return URLEncoding.queryString default: return JSONEncoding.default } @@ -88,7 +90,7 @@ extension RcruitmentPostAPI: BaseAPI { public var task: Moya.Task { switch self { - case .postList: + case .nativePostList: .requestParameters(parameters: bodyParameters ?? [:], encoding: parameterEncoding) case .registerPost(let bodyData): .requestData(bodyData) diff --git a/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift index deabd7db..47ca3703 100644 --- a/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift +++ b/project/Projects/Data/NetworkDataSource/DTO/RecruitmentPost/RecuritmentPostListForWorkerDTO.swift @@ -12,14 +12,14 @@ public struct RecruitmentPostListForWorkerDTO: Codable { public let items: [RecruitmentPostForWorkerDTO] public let next: String? - public let fetchedPostCount: Int + public let total: Int public func toEntity() -> RecruitmentPostListForWorkerVO { return .init( posts: items.map { $0.toEntity() }, nextPageId: next, - fetchedPostCount: fetchedPostCount + fetchedPostCount: total ) } } @@ -43,7 +43,7 @@ public struct RecruitmentPostForWorkerDTO: Codable { public func toEntity() -> RecruitmentPostForWorkerVO { - let weekDayList = weekdays.map({ dayText in + let workDayList = weekdays.map({ dayText in WorkDay.toEntity(text: dayText) }) @@ -56,22 +56,33 @@ public struct RecruitmentPostForWorkerDTO: Codable { formedPayAmount += String(char) } + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + let deadlineDate = applyDeadline != nil ? dateFormatter.date(from: applyDeadline!) : nil + + // distance는 미터단위입니다. + var distanceText: String = "\(distance)m" + if distance >= 1000 { + let kilometers = Double(distance)/1000.0 + distanceText = String(format: "%.1fkm", kilometers) + } + return .init( postId: id, - weekdays: weekDayList, + workDays: workDayList, startTime: startTime, endTime: endTime, roadNameAddress: roadNameAddress, lotNumberAddress: lotNumberAddress, gender: Gender.toEntity(text: gender), age: age, - careLevel: CareGrade(rawValue: careLevel-1)!, + cardGrade: CareGrade(rawValue: careLevel-1)!, isExperiencePreferred: isExperiencePreferred, applyDeadlineType: ApplyDeadlineType.toEntity(text: applyDeadlineType), - applyDeadline: applyDeadline, + applyDeadlineDate: deadlineDate, payType: PaymentType.toEntity(text: payType), payAmount: formedPayAmount, - distance: distance + distanceFromWorkPlace: distanceText ) } } diff --git a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift index 65685f1a..d78b8046 100644 --- a/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/RecruitmentPost/DefualtRecruitmentPostUseCase.swift @@ -56,4 +56,22 @@ public class DefaultRecruitmentPostUseCase: RecruitmentPostUseCase { public func getPostDetailForWorker(id: String) -> RxSwift.Single> { convert(task: repository.getPostDetailForWorker(id: id)) } + + public func getPostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> Single> { + + let stream: Single! + + switch request { + case .native(let nextPageId): + stream = repository.getNativePostListForWorker( + nextPageId: nextPageId, + requestCnt: postCount + ) + case .thirdParty(let nextPageId): + /// 미구현 + fatalError() + } + + return convert(task: stream) + } } diff --git a/project/Projects/Domain/Entity/State/RecruitmentPost/Paging/PostPagingRequestForWorker.swift b/project/Projects/Domain/Entity/State/RecruitmentPost/Paging/PostPagingRequestForWorker.swift new file mode 100644 index 00000000..e2c2602d --- /dev/null +++ b/project/Projects/Domain/Entity/State/RecruitmentPost/Paging/PostPagingRequestForWorker.swift @@ -0,0 +1,13 @@ +// +// PostPagingRequestForWorker.swift +// Entity +// +// Created by choijunios on 8/17/24. +// + +import Foundation + +public enum PostPagingRequestForWorker { + case native(nextPageId: String?) + case thirdParty(nextPageId: String?) +} diff --git a/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift b/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift index 0268a123..08ce2464 100644 --- a/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift +++ b/project/Projects/Domain/Entity/VO/Employ/WorkerEmployCardVO.swift @@ -11,7 +11,7 @@ public struct WorkerEmployCardVO { public let dayLeft: Int public let isBeginnerPossible: Bool - public let timeTakenForWalk: String + public let distanceFromWorkPlace: String public let title: String public let targetAge: Int public let careGrade: CareGrade @@ -20,13 +20,26 @@ public struct WorkerEmployCardVO { public let startTime: String public let endTime: String public let paymentType: PaymentType - public let paymentAmount: Int + public let paymentAmount: String - public init(dayLeft: Int, isBeginnerPossible: Bool, timeTakenForWalk: String, title: String, targetAge: Int, careGrade: CareGrade, targetGender: Gender, days: [WorkDay], startTime: String, endTime: String, paymentType: PaymentType, paymentAmount: Int) { + public init( + dayLeft: Int, + isBeginnerPossible: Bool, + distanceFromWorkPlace: String, + title: String, + targetAge: Int, + careGrade: CareGrade, + targetGender: Gender, + days: [WorkDay], + startTime: String, + endTime: String, + paymentType: PaymentType, + paymentAmount: String + ) { self.dayLeft = dayLeft self.isBeginnerPossible = isBeginnerPossible - self.timeTakenForWalk = timeTakenForWalk + self.distanceFromWorkPlace = distanceFromWorkPlace self.title = title self.targetAge = targetAge self.careGrade = careGrade @@ -38,6 +51,7 @@ public struct WorkerEmployCardVO { self.paymentAmount = paymentAmount } + /// 서버가 입력중인 공고의 확인화면에 사용됩니다. public static func create( workTimeAndPay: WorkTimeAndPayStateObject, customerRequirement: CustomerRequirementStateObject, @@ -63,9 +77,6 @@ public struct WorkerEmployCardVO { // 제목(=도로명주소) let title = addressInfo.addressInfo?.roadAddress.emptyDefault("위치정보 표기 오류") ?? "" - // 도보시간 - let timeTakenForWalk = "도보 n분" - // 생년 let birthYear = Int(customerInformation.birthYear) ?? 1950 let currentYear = calendar.component(.year, from: currentDate) @@ -88,12 +99,12 @@ public struct WorkerEmployCardVO { // 급여타입및 양 let paymentType = workTimeAndPay.paymentType ?? .hourly - let paymentAmount = Int(workTimeAndPay.paymentAmount) ?? 0 + let paymentAmount = workTimeAndPay.paymentAmount return WorkerEmployCardVO( - dayLeft: leftDay ?? 0, + dayLeft: leftDay ?? 31, isBeginnerPossible: isBeginnerPossible, - timeTakenForWalk: timeTakenForWalk, + distanceFromWorkPlace: "500m", title: title, targetAge: targetAge, careGrade: careGrade, @@ -105,6 +116,35 @@ public struct WorkerEmployCardVO { paymentAmount: paymentAmount ) } + + public static func create(vo: RecruitmentPostForWorkerVO) -> WorkerEmployCardVO { + + // 남은 일수 + var leftDay: Int? = nil + let calendar = Calendar.current + let currentDate = Date() + + if vo.applyDeadlineType == .specificDate, let deadlineDate = vo.applyDeadlineDate { + + let component = calendar.dateComponents([.day], from: currentDate, to: deadlineDate) + leftDay = component.day + } + + return .init( + dayLeft: leftDay ?? 31, + isBeginnerPossible: !vo.isExperiencePreferred, + distanceFromWorkPlace: vo.distanceFromWorkPlace, + title: vo.roadNameAddress, + targetAge: vo.age, + careGrade: vo.cardGrade, + targetGender: vo.gender, + days: vo.workDays, + startTime: vo.startTime, + endTime: vo.endTime, + paymentType: vo.payType, + paymentAmount: vo.payAmount + ) + } } @@ -120,7 +160,7 @@ public extension WorkerEmployCardVO { static let mock = WorkerEmployCardVO( dayLeft: 10, isBeginnerPossible: true, - timeTakenForWalk: "도보 15분", + distanceFromWorkPlace: "500m", title: "서울특별시 강남구 신사동", targetAge: 78, careGrade: .four, @@ -129,13 +169,13 @@ public extension WorkerEmployCardVO { startTime: "09:00", endTime: "15:00", paymentType: .hourly, - paymentAmount: 12500 + paymentAmount: "12,500" ) static let `default` = WorkerEmployCardVO( dayLeft: 0, isBeginnerPossible: true, - timeTakenForWalk: "도보 15분", + distanceFromWorkPlace: "8km", title: "기본값", targetAge: 10, careGrade: .one, @@ -144,6 +184,6 @@ public extension WorkerEmployCardVO { startTime: "00:00", endTime: "00:00", paymentType: .hourly, - paymentAmount: 0 + paymentAmount: "12,500" ) } diff --git a/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift b/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift index ff389139..9d833e48 100644 --- a/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift +++ b/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift @@ -7,7 +7,7 @@ import Foundation -public class RecruitmentPostListForWorkerVO { +public struct RecruitmentPostListForWorkerVO { public let posts: [RecruitmentPostForWorkerVO] public let nextPageId: String? @@ -20,10 +20,10 @@ public class RecruitmentPostListForWorkerVO { } } -public class RecruitmentPostForWorkerVO { +public struct RecruitmentPostForWorkerVO { public let postId: String - public let weekdays: [WorkDay] + public let workDays: [WorkDay] public let startTime: String public let endTime: String @@ -32,47 +32,47 @@ public class RecruitmentPostForWorkerVO { public let gender: Gender public let age: Int - public let careLevel: CareGrade + public let cardGrade: CareGrade public let isExperiencePreferred: Bool public let applyDeadlineType: ApplyDeadlineType - public let applyDeadline: String? + public let applyDeadlineDate: Date? public let payType: PaymentType public let payAmount: String - public let distance: Int + public let distanceFromWorkPlace: String public init( postId: String, - weekdays: [WorkDay], + workDays: [WorkDay], startTime: String, endTime: String, roadNameAddress: String, lotNumberAddress: String, gender: Gender, age: Int, - careLevel: CareGrade, + cardGrade: CareGrade, isExperiencePreferred: Bool, applyDeadlineType: ApplyDeadlineType, - applyDeadline: String?, + applyDeadlineDate: Date?, payType: PaymentType, payAmount: String, - distance: Int + distanceFromWorkPlace: String ) { self.postId = postId - self.weekdays = weekdays + self.workDays = workDays self.startTime = startTime self.endTime = endTime self.roadNameAddress = roadNameAddress self.lotNumberAddress = lotNumberAddress self.gender = gender self.age = age - self.careLevel = careLevel + self.cardGrade = cardGrade self.isExperiencePreferred = isExperiencePreferred self.applyDeadlineType = applyDeadlineType - self.applyDeadline = applyDeadline + self.applyDeadlineDate = applyDeadlineDate self.payType = payType self.payAmount = payAmount - self.distance = distance + self.distanceFromWorkPlace = distanceFromWorkPlace } } diff --git a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift index d56cd76b..1e5cf1cf 100644 --- a/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift +++ b/project/Projects/Domain/RepositoryInterface/RecruitmentPost/RecruitmentPostRepository.swift @@ -25,6 +25,6 @@ public protocol RecruitmentPostRepository: RepositoryBase { /// 요양보호사 공고의 상세정보를 조회합니다. func getPostDetailForWorker(id: String) -> Single - /// 요양보호사 메인화면에 표시될 유효한 공고를 조회합니다. - func getPostListForWorker(nextPageId: String?, requestCnt: Int) -> Single + /// 요샹보호사가 확인하는 케어밋 자체 공고정보를 가져옵니다. + func getNativePostListForWorker(nextPageId: String?, requestCnt: Int) -> Single } diff --git a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift index d62dec8c..e782bb3c 100644 --- a/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/RecruitmentPost/RecruitmentPostUseCase.swift @@ -31,4 +31,7 @@ public protocol RecruitmentPostUseCase: UseCaseBase { /// - 근무지 위치(위경도) /// - 센터정보(센터 id, 이름, 도로명 주소) func getPostDetailForWorker(id: String) -> Single> + + /// 요양보호사가 메인화면에 사용할 공고리스트를 호출합니다. + func getPostListForWorker(request: PostPagingRequestForWorker, postCount: Int) -> Single> } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift index 2d617d00..6ae37e2c 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift @@ -16,18 +16,18 @@ public class WorkerEmployCardRO { let showDayLeftTag: Bool let dayLeftTagText: String? let titleText: String - let timeTakenForWalkText: String + let distanceFromWorkPlace: String let targetInfoText: String let workDaysText: String let workTimeText: String let payText: String - init(showBiginnerTag: Bool, showDayLeftTag: Bool, dayLeftTagText: String?, titleText: String, timeTakenForWalkText: String, targetInfoText: String, workDaysText: String, workTimeText: String, payText: String) { + init(showBiginnerTag: Bool, showDayLeftTag: Bool, dayLeftTagText: String?, titleText: String, distanceFromWorkPlace: String, targetInfoText: String, workDaysText: String, workTimeText: String, payText: String) { self.showBiginnerTag = showBiginnerTag self.showDayLeftTag = showDayLeftTag self.dayLeftTagText = dayLeftTagText self.titleText = titleText - self.timeTakenForWalkText = timeTakenForWalkText + self.distanceFromWorkPlace = distanceFromWorkPlace self.targetInfoText = targetInfoText self.workDaysText = workDaysText self.workTimeText = workTimeText @@ -58,7 +58,7 @@ public class WorkerEmployCardRO { showDayLeftTag: showDayLeftTag, dayLeftTagText: dayLeftTagText, titleText: vo.title, - timeTakenForWalkText: vo.timeTakenForWalk, + distanceFromWorkPlace: vo.distanceFromWorkPlace, targetInfoText: targetInfoText, workDaysText: workDaysText, workTimeText: workTimeText, @@ -71,7 +71,7 @@ public class WorkerEmployCardRO { showDayLeftTag: true, dayLeftTagText: "D-14", titleText: "사울시 강남동", - timeTakenForWalkText: "도보 5분", + distanceFromWorkPlace: "1.1km", targetInfoText: "1등급 54세 여성", workDaysText: "", workTimeText: "월, 화, 수", @@ -116,7 +116,7 @@ public class WorkerEmployCard: UIView { let label = IdleLabel(typography: .Subtitle2) return label }() - let timeTakenForWalkLabel: IdleLabel = { + let distanceFromWorkPlaceLabel: IdleLabel = { let label = IdleLabel(typography: .Body3) label.attrTextColor = DSKitAsset.Colors.gray500.color return label @@ -184,7 +184,7 @@ public class WorkerEmployCard: UIView { let titleStack = HStack( [ titleLabel, - timeTakenForWalkLabel + distanceFromWorkPlaceLabel ], spacing: 8, alignment: .bottom @@ -296,7 +296,7 @@ public class WorkerEmployCard: UIView { public func setToPostAppearance() { titleLabel.typography = .Subtitle1 - timeTakenForWalkLabel.isHidden = true + distanceFromWorkPlaceLabel.isHidden = true serviceTargetInfoLabel.typography = .Body3 workDaysLabel.typography = .Body2 workTimeLabel.typography = .Body2 @@ -309,7 +309,7 @@ public class WorkerEmployCard: UIView { dayLeftTag.isHidden = !ro.showDayLeftTag dayLeftTag.textString = ro.dayLeftTagText ?? "" titleLabel.textString = ro.titleText - timeTakenForWalkLabel.textString = ro.timeTakenForWalkText + distanceFromWorkPlaceLabel.textString = ro.distanceFromWorkPlace serviceTargetInfoLabel.textString = ro.targetInfoText workDaysLabel.textString = ro.workDaysText workTimeLabel.textString = ro.workTimeText diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/MainBoard/WorkerRecruitmentBoardCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/MainBoard/WorkerRecruitmentBoardCoordinator.swift index ea9dfd2e..623d9d61 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/MainBoard/WorkerRecruitmentBoardCoordinator.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/MainBoard/WorkerRecruitmentBoardCoordinator.swift @@ -46,9 +46,11 @@ public class WorkerRecruitmentBoardCoordinator: WorkerRecruitmentBoardCoordinata public func start() { let vc = WorkerRecruitmentPostBoardVC() let vm = WorkerRecruitmentPostBoardVM( - coordinator: self + coordinator: self, + recruitmentPostUseCase: recruitmentPostUseCase ) vc.bind(viewModel: vm) + viewControllerRef = vc navigationController.pushViewController(vc, animated: false) } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift index a32d1225..1189da2d 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift @@ -30,12 +30,15 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { tableView.register(Cell.self, forCellReuseIdentifier: Cell.identifier) return tableView }() - let tableHeader = BoardSortigHeaderView() - let ongoingPostCardVO: BehaviorRelay<[WorkerEmployCardVO]> = .init(value: []) + // Paging + var isPaging = true // Observable + let postViewModels: BehaviorRelay<[WorkerEmployCardViewModelable]> = .init(value: []) + let requestNextPage: PublishRelay = .init() + private let disposeBag = DisposeBag() public init() { @@ -54,18 +57,38 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { self.viewModel = viewModel - topContainer.locationLabel.textString = "서울시 영등포구(미구현)" - // Output viewModel - .ongoingPostCardVO? - .drive(ongoingPostCardVO) + .workerLocationTitleText? + .drive(onNext: { [weak self] titleText in + self?.topContainer.locationLabel.textString = titleText + }) + .disposed(by: disposeBag) + + viewModel + .postBoardData? + .drive(onNext: { [weak self] viewModels in + guard let self else { return } + self.postViewModels.accept(viewModels) + self.postTableView.reloadData() + self.isPaging = false + }) + .disposed(by: disposeBag) + + viewModel + .alert? + .drive(onNext: { [weak self] alertVO in + self?.showAlert(vo: alertVO) + }) .disposed(by: disposeBag) // Input - rx.viewWillAppear - .map { _ in () } - .bind(to: viewModel.viewWillAppear) + self.rx.viewDidLoad + .bind(to: viewModel.viewDidLoad) + .disposed(by: disposeBag) + + self.requestNextPage + .bind(to: viewModel.requestNextPage) .disposed(by: disposeBag) } @@ -113,7 +136,7 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { extension WorkerRecruitmentPostBoardVC: UITableViewDataSource, UITableViewDelegate { public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - ongoingPostCardVO.value.count + postViewModels.value.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -121,21 +144,30 @@ extension WorkerRecruitmentPostBoardVC: UITableViewDataSource, UITableViewDelega let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell cell.selectionStyle = .none - // MARK: TODO: PostId를 가져오기 - - if let viewModel = self.viewModel { - let vo = ongoingPostCardVO.value[indexPath.row] - let vm = viewModel.createCellVM( - postId: "00-00000-00000", - vo: vo - ) - cell.bind(viewModel: vm) - } + let vm = postViewModels.value[indexPath.row] + cell.bind(viewModel: vm) return cell } } +// MARK: ScrollView관련 +extension WorkerRecruitmentPostBoardVC { + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + let offsetY = scrollView.contentOffset.y + let contentHeight = scrollView.contentSize.height + let height = scrollView.frame.height + + // 스크롤이 테이블 뷰 Offset의 끝에 가게 되면 다음 페이지를 호출 + if offsetY > (contentHeight - height) { + if !isPaging { + isPaging = true + requestNextPage.accept(()) + } + } + } +} + // MARK: Top Container fileprivate class WorkerMainTopContainer: UIView { diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/WorkerRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/WorkerRecruitmentPostBoardVM.swift index 4b29064a..2a04d398 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/WorkerRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/WorkerRecruitmentPostBoardVM.swift @@ -12,45 +12,154 @@ import RxCocoa import RxSwift import Entity import DSKit +import UseCaseInterface public protocol WorkerRecruitmentPostBoardVMable: DefaultAlertOutputable { - var ongoingPostCardVO: Driver<[WorkerEmployCardVO]>? { get } - var viewWillAppear: PublishRelay { get } - var locationTtitleText: PublishRelay { get } + /// 다음 페이지를 요청합니다. + var requestNextPage: PublishRelay { get } + /// ViewDidLoad + var viewDidLoad: PublishRelay { get } - func createCellVM(postId: String, vo: WorkerEmployCardVO) -> WorkerEmployCardViewModelable + + /// 페이지요청에 대한 결과를 전달합니다. + var postBoardData: Driver<[WorkerEmployCardViewModelable]>? { get } + /// 요양보호사 위치 정보를 전달합니다. + var workerLocationTitleText: Driver? { get } } - public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { + // Output + public var postBoardData: Driver<[WorkerEmployCardViewModelable]>? + public var alert: Driver? + public var workerLocationTitleText: Driver? + + + + // Input + public var viewDidLoad: PublishRelay = .init() + public var requestNextPage: PublishRelay = .init() + + + + // Init weak var coordinator: WorkerRecruitmentBoardCoordinatable? + let recruitmentPostUseCase: RecruitmentPostUseCase - public var ongoingPostCardVO: RxCocoa.Driver<[Entity.WorkerEmployCardVO]>? - public var alert: RxCocoa.Driver? + // Paging + /// 값이 nil이라면 요청을 보내지 않습니다. + var nextPagingRequest: PostPagingRequestForWorker? + /// 가장최신의 데이터를 홀드, 다음 요청시 해당데이터에 새로운 데이터를 더해서 방출 + private let currentPostVO: BehaviorRelay<[RecruitmentPostForWorkerVO]> = .init(value: []) - public var viewWillAppear: RxRelay.PublishRelay = .init() - public var locationTtitleText: RxRelay.PublishRelay = .init() + // Observable + let dispostBag = DisposeBag() - public init(coordinator: WorkerRecruitmentBoardCoordinatable) { - + public init( + coordinator: WorkerRecruitmentBoardCoordinatable, + recruitmentPostUseCase: RecruitmentPostUseCase + ) + { self.coordinator = coordinator + self.recruitmentPostUseCase = recruitmentPostUseCase + self.nextPagingRequest = .native(nextPageId: nil) - let requestOngoingPostResult = viewWillAppear - .flatMap { [unowned self] _ in - publishOngoingPostMocks() + // 상단 위치정보 + workerLocationTitleText = viewDidLoad + .compactMap { [weak self] _ in + self?.fetchWorkerLocation() + } + .asDriver(onErrorJustReturn: "반갑습니다.") + + + let postPageReqeustResult = Observable + .merge( + viewDidLoad.asObservable(), + requestNextPage.asObservable() + ) + .compactMap { [weak self] _ in + // 요청이 없는 경우 요청을 보내지 않는다. + // ThirdPatry에서도 불러올 데이터가 없는 경우입니다. + self?.nextPagingRequest + } + .share() + .flatMap { [recruitmentPostUseCase] request in + recruitmentPostUseCase + .getPostListForWorker( + request: request, + postCount: 10 + ) } .share() + - let requestOngoingPostSuccess = requestOngoingPostResult.compactMap { $0.value } - let requestOngoingPostFailure = requestOngoingPostResult.compactMap { $0.error } + let requestPostListSuccess = postPageReqeustResult.compactMap { $0.value } + let requestPostListFailure = postPageReqeustResult.compactMap { $0.error } - ongoingPostCardVO = requestOngoingPostSuccess.asDriver(onErrorJustReturn: []) + postBoardData = Observable + .zip( + currentPostVO, + requestPostListSuccess + ) + .compactMap { [weak self] (prevPostList, fetchedData) -> [WorkerEmployCardViewModelable]? in + + guard let self else { return nil } + + // 다음 요청설정 + var nextRequest: PostPagingRequestForWorker? + if let prevRequest = self.nextPagingRequest { + + if let nextPageId = fetchedData.nextPageId { + // 다음값이 있는 경우 + switch prevRequest { + case .native: + nextRequest = .native(nextPageId: nextPageId) + case .thirdParty: + nextRequest = .thirdParty(nextPageId: nextPageId) + } + } else { + // 다음값이 없는 경우 + switch prevRequest { + case .native: + nextRequest = .thirdParty(nextPageId: nil) + case .thirdParty: + // 페이징 종료 + nextRequest = nil + } + } + } + self.nextPagingRequest = nextRequest + + // 화면에 표시할 전체리스트 도출 + let fetchedPosts = fetchedData.posts + var mergedPosts = currentPostVO.value + mergedPosts.append(contentsOf: fetchedPosts) + + // 최근값 업데이트 + self.currentPostVO.accept(mergedPosts) + + // ViewModel 생성 + let viewModels = mergedPosts.map { vo in + + let cardVO: WorkerEmployCardVO = .create(vo: vo) + + let vm: WorkerEmployCardVM = .init( + postId: vo.postId, + vo: cardVO, + coordinator: self.coordinator + ) + + return vm + } + + return viewModels + } + .asDriver(onErrorJustReturn: []) - alert = requestOngoingPostFailure + alert = requestPostListFailure .map { error in - DefaultAlertContentVO( + return DefaultAlertContentVO( title: "시스템 오류", message: error.message ) @@ -58,16 +167,9 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { .asDriver(onErrorJustReturn: .default) } - public func createCellVM(postId: String, vo: Entity.WorkerEmployCardVO) -> any DSKit.WorkerEmployCardViewModelable { - WorkerEmployCardVM( - postId: postId, - vo: vo, - coordinator: coordinator - ) - } - - func publishOngoingPostMocks() -> Single> { - return .just(.success((0...10).map { _ in WorkerEmployCardVO.mock })) + /// Test + func fetchWorkerLocation() -> String { + "서울시 영등포구" } } From d91b0afe6b49025bf954bdc260b558f634a18002 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Mon, 19 Aug 2024 11:01:45 +0900 Subject: [PATCH 19/21] =?UTF-8?q?[IDLE-163]=20WorkerStaticPostBoardVMable?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 지원한 공고, 즐겨찾기한 공고에 사용됩니다. --- .../Post/RecruitmentPostListForWorkerVO.swift | 18 +++++ .../CenterRecruitmentPostBoardVM.swift | 0 .../LikedAndApplied/AppliedBoardVC.swift | 43 ----------- .../LikedAndApplied/StarredAndAppliedVC.swift | 48 ++++++------ .../WorkerStaticPostBoardVC.swift} | 57 +++++++------- .../WorkerRecruitmentPostBoardVC.swift | 0 .../RecruitmentPost/AppliedPostBoardVM.swift | 76 +++++++++++++++++++ .../WorkerRecruitmentPostBoardVM.swift | 0 .../RecruitmentPost/StarredPostBoardVM.swift | 75 ++++++++++++++++++ 9 files changed, 218 insertions(+), 99 deletions(-) rename project/Projects/Presentation/Feature/Center/Sources/{View/RecruitmentPost/Board/Post => ViewModel/RecruitmentPost}/CenterRecruitmentPostBoardVM.swift (100%) delete mode 100644 project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/AppliedBoardVC.swift rename project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/{StarredBoardVC.swift => SubVC/WorkerStaticPostBoardVC.swift} (68%) rename project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/{ => OnGoingPostBoard}/WorkerRecruitmentPostBoardVC.swift (100%) create mode 100644 project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift rename project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/{ => OnGoingPostBoard}/WorkerRecruitmentPostBoardVM.swift (100%) create mode 100644 project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift diff --git a/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift b/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift index 9d833e48..6ffcb8c1 100644 --- a/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift +++ b/project/Projects/Domain/Entity/VO/Post/RecruitmentPostListForWorkerVO.swift @@ -75,4 +75,22 @@ public struct RecruitmentPostForWorkerVO { self.payAmount = payAmount self.distanceFromWorkPlace = distanceFromWorkPlace } + + public static let mock = RecruitmentPostForWorkerVO( + postId: "test-post-id", + workDays: [.mon, .wed, .fri], + startTime: "09:00", + endTime: "17:00", + roadNameAddress: "서울시 영등포구 여등포동", + lotNumberAddress: "서울시 영등포구 여등포동", + gender: .female, + age: 54, + cardGrade: .three, + isExperiencePreferred: true, + applyDeadlineType: .specificDate, + applyDeadlineDate: Calendar.current.date(byAdding: .day, value: 7, to: Date()), + payType: .hourly, + payAmount: "15,000", + distanceFromWorkPlace: "2.5km" + ) } diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/CenterRecruitmentPostBoardVM.swift similarity index 100% rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVM.swift rename to project/Projects/Presentation/Feature/Center/Sources/ViewModel/RecruitmentPost/CenterRecruitmentPostBoardVM.swift diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/AppliedBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/AppliedBoardVC.swift deleted file mode 100644 index d7c6c05d..00000000 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/AppliedBoardVC.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// AppliedBoardVC.swift -// WorkerFeature -// -// Created by choijunios on 8/16/24. -// - -import UIKit -import BaseFeature -import PresentationCore -import RxCocoa -import RxSwift -import Entity -import DSKit - -public class AppliedBoardVC: BaseViewController { - - // Init - - // View - - // Observable - private let disposeBag = DisposeBag() - - public init() { - super.init(nibName: nil, bundle: nil) - } - - public required init?(coder: NSCoder) { fatalError() } - - private func setAppearance() { - - } - - private func setLayout() { - - } - - private func setObservable() { - - } -} - diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift index 03d68bd8..6d831f5c 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift @@ -14,17 +14,11 @@ import Entity import DSKit import CenterFeature -public protocol AppliedPostViewModelable { +protocol WorkerStaticPostBoardVMable { + var postBoardData: Driver<[WorkerEmployCardViewModelable]>? { get } + var postViewWillAppear: PublishRelay { get } -} - -public protocol StarredPostViewModelable { - - -} - -public protocol StarredAndAppliedVVMable: AppliedPostViewModelable & StarredPostViewModelable { var alert: Driver? { get } } @@ -52,12 +46,10 @@ public class StarredAndAppliedVC: BaseViewController { } } - var viewModel: StarredAndAppliedVVMable? - private var currentState: TabBarState = .applied - private let viewControllerDict: [TabBarState: UIViewController] = [ - .applied : UIViewController(), - .starred : UIViewController() + private let viewControllerDict: [TabBarState: WorkerStaticPostBoardVC] = [ + .applied : WorkerStaticPostBoardVC(), + .starred : WorkerStaticPostBoardVC() ] // Init @@ -174,19 +166,23 @@ public class StarredAndAppliedVC: BaseViewController { ]) } - public func bind(viewModel: StarredAndAppliedVVMable) { - - self.viewModel = viewModel + func bind( + appliedPostVM: WorkerStaticPostBoardVMable, + starredPostVM: WorkerStaticPostBoardVMable + ) { -// (viewControllerDict[.applied] as? AppliedBoardVC)?.bind(viewModel: viewModel) -// (viewControllerDict[.closedPost] as? StarredBoardVC)?.bind(viewModel: viewModel) -// -// viewModel -// .alert? -// .drive(onNext: { [weak self] alertVO in -// self?.showAlert(vo: alertVO) -// }) -// .disposed(by: disposeBag) + viewControllerDict[.applied]?.bind(viewModel: appliedPostVM) + viewControllerDict[.applied]?.bind(viewModel: starredPostVM) + + Observable + .merge( + appliedPostVM.alert?.asObservable() ?? .empty(), + starredPostVM.alert?.asObservable() ?? .empty() + ) + .subscribe(onNext: { [weak self] alertVO in + self?.showAlert(vo: alertVO) + }) + .disposed(by: disposeBag) } } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerStaticPostBoardVC.swift similarity index 68% rename from project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredBoardVC.swift rename to project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerStaticPostBoardVC.swift index 630b0b51..6fe0db72 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/SubVC/WorkerStaticPostBoardVC.swift @@ -1,5 +1,5 @@ // -// StarredBoardVC.swift +// WorkerStaticPostBoardVC.swift // WorkerFeature // // Created by choijunios on 8/16/24. @@ -13,11 +13,11 @@ import RxSwift import Entity import DSKit -public class StarredBoardVC: BaseViewController { +public class WorkerStaticPostBoardVC: BaseViewController { typealias Cell = WorkerEmployCardCell -// var viewModel: StarredPostViewModelable? + var viewModel: WorkerStaticPostBoardVMable? // View let postTableView: UITableView = { @@ -29,7 +29,7 @@ public class StarredBoardVC: BaseViewController { let tableHeader = BoardSortigHeaderView() - let starredPostCardVO: BehaviorRelay<[WorkerEmployCardVO]> = .init(value: [.mock]) + let postViewModels: BehaviorRelay<[WorkerEmployCardViewModelable]> = .init(value: []) // Observable private let disposeBag = DisposeBag() @@ -87,44 +87,41 @@ public class StarredBoardVC: BaseViewController { } - public func bind(viewModel: StarredPostViewModelable) { + func bind(viewModel: WorkerStaticPostBoardVMable) { -// self.viewModel = viewModel -// -// // Output -// viewModel -// .starredPostCardVO? -// .drive(onNext: { [weak self] vos in -// guard let self else { return } -// starredPostCardVO.accept(vos) -// postTableView.reloadData() -// }) -// .disposed(by: disposeBag) -// -// // Input -// rx.viewWillAppear -// .map { _ in } -// .bind(to: viewModel.requestAppliedPost) -// .disposed(by: disposeBag) + self.viewModel = viewModel + + // Output + viewModel + .postBoardData? + .drive(onNext: { [weak self] viewModels in + guard let self else { return } + self.postViewModels.accept(viewModels) + self.postTableView.reloadData() + }) + .disposed(by: disposeBag) + + // Input + rx.viewWillAppear + .map { _ in } + .bind(to: viewModel.postViewWillAppear) + .disposed(by: disposeBag) } } -extension StarredBoardVC: UITableViewDataSource, UITableViewDelegate { +extension WorkerStaticPostBoardVC: UITableViewDataSource, UITableViewDelegate { public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - starredPostCardVO.value.count + postViewModels.value.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell cell.selectionStyle = .none -// -// if let viewModel = self.viewModel { -// let vo = starredPostCardVO.value[indexPath.row] -// let vm = viewModel.createCellVM(vo: vo) -// cell.bind(viewModel: vm) -// } + + let vm = postViewModels.value[indexPath.row] + cell.bind(viewModel: vm) return cell } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift similarity index 100% rename from project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/WorkerRecruitmentPostBoardVC.swift rename to project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift new file mode 100644 index 00000000..21aa7b8a --- /dev/null +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift @@ -0,0 +1,76 @@ +// +// AppliedPostBoardVM.swift +// WorkerFeature +// +// Created by choijunios on 8/19/24. +// + +import UIKit +import BaseFeature +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit +import UseCaseInterface + + +public class AppliedPostBoardVM: WorkerStaticPostBoardVMable { + + var postViewWillAppear: RxRelay.PublishRelay = .init() + + var postBoardData: RxCocoa.Driver<[any DSKit.WorkerEmployCardViewModelable]>? + var alert: RxCocoa.Driver? + + // Init + weak var coordinator: WorkerRecruitmentBoardCoordinatable? + let recruitmentPostUseCase: RecruitmentPostUseCase + + public init(recruitmentPostUseCase: RecruitmentPostUseCase) { + self.recruitmentPostUseCase = recruitmentPostUseCase + + let requestPostResult = postViewWillAppear + .flatMap { [unowned self] _ in + self.publishAppliedPostMocks() + } + .share() + + let requestPostSuccess = requestPostResult.compactMap { $0.value } + let requestPostFailure = requestPostResult.compactMap { $0.error } + + postBoardData = requestPostSuccess + .map { postForWorkerVos in + + // ViewModel 생성 + let viewModels = postForWorkerVos.map { vo in + + let cardVO: WorkerEmployCardVO = .create(vo: vo) + + let vm: WorkerEmployCardVM = .init( + postId: vo.postId, + vo: cardVO, + coordinator: self.coordinator + ) + + return vm + } + + return viewModels + } + .asDriver(onErrorJustReturn: []) + + alert = requestPostFailure + .map { error in + DefaultAlertContentVO( + title: "지원한 공고 불러오기 오류", + message: error.message + ) + } + .asDriver(onErrorJustReturn: .default) + } + + + func publishAppliedPostMocks() -> Single> { + return .just(.success((0..<10).map { _ in .mock })) + } +} diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/WorkerRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift similarity index 100% rename from project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/WorkerRecruitmentPostBoardVM.swift rename to project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift new file mode 100644 index 00000000..9e464e8b --- /dev/null +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift @@ -0,0 +1,75 @@ +// +// StarredPostBoardVM.swift +// WorkerFeature +// +// Created by choijunios on 8/19/24. +// + +import UIKit +import BaseFeature +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit +import UseCaseInterface + +public class StarredPostBoardVM: WorkerStaticPostBoardVMable { + + var postViewWillAppear: RxRelay.PublishRelay = .init() + + var postBoardData: RxCocoa.Driver<[any DSKit.WorkerEmployCardViewModelable]>? + var alert: RxCocoa.Driver? + + // Init + weak var coordinator: WorkerRecruitmentBoardCoordinatable? + let recruitmentPostUseCase: RecruitmentPostUseCase + + public init(recruitmentPostUseCase: RecruitmentPostUseCase) { + self.recruitmentPostUseCase = recruitmentPostUseCase + + let requestPostResult = postViewWillAppear + .flatMap { [unowned self] _ in + self.publishStarredPostMocks() + } + .share() + + let requestPostSuccess = requestPostResult.compactMap { $0.value } + let requestPostFailure = requestPostResult.compactMap { $0.error } + + postBoardData = requestPostSuccess + .map { postForWorkerVos in + + // ViewModel 생성 + let viewModels = postForWorkerVos.map { vo in + + let cardVO: WorkerEmployCardVO = .create(vo: vo) + + let vm: WorkerEmployCardVM = .init( + postId: vo.postId, + vo: cardVO, + coordinator: self.coordinator + ) + + return vm + } + + return viewModels + } + .asDriver(onErrorJustReturn: []) + + alert = requestPostFailure + .map { error in + DefaultAlertContentVO( + title: "즐겨찾기한 공고 불러오기 오류", + message: error.message + ) + } + .asDriver(onErrorJustReturn: .default) + } + + + func publishStarredPostMocks() -> Single> { + return .just(.success((0..<10).map { _ in .mock })) + } +} From 763185164bb02ed4b6ee60024c3b78c97e73624b Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Mon, 19 Aug 2024 11:25:04 +0900 Subject: [PATCH 20/21] =?UTF-8?q?[IDLE-163]=20=EC=B0=9C=ED=95=9C=EA=B3=B5?= =?UTF-8?q?=EA=B3=A0=EB=B0=8F=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=20=ED=99=94=EB=A9=B4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Main/Worker/WorkerMainCoordinator.swift | 8 +- .../Post/Worker/WorkerEmployCardCell.swift | 5 + .../AppliedAndLikedBoardCoordinator.swift | 93 +++++++++++++++++++ .../{ => Setting}/SettingCoordinator.swift | 0 .../LikedAndApplied/StarredAndAppliedVC.swift | 6 +- .../RecruitmentPost/AppliedPostBoardVM.swift | 78 +++++++++++++++- .../WorkerRecruitmentPostBoardVM.swift | 4 +- .../RecruitmentPost/StarredPostBoardVM.swift | 78 +++++++++++++++- 8 files changed, 257 insertions(+), 15 deletions(-) create mode 100644 project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/AppliedAndLiked/AppliedAndLikedBoardCoordinator.swift rename project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/{ => Setting}/SettingCoordinator.swift (100%) diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift index a7fb847d..2716a55d 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift @@ -79,8 +79,12 @@ class WorkerMainCoordinator: ParentCoordinator { ) ) case .preferredPost: - coordinator = SettingCoordinator( - navigationController: navigationController + coordinator = AppliedAndLikedBoardCoordinator( + depedency: .init( + navigationController: navigationController, + centerProfileUseCase: injector.resolve(CenterProfileUseCase.self), + recruitmentPostUseCase: injector.resolve(RecruitmentPostUseCase.self) + ) ) case .setting: coordinator = SettingCoordinator( diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift index d071a738..4277bc3a 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift @@ -19,6 +19,11 @@ public struct ApplicationInfo { isApplied: true, applicationDateText: "2024. 10. 22" ) + + public init(isApplied: Bool, applicationDateText: String) { + self.isApplied = isApplied + self.applicationDateText = applicationDateText + } } public protocol WorkerEmployCardViewModelable { diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/AppliedAndLiked/AppliedAndLikedBoardCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/AppliedAndLiked/AppliedAndLikedBoardCoordinator.swift new file mode 100644 index 00000000..5f6c552f --- /dev/null +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/AppliedAndLiked/AppliedAndLikedBoardCoordinator.swift @@ -0,0 +1,93 @@ +// +// AppliedAndLikedBoardCoordinator.swift +// RootFeature +// +// Created by choijunios on 8/19/24. +// + +import UIKit +import WorkerFeature +import BaseFeature +import CenterFeature +import PresentationCore +import UseCaseInterface + +public class AppliedAndLikedBoardCoordinator: WorkerRecruitmentBoardCoordinatable { + + public struct Dependency { + let navigationController: UINavigationController + let centerProfileUseCase: CenterProfileUseCase + let recruitmentPostUseCase: RecruitmentPostUseCase + + public init(navigationController: UINavigationController, centerProfileUseCase: CenterProfileUseCase, recruitmentPostUseCase: RecruitmentPostUseCase) { + self.navigationController = navigationController + self.centerProfileUseCase = centerProfileUseCase + self.recruitmentPostUseCase = recruitmentPostUseCase + } + } + + public var childCoordinators: [any PresentationCore.Coordinator] = [] + + public weak var viewControllerRef: UIViewController? + + public var navigationController: UINavigationController + + weak var parent: ParentCoordinator? + + let centerProfileUseCase: CenterProfileUseCase + let recruitmentPostUseCase: RecruitmentPostUseCase + + public init(depedency: Dependency) { + self.navigationController = depedency.navigationController + self.centerProfileUseCase = depedency.centerProfileUseCase + self.recruitmentPostUseCase = depedency.recruitmentPostUseCase + } + + public func start() { + let vc = StarredAndAppliedVC() + let appliedVM = AppliedPostBoardVM( + recruitmentPostUseCase: recruitmentPostUseCase + ) + let starredVM = StarredPostBoardVM( + recruitmentPostUseCase: recruitmentPostUseCase + ) + vc.bind( + appliedPostVM: appliedVM, + starredPostVM: starredVM + ) + viewControllerRef = vc + navigationController.pushViewController(vc, animated: false) + } + + public func coordinatorDidFinish() { + popViewController() + parent?.removeChildCoordinator(self) + } +} + +extension AppliedAndLikedBoardCoordinator { + public func showPostDetail(postId: String) { + let coodinator = PostDetailForWorkerCoodinator( + dependency: .init( + postId: postId, + parent: self, + navigationController: navigationController, + recruitmentPostUseCase: recruitmentPostUseCase + ) + ) + addChildCoordinator(coodinator) + coodinator.start() + } + public func showCenterProfile(centerId: String) { + let coordinator = CenterProfileCoordinator( + dependency: .init( + mode: .otherProfile(id: centerId), + profileUseCase: centerProfileUseCase, + navigationController: navigationController + ) + ) + addChildCoordinator(coordinator) + coordinator.parent = self + coordinator.start() + } +} diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/SettingCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/Setting/SettingCoordinator.swift similarity index 100% rename from project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/SettingCoordinator.swift rename to project/Projects/Presentation/Feature/Root/Sources/Screen/Worker/Coordinator/Setting/SettingCoordinator.swift diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift index 6d831f5c..70c620a3 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/LikedAndApplied/StarredAndAppliedVC.swift @@ -14,7 +14,7 @@ import Entity import DSKit import CenterFeature -protocol WorkerStaticPostBoardVMable { +public protocol WorkerStaticPostBoardVMable { var postBoardData: Driver<[WorkerEmployCardViewModelable]>? { get } var postViewWillAppear: PublishRelay { get } @@ -166,13 +166,13 @@ public class StarredAndAppliedVC: BaseViewController { ]) } - func bind( + public func bind( appliedPostVM: WorkerStaticPostBoardVMable, starredPostVM: WorkerStaticPostBoardVMable ) { viewControllerDict[.applied]?.bind(viewModel: appliedPostVM) - viewControllerDict[.applied]?.bind(viewModel: starredPostVM) + viewControllerDict[.starred]?.bind(viewModel: starredPostVM) Observable .merge( 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 21aa7b8a..0cd453e6 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/AppliedPostBoardVM.swift @@ -17,10 +17,10 @@ import UseCaseInterface public class AppliedPostBoardVM: WorkerStaticPostBoardVMable { - var postViewWillAppear: RxRelay.PublishRelay = .init() + public var postViewWillAppear: RxRelay.PublishRelay = .init() - var postBoardData: RxCocoa.Driver<[any DSKit.WorkerEmployCardViewModelable]>? - var alert: RxCocoa.Driver? + public var postBoardData: RxCocoa.Driver<[any DSKit.WorkerEmployCardViewModelable]>? + public var alert: RxCocoa.Driver? // Init weak var coordinator: WorkerRecruitmentBoardCoordinatable? @@ -46,7 +46,7 @@ public class AppliedPostBoardVM: WorkerStaticPostBoardVMable { let cardVO: WorkerEmployCardVO = .create(vo: vo) - let vm: WorkerEmployCardVM = .init( + let vm: AppliedWorkerEmployCardVM = .init( postId: vo.postId, vo: cardVO, coordinator: self.coordinator @@ -74,3 +74,73 @@ public class AppliedPostBoardVM: WorkerStaticPostBoardVMable { return .just(.success((0..<10).map { _ in .mock })) } } + +class AppliedWorkerEmployCardVM: WorkerEmployCardViewModelable { + + weak var coordinator: WorkerRecruitmentBoardCoordinatable? + + // Init + let postId: String + + public var renderObject: RxCocoa.Driver? + public var applicationInformation: RxCocoa.Driver? + + public var cardClicked: RxRelay.PublishRelay = .init() + public var applyButtonClicked: RxRelay.PublishRelay = .init() + public var starButtonClicked: RxRelay.PublishRelay = .init() + + let disposeBag = DisposeBag() + + public init + ( + postId: String, + vo: WorkerEmployCardVO, + coordinator: WorkerRecruitmentBoardCoordinatable? = nil + ) + { + self.postId = postId + self.coordinator = coordinator + + // MARK: 지원여부 + let applicationInformation: BehaviorRelay = .init( + value: .init( + isApplied: true, + applicationDateText: "날자정보 (미구현)" + ) + ) + self.applicationInformation = applicationInformation.asDriver() + + // MARK: Card RenderObject + let workerEmployCardRO: BehaviorRelay = .init(value: .mock) + renderObject = workerEmployCardRO.asDriver(onErrorJustReturn: .mock) + + workerEmployCardRO.accept(WorkerEmployCardRO.create(vo: vo)) + + // MARK: 버튼 처리 + applyButtonClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + + // 지원하기 버튼 눌림 + }) + .disposed(by: disposeBag) + + cardClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + + coordinator?.showPostDetail( + postId: postId + ) + }) + .disposed(by: disposeBag) + + starButtonClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + + // 즐겨찾기 버튼눌림 + }) + .disposed(by: disposeBag) + } +} diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift index 2a04d398..9f8a5dab 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 @@ -144,7 +144,7 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { let cardVO: WorkerEmployCardVO = .create(vo: vo) - let vm: WorkerEmployCardVM = .init( + let vm: OngoindWorkerEmployCardVM = .init( postId: vo.postId, vo: cardVO, coordinator: self.coordinator @@ -173,7 +173,7 @@ public class WorkerRecruitmentPostBoardVM: WorkerRecruitmentPostBoardVMable { } } -public class WorkerEmployCardVM: WorkerEmployCardViewModelable { +class OngoindWorkerEmployCardVM: WorkerEmployCardViewModelable { weak var coordinator: WorkerRecruitmentBoardCoordinatable? 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 9e464e8b..4070ff96 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/StarredPostBoardVM.swift @@ -16,10 +16,10 @@ import UseCaseInterface public class StarredPostBoardVM: WorkerStaticPostBoardVMable { - var postViewWillAppear: RxRelay.PublishRelay = .init() + public var postViewWillAppear: RxRelay.PublishRelay = .init() - var postBoardData: RxCocoa.Driver<[any DSKit.WorkerEmployCardViewModelable]>? - var alert: RxCocoa.Driver? + public var postBoardData: RxCocoa.Driver<[any DSKit.WorkerEmployCardViewModelable]>? + public var alert: RxCocoa.Driver? // Init weak var coordinator: WorkerRecruitmentBoardCoordinatable? @@ -45,7 +45,7 @@ public class StarredPostBoardVM: WorkerStaticPostBoardVMable { let cardVO: WorkerEmployCardVO = .create(vo: vo) - let vm: WorkerEmployCardVM = .init( + let vm: StarredWorkerEmployCardVM = .init( postId: vo.postId, vo: cardVO, coordinator: self.coordinator @@ -73,3 +73,73 @@ public class StarredPostBoardVM: WorkerStaticPostBoardVMable { return .just(.success((0..<10).map { _ in .mock })) } } + +class StarredWorkerEmployCardVM: WorkerEmployCardViewModelable { + + weak var coordinator: WorkerRecruitmentBoardCoordinatable? + + // Init + let postId: String + + public var renderObject: RxCocoa.Driver? + public var applicationInformation: RxCocoa.Driver? + + public var cardClicked: RxRelay.PublishRelay = .init() + public var applyButtonClicked: RxRelay.PublishRelay = .init() + public var starButtonClicked: RxRelay.PublishRelay = .init() + + let disposeBag = DisposeBag() + + public init + ( + postId: String, + vo: WorkerEmployCardVO, + coordinator: WorkerRecruitmentBoardCoordinatable? = nil + ) + { + self.postId = postId + self.coordinator = coordinator + + // MARK: 지원여부 + let applicationInformation: BehaviorRelay = .init( + value: .init( + isApplied: false, + applicationDateText: "" + ) + ) + self.applicationInformation = applicationInformation.asDriver() + + // MARK: Card RenderObject + let workerEmployCardRO: BehaviorRelay = .init(value: .mock) + renderObject = workerEmployCardRO.asDriver(onErrorJustReturn: .mock) + + workerEmployCardRO.accept(WorkerEmployCardRO.create(vo: vo)) + + // MARK: 버튼 처리 + applyButtonClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + + // 지원하기 버튼 눌림 + }) + .disposed(by: disposeBag) + + cardClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + + coordinator?.showPostDetail( + postId: postId + ) + }) + .disposed(by: disposeBag) + + starButtonClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + + // 즐겨찾기 버튼눌림 + }) + .disposed(by: disposeBag) + } +} From 717112e92547387d2e4ac9a90d1fc98bbcd4c17e Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Mon, 19 Aug 2024 14:02:11 +0900 Subject: [PATCH 21/21] =?UTF-8?q?[IDLE-000]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DSKit/ExampleApp/Sources/ViewController3.swift | 1 - .../Feature/Base/ExampleApp/Sources/SceneDelegate.swift | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController3.swift b/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController3.swift index 736e6c9b..9cc654d7 100644 --- a/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController3.swift +++ b/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController3.swift @@ -54,7 +54,6 @@ extension ViewController3: UITableViewDelegate, UITableViewDataSource { guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: WorkerEmployCardCell.self), for: indexPath) as? WorkerEmployCardCell else { fatalError("Unable to dequeue WorkerEmployCard") } - cell.bind(vo: .mock) return cell } diff --git a/project/Projects/Presentation/Feature/Base/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Base/ExampleApp/Sources/SceneDelegate.swift index 2c9f771e..7c3560ed 100644 --- a/project/Projects/Presentation/Feature/Base/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/Base/ExampleApp/Sources/SceneDelegate.swift @@ -16,12 +16,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let windowScene = scene as? UIWindowScene else { return } - let vc = PostDetailVC() - window = UIWindow(windowScene: windowScene) - window?.rootViewController = vc - - vc.bind() + window?.rootViewController = UIViewController() window?.makeKeyAndVisible() }