From 33f3827e45aa57dc3dfa0138c7bedc904fd7310d Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Mon, 12 Aug 2024 10:47:33 +0900 Subject: [PATCH 01/20] =?UTF-8?q?[IDLE-141]=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=EA=B0=80=20Prefix=EB=A1=9C=20=EB=B0=B0=EC=B9=98=EB=90=9C=20?= =?UTF-8?q?=EB=9D=BC=EB=B2=A8=20=EB=B2=84=ED=8A=BC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post_check.imageset/Contents.json | 15 +++ .../post_check.imageset/post_check.svg | 10 ++ .../post_edit.imageset/Contents.json | 15 +++ .../post_edit.imageset/post_edit.svg | 10 ++ .../CommonUI/Button/ImagePrefixButton.swift | 91 +++++++++++++++++++ .../Card/Post/Center/CenterEmployCard.swift | 77 ++++++++++++++++ .../Post/{ => Worker}/WorkerEmployCard.swift | 0 .../{ => Worker}/WorkerEmployCardCell.swift | 0 8 files changed, 218 insertions(+) create mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_check.imageset/Contents.json create mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_check.imageset/post_check.svg create mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_edit.imageset/Contents.json create mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_edit.imageset/post_edit.svg create mode 100644 project/Projects/Presentation/DSKit/Sources/CommonUI/Button/ImagePrefixButton.swift create mode 100644 project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift rename project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/{ => Worker}/WorkerEmployCard.swift (100%) rename project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/{ => Worker}/WorkerEmployCardCell.swift (100%) diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_check.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_check.imageset/Contents.json new file mode 100644 index 00000000..f3c70e18 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_check.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "post_check.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_check.imageset/post_check.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_check.imageset/post_check.svg new file mode 100644 index 00000000..6f023a6a --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_check.imageset/post_check.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_edit.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_edit.imageset/Contents.json new file mode 100644 index 00000000..88de3996 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_edit.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "post_edit.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_edit.imageset/post_edit.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_edit.imageset/post_edit.svg new file mode 100644 index 00000000..5223407a --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/post_edit.imageset/post_edit.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Button/ImagePrefixButton.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Button/ImagePrefixButton.swift new file mode 100644 index 00000000..fe5da13f --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Button/ImagePrefixButton.swift @@ -0,0 +1,91 @@ +// +// ImagePrefixButton.swift +// DSKit +// +// Created by choijunios on 8/12/24. +// + +import UIKit +import RxSwift +import RxCocoa + +public class ImagePrefixButton: TappableUIView { + + let icon: UIImageView = { + let icon = UIImageView() + icon.contentMode = .scaleAspectFit + return icon + }() + + let label: IdleLabel = { + let label = IdleLabel(typography: .Body3) + return label + }() + + private let disposeBag = DisposeBag() + + public init(iconImage: UIImage) { + super.init() + + icon.image = iconImage + + setAppearance() + setLayout() + setObservable() + } + + public required init?(coder: NSCoder) { fatalError() } + + private func setAppearance() { + + } + + private func setLayout() { + let mainStack = HStack([ + icon, + label + ], spacing: 2, alignment: .center) + + NSLayoutConstraint.activate([ + icon.widthAnchor.constraint(equalToConstant: 24), + icon.heightAnchor.constraint(equalTo: icon.widthAnchor), + ]) + + [ + mainStack + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(mainStack) + } + + NSLayoutConstraint.activate([ + mainStack.topAnchor.constraint(equalTo: self.topAnchor), + mainStack.leftAnchor.constraint(equalTo: self.leftAnchor), + mainStack.rightAnchor.constraint(equalTo: self.rightAnchor), + mainStack.bottomAnchor.constraint(equalTo: self.bottomAnchor), + ]) + } + + private func setObservable() { + self.rx.tap + .subscribe { [weak self] _ in + self?.alpha = 0.5 + UIView.animate(withDuration: 0.35) { [weak self] in + self?.alpha = 1.0 + } + } + .disposed(by: disposeBag) + } +} + +@available(iOS 17.0, *) +#Preview("Preview", traits: .defaultLayout) { + + let btn = ImagePrefixButton( + iconImage: DSKitAsset.Icons.editPhoto.image + ) + btn.label.textString = "공고 수정" + btn.tintColor = .black + return btn +} + diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift new file mode 100644 index 00000000..9fd8f3be --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift @@ -0,0 +1,77 @@ +// +// CenterEmployCard.swift +// DSKit +// +// Created by choijunios on 8/12/24. +// + +import UIKit +import RxSwift +import RxCocoa +import Entity + +public class CenterEmployCard: TappableUIView { + + // Init + + // View + + // Row1 + let durationLabel: IdleLabel = { + let label = IdleLabel(typography: .Body3) + label.textColor = DSKitAsset.Colors.gray300.color + return label + }() + + // Row2 + let poseTitleLabel: IdleLabel = { + let label = IdleLabel(typography: .Subtitle2) + label.textColor = DSKitAsset.Colors.gray300.color + return label + }() + + // Row3 + let nameLabel: IdleLabel = { + let label = IdleLabel(typography: .Body2) + label.textColor = DSKitAsset.Colors.gray500.color + return label + }() + let informationLabel: IdleLabel = { + let label = IdleLabel(typography: .Body2) + label.textColor = DSKitAsset.Colors.gray500.color + return label + }() + + // Row4 + let checkApplyButton: IdlePrimaryCardButton = { + let btn = IdlePrimaryCardButton(level: .medium) + btn.label.textString = "" + return btn + }() + + // Row5 + + + + // Observable + private let disposeBag = DisposeBag() + + public override init() { + super.init() + } + + public required init?(coder: NSCoder) { fatalError() } + + private func setAppearance() { + + } + + private func setLayout() { + + } + + private func setObservable() { + + } +} + diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/WorkerEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift similarity index 100% rename from project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/WorkerEmployCard.swift rename to project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCard.swift diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/WorkerEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift similarity index 100% rename from project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/WorkerEmployCardCell.swift rename to project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/WorkerEmployCardCell.swift From 6f0d0551a717bceb467d96b3f6aa79d582c83a57 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Mon, 12 Aug 2024 11:41:58 +0900 Subject: [PATCH 02/20] =?UTF-8?q?[IDLE-141]=20CenterEmployCard=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Card/Post/Center/CenterEmployCard.swift | 201 +++++++++++++++++- 1 file changed, 192 insertions(+), 9 deletions(-) diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift index 9fd8f3be..f3f5b646 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift @@ -10,6 +10,53 @@ import RxSwift import RxCocoa import Entity +public struct CenterEmployCardRO { + public let startDay: String + public let endDay: String + public let postTitle: String + public let nameText: String + public let careGradeText: String + public let ageText: String + public let genderText: String + public let applicantCount: Int + + public init(startDay: String, endDay: String, postTitle: String, nameText: String, careGradeText: String, ageText: String, genderText: String, applicantCount: Int) { + self.startDay = startDay + self.endDay = endDay + self.postTitle = postTitle + self.nameText = nameText + self.careGradeText = careGradeText + self.ageText = ageText + self.genderText = genderText + self.applicantCount = applicantCount + } + + static let mock: CenterEmployCardRO = .init( + startDay: "2024. 07. 10", + endDay: "2024. 07. 31", + postTitle: "서울특별시 강남구 신사동", + nameText: "홍길동", + careGradeText: "1등급", + ageText: "78세", + genderText: "여성", + applicantCount: 2 + ) +} + +public protocol CenterEmployCardViewModelable { + + // Output + var renderObject: Driver? { get } + + // Input + var postCardClicked: PublishRelay { get } + + // - Buttons + var checkApplicantBtnClicked: PublishRelay { get } + var editPostBtnClicked: PublishRelay { get } + var terminatePostBtnClicked: PublishRelay { get } +} + public class CenterEmployCard: TappableUIView { // Init @@ -19,38 +66,56 @@ public class CenterEmployCard: TappableUIView { // Row1 let durationLabel: IdleLabel = { let label = IdleLabel(typography: .Body3) - label.textColor = DSKitAsset.Colors.gray300.color + label.attrTextColor = DSKitAsset.Colors.gray300.color return label }() // Row2 - let poseTitleLabel: IdleLabel = { + let postTitleLabel: IdleLabel = { let label = IdleLabel(typography: .Subtitle2) - label.textColor = DSKitAsset.Colors.gray300.color return label }() // Row3 let nameLabel: IdleLabel = { let label = IdleLabel(typography: .Body2) - label.textColor = DSKitAsset.Colors.gray500.color + label.attrTextColor = DSKitAsset.Colors.gray500.color return label }() let informationLabel: IdleLabel = { let label = IdleLabel(typography: .Body2) - label.textColor = DSKitAsset.Colors.gray500.color + label.attrTextColor = DSKitAsset.Colors.gray500.color return label }() // Row4 - let checkApplyButton: IdlePrimaryCardButton = { + let checkApplicantsButton: IdlePrimaryCardButton = { let btn = IdlePrimaryCardButton(level: .medium) btn.label.textString = "" return btn }() // Row5 - + let editPostButton: ImagePrefixButton = { + let button = ImagePrefixButton( + iconImage: DSKitAsset.Icons.postEdit.image + ) + button.icon.tintColor = DSKitAsset.Colors.gray300.color + button.label.textString = "공고 수정" + button.label.attrTextColor = DSKitAsset.Colors.gray500.color + + return button + }() + let terminatePostButton: ImagePrefixButton = { + let button = ImagePrefixButton( + iconImage: DSKitAsset.Icons.postCheck.image + ) + button.icon.tintColor = DSKitAsset.Colors.gray300.color + button.label.textString = "채용 종료" + button.label.attrTextColor = DSKitAsset.Colors.gray500.color + + return button + }() // Observable @@ -58,20 +123,138 @@ public class CenterEmployCard: TappableUIView { public override init() { super.init() + + setAppearance() + setLayout() + setObservable() } public required init?(coder: NSCoder) { fatalError() } private func setAppearance() { - + self.backgroundColor = .white + self.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor + self.layer.borderWidth = 1.0 + self.layer.cornerRadius = 12 } private func setLayout() { + self.layoutMargins = .init(top: 16, left: 16, bottom: 16, right: 16) + + // InfoLabel + let divider = UIView() + divider.backgroundColor = DSKitAsset.Colors.gray300.color + let infoStack = HStack([ + nameLabel, + divider, + informationLabel, + ], spacing: 8) + + NSLayoutConstraint.activate([ + divider.widthAnchor.constraint(equalToConstant: 1), + divider.topAnchor.constraint(equalTo: infoStack.topAnchor, constant: 5), + divider.bottomAnchor.constraint(equalTo: infoStack.bottomAnchor, constant: -5), + ]) + + let textStack = VStack([ + durationLabel, + Spacer(height: 4), + postTitleLabel, + Spacer(height: 2), + infoStack, + ], alignment: .leading) + + let buttonStack = HStack([ + editPostButton, + terminatePostButton, + ], spacing: 4) + + let contentStack = VStack([ + HStack([textStack, Spacer()]), + Spacer(height: 12), + checkApplicantsButton, + HStack([buttonStack, Spacer()]) + ], alignment: .fill) + + [ + contentStack + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + self.addSubview($0) + } + + NSLayoutConstraint.activate([ + contentStack.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor), + contentStack.leftAnchor.constraint(equalTo: self.layoutMarginsGuide.leftAnchor), + contentStack.rightAnchor.constraint(equalTo: self.layoutMarginsGuide.rightAnchor), + contentStack.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor), + ]) } - private func setObservable() { + private func setObservable() { } + + public func binc(viewModel: CenterEmployCardViewModelable) { + // Output + viewModel + .renderObject? + .drive(onNext: { [weak self] ro in + // public let applicantCount: Int + guard let self else { return } + durationLabel.textString = "\(ro.startDay) ~ \(ro.endDay)" + postTitleLabel.textString = ro.postTitle + nameLabel.textString = ro.nameText + informationLabel.textString = "\(ro.careGradeText) \(ro.ageText) \(ro.genderText)" + checkApplicantsButton.label.textString = "지원자 \(ro.applicantCount)명 조회" + }) + .disposed(by: disposeBag) + + // Input + [ + self.rx.tap + .bind(to: viewModel.postCardClicked), + + checkApplicantsButton + .rx.tap + .bind(to: viewModel.checkApplicantBtnClicked), + + editPostButton + .rx.tap + .bind(to: viewModel.editPostBtnClicked), + + terminatePostButton + .rx.tap + .bind(to: viewModel.terminatePostBtnClicked), + ].forEach { + $0.disposed(by: disposeBag) + } + (viewModel as? TextVM)?.publishObect.accept(.mock) } } +fileprivate class TextVM: CenterEmployCardViewModelable { + + public let publishObect: PublishRelay = .init() + + var renderObject: RxCocoa.Driver? + + var postCardClicked: RxRelay.PublishRelay = .init() + var checkApplicantBtnClicked: RxRelay.PublishRelay = .init() + var editPostBtnClicked: RxRelay.PublishRelay = .init() + var terminatePostBtnClicked: RxRelay.PublishRelay = .init() + + init() { + + renderObject = publishObect.asDriver(onErrorJustReturn: .mock) + } +} + +@available(iOS 17.0, *) +#Preview("Preview", traits: .defaultLayout) { + let btn = CenterEmployCard() + let vm = TextVM() + btn.binc(viewModel: vm) + + return btn +} From 8d5e6b8ced6574e15c2fe0ba59bf764486cc9903 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Mon, 12 Aug 2024 13:36:54 +0900 Subject: [PATCH 03/20] =?UTF-8?q?[IDLE-141]=20CenterEmployCardCell=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Card/Post/Center/CenterEmployCard.swift | 44 ++----- .../Post/Center/CenterEmployCardCell.swift | 112 ++++++++++++++++++ 2 files changed, 119 insertions(+), 37 deletions(-) create mode 100644 project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift index f3f5b646..2f8fb169 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift @@ -194,42 +194,13 @@ public class CenterEmployCard: TappableUIView { private func setObservable() { } - public func binc(viewModel: CenterEmployCardViewModelable) { - // Output - viewModel - .renderObject? - .drive(onNext: { [weak self] ro in - // public let applicantCount: Int - guard let self else { return } - durationLabel.textString = "\(ro.startDay) ~ \(ro.endDay)" - postTitleLabel.textString = ro.postTitle - nameLabel.textString = ro.nameText - informationLabel.textString = "\(ro.careGradeText) \(ro.ageText) \(ro.genderText)" - checkApplicantsButton.label.textString = "지원자 \(ro.applicantCount)명 조회" - }) - .disposed(by: disposeBag) + public func binc(ro: CenterEmployCardRO) { - // Input - [ - self.rx.tap - .bind(to: viewModel.postCardClicked), - - checkApplicantsButton - .rx.tap - .bind(to: viewModel.checkApplicantBtnClicked), - - editPostButton - .rx.tap - .bind(to: viewModel.editPostBtnClicked), - - terminatePostButton - .rx.tap - .bind(to: viewModel.terminatePostBtnClicked), - ].forEach { - $0.disposed(by: disposeBag) - } - - (viewModel as? TextVM)?.publishObect.accept(.mock) + durationLabel.textString = "\(ro.startDay) ~ \(ro.endDay)" + postTitleLabel.textString = ro.postTitle + nameLabel.textString = ro.nameText + informationLabel.textString = "\(ro.careGradeText) \(ro.ageText) \(ro.genderText)" + checkApplicantsButton.label.textString = "지원자 \(ro.applicantCount)명 조회" } } @@ -253,8 +224,7 @@ fileprivate class TextVM: CenterEmployCardViewModelable { @available(iOS 17.0, *) #Preview("Preview", traits: .defaultLayout) { let btn = CenterEmployCard() - let vm = TextVM() - btn.binc(viewModel: vm) + btn.binc(ro: .mock) return btn } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift new file mode 100644 index 00000000..7d89a1e3 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift @@ -0,0 +1,112 @@ +// +// CenterEmployCardCell.swift +// DSKit +// +// Created by choijunios on 8/12/24. +// + +import UIKit +import RxSwift +import RxCocoa +import Entity + +public class CenterEmployCardCell: UITableViewCell { + + let tappableArea: TappableUIView = .init() + + let cardView = CenterEmployCard() + + private var disposables: [Disposable?]? + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setAppearance() + setLayout() + } + public required init?(coder: NSCoder) { fatalError() } + + public override func prepareForReuse() { + disposables?.forEach { $0?.dispose() } + disposables = nil + } + + func setAppearance() { + contentView.layer.borderWidth = 1 + contentView.layer.cornerRadius = 12 + contentView.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor + } + + func setLayout() { + + [ + cardView + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview($0) + } + + NSLayoutConstraint.activate([ + cardView.topAnchor.constraint(equalTo: contentView.topAnchor), + cardView.leftAnchor.constraint(equalTo: contentView.leftAnchor), + cardView.rightAnchor.constraint(equalTo: contentView.rightAnchor), + cardView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + ]) + } + + public func binc(viewModel: CenterEmployCardViewModelable) { + + let disposables: [Disposable?] = [ + // Output + viewModel + .renderObject? + .drive(onNext: { [cardView] ro in + cardView.binc(ro: ro) + }), + + // Input + cardView.rx.tap + .bind(to: viewModel.postCardClicked), + + cardView.checkApplicantsButton + .rx.tap + .bind(to: viewModel.checkApplicantBtnClicked), + + cardView.editPostButton + .rx.tap + .bind(to: viewModel.editPostBtnClicked), + + cardView.terminatePostButton + .rx.tap + .bind(to: viewModel.terminatePostBtnClicked), + ] + + self.disposables = disposables + + (viewModel as? TextVM)?.publishObect.accept(.mock) + } +} + +fileprivate class TextVM: CenterEmployCardViewModelable { + + public let publishObect: PublishRelay = .init() + + var renderObject: RxCocoa.Driver? + + var postCardClicked: RxRelay.PublishRelay = .init() + var checkApplicantBtnClicked: RxRelay.PublishRelay = .init() + var editPostBtnClicked: RxRelay.PublishRelay = .init() + var terminatePostBtnClicked: RxRelay.PublishRelay = .init() + + init() { + + renderObject = publishObect.asDriver(onErrorJustReturn: .mock) + } +} + +@available(iOS 17.0, *) +#Preview("Preview", traits: .defaultLayout) { + let btn = CenterEmployCard() + let vm = TextVM() + + return btn +} From 83e9354526eda44fc350050a4506911fcb84c7bd Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Mon, 12 Aug 2024 13:43:47 +0900 Subject: [PATCH 04/20] =?UTF-8?q?[IDLE-141]=20CenterEmployCardInfoView?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Card/Post/Center/CenterEmployCard.swift | 61 +++------------- .../Center/CenterEmployCardInfoView.swift | 70 +++++++++++++++++++ 2 files changed, 78 insertions(+), 53 deletions(-) create mode 100644 project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardInfoView.swift diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift index 2f8fb169..135a5cf5 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift @@ -63,30 +63,8 @@ public class CenterEmployCard: TappableUIView { // View - // Row1 - let durationLabel: IdleLabel = { - let label = IdleLabel(typography: .Body3) - label.attrTextColor = DSKitAsset.Colors.gray300.color - return label - }() - - // Row2 - let postTitleLabel: IdleLabel = { - let label = IdleLabel(typography: .Subtitle2) - return label - }() - - // Row3 - let nameLabel: IdleLabel = { - let label = IdleLabel(typography: .Body2) - label.attrTextColor = DSKitAsset.Colors.gray500.color - return label - }() - let informationLabel: IdleLabel = { - let label = IdleLabel(typography: .Body2) - label.attrTextColor = DSKitAsset.Colors.gray500.color - return label - }() + // Row1, 2, 3 View + let centerEmployCardInfoView = CenterEmployCardInfoView() // Row4 let checkApplicantsButton: IdlePrimaryCardButton = { @@ -141,37 +119,14 @@ public class CenterEmployCard: TappableUIView { private func setLayout() { self.layoutMargins = .init(top: 16, left: 16, bottom: 16, right: 16) - - // InfoLabel - let divider = UIView() - divider.backgroundColor = DSKitAsset.Colors.gray300.color - let infoStack = HStack([ - nameLabel, - divider, - informationLabel, - ], spacing: 8) - - NSLayoutConstraint.activate([ - divider.widthAnchor.constraint(equalToConstant: 1), - divider.topAnchor.constraint(equalTo: infoStack.topAnchor, constant: 5), - divider.bottomAnchor.constraint(equalTo: infoStack.bottomAnchor, constant: -5), - ]) - - let textStack = VStack([ - durationLabel, - Spacer(height: 4), - postTitleLabel, - Spacer(height: 2), - infoStack, - ], alignment: .leading) - + let buttonStack = HStack([ editPostButton, terminatePostButton, ], spacing: 4) let contentStack = VStack([ - HStack([textStack, Spacer()]), + HStack([centerEmployCardInfoView, Spacer()]), Spacer(height: 12), checkApplicantsButton, HStack([buttonStack, Spacer()]) @@ -196,10 +151,10 @@ public class CenterEmployCard: TappableUIView { public func binc(ro: CenterEmployCardRO) { - durationLabel.textString = "\(ro.startDay) ~ \(ro.endDay)" - postTitleLabel.textString = ro.postTitle - nameLabel.textString = ro.nameText - informationLabel.textString = "\(ro.careGradeText) \(ro.ageText) \(ro.genderText)" + centerEmployCardInfoView.durationLabel.textString = "\(ro.startDay) ~ \(ro.endDay)" + centerEmployCardInfoView.postTitleLabel.textString = ro.postTitle + centerEmployCardInfoView.nameLabel.textString = ro.nameText + centerEmployCardInfoView.informationLabel.textString = "\(ro.careGradeText) \(ro.ageText) \(ro.genderText)" checkApplicantsButton.label.textString = "지원자 \(ro.applicantCount)명 조회" } } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardInfoView.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardInfoView.swift new file mode 100644 index 00000000..aa095f36 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardInfoView.swift @@ -0,0 +1,70 @@ +// +// CenterEmployCardInfoView.swift +// DSKit +// +// Created by choijunios on 8/12/24. +// + +import UIKit + +public class CenterEmployCardInfoView: VStack { + // Row1 + let durationLabel: IdleLabel = { + let label = IdleLabel(typography: .Body3) + label.attrTextColor = DSKitAsset.Colors.gray300.color + return label + }() + + // Row2 + let postTitleLabel: IdleLabel = { + let label = IdleLabel(typography: .Subtitle2) + return label + }() + + // Row3 + let nameLabel: IdleLabel = { + let label = IdleLabel(typography: .Body2) + label.attrTextColor = DSKitAsset.Colors.gray500.color + return label + }() + let informationLabel: IdleLabel = { + let label = IdleLabel(typography: .Body2) + label.attrTextColor = DSKitAsset.Colors.gray500.color + return label + }() + + init() { + super.init([], alignment: .leading) + setLayout() + } + + public required init(coder: NSCoder) { fatalError() } + + func setLayout() { + + // InfoLabel + let divider = UIView() + divider.backgroundColor = DSKitAsset.Colors.gray300.color + let infoStack = HStack([ + nameLabel, + divider, + informationLabel, + ], spacing: 8) + + NSLayoutConstraint.activate([ + divider.widthAnchor.constraint(equalToConstant: 1), + divider.topAnchor.constraint(equalTo: infoStack.topAnchor, constant: 5), + divider.bottomAnchor.constraint(equalTo: infoStack.bottomAnchor, constant: -5), + ]) + + [ + durationLabel, + Spacer(height: 4), + postTitleLabel, + Spacer(height: 2), + infoStack, + ].forEach { + self.addArrangedSubview($0) + } + } +} From 88f63a73d146aba5efb4c381a9f72240d6a1bd47 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Mon, 12 Aug 2024 14:15:38 +0900 Subject: [PATCH 05/20] =?UTF-8?q?[IDLE-141]=20PostInfoCardView=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 지원자 확인하기 화면에쓰이는 컴포넌트 입니다. --- .../workerProfilePlaceholder.svg | 4 - .../Contents.json | 2 +- .../worker_profile_placeholder.svg | 12 +++ .../Center}/CenterInfoCardView.swift | 0 .../Profile/Worker /PostInfoCardView.swift | 79 +++++++++++++++++++ .../EditWorkerProfileViewController.swift | 17 +--- 6 files changed, 96 insertions(+), 18 deletions(-) delete mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerProfilePlaceholder.imageset/workerProfilePlaceholder.svg rename project/Projects/Presentation/DSKit/Resources/Icons.xcassets/{workerProfilePlaceholder.imageset => worker_profile_placeholder.imageset}/Contents.json (69%) create mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_profile_placeholder.imageset/worker_profile_placeholder.svg rename project/Projects/Presentation/DSKit/Sources/CommonUI/Card/{CenterCard => Profile/Center}/CenterInfoCardView.swift (100%) create mode 100644 project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Worker /PostInfoCardView.swift diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerProfilePlaceholder.imageset/workerProfilePlaceholder.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerProfilePlaceholder.imageset/workerProfilePlaceholder.svg deleted file mode 100644 index 3c7f3e32..00000000 --- a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerProfilePlaceholder.imageset/workerProfilePlaceholder.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerProfilePlaceholder.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_profile_placeholder.imageset/Contents.json similarity index 69% rename from project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerProfilePlaceholder.imageset/Contents.json rename to project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_profile_placeholder.imageset/Contents.json index d4ce8ef1..9043bdbb 100644 --- a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/workerProfilePlaceholder.imageset/Contents.json +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_profile_placeholder.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "workerProfilePlaceholder.svg", + "filename" : "worker_profile_placeholder.svg", "idiom" : "universal" } ], diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_profile_placeholder.imageset/worker_profile_placeholder.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_profile_placeholder.imageset/worker_profile_placeholder.svg new file mode 100644 index 00000000..68732a5e --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/worker_profile_placeholder.imageset/worker_profile_placeholder.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/CenterCard/CenterInfoCardView.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Center/CenterInfoCardView.swift similarity index 100% rename from project/Projects/Presentation/DSKit/Sources/CommonUI/Card/CenterCard/CenterInfoCardView.swift rename to project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Center/CenterInfoCardView.swift diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Worker /PostInfoCardView.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Worker /PostInfoCardView.swift new file mode 100644 index 00000000..e642e14d --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Worker /PostInfoCardView.swift @@ -0,0 +1,79 @@ +// +// WorkerInfoCardView.swift +// DSKit +// +// Created by choijunios on 8/12/24. +// + +import UIKit +import PresentationCore +import RxCocoa +import RxSwift +import Entity + +public class PostInfoCardView: TappableUIView { + + // View + let contentView = CenterEmployCardInfoView() + + // Observable + private let disposeBag = DisposeBag() + + public override init() { + super.init() + + setAppearance() + setLayout() + setObservable() + } + + public required init?(coder: NSCoder) { fatalError() } + + private func setAppearance() { + self.backgroundColor = .white + self.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor + self.layer.borderWidth = 1.0 + self.layer.cornerRadius = 12 + } + + private func setLayout() { + self.layoutMargins = .init( + top: 16, + left: 16, + bottom: 16, + right: 16 + ) + + let mainStack = VStack([ + HStack([contentView, Spacer()]) + ],alignment: .fill) + + [ + mainStack + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + self.addSubview($0) + } + + NSLayoutConstraint.activate([ + mainStack.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor), + mainStack.leftAnchor.constraint(equalTo: self.layoutMarginsGuide.leftAnchor), + mainStack.rightAnchor.constraint(equalTo: self.layoutMarginsGuide.rightAnchor), + mainStack.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor), + ]) + } + + private func setObservable() { } +} + +@available(iOS 17.0, *) +#Preview("Preview", traits: .defaultLayout) { + + let view = PostInfoCardView() + view.contentView.durationLabel.textString = "2024. 07. 10 ~ 2024. 07. 31" + view.contentView.informationLabel.textString = "1등급 78세 여성" + view.contentView.nameLabel.textString = "홍길동" + view.contentView.postTitleLabel.textString = "서울특별시 강남구 신사동" + + return view +} diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/EditWorkerProfileViewController.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/profile/EditWorkerProfileViewController.swift index 3467c0c9..a2fae57d 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/EditWorkerProfileViewController.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/profile/EditWorkerProfileViewController.swift @@ -45,23 +45,14 @@ public class EditWorkerProfileViewController: BaseViewController { }() // 프로필 이미지 - let profileImageContainer: UIView = { + let profileImageContainer: UIImageView = { - let view = UIView() + let view = UIImageView() view.backgroundColor = DSKitAsset.Colors.orange100.color view.layer.cornerRadius = 48 view.clipsToBounds = true - - /// PlaceHolderImage - let imageView = DSKitAsset.Icons.workerProfilePlaceholder.image.toView() - view.addSubview(imageView) - imageView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 26), - imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 25), - imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -29), - imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -27), - ]) + view.image = DSKitAsset.Icons.workerProfilePlaceholder.image + view.contentMode = .scaleAspectFit return view }() From e89aeb2bd4d833a3a05e80761005c48b0c306767 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Mon, 12 Aug 2024 14:49:46 +0900 Subject: [PATCH 06/20] =?UTF-8?q?[IDLE-141]=20IdleSecondaryButton=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gray0.colorset/Contents.json | 38 ++++ .../ApplyScreen/ApplicantCardView.swift | 102 +++++++++ .../Worker}/PostInfoCardView.swift | 0 .../Button/IdleSecondaryButton.swift | 12 +- .../Button/IdleThirdinaryButton.swift | 214 ++++++++++++++++++ .../profile/WorkerProfileViewController.swift | 16 +- 6 files changed, 364 insertions(+), 18 deletions(-) create mode 100644 project/Projects/Presentation/DSKit/Resources/Colors.xcassets/gray0.colorset/Contents.json create mode 100644 project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardView.swift rename project/Projects/Presentation/DSKit/Sources/CommonUI/Card/{Profile/Worker => Post/Worker}/PostInfoCardView.swift (100%) create mode 100644 project/Projects/Presentation/DSKit/Sources/Component/Button/IdleThirdinaryButton.swift diff --git a/project/Projects/Presentation/DSKit/Resources/Colors.xcassets/gray0.colorset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Colors.xcassets/gray0.colorset/Contents.json new file mode 100644 index 00000000..951b9076 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Colors.xcassets/gray0.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardView.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardView.swift new file mode 100644 index 00000000..a15066dc --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardView.swift @@ -0,0 +1,102 @@ +// +// ApplicantCardView.swift +// DSKit +// +// Created by choijunios on 8/12/24. +// + +import UIKit +import PresentationCore +import RxCocoa +import RxSwift +import Entity + +public class ApplicantCardView: UIView { + + // View + // Profile + let profileImageContainer: UIImageView = { + + let view = UIImageView() + view.backgroundColor = DSKitAsset.Colors.orange100.color + view.layer.cornerRadius = 36 + view.clipsToBounds = true + view.image = DSKitAsset.Icons.workerProfilePlaceholder.image + view.contentMode = .scaleAspectFit + + return view + }() + let workerProfileImage: UIImageView = { + + let imageView = UIImageView() + imageView.layer.cornerRadius = 36 + imageView.contentMode = .scaleAspectFill + imageView.clipsToBounds = true + + return imageView + }() + // Star + public let starButton: IconWithColorStateButton = { + let button = IconWithColorStateButton( + representImage: DSKitAsset.Icons.subscribeStar.image, + normalColor: DSKitAsset.Colors.gray200.color, + accentColor: DSKitAsset.Colors.orange300.color + ) + return button + }() + + // Row1 + let workingTag: TagLabel = { + let label = TagLabel( + text: "", + typography: .caption, + textColor: DSKitAsset.Colors.orange500.color, + backgroundColor: DSKitAsset.Colors.orange100.color + ) + return label + }() + + // Row2 + let nameLabel: IdleLabel = { + let label = IdleLabel(typography: .Subtitle2) + + return label + }() + let infoLabel: IdleLabel = { + let label = IdleLabel(typography: .Body3) + label.attrTextColor = DSKitAsset.Colors.gray500.color + return label + }() + let expLabel: IdleLabel = { + let label = IdleLabel(typography: .Body3) + label.attrTextColor = DSKitAsset.Colors.gray500.color + return label + }() + + + + // Observable + private let disposeBag = DisposeBag() + + public init() { + super.init(frame: .zero) + } + + public required init?(coder: NSCoder) { fatalError() } + + private func setAppearance() { + + } + + private func setLayout() { + NSLayoutConstraint.activate([ + starButton.widthAnchor.constraint(equalToConstant: 22), + starButton.heightAnchor.constraint(equalTo: starButton.widthAnchor), + ]) + } + + private func setObservable() { + + } + +} diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Worker /PostInfoCardView.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/PostInfoCardView.swift similarity index 100% rename from project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Profile/Worker /PostInfoCardView.swift rename to project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/PostInfoCardView.swift diff --git a/project/Projects/Presentation/DSKit/Sources/Component/Button/IdleSecondaryButton.swift b/project/Projects/Presentation/DSKit/Sources/Component/Button/IdleSecondaryButton.swift index 310348b0..1f737799 100644 --- a/project/Projects/Presentation/DSKit/Sources/Component/Button/IdleSecondaryButton.swift +++ b/project/Projects/Presentation/DSKit/Sources/Component/Button/IdleSecondaryButton.swift @@ -30,7 +30,7 @@ public enum IdleSecondaryButtonLevel { var idleTextColor: UIColor { switch self { case .medium: - DSKitAsset.Colors.orange500.color + DSKitAsset.Colors.gray300.color } } @@ -52,14 +52,14 @@ public enum IdleSecondaryButtonLevel { var idleBackgroundColor: UIColor { switch self { case .medium: - .white + DSKitAsset.Colors.gray0.color } } var accentBackgroundColor: UIColor { switch self { case .medium: - DSKitAsset.Colors.orange100.color + DSKitAsset.Colors.gray0.color } } @@ -74,14 +74,14 @@ public enum IdleSecondaryButtonLevel { var idleBorderColor: UIColor { switch self { case .medium: - DSKitAsset.Colors.orange400.color + DSKitAsset.Colors.gray200.color } } var accentBorderColor: UIColor { switch self { case .medium: - DSKitAsset.Colors.orange300.color + DSKitAsset.Colors.gray200.color } } @@ -208,7 +208,7 @@ public class IdleSecondaryButton: TappableUIView { let button = IdleSecondaryButton(level: .medium) button.label.textString = "다음" - button.setEnabled(false) + button.setEnabled(true) return button } diff --git a/project/Projects/Presentation/DSKit/Sources/Component/Button/IdleThirdinaryButton.swift b/project/Projects/Presentation/DSKit/Sources/Component/Button/IdleThirdinaryButton.swift new file mode 100644 index 00000000..0cbb51f1 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/Component/Button/IdleThirdinaryButton.swift @@ -0,0 +1,214 @@ +// +// IdleThirdinaryButton.swift +// DSKit +// +// Created by choijunios on 8/12/24. +// + +import UIKit +import RxSwift +import RxCocoa + +public enum IdleThirdinaryButtonLevel { + + case medium + + var buttonHeight: CGFloat { + switch self { + case .medium: + 56 + } + } + + var typography: Typography { + switch self { + case .medium: + .Heading4 + } + } + //textColor + var idleTextColor: UIColor { + switch self { + case .medium: + DSKitAsset.Colors.gray300.color + } + } + + var accentTextColor: UIColor { + switch self { + case .medium: + DSKitAsset.Colors.gray300.color + } + } + + var disabledTextColor: UIColor { + switch self { + case .medium: + DSKitAsset.Colors.gray300.color + } + } + + //background + var idleBackgroundColor: UIColor { + switch self { + case .medium: + .white + } + } + + var accentBackgroundColor: UIColor { + switch self { + case .medium: + DSKitAsset.Colors.orange100.color + } + } + + var disabledBackgroundColor: UIColor { + switch self { + case .medium: + DSKitAsset.Colors.gray050.color + } + } + + //border + var idleBorderColor: UIColor { + switch self { + case .medium: + DSKitAsset.Colors.orange400.color + } + } + + var accentBorderColor: UIColor { + switch self { + case .medium: + DSKitAsset.Colors.orange300.color + } + } + + var disabledBorderColor: UIColor { + switch self { + case .medium: + DSKitAsset.Colors.gray200.color + } + } +} + +public class IdleThirdinaryButton: TappableUIView { + + // State + public private(set) var isEnabled: Bool = true + + // Init + public let level: IdleSecondaryButtonLevel + + // View + public private(set) lazy var label: IdleLabel = { + + let label = IdleLabel(typography: level.typography) + label.attrTextColor = .white + return label + }() + + public override var intrinsicContentSize: CGSize { + .init(width: super.intrinsicContentSize.width, height: level.buttonHeight) + } + + private let disposeBag = DisposeBag() + + public init( + level: IdleSecondaryButtonLevel + ) { + + self.level = level + super.init() + + setApearance() + setAutoLayout() + setObservable() + } + + required init?(coder: NSCoder) { fatalError() } + + private func setApearance() { + self.layer.cornerRadius = 8 + self.clipsToBounds = true + self.layer.borderWidth = 1.0 + + // InitialSetting + backgroundColor = level.idleBackgroundColor + label.attrTextColor = level.idleTextColor + layer.borderColor = level.idleBorderColor.cgColor + } + + private func setAutoLayout() { + + [ + label + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + self.addSubview($0) + } + + NSLayoutConstraint.activate([ + label.centerYAnchor.constraint(equalTo: self.centerYAnchor), + label.centerXAnchor.constraint(equalTo: self.centerXAnchor), + ]) + } + + private func setObservable() { + + self.rx.tap + .subscribe { [weak self] _ in + guard let self else { return } + + setToAccent() + + UIView.animate(withDuration: 0.3) { [weak self] in + guard let self else { return } + + setToIdle() + } + } + .disposed(by: disposeBag) + } + + public func setEnabled(_ isEnabled: Bool) { + self.isEnabled = isEnabled + self.isUserInteractionEnabled = isEnabled + + if isEnabled { + setToIdle() + } + else { + setToDisabled() + } + } + + private func setToIdle() { + backgroundColor = level.idleBackgroundColor + label.attrTextColor = level.idleTextColor + layer.borderColor = level.idleBorderColor.cgColor + } + + private func setToAccent() { + backgroundColor = level.accentBackgroundColor + label.attrTextColor = level.accentTextColor + layer.borderColor = level.accentBorderColor.cgColor + } + + private func setToDisabled() { + backgroundColor = level.disabledBackgroundColor + label.attrTextColor = level.disabledTextColor + layer.borderColor = level.disabledBorderColor.cgColor + } +} + +@available(iOS 17.0, *) +#Preview("Preview", traits: .defaultLayout) { + + let button = IdleSecondaryButton(level: .medium) + button.label.textString = "다음" + button.setEnabled(false) + return button +} + diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/WorkerProfileViewController.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/profile/WorkerProfileViewController.swift index 81489cbb..341c3e84 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/WorkerProfileViewController.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/profile/WorkerProfileViewController.swift @@ -58,22 +58,14 @@ public class WorkerProfileViewController: DisposableViewController { }() // 프로필 이미지 - let profileImageContainer: UIView = { + let profileImageContainer: UIImageView = { - let view = UIView() + let view = UIImageView() view.backgroundColor = DSKitAsset.Colors.orange100.color view.layer.cornerRadius = 48 view.clipsToBounds = true - - let imageView = DSKitAsset.Icons.workerProfilePlaceholder.image.toView() - view.addSubview(imageView) - imageView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 26), - imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 25), - imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -29), - imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -27), - ]) + view.image = DSKitAsset.Icons.workerProfilePlaceholder.image + view.contentMode = .scaleAspectFit return view }() From 73e8c562ac36d2fd23f88bfbef2880e64f89a5c7 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Mon, 12 Aug 2024 15:25:49 +0900 Subject: [PATCH 07/20] =?UTF-8?q?[IDLE-141]=20ApplicantCardView=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ApplyScreen/ApplicantCardView.swift | 150 +++++++++++++++++- .../Sources/UIImageView+KingFisher.swift | 24 +++ .../EditWorkerProfileViewController.swift | 1 - .../profile/WorkerProfileViewController.swift | 13 -- 4 files changed, 171 insertions(+), 17 deletions(-) create mode 100644 project/Projects/Presentation/DSKit/Sources/UIImageView+KingFisher.swift diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardView.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardView.swift index a15066dc..81128cfd 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardView.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardView.swift @@ -10,6 +10,38 @@ import PresentationCore import RxCocoa import RxSwift import Entity +import Kingfisher + +public struct ApplicantCardRO { + + public let profileUrl: URL? + public let isJobFinding: Bool + public let isStared: Bool + public let name: String + public let ageText: String + public let genderText: String + public let expText: String + + public init(profileUrl: URL?, isJobFinding: Bool, isStared: Bool, name: String, ageText: String, genderText: String, expText: String) { + self.profileUrl = profileUrl + self.isJobFinding = isJobFinding + self.isStared = isStared + self.name = name + self.ageText = ageText + self.genderText = genderText + self.expText = expText + } + + static let mock: ApplicantCardRO = .init( + profileUrl: URL(string: "https://dummyimage.com/600x400/00ffbf/0011ff&text=worker+profile"), + isJobFinding: false, + isStared: false, + name: "홍길동", + ageText: "51세", + genderText: "여성", + expText: "1년차" + ) +} public class ApplicantCardView: UIView { @@ -29,7 +61,6 @@ public class ApplicantCardView: UIView { let workerProfileImage: UIImageView = { let imageView = UIImageView() - imageView.layer.cornerRadius = 36 imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true @@ -62,6 +93,8 @@ public class ApplicantCardView: UIView { return label }() + + // Row3 let infoLabel: IdleLabel = { let label = IdleLabel(typography: .Body3) label.attrTextColor = DSKitAsset.Colors.gray500.color @@ -73,30 +106,141 @@ public class ApplicantCardView: UIView { return label }() - + // 버튼들 + let showProfileButton: IdleSecondaryButton = { + let button = IdleSecondaryButton(level: .medium) + button.label.textString = "프로필 보기" + return button + }() + let employButton: IdlePrimaryButton = { + let button = IdlePrimaryButton(level: .medium) + button.label.textString = "채용하기" + return button + }() // Observable private let disposeBag = DisposeBag() public init() { super.init(frame: .zero) + setAppearance() + setLayout() + setObservable() } public required init?(coder: NSCoder) { fatalError() } private func setAppearance() { - + self.backgroundColor = .white + self.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor + self.layer.borderWidth = 1.0 + self.layer.cornerRadius = 12 } private func setLayout() { + + self.layoutMargins = .init(top: 12, left: 16, bottom: 12, right: 16) + + profileImageContainer.addSubview(workerProfileImage) + workerProfileImage.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + + profileImageContainer.widthAnchor.constraint(equalToConstant: 72), + profileImageContainer.heightAnchor.constraint(equalTo: profileImageContainer.widthAnchor), + + workerProfileImage.topAnchor.constraint(equalTo: profileImageContainer.topAnchor), + workerProfileImage.leftAnchor.constraint(equalTo: profileImageContainer.leftAnchor), + workerProfileImage.rightAnchor.constraint(equalTo: profileImageContainer.rightAnchor), + workerProfileImage.bottomAnchor.constraint(equalTo: profileImageContainer.bottomAnchor), + ]) + + let nameTitleLabel: IdleLabel = .init(typography: .Body3) + nameTitleLabel.textString = "요양보호사" + + let nameStack = HStack([ + nameLabel, + nameTitleLabel, + ], spacing: 2, alignment: .center) + + let divider = UIView() + divider.backgroundColor = DSKitAsset.Colors.gray300.color + let detailWorkerInfoStack = HStack([ + infoLabel, + divider, + expLabel, + ], spacing: 8) + + NSLayoutConstraint.activate([ + divider.widthAnchor.constraint(equalToConstant: 1), + divider.topAnchor.constraint(equalTo: detailWorkerInfoStack.topAnchor, constant: 3), + divider.bottomAnchor.constraint(equalTo: detailWorkerInfoStack.bottomAnchor, constant: -3), + ]) + + let labelStack = VStack([ + workingTag, + nameStack, + detailWorkerInfoStack, + ], spacing: 2, alignment: .leading) + + let workerInfoStack = HStack([ + profileImageContainer, + labelStack, + Spacer(), + starButton + ], spacing: 16, alignment: .top) + NSLayoutConstraint.activate([ starButton.widthAnchor.constraint(equalToConstant: 22), starButton.heightAnchor.constraint(equalTo: starButton.widthAnchor), ]) + + let buttonStack = HStack([ + showProfileButton, employButton + ], spacing: 8, distribution: .fillEqually) + + let mainStack = VStack([ + workerInfoStack, + buttonStack + ], spacing: 12, alignment: .fill) + + [ + mainStack + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + self.addSubview($0) + } + + NSLayoutConstraint.activate([ + mainStack.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor), + mainStack.leadingAnchor.constraint(equalTo: self.layoutMarginsGuide.leadingAnchor), + mainStack.rightAnchor.constraint(equalTo: self.layoutMarginsGuide.rightAnchor), + mainStack.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor), + ]) } private func setObservable() { } + public func bind(ro: ApplicantCardRO) { + + if let imageUrl = ro.profileUrl { + workerProfileImage + .setImage(url: imageUrl) + } + + workingTag.textString = ro.isJobFinding ? "구직중" : "휴식중" + starButton.setState(ro.isStared ? .accent : .normal) + nameLabel.textString = ro.name + infoLabel.textString = "\(ro.ageText) \(ro.genderText)" + expLabel.textString = "\(ro.expText)" + } +} + +@available(iOS 17.0, *) +#Preview("Preview", traits: .defaultLayout) { + let cardView = ApplicantCardView() + cardView.bind(ro: .mock) + return cardView } diff --git a/project/Projects/Presentation/DSKit/Sources/UIImageView+KingFisher.swift b/project/Projects/Presentation/DSKit/Sources/UIImageView+KingFisher.swift new file mode 100644 index 00000000..cd2e7327 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/UIImageView+KingFisher.swift @@ -0,0 +1,24 @@ +// +// UIImageView+KingFisher.swift +// DSKit +// +// Created by choijunios on 8/12/24. +// + +import UIKit +import Kingfisher + +public extension UIImageView { + + /// KingFisher를 사용해 이미지를 적용합니다. + /// option + /// - let pngSerializer = FormatIndicatedCacheSerializer.png + func setImage(url: URL) { + let pngSerializer = FormatIndicatedCacheSerializer.png + self + .kf.setImage( + with: url, + options: [.cacheSerializer(pngSerializer)] + ) + } +} diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/EditWorkerProfileViewController.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/profile/EditWorkerProfileViewController.swift index a2fae57d..a6b4640a 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/EditWorkerProfileViewController.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/profile/EditWorkerProfileViewController.swift @@ -12,7 +12,6 @@ import RxCocoa import DSKit import Entity import BaseFeature -import Kingfisher protocol WorkerProfileEditViewModelable: WorkerProfileViewModelable { diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/WorkerProfileViewController.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/profile/WorkerProfileViewController.swift index 341c3e84..0fd8061a 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/WorkerProfileViewController.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/profile/WorkerProfileViewController.swift @@ -12,7 +12,6 @@ import RxCocoa import DSKit import Entity import BaseFeature -import Kingfisher public protocol WorkerProfileViewModelable { @@ -23,18 +22,6 @@ public protocol WorkerProfileViewModelable { var profileRenderObject: Driver? { get } } -extension UIImageView { - - func setImage(url: URL) { - let pngSerializer = FormatIndicatedCacheSerializer.png - self - .kf.setImage( - with: url, - options: [.cacheSerializer(pngSerializer)] - ) - } -} - public class WorkerProfileViewController: DisposableViewController { private var viewModel: (any WorkerProfileViewModelable)? From 1e1503c3ff3d29fdf43a067acbbbd55a6ce250dc Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Mon, 12 Aug 2024 15:43:44 +0900 Subject: [PATCH 08/20] =?UTF-8?q?[IDLE-141]=20ApplicantCardCell=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Card/Post/Center/CenterEmployCard.swift | 9 +-- .../Post/Center/CenterEmployCardCell.swift | 39 +-------- ...cantCardView.swift => ApplicantCard.swift} | 15 +++- .../ApplyScreen/ApplicantCardCell.swift | 81 +++++++++++++++++++ 4 files changed, 101 insertions(+), 43 deletions(-) rename project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/{ApplicantCardView.swift => ApplicantCard.swift} (95%) create mode 100644 project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift index 135a5cf5..2241ecfb 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift @@ -49,7 +49,7 @@ public protocol CenterEmployCardViewModelable { var renderObject: Driver? { get } // Input - var postCardClicked: PublishRelay { get } + var cardClicked: PublishRelay { get } // - Buttons var checkApplicantBtnClicked: PublishRelay { get } @@ -149,7 +149,7 @@ public class CenterEmployCard: TappableUIView { private func setObservable() { } - public func binc(ro: CenterEmployCardRO) { + public func bind(ro: CenterEmployCardRO) { centerEmployCardInfoView.durationLabel.textString = "\(ro.startDay) ~ \(ro.endDay)" centerEmployCardInfoView.postTitleLabel.textString = ro.postTitle @@ -165,7 +165,7 @@ fileprivate class TextVM: CenterEmployCardViewModelable { var renderObject: RxCocoa.Driver? - var postCardClicked: RxRelay.PublishRelay = .init() + var cardClicked: RxRelay.PublishRelay = .init() var checkApplicantBtnClicked: RxRelay.PublishRelay = .init() var editPostBtnClicked: RxRelay.PublishRelay = .init() var terminatePostBtnClicked: RxRelay.PublishRelay = .init() @@ -179,7 +179,6 @@ fileprivate class TextVM: CenterEmployCardViewModelable { @available(iOS 17.0, *) #Preview("Preview", traits: .defaultLayout) { let btn = CenterEmployCard() - btn.binc(ro: .mock) - + btn.bind(ro: .mock) return btn } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift index 7d89a1e3..29bb8592 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift @@ -12,8 +12,6 @@ import Entity public class CenterEmployCardCell: UITableViewCell { - let tappableArea: TappableUIView = .init() - let cardView = CenterEmployCard() private var disposables: [Disposable?]? @@ -30,11 +28,7 @@ public class CenterEmployCardCell: UITableViewCell { disposables = nil } - func setAppearance() { - contentView.layer.borderWidth = 1 - contentView.layer.cornerRadius = 12 - contentView.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor - } + func setAppearance() { } func setLayout() { @@ -60,12 +54,12 @@ public class CenterEmployCardCell: UITableViewCell { viewModel .renderObject? .drive(onNext: { [cardView] ro in - cardView.binc(ro: ro) + cardView.bind(ro: ro) }), // Input cardView.rx.tap - .bind(to: viewModel.postCardClicked), + .bind(to: viewModel.cardClicked), cardView.checkApplicantsButton .rx.tap @@ -81,32 +75,5 @@ public class CenterEmployCardCell: UITableViewCell { ] self.disposables = disposables - - (viewModel as? TextVM)?.publishObect.accept(.mock) - } -} - -fileprivate class TextVM: CenterEmployCardViewModelable { - - public let publishObect: PublishRelay = .init() - - var renderObject: RxCocoa.Driver? - - var postCardClicked: RxRelay.PublishRelay = .init() - var checkApplicantBtnClicked: RxRelay.PublishRelay = .init() - var editPostBtnClicked: RxRelay.PublishRelay = .init() - var terminatePostBtnClicked: RxRelay.PublishRelay = .init() - - init() { - - renderObject = publishObect.asDriver(onErrorJustReturn: .mock) } } - -@available(iOS 17.0, *) -#Preview("Preview", traits: .defaultLayout) { - let btn = CenterEmployCard() - let vm = TextVM() - - return btn -} diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardView.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCard.swift similarity index 95% rename from project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardView.swift rename to project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCard.swift index 81128cfd..9714cec7 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardView.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCard.swift @@ -43,7 +43,18 @@ public struct ApplicantCardRO { ) } -public class ApplicantCardView: UIView { +public protocol ApplicantCardViewModelable { + + // - Buttons + var showProfileButtonClicked: PublishRelay { get } + var employButtonClicked: PublishRelay { get } + var staredThisWorker: PublishRelay { get } + + // Output + var renderObject: Driver? { get } +} + +public class ApplicantCard: UIView { // View // Profile @@ -240,7 +251,7 @@ public class ApplicantCardView: UIView { @available(iOS 17.0, *) #Preview("Preview", traits: .defaultLayout) { - let cardView = ApplicantCardView() + let cardView = ApplicantCard() cardView.bind(ro: .mock) return cardView } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift new file mode 100644 index 00000000..81a9bfb6 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift @@ -0,0 +1,81 @@ +// +// ApplicantCardCell.swift +// DSKit +// +// Created by choijunios on 8/12/24. +// + + +import UIKit +import RxSwift +import RxCocoa +import Entity + +public class ApplicantCardCell: UITableViewCell { + + let cardView = ApplicantCard() + + private var disposables: [Disposable?]? + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setAppearance() + setLayout() + } + public required init?(coder: NSCoder) { fatalError() } + + public override func prepareForReuse() { + disposables?.forEach { $0?.dispose() } + disposables = nil + } + + func setAppearance() { } + + func setLayout() { + + [ + cardView + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview($0) + } + + NSLayoutConstraint.activate([ + cardView.topAnchor.constraint(equalTo: contentView.topAnchor), + cardView.leftAnchor.constraint(equalTo: contentView.leftAnchor), + cardView.rightAnchor.constraint(equalTo: contentView.rightAnchor), + cardView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + ]) + } + + public func binc(viewModel: ApplicantCardViewModelable) { + + let disposables: [Disposable?] = [ + // Output + viewModel + .renderObject? + .drive(onNext: { [cardView] ro in + cardView.bind(ro: ro) + }), + + // Input + cardView + .starButton.eventPublisher + .map { state in + state == .accent + } + .bind(to: viewModel.staredThisWorker), + + cardView + .showProfileButton.rx.tap + .bind(to: viewModel.showProfileButtonClicked), + + cardView + .employButton.rx.tap + .bind(to: viewModel.employButtonClicked), + ] + + self.disposables = disposables + } +} + From d793de5388679eb6d1d4c4b1e2a7ccda9bfe7752 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Mon, 12 Aug 2024 18:42:50 +0900 Subject: [PATCH 09/20] =?UTF-8?q?[IDLE-141]=20=EC=83=81=EB=8B=A8=20?= =?UTF-8?q?=ED=83=AD=EB=B0=94=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bell.imageset/Contents.json | 15 ++ .../Icons.xcassets/bell.imageset/bell.svg | 3 + .../Component/Button/SingleImageButton.swift | 46 ++++ .../Component/ImageView/IdleImageView.swift | 9 + .../TabControl/IdleTabControlBar.swift | 208 ++++++++++++++++++ .../Board/CenterRecruitmentPostBoardVC.swift | 50 +++++ .../ApplicationDetailView.swift | 0 .../CustomerInformationView.swift | 0 .../CustomerRequirementView.swift | 0 .../{ => Register}/EditPost/EditPostVC.swift | 0 .../RegisterCompleteVC.swift | 0 .../RegisterRecruitmentPostVC.swift | 0 .../WorkTimeAndPay/WorkTimeAndPayView.swift | 0 13 files changed, 331 insertions(+) create mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/bell.imageset/Contents.json create mode 100644 project/Projects/Presentation/DSKit/Resources/Icons.xcassets/bell.imageset/bell.svg create mode 100644 project/Projects/Presentation/DSKit/Sources/Component/Button/SingleImageButton.swift create mode 100644 project/Projects/Presentation/DSKit/Sources/Component/TabControl/IdleTabControlBar.swift create mode 100644 project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVC.swift rename project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/{ => Register}/ApplocationDetail/ApplicationDetailView.swift (100%) rename project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/{ => Register}/CustomerInformation/CustomerInformationView.swift (100%) rename project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/{ => Register}/CustomerRequirement/CustomerRequirementView.swift (100%) rename project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/{ => Register}/EditPost/EditPostVC.swift (100%) rename project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/{ => Register}/RegisterCompleteScreen/RegisterCompleteVC.swift (100%) rename project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/{ => Register}/RegisterRecruitmentPostVC.swift (100%) rename project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/{ => Register}/WorkTimeAndPay/WorkTimeAndPayView.swift (100%) diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/bell.imageset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/bell.imageset/Contents.json new file mode 100644 index 00000000..e3c8da20 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/bell.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "bell.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/bell.imageset/bell.svg b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/bell.imageset/bell.svg new file mode 100644 index 00000000..b113db53 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Resources/Icons.xcassets/bell.imageset/bell.svg @@ -0,0 +1,3 @@ + + + diff --git a/project/Projects/Presentation/DSKit/Sources/Component/Button/SingleImageButton.swift b/project/Projects/Presentation/DSKit/Sources/Component/Button/SingleImageButton.swift new file mode 100644 index 00000000..30a63df1 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/Component/Button/SingleImageButton.swift @@ -0,0 +1,46 @@ +// +// SingleImageButton.swift +// DSKit +// +// Created by choijunios on 8/12/24. +// + +import UIKit +import RxSwift +import RxCocoa + +public class SingleImageButton: TappableUIView { + + let imageView: UIImageView = { + let view = UIImageView() + view.contentMode = .scaleAspectFit + return view + }() + + let disposeBag = DisposeBag() + + public override init() { + + super.init() + + NSLayoutConstraint.activate([ + imageView.topAnchor.constraint(equalTo: self.topAnchor), + imageView.leftAnchor.constraint(equalTo: self.leftAnchor), + imageView.rightAnchor.constraint(equalTo: self.rightAnchor), + imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + ]) + + rx.tap + .subscribe { [weak self] _ in + self?.alpha = 0.5 + UIView.animate(withDuration: 0.35) { [weak self] in + self?.alpha = 1.0 + } + } + .disposed(by: disposeBag) + } + + required init?(coder: NSCoder) { + fatalError() + } +} diff --git a/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift b/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift index 1ffea61c..afac6eae 100644 --- a/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift +++ b/project/Projects/Presentation/DSKit/Sources/Component/ImageView/IdleImageView.swift @@ -37,4 +37,13 @@ public extension UIImage { view.contentMode = .scaleAspectFit return view } + + /// SingleImageButton을 만듭니다. + func toButton(tintColor: UIColor) -> SingleImageButton { + self.withRenderingMode(.alwaysTemplate) + let btn = SingleImageButton() + btn.tintColor = tintColor + btn.imageView.image = self + return btn + } } diff --git a/project/Projects/Presentation/DSKit/Sources/Component/TabControl/IdleTabControlBar.swift b/project/Projects/Presentation/DSKit/Sources/Component/TabControl/IdleTabControlBar.swift new file mode 100644 index 00000000..ad60a737 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/Component/TabControl/IdleTabControlBar.swift @@ -0,0 +1,208 @@ +// +// IdleTabControlBar.swift +// DSKit +// +// Created by choijunios on 8/12/24. +// + +import UIKit +import RxCocoa +import RxSwift + +public protocol IdleTabItem { + associatedtype ID: Equatable + var id: ID { get } + var tabLabelText: String { get } +} + +fileprivate class IdleTabBarCell: TappableUIView { + + let label: IdleLabel = .init(typography: .Subtitle3) + + override init() { + + super.init() + + self.addSubview(label) + label.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + label.centerYAnchor.constraint(equalTo: self.centerYAnchor), + label.centerXAnchor.constraint(equalTo: self.centerXAnchor), + ]) + } + + required init?(coder: NSCoder) { + fatalError() + } +} + +public class IdleTabControlBar: UIView { + + let items: [Item] + + let statePublisher: BehaviorRelay + + private var buttons: [IdleTabBarCell]! + let movingBar: UIView = { + let view = Spacer(height: 2) + view.backgroundColor = DSKitAsset.Colors.gray900.color + return view + }() + + public private(set) var currentIndex: Int? + + public override var intrinsicContentSize: CGSize { + .init( + width: super.intrinsicContentSize.width, + height: 48 + ) + } + + let disposeBag = DisposeBag() + + public init?(items: [Item], initialItem: Item) { + + if items.isEmpty { return nil } + + self.items = items + self.statePublisher = .init(value: initialItem) + super.init(frame: .zero) + setLayout() + selectItem(item: initialItem, animated: false) + setObservable() + } + public required init?(coder: NSCoder) { fatalError() } + + private func setLayout() { + + buttons = items.map { item in + let btn = IdleTabBarCell() + btn.label.textString = item.tabLabelText + return btn + } + + let buttonStack = HStack(buttons, alignment: .fill, distribution: .fillEqually) + + + let barBackGroundView = Spacer(height: 1) + barBackGroundView.backgroundColor = DSKitAsset.Colors.gray100.color + + [ + buttonStack, + barBackGroundView, + movingBar, + ].enumerated().forEach { index, view in + view.layer.zPosition = CGFloat(index) + view.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(view) + } + + NSLayoutConstraint.activate([ + buttonStack.topAnchor.constraint(equalTo: self.topAnchor), + buttonStack.leftAnchor.constraint(equalTo: self.leftAnchor), + buttonStack.rightAnchor.constraint(equalTo: self.rightAnchor), + buttonStack.bottomAnchor.constraint(equalTo: self.bottomAnchor), + + barBackGroundView.leftAnchor.constraint(equalTo: self.leftAnchor), + barBackGroundView.rightAnchor.constraint(equalTo: self.rightAnchor), + barBackGroundView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + + movingBar.leftAnchor.constraint(equalTo: self.leftAnchor), + movingBar.bottomAnchor.constraint(equalTo: self.bottomAnchor), + movingBar.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1.0/CGFloat(items.count)) + ]) + } + + private func setObservable() { + + buttons + .enumerated() + .forEach { (index, tappableView) in + tappableView + .rx.tap + .map { [weak self] _ in + self?.items[index] + } + .compactMap { $0 } + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak self] item in + self?.selectItem(item: item) + }) + .disposed(by: disposeBag) + } + } + + public func selectItem(item: Item, animated: Bool = true) { + + let moveToIndex = items.firstIndex(where: { $0.id == item.id }) + + if currentIndex == moveToIndex { return } + + UIView.animate(withDuration: animated ? 0.2 : 0) { [weak self] in + + guard let self else { return } + + // 라벨색상변경 + buttons + .enumerated() + .forEach { [weak self] (index, view) in + + let isSelected = item.id == self?.items[index].id + view.label.attrTextColor = isSelected ? DSKitAsset.Colors.gray900.color : DSKitAsset.Colors.gray300.color + } + + // 하단 바이동 + currentIndex = moveToIndex + layoutSubviews() + } + } + + public override func layoutSubviews() { + super.layoutSubviews() + + // 레이아웃 요청 이후, 레이아웃 적용 이후 + movingBar.transform = .init(translationX: movingBar.bounds.width * CGFloat(currentIndex ?? 0), y: 0) + } +} + +fileprivate enum TestTab { + case tab1 + case tab2 +} + + +fileprivate struct TestItem: IdleTabItem { + typealias ID = TestTab + var tabLabelText: String + var id: TestTab +} + +@available(iOS 17.0, *) +#Preview("Preview", traits: .defaultLayout) { + + let item1 = TestItem( + tabLabelText: "진행 중인 공고", + id: .tab1 + ) + let item2 = TestItem( + tabLabelText: "이전 공고", + id: .tab2 + ) + + let tabBar = IdleTabControlBar( + items: [item1, item2], + initialItem: item1 + )! + + let vc = UIViewController() + vc.view.addSubview(tabBar) + tabBar.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + tabBar.leftAnchor.constraint(equalTo: vc.view.leftAnchor), + tabBar.rightAnchor.constraint(equalTo: vc.view.rightAnchor), + tabBar.centerYAnchor.constraint(equalTo: vc.view.centerYAnchor), + ]) + + return vc +} diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVC.swift new file mode 100644 index 00000000..cff69314 --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVC.swift @@ -0,0 +1,50 @@ +// +// CenterRecruitmentPostBoardVC.swift +// CenterFeature +// +// Created by choijunios on 8/12/24. +// + +import UIKit +import BaseFeature +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit + +public class CenterRecruitmentPostBoardVC: BaseViewController { + + // Init + + // View + let titleLabel: IdleLabel = { + let label = IdleLabel(typography: .Heading1) + label.textString = "공고 관리" + return label + }() + + + + // 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/Center/Sources/View/RecruitmentPost/ApplocationDetail/ApplicationDetailView.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/ApplocationDetail/ApplicationDetailView.swift similarity index 100% rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/ApplocationDetail/ApplicationDetailView.swift rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/ApplocationDetail/ApplicationDetailView.swift diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/CustomerInformation/CustomerInformationView.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/CustomerInformation/CustomerInformationView.swift similarity index 100% rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/CustomerInformation/CustomerInformationView.swift rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/CustomerInformation/CustomerInformationView.swift diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/CustomerRequirement/CustomerRequirementView.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/CustomerRequirement/CustomerRequirementView.swift similarity index 100% rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/CustomerRequirement/CustomerRequirementView.swift rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/CustomerRequirement/CustomerRequirementView.swift diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/EditPost/EditPostVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/EditPost/EditPostVC.swift similarity index 100% rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/EditPost/EditPostVC.swift rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/EditPost/EditPostVC.swift diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/RegisterCompleteScreen/RegisterCompleteVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/RegisterCompleteScreen/RegisterCompleteVC.swift similarity index 100% rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/RegisterCompleteScreen/RegisterCompleteVC.swift rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/RegisterCompleteScreen/RegisterCompleteVC.swift diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/RegisterRecruitmentPostVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/RegisterRecruitmentPostVC.swift similarity index 100% rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/RegisterRecruitmentPostVC.swift rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/RegisterRecruitmentPostVC.swift diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/WorkTimeAndPay/WorkTimeAndPayView.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/WorkTimeAndPay/WorkTimeAndPayView.swift similarity index 100% rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/WorkTimeAndPay/WorkTimeAndPayView.swift rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Register/WorkTimeAndPay/WorkTimeAndPayView.swift From 780ff73f36a97657c23da9c56f8aa33293a164ef Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Mon, 12 Aug 2024 19:33:02 +0900 Subject: [PATCH 10/20] =?UTF-8?q?[IDLE-141]=20=EC=83=81=EB=8B=A8=20?= =?UTF-8?q?=ED=83=AD=EB=B0=94=20=EC=9E=91=EC=97=85=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TabControl/IdleTabControlBar.swift | 3 +- .../ExampleApp/Sources/SceneDelegate.swift | 6 +- .../Board/CenterRecruitmentPostBoardVC.swift | 191 ++++++++++++++++++ 3 files changed, 196 insertions(+), 4 deletions(-) diff --git a/project/Projects/Presentation/DSKit/Sources/Component/TabControl/IdleTabControlBar.swift b/project/Projects/Presentation/DSKit/Sources/Component/TabControl/IdleTabControlBar.swift index ad60a737..a7f3db0a 100644 --- a/project/Projects/Presentation/DSKit/Sources/Component/TabControl/IdleTabControlBar.swift +++ b/project/Projects/Presentation/DSKit/Sources/Component/TabControl/IdleTabControlBar.swift @@ -40,7 +40,7 @@ public class IdleTabControlBar: UIView { let items: [Item] - let statePublisher: BehaviorRelay + public let statePublisher: BehaviorRelay private var buttons: [IdleTabBarCell]! let movingBar: UIView = { @@ -127,6 +127,7 @@ public class IdleTabControlBar: UIView { .observe(on: MainScheduler.instance) .subscribe(onNext: { [weak self] item in self?.selectItem(item: item) + self?.statePublisher.accept(item) }) .disposed(by: disposeBag) } diff --git a/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift index c3e1686d..76918688 100644 --- a/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/Center/ExampleApp/Sources/SceneDelegate.swift @@ -45,14 +45,14 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { navigationController: navigationController ) - let vc = PostOverviewVC() + let vc = CenterRecruitmentPostBoardVC() - vc.bind(viewModel: vm) +// vc.bind(viewModel: vm) window = UIWindow(windowScene: windowScene) window?.rootViewController = vc window?.makeKeyAndVisible() - coordinator.start() +// coordinator.start() } } diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVC.swift index cff69314..13769af0 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVC.swift @@ -14,6 +14,34 @@ import Entity import DSKit public class CenterRecruitmentPostBoardVC: BaseViewController { + enum TabBarState: Int, CaseIterable { + case current = 0 + case prev = 1 + + var titleText: String { + switch self { + case .current: + "진행 중인 공고" + case .prev: + "이전 공고" + } + } + } + struct TabBarItem: IdleTabItem { + var id: TabBarState + var tabLabelText: String + + init(id: TabBarState) { + self.id = id + self.tabLabelText = id.titleText + } + } + + private var currentState: TabBarState = .current + private let viewControllerDict: [TabBarState: UIViewController] = [ + .current : CurrentPostVC(), + .prev : PrevPostVC() + ] // Init @@ -24,7 +52,117 @@ public class CenterRecruitmentPostBoardVC: BaseViewController { 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.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.safeAreaLayoutGuide.bottomAnchor) + ]) + } +} + +// MARK: Current VC +public class CurrentPostVC: BaseViewController { + + // Init + + // View // Observable private let disposeBag = DisposeBag() @@ -35,10 +173,52 @@ public class CenterRecruitmentPostBoardVC: BaseViewController { public required init?(coder: NSCoder) { fatalError() } + public override func viewDidLoad() { + setAppearance() + } + private func setAppearance() { + view.backgroundColor = .yellow + } + + private func setLayout() { + + } + + private func setObservable() { } + public override func viewWillAppear(_ animated: Bool) { + + print("CurrentPostVC") + } +} + +// MARK: Prev VC +public class PrevPostVC: BaseViewController { + + // Init + + // 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() + } + + private func setAppearance() { + view.backgroundColor = .red + } + private func setLayout() { } @@ -46,5 +226,16 @@ public class CenterRecruitmentPostBoardVC: BaseViewController { private func setObservable() { } + + public override func viewWillAppear(_ animated: Bool) { + + print("PrevPostVC") + } } + +@available(iOS 17.0, *) +#Preview("Preview", traits: .defaultLayout) { + + CenterRecruitmentPostBoardVC() +} From 032c468674b6bc90f3d8bcfdb2e0996b5a7ebed0 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 13 Aug 2024 15:08:54 +0900 Subject: [PATCH 11/20] =?UTF-8?q?[IDLE-141]=20=EA=B3=B5=EA=B3=A0=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EB=A9=94=EC=9D=B8=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=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 --- .../Auth/AuthCoordinator+Extension.swift | 2 +- .../Auth/AuthCoordinator.swift | 7 +- .../Auth/Center/CenterAuthCoorinator.swift | 5 + .../Auth/Worker/WorkerAuthCoordinator.swift | 7 +- .../Main/Center /CenterMainCoordinator.swift | 7 + .../CenterProfileRegisterCoordinator.swift | 5 + .../Center /RegisterPostCoordinator.swift | 5 + .../Main/Worker/WorkerMainCoordinator.swift | 6 + .../RootCoordinator/RootCoordinator.swift | 10 +- .../RecruitmentPost/CenterEmployCardVO.swift | 63 ++++++ .../CommonUI/Button/ImagePrefixButton.swift | 24 ++- .../Card/Post/Center/CenterEmployCard.swift | 30 ++- .../Post/Center/CenterEmployCardCell.swift | 10 +- .../Post/Worker/WorkerEmployCardCell.swift | 2 + .../Sources/CommonUI/TabBar/IdleTabBar.swift | 11 +- .../Board/CenterRecruitmentPostBoardVC.swift | 107 +++------- .../Board/CenterRecruitmentPostBoardVM.swift | 75 +++++++ .../Board/SubVC/ClosedPostVC.swift | 128 ++++++++++++ .../Board/SubVC/OnGoingPostVC.swift | 194 ++++++++++++++++++ .../RecruitmentManagementCoordinator.swift | 13 +- .../Main/CenterMainCoordinatable.swift | 3 +- .../CenterPostBoardCoordinatable.swift | 15 ++ 22 files changed, 615 insertions(+), 114 deletions(-) create mode 100644 project/Projects/Domain/Entity/Error/RecruitmentPost/CenterEmployCardVO.swift create mode 100644 project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVM.swift create mode 100644 project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/ClosedPostVC.swift create mode 100644 project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/OnGoingPostVC.swift create mode 100644 project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CenterPostBoardCoordinatable.swift diff --git a/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator+Extension.swift b/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator+Extension.swift index a8beb774..0d965c11 100644 --- a/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator+Extension.swift +++ b/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator+Extension.swift @@ -1,6 +1,6 @@ // // AuthCoordinator+Extension.swift -// AuthFeature +// Idle-iOS // // Created by choijunios on 6/30/24. // diff --git a/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator.swift index 73f4e660..699393c2 100644 --- a/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Auth/AuthCoordinator.swift @@ -1,6 +1,6 @@ // // AuthCoordinator.swift -// AuthFeature +// Idle-iOS // // Created by choijunios on 6/30/24. // @@ -11,6 +11,11 @@ import AuthFeature class AuthCoordinator: ParentCoordinator { + struct Dependency { + let navigationController: UINavigationController + let injector: Injector + } + var childCoordinators: [Coordinator] = [] var parent: ParentCoordinator? diff --git a/project/Projects/App/Sources/RootCoordinator/Auth/Center/CenterAuthCoorinator.swift b/project/Projects/App/Sources/RootCoordinator/Auth/Center/CenterAuthCoorinator.swift index 03d40b12..f921e334 100644 --- a/project/Projects/App/Sources/RootCoordinator/Auth/Center/CenterAuthCoorinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Auth/Center/CenterAuthCoorinator.swift @@ -13,6 +13,11 @@ import ConcreteRepository class CenterAuthCoorinator: ParentCoordinator { + struct Dependency { + let navigationController: UINavigationController + let injector: Injector + } + var childCoordinators: [Coordinator] = [] var parent: AuthCoordinatable? diff --git a/project/Projects/App/Sources/RootCoordinator/Auth/Worker/WorkerAuthCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Auth/Worker/WorkerAuthCoordinator.swift index 8f57a72d..bf39c0f7 100644 --- a/project/Projects/App/Sources/RootCoordinator/Auth/Worker/WorkerAuthCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Auth/Worker/WorkerAuthCoordinator.swift @@ -1,6 +1,6 @@ // // WorkerAuthCoordinator.swift -// AuthFeature +// Idle-iOS // // Created by choijunios on 6/30/24. // @@ -12,6 +12,11 @@ import AuthFeature class WorkerAuthCoordinator: ParentCoordinator { + struct Dependency { + let navigationController: UINavigationController + let injector: Injector + } + var childCoordinators: [Coordinator] = [] var navigationController: UINavigationController diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift index 6cbad864..c1e4f148 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift @@ -11,6 +11,12 @@ import PresentationCore import RootFeature class CenterMainCoordinator: CenterMainCoordinatable { + + struct Dependency { + let navigationController: UINavigationController + let injector: Injector + } + var childCoordinators: [Coordinator] = [] var parent: ParentCoordinator? @@ -95,6 +101,7 @@ enum CenterMainScreen: Int, CaseIterable { } } +// Test extension CenterMainCoordinator { /// 센터 정보등록 창을 표시합니다. diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterProfileRegisterCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterProfileRegisterCoordinator.swift index 3729246a..eb2538f4 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterProfileRegisterCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterProfileRegisterCoordinator.swift @@ -12,6 +12,11 @@ import PresentationCore import UseCaseInterface class CenterProfileRegisterCoordinator: CenterProfileRegisterCoordinatable { + + struct Dependency { + let navigationController: UINavigationController + let injector: Injector + } var childCoordinators: [Coordinator] = [] diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Center /RegisterPostCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Center /RegisterPostCoordinator.swift index e58d5833..ad77cf20 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Center /RegisterPostCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Center /RegisterPostCoordinator.swift @@ -14,6 +14,11 @@ import UseCaseInterface class RegisterRecruitmentPostCoordinator: RegisterRecruitmentPostCoordinatable { + struct Dependency { + let navigationController: UINavigationController + let injector: Injector + } + var childCoordinators: [Coordinator] = [] var parent: ParentCoordinator? diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift index a6233de5..1bdea884 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/WorkerMainCoordinator.swift @@ -11,6 +11,12 @@ import PresentationCore import RootFeature class WorkerMainCoordinator: ParentCoordinator { + + struct Dependency { + let navigationController: UINavigationController + let injector: Injector + } + var childCoordinators: [Coordinator] = [] var parent: ParentCoordinator? diff --git a/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift index c4fe48ff..e0e9da8e 100644 --- a/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/RootCoordinator.swift @@ -8,13 +8,13 @@ import UIKit import PresentationCore -struct Dependency { - let navigationController: UINavigationController - let injector: Injector -} - class RootCoordinator: ParentCoordinator { + struct Dependency { + let navigationController: UINavigationController + let injector: Injector + } + var childCoordinators: [Coordinator] = [] let navigationController: UINavigationController diff --git a/project/Projects/Domain/Entity/Error/RecruitmentPost/CenterEmployCardVO.swift b/project/Projects/Domain/Entity/Error/RecruitmentPost/CenterEmployCardVO.swift new file mode 100644 index 00000000..e7aa6f09 --- /dev/null +++ b/project/Projects/Domain/Entity/Error/RecruitmentPost/CenterEmployCardVO.swift @@ -0,0 +1,63 @@ +// +// CenterEmployCardVO.swift +// Entity +// +// Created by choijunios on 8/13/24. +// + +import Foundation + +public class CenterEmployCardVO { + + public let postId: String + public let isOngoing: Bool + + // For rendering + public let startDay: String + public let endDay: String? + public let postTitle: String + public let name: String + public let careGrade: CareGrade + public let age: Int + public let gender: Gender + public let applicantCount: Int + + public init( + isOngoing: Bool, + postId: String, + startDay: String, + endDay: String?, + postTitle: String, + name: String, + careGrade: CareGrade, + age: Int, + gender: Gender, + applicantCount: Int + ) { + self.isOngoing = isOngoing + self.postId = postId + self.startDay = startDay + self.endDay = endDay + self.postTitle = postTitle + self.name = name + self.careGrade = careGrade + self.age = age + self.gender = gender + self.applicantCount = applicantCount + } + + public static var mock: CenterEmployCardVO { + .init( + isOngoing: true, + postId: "00-00000-00000", + startDay: "12:00", + endDay: nil, + postTitle: "서울특별시 강남구 신사동", + name: "홍길동", + careGrade: .one, + age: 78, + gender: .female, + applicantCount: 78 + ) + } +} diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Button/ImagePrefixButton.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Button/ImagePrefixButton.swift index fe5da13f..a5647fa8 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Button/ImagePrefixButton.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Button/ImagePrefixButton.swift @@ -9,7 +9,14 @@ import UIKit import RxSwift import RxCocoa -public class ImagePrefixButton: TappableUIView { +public class ImageTextButton: TappableUIView { + + public enum ImagePose { + case prefix + case postfix + } + + let imagePose: ImagePose let icon: UIImageView = { let icon = UIImageView() @@ -17,14 +24,15 @@ public class ImagePrefixButton: TappableUIView { return icon }() - let label: IdleLabel = { + public let label: IdleLabel = { let label = IdleLabel(typography: .Body3) return label }() private let disposeBag = DisposeBag() - public init(iconImage: UIImage) { + public init(iconImage: UIImage, position: ImagePose) { + self.imagePose = position super.init() icon.image = iconImage @@ -41,9 +49,12 @@ public class ImagePrefixButton: TappableUIView { } private func setLayout() { - let mainStack = HStack([ + let mainStack = imagePose == .prefix ? HStack([ icon, label + ], spacing: 2, alignment: .center) : HStack([ + label, + icon ], spacing: 2, alignment: .center) NSLayoutConstraint.activate([ @@ -81,8 +92,9 @@ public class ImagePrefixButton: TappableUIView { @available(iOS 17.0, *) #Preview("Preview", traits: .defaultLayout) { - let btn = ImagePrefixButton( - iconImage: DSKitAsset.Icons.editPhoto.image + let btn = ImageTextButton( + iconImage: DSKitAsset.Icons.editPhoto.image, + position: .postfix ) btn.label.textString = "공고 수정" btn.tintColor = .black diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift index 2241ecfb..9a2daceb 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCard.swift @@ -31,7 +31,7 @@ public struct CenterEmployCardRO { self.applicantCount = applicantCount } - static let mock: CenterEmployCardRO = .init( + public static let mock: CenterEmployCardRO = .init( startDay: "2024. 07. 10", endDay: "2024. 07. 31", postTitle: "서울특별시 강남구 신사동", @@ -41,6 +41,20 @@ public struct CenterEmployCardRO { genderText: "여성", applicantCount: 2 ) + + + public static func create(_ vo: CenterEmployCardVO) -> CenterEmployCardRO { + .init( + startDay: vo.startDay, + endDay: vo.endDay ?? "채용 시까지", + postTitle: vo.postTitle, + nameText: vo.name, + careGradeText: "\(vo.careGrade.textForCellBtn)등급", + ageText: "\(vo.age)세", + genderText: vo.gender.twoLetterKoreanWord, + applicantCount: vo.applicantCount + ) + } } public protocol CenterEmployCardViewModelable { @@ -74,9 +88,10 @@ public class CenterEmployCard: TappableUIView { }() // Row5 - let editPostButton: ImagePrefixButton = { - let button = ImagePrefixButton( - iconImage: DSKitAsset.Icons.postEdit.image + let editPostButton: ImageTextButton = { + let button = ImageTextButton( + iconImage: DSKitAsset.Icons.postEdit.image, + position: .prefix ) button.icon.tintColor = DSKitAsset.Colors.gray300.color button.label.textString = "공고 수정" @@ -84,9 +99,10 @@ public class CenterEmployCard: TappableUIView { return button }() - let terminatePostButton: ImagePrefixButton = { - let button = ImagePrefixButton( - iconImage: DSKitAsset.Icons.postCheck.image + let terminatePostButton: ImageTextButton = { + let button = ImageTextButton( + iconImage: DSKitAsset.Icons.postCheck.image, + position: .prefix ) button.icon.tintColor = DSKitAsset.Colors.gray300.color button.label.textString = "채용 종료" diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift index 29bb8592..90b56d4a 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift @@ -12,6 +12,8 @@ import Entity public class CenterEmployCardCell: UITableViewCell { + public static let identifier = String(describing: CenterEmployCardCell.self) + let cardView = CenterEmployCard() private var disposables: [Disposable?]? @@ -28,6 +30,12 @@ public class CenterEmployCardCell: UITableViewCell { disposables = nil } + public override func layoutSubviews() { + super.layoutSubviews() + + contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 0, left: 20, bottom: 8, right: 20)) + } + func setAppearance() { } func setLayout() { @@ -47,7 +55,7 @@ public class CenterEmployCardCell: UITableViewCell { ]) } - public func binc(viewModel: CenterEmployCardViewModelable) { + public func bind(viewModel: CenterEmployCardViewModelable) { let disposables: [Disposable?] = [ // Output 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 93384959..4805f6ed 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 @@ -12,6 +12,8 @@ import Entity public class WorkerEmployCardCell: UITableViewCell { + public static let identifier = String(describing: WorkerEmployCardCell.self) + let tappableArea: TappableUIView = .init() let cardView = WorkerEmployCard() diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift index b9275cc0..e2e8ae8b 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift @@ -19,6 +19,9 @@ public class IdleTabBar: UIViewController { public private(set) var viewControllers: [UIViewController] = [] private var tabBarItems: [IdleTabBarItem] = [] + // View + var tabBarItemStack: UIView! + // 탭바 아이템 private var tabBarItemViews: [IdleTabBarItemViewable] = [] @@ -71,13 +74,15 @@ public class IdleTabBar: UIViewController { tabBarItemViews = tabBarItems.map { item in TextButtonType1(labelText: item.name) } - + let tabBarItemStack = HStack( tabBarItemViews, alignment: .fill, distribution: .fillEqually ) + self.tabBarItemStack = tabBarItemStack + view.addSubview(tabBarItemStack) tabBarItemStack.translatesAutoresizingMaskIntoConstraints = false @@ -122,10 +127,10 @@ public class IdleTabBar: UIViewController { let currentView = currentVC.view! NSLayoutConstraint.activate([ - currentView.topAnchor.constraint(equalTo: view.topAnchor), + currentView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), currentView.leadingAnchor.constraint(equalTo: view.leadingAnchor), currentView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - currentView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor) + currentView.bottomAnchor.constraint(equalTo: tabBarItemStack.topAnchor) ]) } diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVC.swift index 13769af0..073483ce 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVC.swift @@ -15,14 +15,14 @@ import DSKit public class CenterRecruitmentPostBoardVC: BaseViewController { enum TabBarState: Int, CaseIterable { - case current = 0 - case prev = 1 + case onGoingPost = 0 + case closedPost = 1 var titleText: String { switch self { - case .current: + case .onGoingPost: "진행 중인 공고" - case .prev: + case .closedPost: "이전 공고" } } @@ -37,10 +37,12 @@ public class CenterRecruitmentPostBoardVC: BaseViewController { } } - private var currentState: TabBarState = .current + var viewModel: CenterRecruitmentPostBoardViewModelable? + + private var currentState: TabBarState = .onGoingPost private let viewControllerDict: [TabBarState: UIViewController] = [ - .current : CurrentPostVC(), - .prev : PrevPostVC() + .onGoingPost : OnGoingPostVC(), + .closedPost : ClosedPostVC() ] // Init @@ -81,14 +83,14 @@ public class CenterRecruitmentPostBoardVC: BaseViewController { private func setLayout() { [ titleLabel, - tabBar + tabBar, ].forEach { $0.translatesAutoresizingMaskIntoConstraints = false view.addSubview($0) } NSLayoutConstraint.activate([ - titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 21), + titleLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 21), titleLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20), tabBar.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8), @@ -146,94 +148,33 @@ public class CenterRecruitmentPostBoardVC: BaseViewController { 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.safeAreaLayoutGuide.bottomAnchor) + vc.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) } -} - -// MARK: Current VC -public class CurrentPostVC: BaseViewController { - - // Init - - // 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() - } - - private func setAppearance() { - view.backgroundColor = .yellow - } - - private func setLayout() { - - } - - private func setObservable() { - - } - - public override func viewWillAppear(_ animated: Bool) { - - print("CurrentPostVC") - } -} - -// MARK: Prev VC -public class PrevPostVC: BaseViewController { - - // Init - // 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() - } - - private func setAppearance() { - view.backgroundColor = .red - } - - private func setLayout() { - - } - - private func setObservable() { + public func bind(viewModel: CenterRecruitmentPostBoardViewModelable) { - } - - public override func viewWillAppear(_ animated: Bool) { + self.viewModel = viewModel + + (viewControllerDict[.onGoingPost] as? OnGoingPostVC)?.bind(viewModel: viewModel) + (viewControllerDict[.closedPost] as? ClosedPostVC)?.bind(viewModel: viewModel) - print("PrevPostVC") + viewModel + .alert? + .drive(onNext: { [weak self] alertVO in + self?.showAlert(vo: alertVO) + }) + .disposed(by: disposeBag) } } - @available(iOS 17.0, *) #Preview("Preview", traits: .defaultLayout) { diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVM.swift new file mode 100644 index 00000000..0d18ee6f --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVM.swift @@ -0,0 +1,75 @@ +// +// CenterRecruitmentPostBoardVM.swift +// CenterFeature +// +// Created by choijunios on 8/13/24. +// + +import Foundation +import UIKit +import BaseFeature +import PresentationCore +import RxCocoa +import RxSwift +import Entity + +public protocol CenterRecruitmentPostBoardViewModelable: OnGoingPostViewModelable & ClosedPostViewModelable { + var alert: Driver? { get } +} + + +public class CenterRecruitmentPostBoardVM: CenterRecruitmentPostBoardViewModelable { + + public var requestOngoingPost: PublishRelay = .init() + public var requestClosedPost: PublishRelay = .init() + + public var ongoingPostCardVO: Driver<[CenterEmployCardVO]>? + public var closedPostCardVO: Driver<[CenterEmployCardVO]>? + + public var alert: Driver? + + public init() { + + let requestOngoingPostResult = requestOngoingPost + .flatMap { [unowned self] _ in + publishOngoingPostMocks() + } + .share() + + let requestOngoingPostSuccess = requestOngoingPostResult.compactMap { $0.value } + let requestOngoingPostFailure = requestOngoingPostResult.compactMap { $0.error } + + ongoingPostCardVO = requestOngoingPostSuccess.asDriver(onErrorJustReturn: []) + + + let requestClosedPostResult = requestClosedPost + .flatMap { [unowned self] _ in + publishClosedPostMocks() + } + .share() + + let requestClosedPostSuccess = requestClosedPostResult.compactMap { $0.value } + let requestClosedPostFailure = requestClosedPostResult.compactMap { $0.error } + + closedPostCardVO = requestClosedPostSuccess.asDriver(onErrorJustReturn: []) + + alert = Observable.merge( + requestOngoingPostFailure, + requestClosedPostFailure + ).map { error in + DefaultAlertContentVO( + title: "시스템 오류", + message: error.message + ) + } + .asDriver(onErrorJustReturn: .default) + } + + func publishOngoingPostMocks() -> Single> { + return .just(.success((0...10).map { _ in CenterEmployCardVO.mock })) + } + + func publishClosedPostMocks() -> Single> { + return .just(.success((0...10).map { _ in CenterEmployCardVO.mock })) + } +} diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/ClosedPostVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/ClosedPostVC.swift new file mode 100644 index 00000000..91429431 --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/ClosedPostVC.swift @@ -0,0 +1,128 @@ +// +// ClosedPostVC.swift +// CenterFeature +// +// Created by choijunios on 8/13/24. +// + +import UIKit +import BaseFeature +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit + +public protocol ClosedPostViewModelable { + + var closedPostCardVO: Driver<[CenterEmployCardVO]>? { get } + var requestClosedPost: PublishRelay { get } +} + +public class ClosedPostVC: BaseViewController { + + typealias Cell = CenterEmployCardCell + + // View + let postTableView: UITableView = { + let tableView = UITableView() + tableView.rowHeight = UITableView.automaticDimension + tableView.register(Cell.self, forCellReuseIdentifier: Cell.identifier) + return tableView + }() + + let tableHeader = BoardSortigHeaderView() + + let closedPostCardVO: BehaviorRelay<[CenterEmployCardVO]> = .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() + 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: ClosedPostViewModelable) { + + // Output + viewModel + .closedPostCardVO? + .drive(onNext: { [weak self] vos in + guard let self else { return } + closedPostCardVO.accept(vos) + postTableView.reloadData() + }) + .disposed(by: disposeBag) + + // Input + rx.viewWillAppear + .map { _ in } + .bind(to: viewModel.requestClosedPost) + .disposed(by: disposeBag) + } +} + +extension ClosedPostVC: UITableViewDataSource, UITableViewDelegate { + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + closedPostCardVO.value.count + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell + let vo = closedPostCardVO.value[indexPath.row] + let vm = CenterEmployCardVM(vo: vo) + cell.bind(viewModel: vm) + cell.selectionStyle = .none + return cell + } +} diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/OnGoingPostVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/OnGoingPostVC.swift new file mode 100644 index 00000000..25bb877e --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/OnGoingPostVC.swift @@ -0,0 +1,194 @@ +// +// OnGoingPostVC.swift +// CenterFeature +// +// Created by choijunios on 8/13/24. +// + +import UIKit +import BaseFeature +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit + +public protocol OnGoingPostViewModelable { + + var ongoingPostCardVO: Driver<[CenterEmployCardVO]>? { get } + var requestOngoingPost: PublishRelay { get } +} + +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), + ]) + } +} + +public class OnGoingPostVC: BaseViewController { + + typealias Cell = CenterEmployCardCell + + // View + 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<[CenterEmployCardVO]> = .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: OnGoingPostViewModelable) { + + // Output + viewModel + .ongoingPostCardVO? + .drive(onNext: { [weak self] vos in + guard let self else { return } + ongoingPostCardVO.accept(vos) + postTableView.reloadData() + }) + .disposed(by: disposeBag) + + // Input + rx.viewWillAppear + .map { _ in } + .bind(to: viewModel.requestOngoingPost) + .disposed(by: disposeBag) + } +} + +extension OnGoingPostVC: 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 + let vo = ongoingPostCardVO.value[indexPath.section] + let vm = CenterEmployCardVM(vo: vo) + cell.bind(viewModel: vm) + cell.selectionStyle = .none + return cell + } +} + +// MARK: 카드 뷰에 사용될 ViewModel +class CenterEmployCardVM: CenterEmployCardViewModelable { + + // Init + var id: String + + // Output + var renderObject: Driver? + + // Input + var cardClicked: PublishRelay = .init() + var checkApplicantBtnClicked: PublishRelay = .init() + var editPostBtnClicked: PublishRelay = .init() + var terminatePostBtnClicked: PublishRelay = .init() + + init(vo: CenterEmployCardVO) { + self.id = vo.postId + + // MARK: RenderObject + let publishRelay: BehaviorRelay = .init(value: .mock) + renderObject = publishRelay.asDriver(onErrorJustReturn: .mock) + + publishRelay.accept(CenterEmployCardRO.create(vo)) + + // MARK: 버튼 처리 + } +} diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift index 0e0b7820..d4fc8952 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift @@ -6,6 +6,7 @@ // import UIKit +import CenterFeature import PresentationCore public class RecruitmentManagementCoordinator: ChildCoordinator { @@ -25,22 +26,26 @@ public class RecruitmentManagementCoordinator: ChildCoordinator { } public func start() { - let vc = RecuitmentManagementVC(coordinator: self) + let vc = CenterRecruitmentPostBoardVC() + let vm = CenterRecruitmentPostBoardVM() + vc.bind(viewModel: vm) navigationController.pushViewController(vc, animated: false) } public func coordinatorDidFinish() { - + popViewController() + parent?.removeChildCoordinator(self) } } extension RecruitmentManagementCoordinator { func showCenterRegisterScreen() { - parent?.centerProfileRegister() + } func showRegisterRecruitmentPostScreen() { - parent?.registerRecruitmentPost() + } + } diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Main/CenterMainCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Main/CenterMainCoordinatable.swift index 6760b66b..7fdb10a3 100644 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Main/CenterMainCoordinatable.swift +++ b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/Main/CenterMainCoordinatable.swift @@ -8,6 +8,5 @@ import Foundation public protocol CenterMainCoordinatable: ParentCoordinator { - func centerProfileRegister() - func registerRecruitmentPost() + } diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CenterPostBoardCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CenterPostBoardCoordinatable.swift new file mode 100644 index 00000000..ea8a3374 --- /dev/null +++ b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CenterPostBoardCoordinatable.swift @@ -0,0 +1,15 @@ +// +// CenterPostBoardCoordinatable.swift +// PresentationCore +// +// Created by choijunios on 8/13/24. +// + +import Foundation + +public protocol CenterPostBoardTabCoordinatable: ParentCoordinator { + + func showApplicantScreen() + func postDetailScreen() + func showPostEditScreen() +} From 6161eeec8789276032c93e16b7dfdde66c12b89a Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 13 Aug 2024 15:42:00 +0900 Subject: [PATCH 12/20] =?UTF-8?q?[IDLE-141]=20ViewModel=EC=9D=84=20?= =?UTF-8?q?=EC=B0=B8=EC=A1=B0=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CenterProfileRegisterCoordinator.swift | 0 .../RegisterPostCoordinator.swift | 0 .../Post/Center/CenterEmployCardCell.swift | 6 ++++ .../Board/CenterRecruitmentPostBoardVM.swift | 29 +++++++++++++++++++ .../Board/SubVC/OnGoingPostVC.swift | 28 ------------------ 5 files changed, 35 insertions(+), 28 deletions(-) rename project/Projects/App/Sources/RootCoordinator/Main/Center /{ => TabCoordinator}/CenterProfileRegisterCoordinator.swift (100%) rename project/Projects/App/Sources/RootCoordinator/Main/Center /{ => TabCoordinator}/RegisterPostCoordinator.swift (100%) diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterProfileRegisterCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Center /TabCoordinator/CenterProfileRegisterCoordinator.swift similarity index 100% rename from project/Projects/App/Sources/RootCoordinator/Main/Center /CenterProfileRegisterCoordinator.swift rename to project/Projects/App/Sources/RootCoordinator/Main/Center /TabCoordinator/CenterProfileRegisterCoordinator.swift diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Center /RegisterPostCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Center /TabCoordinator/RegisterPostCoordinator.swift similarity index 100% rename from project/Projects/App/Sources/RootCoordinator/Main/Center /RegisterPostCoordinator.swift rename to project/Projects/App/Sources/RootCoordinator/Main/Center /TabCoordinator/RegisterPostCoordinator.swift diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift index 90b56d4a..bb6acaf3 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Center/CenterEmployCardCell.swift @@ -12,6 +12,8 @@ import Entity public class CenterEmployCardCell: UITableViewCell { + var viewModel: CenterEmployCardViewModelable? + public static let identifier = String(describing: CenterEmployCardCell.self) let cardView = CenterEmployCard() @@ -26,6 +28,8 @@ public class CenterEmployCardCell: UITableViewCell { public required init?(coder: NSCoder) { fatalError() } public override func prepareForReuse() { + viewModel = nil + disposables?.forEach { $0?.dispose() } disposables = nil } @@ -57,6 +61,8 @@ public class CenterEmployCardCell: UITableViewCell { public func bind(viewModel: CenterEmployCardViewModelable) { + self.viewModel = viewModel + let disposables: [Disposable?] = [ // Output viewModel diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVM.swift index 0d18ee6f..ec40e97e 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVM.swift @@ -12,6 +12,7 @@ import PresentationCore import RxCocoa import RxSwift import Entity +import DSKit public protocol CenterRecruitmentPostBoardViewModelable: OnGoingPostViewModelable & ClosedPostViewModelable { var alert: Driver? { get } @@ -73,3 +74,31 @@ public class CenterRecruitmentPostBoardVM: CenterRecruitmentPostBoardViewModelab return .just(.success((0...10).map { _ in CenterEmployCardVO.mock })) } } + +// MARK: 카드 뷰에 사용될 ViewModel +class CenterEmployCardVM: CenterEmployCardViewModelable { + + // Init + var id: String + + // Output + var renderObject: Driver? + + // Input + var cardClicked: PublishRelay = .init() + var checkApplicantBtnClicked: PublishRelay = .init() + var editPostBtnClicked: PublishRelay = .init() + var terminatePostBtnClicked: PublishRelay = .init() + + init(vo: CenterEmployCardVO) { + self.id = vo.postId + + // MARK: RenderObject + let publishRelay: BehaviorRelay = .init(value: .mock) + renderObject = publishRelay.asDriver(onErrorJustReturn: .mock) + + publishRelay.accept(CenterEmployCardRO.create(vo)) + + // MARK: 버튼 처리 + } +} diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/OnGoingPostVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/OnGoingPostVC.swift index 25bb877e..efa8b9b3 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/OnGoingPostVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/OnGoingPostVC.swift @@ -164,31 +164,3 @@ extension OnGoingPostVC: UITableViewDataSource, UITableViewDelegate { return cell } } - -// MARK: 카드 뷰에 사용될 ViewModel -class CenterEmployCardVM: CenterEmployCardViewModelable { - - // Init - var id: String - - // Output - var renderObject: Driver? - - // Input - var cardClicked: PublishRelay = .init() - var checkApplicantBtnClicked: PublishRelay = .init() - var editPostBtnClicked: PublishRelay = .init() - var terminatePostBtnClicked: PublishRelay = .init() - - init(vo: CenterEmployCardVO) { - self.id = vo.postId - - // MARK: RenderObject - let publishRelay: BehaviorRelay = .init(value: .mock) - renderObject = publishRelay.asDriver(onErrorJustReturn: .mock) - - publishRelay.accept(CenterEmployCardRO.create(vo)) - - // MARK: 버튼 처리 - } -} From 22f2922df3e5ad30026eb043c2e4427f9cbb4174 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 13 Aug 2024 18:47:13 +0900 Subject: [PATCH 13/20] =?UTF-8?q?[IDLE-141]=20=EC=A7=80=EC=9B=90=EC=9E=90?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EC=B0=BD=EC=9C=BC=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=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 --- .../Main/Center /CenterMainCoordinator.swift | 4 +- .../CenterProfileRegisterCoordinator.swift | 0 .../RegisterPostCoordinator.swift | 0 .../Entity/VO/Employ/PostApplicantVO.swift | 45 ++++ .../Worker/ApplyScreen/ApplicantCard.swift | 15 +- .../ApplyScreen/ApplicantCardCell.swift | 10 +- .../Card/Post/Worker/PostInfoCardView.swift | 8 + .../CheckApplicant/CheckApplicantVC.swift | 247 ++++++++++++++++++ .../CheckApplicant/CheckApplicantVM.swift | 51 ++++ .../CenterRecruitmentPostBoardVC.swift | 0 .../CenterRecruitmentPostBoardVM.swift | 26 +- .../Board/{ => Post}/SubVC/ClosedPostVC.swift | 19 +- .../{ => Post}/SubVC/OnGoingPostVC.swift | 86 +++--- .../CheckApplicantCoordinator.swift | 56 ++++ .../RecruitmentManagementCoordinator.swift | 30 ++- .../Center/View/RecuitmentManagementVC.swift | 11 - .../RecruitmentManagementCoordinatable.swift | 13 + 17 files changed, 549 insertions(+), 72 deletions(-) rename project/Projects/App/Sources/RootCoordinator/Main/Center /{TabCoordinator => OtherCoordinator}/CenterProfileRegisterCoordinator.swift (100%) rename project/Projects/App/Sources/RootCoordinator/Main/Center /{TabCoordinator => OtherCoordinator}/RegisterPostCoordinator.swift (100%) create mode 100644 project/Projects/Domain/Entity/VO/Employ/PostApplicantVO.swift create mode 100644 project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift create mode 100644 project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift rename project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/{ => Post}/CenterRecruitmentPostBoardVC.swift (100%) rename project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/{ => Post}/CenterRecruitmentPostBoardVM.swift (79%) rename project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/{ => Post}/SubVC/ClosedPostVC.swift (89%) rename project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/{ => Post}/SubVC/OnGoingPostVC.swift (91%) create mode 100644 project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift create mode 100644 project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/RecruitmentManagementCoordinatable.swift diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift index c1e4f148..c3bc94f1 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift @@ -52,7 +52,7 @@ class CenterMainCoordinator: CenterMainCoordinatable { func createNavForTab(tab: CenterMainScreen) -> UINavigationController { let tabNavController = UINavigationController() - tabNavController.setNavigationBarHidden(false, animated: false) + tabNavController.setNavigationBarHidden(true, animated: false) startTabCoordinator( tab: tab, @@ -64,7 +64,7 @@ class CenterMainCoordinator: CenterMainCoordinatable { // #2. 생성한 컨트롤러를 각 탭별 Coordinator에 전달 func startTabCoordinator(tab: CenterMainScreen, navigationController: UINavigationController) { - var coordinator: ChildCoordinator! + var coordinator: Coordinator! switch tab { case .recruitmentManage: diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Center /TabCoordinator/CenterProfileRegisterCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Center /OtherCoordinator/CenterProfileRegisterCoordinator.swift similarity index 100% rename from project/Projects/App/Sources/RootCoordinator/Main/Center /TabCoordinator/CenterProfileRegisterCoordinator.swift rename to project/Projects/App/Sources/RootCoordinator/Main/Center /OtherCoordinator/CenterProfileRegisterCoordinator.swift diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Center /TabCoordinator/RegisterPostCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Center /OtherCoordinator/RegisterPostCoordinator.swift similarity index 100% rename from project/Projects/App/Sources/RootCoordinator/Main/Center /TabCoordinator/RegisterPostCoordinator.swift rename to project/Projects/App/Sources/RootCoordinator/Main/Center /OtherCoordinator/RegisterPostCoordinator.swift diff --git a/project/Projects/Domain/Entity/VO/Employ/PostApplicantVO.swift b/project/Projects/Domain/Entity/VO/Employ/PostApplicantVO.swift new file mode 100644 index 00000000..a1e29574 --- /dev/null +++ b/project/Projects/Domain/Entity/VO/Employ/PostApplicantVO.swift @@ -0,0 +1,45 @@ +// +// PostApplicantVO.swift +// Entity +// +// Created by choijunios on 8/13/24. +// + +import Foundation + +public struct PostApplicantVO { + + // + public let workerId: String + + // For Render + public let profileUrl: URL? + public let isJobFinding: Bool + public let isStared: Bool + public let name: String + public let age: Int + public let gender: Gender + public let expYear: Int? + + public init(workerId: String, profileUrl: URL?, isJobFinding: Bool, isStared: Bool, name: String, age: Int, gender: Gender, expYear: Int?) { + self.workerId = workerId + self.profileUrl = profileUrl + self.isJobFinding = isJobFinding + self.isStared = isStared + self.name = name + self.age = age + self.gender = gender + self.expYear = expYear + } + + public static let mock: PostApplicantVO = .init( + workerId: "11111-222222-333333", + profileUrl: URL(string: "https://dummyimage.com/600x400/00ffbf/0011ff&text=worker+profile"), + isJobFinding: false, + isStared: false, + name: "홍길동", + age: 51, + gender: .female, + expYear: nil + ) +} diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCard.swift index 9714cec7..e23fa599 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCard.swift @@ -11,6 +11,7 @@ import RxCocoa import RxSwift import Entity import Kingfisher +import Entity public struct ApplicantCardRO { @@ -32,7 +33,7 @@ public struct ApplicantCardRO { self.expText = expText } - static let mock: ApplicantCardRO = .init( + public static let mock: ApplicantCardRO = .init( profileUrl: URL(string: "https://dummyimage.com/600x400/00ffbf/0011ff&text=worker+profile"), isJobFinding: false, isStared: false, @@ -41,6 +42,18 @@ public struct ApplicantCardRO { genderText: "여성", expText: "1년차" ) + + public static func create(vo: PostApplicantVO) -> ApplicantCardRO { + .init( + profileUrl: vo.profileUrl, + isJobFinding: vo.isJobFinding, + isStared: vo.isStared, + name: vo.name, + ageText: "\(vo.age)세", + genderText: vo.gender.twoLetterKoreanWord, + expText: vo.expYear == nil ? "신입" : "\(vo.expYear!)년차" + ) + } } public protocol ApplicantCardViewModelable { diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift index 81a9bfb6..dd8dabe8 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift @@ -13,6 +13,8 @@ import Entity public class ApplicantCardCell: UITableViewCell { + public static let identifier = String(describing: ApplicantCardCell.self) + let cardView = ApplicantCard() private var disposables: [Disposable?]? @@ -31,6 +33,12 @@ public class ApplicantCardCell: UITableViewCell { func setAppearance() { } + public override func layoutSubviews() { + super.layoutSubviews() + + contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 0, left: 0, bottom: 8, right: 0)) + } + func setLayout() { [ @@ -48,7 +56,7 @@ public class ApplicantCardCell: UITableViewCell { ]) } - public func binc(viewModel: ApplicantCardViewModelable) { + public func bind(viewModel: ApplicantCardViewModelable) { let disposables: [Disposable?] = [ // Output diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/PostInfoCardView.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/PostInfoCardView.swift index e642e14d..5c604efe 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/PostInfoCardView.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/PostInfoCardView.swift @@ -64,6 +64,14 @@ public class PostInfoCardView: TappableUIView { } private func setObservable() { } + + public func bind(vo: CenterEmployCardVO) { + let ro = CenterEmployCardRO.create(vo) + contentView.durationLabel.textString = "\(ro.startDay) ~ \(ro.endDay)" + contentView.informationLabel.textString = "\(ro.careGradeText) \(ro.ageText) \(ro.genderText)" + contentView.nameLabel.textString = ro.nameText + contentView.postTitleLabel.textString = ro.postTitle + } } @available(iOS 17.0, *) diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift new file mode 100644 index 00000000..cabff188 --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift @@ -0,0 +1,247 @@ +// +// CheckApplicantVC.swift +// CenterFeature +// +// Created by choijunios on 8/13/24. +// + +import UIKit +import BaseFeature +import PresentationCore +import RxCocoa +import RxSwift +import Entity +import DSKit + +class MyTableView: UITableView { + + override var intrinsicContentSize: CGSize { + + return CGSize(width: contentSize.width, height: contentSize.height+self.contentInset.top+self.contentInset.bottom) + } + + override func layoutSubviews() { + super.layoutSubviews() + + invalidateIntrinsicContentSize() + } +} + +public protocol CheckApplicantViewModelable { + // Input + var requestpostApplicantVO: PublishRelay { get } + + // Output + var postApplicantVO: Driver<[PostApplicantVO]>? { get } + var postCardVO: CenterEmployCardVO { get } + var alert: Driver? { get } +} + +public class CheckApplicantVC: BaseViewController { + + typealias Cell = ApplicantCardCell + + var viewModel: CheckApplicantViewModelable? + + // Init + + // View + let navigationBar: NavigationBarType1 = { + let view = NavigationBarType1(navigationTitle: "지원자 확인") + return view + }() + let postSummaryCard: PostInfoCardView = { + let view = PostInfoCardView() + return view + }() + + let applicantTitleLabel: IdleLabel = { + let label = IdleLabel(typography: .Subtitle2) + label.textString = "위 공고에 지원한 보호사 목록이에요." + return label + }() + + let applicantTableView: MyTableView = { + let tableView = MyTableView() + return tableView + }() + + // Observable + private let disposeBag = DisposeBag() + + let postApplicantVO: BehaviorRelay<[PostApplicantVO]> = .init(value: []) + + 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 setAppearance() { + view.backgroundColor = DSKitAsset.Colors.gray0.color + } + + private func setLayout() { + + let divider = Spacer(height: 8) + divider.backgroundColor = DSKitAsset.Colors.gray050.color + + + let contentView = UIView() + contentView.backgroundColor = DSKitAsset.Colors.gray0.color + + [ + postSummaryCard, + + divider, + + applicantTitleLabel, + applicantTableView, + ].forEach { + contentView.addSubview($0) + $0.translatesAutoresizingMaskIntoConstraints = false + } + + NSLayoutConstraint.activate([ + postSummaryCard.topAnchor.constraint(equalTo: contentView.topAnchor), + postSummaryCard.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20), + postSummaryCard.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20), + + divider.topAnchor.constraint(equalTo: postSummaryCard.bottomAnchor, constant: 20), + divider.leftAnchor.constraint(equalTo: contentView.leftAnchor), + divider.rightAnchor.constraint(equalTo: contentView.rightAnchor), + + applicantTitleLabel.topAnchor.constraint(equalTo: divider.topAnchor, constant: 20), + applicantTitleLabel.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20), + + applicantTableView.topAnchor.constraint(equalTo: applicantTitleLabel.bottomAnchor, constant: 20), + applicantTableView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 20), + applicantTableView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20), + applicantTableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + ]) + + let scrollView = UIScrollView() + scrollView.delaysContentTouches = false + scrollView.contentInset = .init( + top: 36, + left: 0, + bottom: 20, + right: 0 + ) + let contentGuide = scrollView.contentLayoutGuide + let frameGuide = scrollView.frameLayoutGuide + + scrollView.addSubview(contentView) + contentView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + contentView.topAnchor.constraint(equalTo: contentGuide.topAnchor), + contentView.leftAnchor.constraint(equalTo: contentGuide.leftAnchor), + contentView.rightAnchor.constraint(equalTo: contentGuide.rightAnchor), + contentView.bottomAnchor.constraint(equalTo: contentGuide.bottomAnchor), + + contentView.widthAnchor.constraint(equalTo: frameGuide.widthAnchor), + ]) + + [ + navigationBar, + scrollView, + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + view.addSubview($0) + } + + NSLayoutConstraint.activate([ + navigationBar.topAnchor.constraint(equalTo: view.topAnchor, constant: 21), + navigationBar.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 12), + + scrollView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), + scrollView.leftAnchor.constraint(equalTo: view.leftAnchor), + scrollView.rightAnchor.constraint(equalTo: view.rightAnchor), + scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + + } + + private func setTableView() { + applicantTableView.rowHeight = UITableView.automaticDimension + applicantTableView.register(Cell.self, forCellReuseIdentifier: Cell.identifier) + applicantTableView.isScrollEnabled = false + applicantTableView.dataSource = self + applicantTableView.delegate = self + applicantTableView.separatorStyle = .none + applicantTableView.delaysContentTouches = false + } + + private func setObservable() { } + + public func bind(viewModel: CheckApplicantViewModelable) { + + self.viewModel = viewModel + + postSummaryCard + .bind(vo: viewModel.postCardVO) + + viewModel + .postApplicantVO? + .drive(onNext: { [weak self] vo in + guard let self else { return } + postApplicantVO.accept(vo) + applicantTableView.reloadData() + }) + .disposed(by: disposeBag) + + rx.viewWillAppear + .map { _ in } + .bind(to: viewModel.requestpostApplicantVO) + .disposed(by: disposeBag) + } +} + +extension CheckApplicantVC: UITableViewDataSource, UITableViewDelegate { + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + postApplicantVO.value.count + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell + let vm = ApplicantCardVM(vo: postApplicantVO.value[indexPath.row]) + cell.bind(viewModel: vm) + cell.selectionStyle = .none + return cell + } +} + +// MARK: ApplicantCardVM +public class ApplicantCardVM: ApplicantCardViewModelable { + + // Init + let id: String + + public var showProfileButtonClicked: PublishRelay = .init() + public var employButtonClicked: PublishRelay = .init() + public var staredThisWorker: PublishRelay = .init() + + public var renderObject: Driver? + + public init(vo: PostApplicantVO) { + self.id = vo.workerId + + // MARK: RenderObject + let publishRelay: BehaviorRelay = .init(value: .mock) + renderObject = publishRelay.asDriver(onErrorJustReturn: .mock) + + publishRelay.accept(ApplicantCardRO.create(vo: vo)) + + // MARK: 버튼 처리 + } +} diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift new file mode 100644 index 00000000..091bcb46 --- /dev/null +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift @@ -0,0 +1,51 @@ +// +// CheckApplicantVM.swift +// CenterFeature +// +// Created by choijunios on 8/13/24. +// + +import Foundation +import RxSwift +import RxCocoa +import Entity +import DSKit + +public class CheckApplicantVM: CheckApplicantViewModelable { + + public var requestpostApplicantVO: PublishRelay = .init() + public var postCardVO: CenterEmployCardVO + + public var postApplicantVO: Driver<[PostApplicantVO]>? + public var alert: RxCocoa.Driver? + + public init(postCardVO: CenterEmployCardVO) { + self.postCardVO = postCardVO + + let requestPostApplicantVOResult = requestpostApplicantVO + .flatMap { [unowned self] _ in + publishPostApplicantVOMocks() + } + .share() + + let requestPostApplicantSuccess = requestPostApplicantVOResult.compactMap { $0.value } + let requestPostApplicantFailure = requestPostApplicantVOResult.compactMap { $0.error } + + postApplicantVO = requestPostApplicantSuccess.asDriver(onErrorJustReturn: []) + + alert = requestPostApplicantFailure + .map { error in + + DefaultAlertContentVO( + title: "시스템 오류", + message: error.message + ) + } + .asDriver(onErrorJustReturn: .default) + } + + func publishPostApplicantVOMocks() -> Single> { + + .just(.success((0...10).map { _ in PostApplicantVO.mock })) + } +} diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVC.swift similarity index 100% rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVC.swift rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVC.swift diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVM.swift similarity index 79% rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVM.swift rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVM.swift index ec40e97e..1587474d 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CenterRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVM.swift @@ -20,6 +20,8 @@ public protocol CenterRecruitmentPostBoardViewModelable: OnGoingPostViewModelabl public class CenterRecruitmentPostBoardVM: CenterRecruitmentPostBoardViewModelable { + + weak var coordinator: RecruitmentManagementCoordinatable? public var requestOngoingPost: PublishRelay = .init() public var requestClosedPost: PublishRelay = .init() @@ -29,7 +31,8 @@ public class CenterRecruitmentPostBoardVM: CenterRecruitmentPostBoardViewModelab public var alert: Driver? - public init() { + public init(coordinator: RecruitmentManagementCoordinatable?) { + self.coordinator = coordinator let requestOngoingPostResult = requestOngoingPost .flatMap { [unowned self] _ in @@ -73,13 +76,22 @@ public class CenterRecruitmentPostBoardVM: CenterRecruitmentPostBoardViewModelab func publishClosedPostMocks() -> Single> { return .just(.success((0...10).map { _ in CenterEmployCardVO.mock })) } + + public func createCellVM(vo: CenterEmployCardVO) -> any CenterEmployCardViewModelable { + CenterEmployCardVM( + vo: vo, + coordinator: coordinator + ) + } } // MARK: 카드 뷰에 사용될 ViewModel class CenterEmployCardVM: CenterEmployCardViewModelable { + weak var coordinator: RecruitmentManagementCoordinatable? + // Init - var id: String + let id: String // Output var renderObject: Driver? @@ -90,8 +102,11 @@ class CenterEmployCardVM: CenterEmployCardViewModelable { var editPostBtnClicked: PublishRelay = .init() var terminatePostBtnClicked: PublishRelay = .init() - init(vo: CenterEmployCardVO) { + let disposeBag = DisposeBag() + + init(vo: CenterEmployCardVO, coordinator: RecruitmentManagementCoordinatable? = nil) { self.id = vo.postId + self.coordinator = coordinator // MARK: RenderObject let publishRelay: BehaviorRelay = .init(value: .mock) @@ -100,5 +115,10 @@ class CenterEmployCardVM: CenterEmployCardViewModelable { publishRelay.accept(CenterEmployCardRO.create(vo)) // MARK: 버튼 처리 + checkApplicantBtnClicked + .subscribe(onNext: { [weak self] _ in + self?.coordinator?.showCheckingApplicantScreen(vo) + }) + .disposed(by: disposeBag) } } diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/ClosedPostVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/ClosedPostVC.swift similarity index 89% rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/ClosedPostVC.swift rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/ClosedPostVC.swift index 91429431..031381bb 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/ClosedPostVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/ClosedPostVC.swift @@ -17,12 +17,16 @@ public protocol ClosedPostViewModelable { var closedPostCardVO: Driver<[CenterEmployCardVO]>? { get } var requestClosedPost: PublishRelay { get } + + func createCellVM(vo: CenterEmployCardVO) -> CenterEmployCardViewModelable } public class ClosedPostVC: BaseViewController { typealias Cell = CenterEmployCardCell + var viewModel: ClosedPostViewModelable? + // View let postTableView: UITableView = { let tableView = UITableView() @@ -33,11 +37,11 @@ public class ClosedPostVC: BaseViewController { let tableHeader = BoardSortigHeaderView() - let closedPostCardVO: BehaviorRelay<[CenterEmployCardVO]> = .init(value: []) - // Observable private let disposeBag = DisposeBag() + let closedPostCardVO: BehaviorRelay<[CenterEmployCardVO]> = .init(value: []) + public init() { super.init(nibName: nil, bundle: nil) } @@ -92,6 +96,8 @@ public class ClosedPostVC: BaseViewController { public func bind(viewModel: ClosedPostViewModelable) { + self.viewModel = viewModel + // Output viewModel .closedPostCardVO? @@ -119,10 +125,13 @@ extension ClosedPostVC: UITableViewDataSource, UITableViewDelegate { public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell - let vo = closedPostCardVO.value[indexPath.row] - let vm = CenterEmployCardVM(vo: vo) - cell.bind(viewModel: vm) cell.selectionStyle = .none + + if let viewModel = self.viewModel { + let vo = closedPostCardVO.value[indexPath.row] + let vm = viewModel.createCellVM(vo: vo) + cell.bind(viewModel: vm) + } return cell } } diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/OnGoingPostVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/OnGoingPostVC.swift similarity index 91% rename from project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/OnGoingPostVC.swift rename to project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/OnGoingPostVC.swift index efa8b9b3..1f8bcce9 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/SubVC/OnGoingPostVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/SubVC/OnGoingPostVC.swift @@ -17,48 +17,16 @@ public protocol OnGoingPostViewModelable { var ongoingPostCardVO: Driver<[CenterEmployCardVO]>? { get } var requestOngoingPost: PublishRelay { get } -} - -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), - ]) - } + func createCellVM(vo: CenterEmployCardVO) -> CenterEmployCardViewModelable } public class OnGoingPostVC: BaseViewController { typealias Cell = CenterEmployCardCell + var viewModel: OnGoingPostViewModelable? + // View let postTableView: UITableView = { let tableView = UITableView() @@ -130,6 +98,8 @@ public class OnGoingPostVC: BaseViewController { public func bind(viewModel: OnGoingPostViewModelable) { + self.viewModel = viewModel + // Output viewModel .ongoingPostCardVO? @@ -157,10 +127,50 @@ extension OnGoingPostVC: UITableViewDataSource, UITableViewDelegate { public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell - let vo = ongoingPostCardVO.value[indexPath.section] - let vm = CenterEmployCardVM(vo: vo) - cell.bind(viewModel: vm) 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 } } + +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/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift new file mode 100644 index 00000000..a97be369 --- /dev/null +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift @@ -0,0 +1,56 @@ +// +// CheckApplicantCoordinator.swift +// RootFeature +// +// Created by choijunios on 8/13/24. +// + +import UIKit +import PresentationCore +import UseCaseInterface +import Entity +import CenterFeature +import WorkerFeature +import BaseFeature + +public class CheckApplicantCoordinator: ParentCoordinator { + + public var childCoordinators: [any PresentationCore.Coordinator] = [] + + public struct Dependency { + let navigationController: UINavigationController + let centerEmployCardVO: CenterEmployCardVO + } + + public weak var viewControllerRef: UIViewController? + public weak var parent: ParentCoordinator? + + public let navigationController: UINavigationController + let centerEmployCardVO: CenterEmployCardVO + + + public init( + dependency: Dependency + ) { + self.navigationController = dependency.navigationController + self.centerEmployCardVO = dependency.centerEmployCardVO + } + + deinit { + printIfDebug("\(String(describing: RegisterRecruitmentCoordinator.self))") + } + + public func start() { + let vc = CheckApplicantVC() + let vm = CheckApplicantVM( + postCardVO: centerEmployCardVO + ) + vc.bind(viewModel: vm) + viewControllerRef = vc + navigationController.pushViewController(vc, animated: true) + } + + public func coordinatorDidFinish() { + parent?.removeChildCoordinator(self) + } +} diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift index d4fc8952..39018096 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift @@ -8,8 +8,12 @@ import UIKit import CenterFeature import PresentationCore +import Entity -public class RecruitmentManagementCoordinator: ChildCoordinator { + +public class RecruitmentManagementCoordinator: RecruitmentManagementCoordinatable { + + public var childCoordinators: [any PresentationCore.Coordinator] = [] public weak var viewControllerRef: UIViewController? @@ -27,8 +31,9 @@ public class RecruitmentManagementCoordinator: ChildCoordinator { public func start() { let vc = CenterRecruitmentPostBoardVC() - let vm = CenterRecruitmentPostBoardVM() + let vm = CenterRecruitmentPostBoardVM(coordinator: self) vc.bind(viewModel: vm) + viewControllerRef = vc navigationController.pushViewController(vc, animated: false) } @@ -38,14 +43,17 @@ public class RecruitmentManagementCoordinator: ChildCoordinator { } } -extension RecruitmentManagementCoordinator { - - func showCenterRegisterScreen() { - - } - - func showRegisterRecruitmentPostScreen() { - +public extension RecruitmentManagementCoordinator { + + func showCheckingApplicantScreen(_ centerEmployCardVO: CenterEmployCardVO) { + let coordinator = CheckApplicantCoordinator( + dependency: .init( + navigationController: navigationController, + centerEmployCardVO: centerEmployCardVO + ) + ) + addChildCoordinator(coordinator) + coordinator.parent = self + coordinator.start() } - } diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/View/RecuitmentManagementVC.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/View/RecuitmentManagementVC.swift index b296948a..4532a107 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/View/RecuitmentManagementVC.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/View/RecuitmentManagementVC.swift @@ -41,17 +41,6 @@ public class RecuitmentManagementVC: UIViewController { button2.setTitleColor(.black, for: .normal) button2.isUserInteractionEnabled = true - button1.rx.tap - .subscribe { [weak coordinator] _ in - coordinator?.showCenterRegisterScreen() - } - .disposed(by: dispoesBag) - - button2.rx.tap - .subscribe { [weak coordinator] _ in - coordinator?.showRegisterRecruitmentPostScreen() - } - .disposed(by: dispoesBag) [ label, button1, diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/RecruitmentManagementCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/RecruitmentManagementCoordinatable.swift new file mode 100644 index 00000000..5ca23ec9 --- /dev/null +++ b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/RecruitmentManagementCoordinatable.swift @@ -0,0 +1,13 @@ +// +// RecruitmentManagementCoordinatable.swift +// PresentationCore +// +// Created by choijunios on 8/13/24. +// + +import Entity + +public protocol RecruitmentManagementCoordinatable: ParentCoordinator { + + func showCheckingApplicantScreen(_ centerEmployCardVO: CenterEmployCardVO) +} From 658e0eceec1e2cfd877667336cc96dac882e9166 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Tue, 13 Aug 2024 19:52:49 +0900 Subject: [PATCH 14/20] =?UTF-8?q?[IDLE-141]=20=EC=A7=80=EC=9B=90=EC=9E=90?= =?UTF-8?q?=20=ED=99=95=EC=9D=B8=EC=B0=BD=20=EB=82=98=EA=B0=80=EA=B8=B0=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CheckApplicant/CheckApplicantVC.swift | 15 ++++------ .../CheckApplicant/CheckApplicantVM.swift | 29 ++++++++++++++++++- .../CheckApplicantCoordinator.swift | 12 ++++++-- .../CheckApplicantCoordinatable.swift | 13 +++++++++ 4 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CheckApplicantCoordinatable.swift diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift index cabff188..3e4a4314 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift @@ -27,16 +27,6 @@ class MyTableView: UITableView { } } -public protocol CheckApplicantViewModelable { - // Input - var requestpostApplicantVO: PublishRelay { get } - - // Output - var postApplicantVO: Driver<[PostApplicantVO]>? { get } - var postCardVO: CenterEmployCardVO { get } - var alert: Driver? { get } -} - public class CheckApplicantVC: BaseViewController { typealias Cell = ApplicantCardCell @@ -187,6 +177,11 @@ public class CheckApplicantVC: BaseViewController { self.viewModel = viewModel + navigationBar + .eventPublisher + .bind(to: viewModel.exitButtonClicked) + .disposed(by: disposeBag) + postSummaryCard .bind(vo: viewModel.postCardVO) diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift index 091bcb46..ea55e1db 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift @@ -10,18 +10,44 @@ import RxSwift import RxCocoa import Entity import DSKit +import PresentationCore + +public protocol CheckApplicantViewModelable { + // Input + var requestpostApplicantVO: PublishRelay { get } + var exitButtonClicked: PublishRelay { get } + + // Output + var postApplicantVO: Driver<[PostApplicantVO]>? { get } + var postCardVO: CenterEmployCardVO { get } + var alert: Driver? { get } +} public class CheckApplicantVM: CheckApplicantViewModelable { + weak var coorindator: CheckApplicantCoordinatable? + + public var exitButtonClicked: PublishRelay = .init() public var requestpostApplicantVO: PublishRelay = .init() public var postCardVO: CenterEmployCardVO public var postApplicantVO: Driver<[PostApplicantVO]>? public var alert: RxCocoa.Driver? - public init(postCardVO: CenterEmployCardVO) { + let disposeBag = DisposeBag() + + public init(postCardVO: CenterEmployCardVO, coorindator: CheckApplicantCoordinatable?) { self.postCardVO = postCardVO + self.coorindator = coorindator + + exitButtonClicked + .subscribe(onNext: { [weak self] _ in + + self?.coorindator?.taskFinished() + }) + .disposed(by: disposeBag) + // Input let requestPostApplicantVOResult = requestpostApplicantVO .flatMap { [unowned self] _ in publishPostApplicantVOMocks() @@ -31,6 +57,7 @@ public class CheckApplicantVM: CheckApplicantViewModelable { let requestPostApplicantSuccess = requestPostApplicantVOResult.compactMap { $0.value } let requestPostApplicantFailure = requestPostApplicantVOResult.compactMap { $0.error } + // Output postApplicantVO = requestPostApplicantSuccess.asDriver(onErrorJustReturn: []) alert = requestPostApplicantFailure diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift index a97be369..0e5db20d 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift @@ -13,7 +13,7 @@ import CenterFeature import WorkerFeature import BaseFeature -public class CheckApplicantCoordinator: ParentCoordinator { +public class CheckApplicantCoordinator: CheckApplicantCoordinatable { public var childCoordinators: [any PresentationCore.Coordinator] = [] @@ -43,14 +43,20 @@ public class CheckApplicantCoordinator: ParentCoordinator { public func start() { let vc = CheckApplicantVC() let vm = CheckApplicantVM( - postCardVO: centerEmployCardVO + postCardVO: centerEmployCardVO, + coorindator: self ) vc.bind(viewModel: vm) viewControllerRef = vc navigationController.pushViewController(vc, animated: true) } +} + +extension CheckApplicantCoordinator { - public func coordinatorDidFinish() { + public func taskFinished() { + clearChildren() + popViewController() parent?.removeChildCoordinator(self) } } diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CheckApplicantCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CheckApplicantCoordinatable.swift new file mode 100644 index 00000000..f144273d --- /dev/null +++ b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CheckApplicantCoordinatable.swift @@ -0,0 +1,13 @@ +// +// CheckApplicantCoordinatable.swift +// PresentationCore +// +// Created by choijunios on 8/13/24. +// + +import Foundation + +public protocol CheckApplicantCoordinatable: ParentCoordinator { + + func taskFinished() +} From a823e1dc45ee574dbc1639dff9effc9b59c8dbe7 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 14 Aug 2024 11:17:53 +0900 Subject: [PATCH 15/20] =?UTF-8?q?[IDLE-141]=20=EC=A7=80=EC=9B=90=EC=9E=90?= =?UTF-8?q?=20=ED=99=95=EC=9D=B8=EC=B0=BD=EC=97=90=EC=84=9C=20=EC=9A=94?= =?UTF-8?q?=EC=96=91=EB=B3=B4=ED=98=B8=EC=82=AC=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=9D=B4=EB=8F=99=20=EB=A1=9C=EC=A7=81=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 프록시맨을 사용해 요양보호사의 testId에 대해 응답을 모킹처리하였습니다. --- .../Sources/DI/Assembly/DomainAssembly.swift | 7 ++ .../Main/Center /CenterMainCoordinator.swift | 8 ++- .../Entity/VO/Employ/PostApplicantVO.swift | 2 +- .../ApplyScreen/ApplicantCardCell.swift | 4 ++ .../Sources/CommonUI/TabBar/IdleTabBar.swift | 2 +- .../CheckApplicant/CheckApplicantVC.swift | 35 ++------- .../CheckApplicant/CheckApplicantVM.swift | 44 ++++++++++++ .../Post/CenterRecruitmentPostBoardVC.swift | 2 +- .../CheckApplicantCoordinator.swift | 23 +++++- .../RecruitmentManagementCoordinator.swift | 26 +++++-- .../Profile/WorkerProfileCoordinator.swift | 71 +++++++++++++++++++ .../EditWorkerProfileViewController.swift | 0 .../WorkerProfileViewController.swift | 12 ++++ .../WorkerMyProfileViewModel.swift | 0 .../WorkerProfileViewModel.swift | 0 .../CheckApplicantCoordinatable.swift | 1 + 16 files changed, 197 insertions(+), 40 deletions(-) create mode 100644 project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift rename project/Projects/Presentation/Feature/Worker/Sources/View/{profile => Profile}/EditWorkerProfileViewController.swift (100%) rename project/Projects/Presentation/Feature/Worker/Sources/View/{profile => Profile}/WorkerProfileViewController.swift (97%) rename project/Projects/Presentation/Feature/Worker/Sources/ViewModel/{profile => Profile}/WorkerMyProfileViewModel.swift (100%) rename project/Projects/Presentation/Feature/Worker/Sources/ViewModel/{profile => Profile}/WorkerProfileViewModel.swift (100%) diff --git a/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift b/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift index af9f6a96..a75b3866 100644 --- a/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift +++ b/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift @@ -38,5 +38,12 @@ public struct DomainAssembly: Assembly { repository: repository ) } + + container.register(WorkerProfileUseCase.self) { resolver in + let repository = resolver.resolve(UserProfileRepository.self)! + + return DefaultWorkerProfileUseCase(repository: repository) + } + } } diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift index c3bc94f1..f681ed47 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Center /CenterMainCoordinator.swift @@ -9,6 +9,7 @@ import UIKit import DSKit import PresentationCore import RootFeature +import UseCaseInterface class CenterMainCoordinator: CenterMainCoordinatable { @@ -69,8 +70,11 @@ class CenterMainCoordinator: CenterMainCoordinatable { switch tab { case .recruitmentManage: coordinator = RecruitmentManagementCoordinator( - parent: self, - navigationController: navigationController + dependency: .init( + parent: self, + navigationController: navigationController, + workerProfileUseCase: injector.resolve(WorkerProfileUseCase.self) + ) ) case .setting: diff --git a/project/Projects/Domain/Entity/VO/Employ/PostApplicantVO.swift b/project/Projects/Domain/Entity/VO/Employ/PostApplicantVO.swift index a1e29574..89f391a2 100644 --- a/project/Projects/Domain/Entity/VO/Employ/PostApplicantVO.swift +++ b/project/Projects/Domain/Entity/VO/Employ/PostApplicantVO.swift @@ -33,7 +33,7 @@ public struct PostApplicantVO { } public static let mock: PostApplicantVO = .init( - workerId: "11111-222222-333333", + workerId: "testworkerId", profileUrl: URL(string: "https://dummyimage.com/600x400/00ffbf/0011ff&text=worker+profile"), isJobFinding: false, isStared: false, diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift index dd8dabe8..c3cd9a30 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift @@ -15,6 +15,8 @@ public class ApplicantCardCell: UITableViewCell { public static let identifier = String(describing: ApplicantCardCell.self) + var viewModel: ApplicantCardViewModelable? + let cardView = ApplicantCard() private var disposables: [Disposable?]? @@ -58,6 +60,8 @@ public class ApplicantCardCell: UITableViewCell { public func bind(viewModel: ApplicantCardViewModelable) { + self.viewModel = viewModel + let disposables: [Disposable?] = [ // Output viewModel diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift index e2e8ae8b..33bcba96 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/TabBar/IdleTabBar.swift @@ -127,7 +127,7 @@ public class IdleTabBar: UIViewController { let currentView = currentVC.view! NSLayoutConstraint.activate([ - currentView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + currentView.topAnchor.constraint(equalTo: view.topAnchor), currentView.leadingAnchor.constraint(equalTo: view.leadingAnchor), currentView.trailingAnchor.constraint(equalTo: view.trailingAnchor), currentView.bottomAnchor.constraint(equalTo: tabBarItemStack.topAnchor) diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift index 3e4a4314..bdb37ff8 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVC.swift @@ -150,7 +150,7 @@ public class CheckApplicantVC: BaseViewController { } NSLayoutConstraint.activate([ - navigationBar.topAnchor.constraint(equalTo: view.topAnchor, constant: 21), + navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 21), navigationBar.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 12), scrollView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), @@ -209,34 +209,13 @@ extension CheckApplicantVC: UITableViewDataSource, UITableViewDelegate { public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell - let vm = ApplicantCardVM(vo: postApplicantVO.value[indexPath.row]) - cell.bind(viewModel: vm) - cell.selectionStyle = .none - return cell - } -} - -// MARK: ApplicantCardVM -public class ApplicantCardVM: ApplicantCardViewModelable { - - // Init - let id: String - - public var showProfileButtonClicked: PublishRelay = .init() - public var employButtonClicked: PublishRelay = .init() - public var staredThisWorker: PublishRelay = .init() - - public var renderObject: Driver? - - public init(vo: PostApplicantVO) { - self.id = vo.workerId - // MARK: RenderObject - let publishRelay: BehaviorRelay = .init(value: .mock) - renderObject = publishRelay.asDriver(onErrorJustReturn: .mock) - - publishRelay.accept(ApplicantCardRO.create(vo: vo)) + if let viewModel = self.viewModel { + let vm = viewModel.createApplicantCardVM(vo: postApplicantVO.value[indexPath.row]) + cell.bind(viewModel: vm) + cell.selectionStyle = .none + } - // MARK: 버튼 처리 + return cell } } diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift index ea55e1db..e514744a 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/CheckApplicant/CheckApplicantVM.swift @@ -21,6 +21,8 @@ public protocol CheckApplicantViewModelable { var postApplicantVO: Driver<[PostApplicantVO]>? { get } var postCardVO: CenterEmployCardVO { get } var alert: Driver? { get } + + func createApplicantCardVM(vo: PostApplicantVO) -> ApplicantCardVM } public class CheckApplicantVM: CheckApplicantViewModelable { @@ -71,8 +73,50 @@ public class CheckApplicantVM: CheckApplicantViewModelable { .asDriver(onErrorJustReturn: .default) } + public func createApplicantCardVM(vo: PostApplicantVO) -> ApplicantCardVM { + .init(vo: vo, coordinator: coorindator) + } + func publishPostApplicantVOMocks() -> Single> { .just(.success((0...10).map { _ in PostApplicantVO.mock })) } } + + +// MARK: ApplicantCardVM +public class ApplicantCardVM: ApplicantCardViewModelable { + + // Init + let id: String + weak var coordinator: CheckApplicantCoordinatable? + + public var showProfileButtonClicked: PublishRelay = .init() + public var employButtonClicked: PublishRelay = .init() + public var staredThisWorker: PublishRelay = .init() + + public var renderObject: Driver? + + let disposeBag = DisposeBag() + + public init(vo: PostApplicantVO, coordinator: CheckApplicantCoordinatable?) { + self.id = vo.workerId + self.coordinator = coordinator + + // MARK: RenderObject + let publishRelay: BehaviorRelay = .init(value: .mock) + renderObject = publishRelay.asDriver(onErrorJustReturn: .mock) + + publishRelay.accept(ApplicantCardRO.create(vo: vo)) + + // MARK: 버튼 처리 + showProfileButtonClicked + .subscribe(onNext: { [weak self] _ in + guard let self else { return } + coordinator?.showWorkerProfileScreen( + profileId: id + ) + }) + .disposed(by: disposeBag) + } +} diff --git a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVC.swift index 073483ce..6cd334b8 100644 --- a/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Center/Sources/View/RecruitmentPost/Board/Post/CenterRecruitmentPostBoardVC.swift @@ -90,7 +90,7 @@ public class CenterRecruitmentPostBoardVC: BaseViewController { } NSLayoutConstraint.activate([ - titleLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 21), + 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), diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift index 0e5db20d..e1cde0fa 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/CheckApplicantCoordinator.swift @@ -20,6 +20,13 @@ public class CheckApplicantCoordinator: CheckApplicantCoordinatable { public struct Dependency { let navigationController: UINavigationController let centerEmployCardVO: CenterEmployCardVO + let workerProfileUseCase: WorkerProfileUseCase + + public init(navigationController: UINavigationController, centerEmployCardVO: CenterEmployCardVO, workerProfileUseCase: WorkerProfileUseCase) { + self.navigationController = navigationController + self.centerEmployCardVO = centerEmployCardVO + self.workerProfileUseCase = workerProfileUseCase + } } public weak var viewControllerRef: UIViewController? @@ -27,13 +34,14 @@ public class CheckApplicantCoordinator: CheckApplicantCoordinatable { public let navigationController: UINavigationController let centerEmployCardVO: CenterEmployCardVO - + let workerProfileUseCase: WorkerProfileUseCase public init( dependency: Dependency ) { self.navigationController = dependency.navigationController self.centerEmployCardVO = dependency.centerEmployCardVO + self.workerProfileUseCase = dependency.workerProfileUseCase } deinit { @@ -55,8 +63,19 @@ public class CheckApplicantCoordinator: CheckApplicantCoordinatable { extension CheckApplicantCoordinator { public func taskFinished() { - clearChildren() popViewController() parent?.removeChildCoordinator(self) } + + public func showWorkerProfileScreen(profileId: String) { + let coordinator = WorkerProfileCoordinator( + dependency: .init( + profileMode: .otherProfile(id: profileId), + navigationController: navigationController, + workerProfileUseCase: workerProfileUseCase + ) + ) + addChildCoordinator(coordinator) + coordinator.start() + } } diff --git a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift index 39018096..57671e8a 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Screen/Center/Coordinator/RecruitmentManagementCoordinator.swift @@ -8,11 +8,24 @@ import UIKit import CenterFeature import PresentationCore +import UseCaseInterface import Entity public class RecruitmentManagementCoordinator: RecruitmentManagementCoordinatable { + public struct Dependency { + weak var parent: CenterMainCoordinatable? + let navigationController: UINavigationController + let workerProfileUseCase: WorkerProfileUseCase + + public init(parent: CenterMainCoordinatable? = nil, navigationController: UINavigationController, workerProfileUseCase: WorkerProfileUseCase) { + self.parent = parent + self.navigationController = navigationController + self.workerProfileUseCase = workerProfileUseCase + } + } + public var childCoordinators: [any PresentationCore.Coordinator] = [] public weak var viewControllerRef: UIViewController? @@ -21,12 +34,14 @@ public class RecruitmentManagementCoordinator: RecruitmentManagementCoordinatabl public weak var parent: CenterMainCoordinatable? + let workerProfileUseCase: WorkerProfileUseCase + public init( - parent: CenterMainCoordinatable, - navigationController: UINavigationController + dependency: Dependency ) { - self.parent = parent - self.navigationController = navigationController + self.parent = dependency.parent + self.navigationController = dependency.navigationController + self.workerProfileUseCase = dependency.workerProfileUseCase } public func start() { @@ -49,7 +64,8 @@ public extension RecruitmentManagementCoordinator { let coordinator = CheckApplicantCoordinator( dependency: .init( navigationController: navigationController, - centerEmployCardVO: centerEmployCardVO + centerEmployCardVO: centerEmployCardVO, + workerProfileUseCase: workerProfileUseCase ) ) addChildCoordinator(coordinator) diff --git a/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift b/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift new file mode 100644 index 00000000..5a9cde69 --- /dev/null +++ b/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift @@ -0,0 +1,71 @@ +// +// WorkerProfileCoordinator.swift +// WorkerFeature +// +// Created by choijunios on 8/14/24. +// + +import UIKit +import PresentationCore +import UseCaseInterface +import Entity + + +public class WorkerProfileCoordinator: ChildCoordinator { + + public struct Dependency { + public let profileMode: ProfileMode + public let navigationController: UINavigationController + public let workerProfileUseCase: WorkerProfileUseCase + + public init(profileMode: ProfileMode, navigationController: UINavigationController, workerProfileUseCase: WorkerProfileUseCase) { + self.profileMode = profileMode + self.navigationController = navigationController + self.workerProfileUseCase = workerProfileUseCase + } + } + + public weak var viewControllerRef: UIViewController? + public weak var parent: ParentCoordinator? + + public let navigationController: UINavigationController + let profileMode: ProfileMode + let workerProfileUseCase: WorkerProfileUseCase + + public init( + dependency: Dependency + ) { + self.navigationController = dependency.navigationController + self.profileMode = dependency.profileMode + self.workerProfileUseCase = dependency.workerProfileUseCase + } + + deinit { + printIfDebug("\(String(describing: WorkerProfileCoordinator.self))") + } + + public func start() { + var vm: WorkerProfileViewModelable! + let vc = WorkerProfileViewController() + + switch profileMode { + case .myProfile: + vm = WorkerMyProfileViewModel( + workerProfileUseCase: workerProfileUseCase + ) + case .otherProfile(let id): + vm = WorkerProfileViewModel( + workerProfileUseCase: workerProfileUseCase, + workerId: id + ) + } + vc.bind(vm) + viewControllerRef = vc + navigationController.pushViewController(vc, animated: true) + } + + public func coordinatorDidFinish() { + popViewController(animated: true) + parent?.removeChildCoordinator(self) + } +} diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/EditWorkerProfileViewController.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/EditWorkerProfileViewController.swift similarity index 100% rename from project/Projects/Presentation/Feature/Worker/Sources/View/profile/EditWorkerProfileViewController.swift rename to project/Projects/Presentation/Feature/Worker/Sources/View/Profile/EditWorkerProfileViewController.swift diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/WorkerProfileViewController.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift similarity index 97% rename from project/Projects/Presentation/Feature/Worker/Sources/View/profile/WorkerProfileViewController.swift rename to project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift index 0fd8061a..0b4c9b3c 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/profile/WorkerProfileViewController.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift @@ -172,6 +172,7 @@ public class WorkerProfileViewController: DisposableViewController { setApearance() setAutoLayout() + setObservable() } public required init?(coder: NSCoder) { @@ -346,6 +347,17 @@ public class WorkerProfileViewController: DisposableViewController { ]) } + private func setObservable() { + + navigationBar + .eventPublisher + .observe(on: MainScheduler.instance) + .subscribe { [weak self] _ in + self?.navigationController?.popViewController(animated: true) + } + .disposed(by: disposeBag) + } + public func bind(_ viewModel: any WorkerProfileViewModelable) { self.viewModel = viewModel diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/profile/WorkerMyProfileViewModel.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerMyProfileViewModel.swift similarity index 100% rename from project/Projects/Presentation/Feature/Worker/Sources/ViewModel/profile/WorkerMyProfileViewModel.swift rename to project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerMyProfileViewModel.swift diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/profile/WorkerProfileViewModel.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModel.swift similarity index 100% rename from project/Projects/Presentation/Feature/Worker/Sources/ViewModel/profile/WorkerProfileViewModel.swift rename to project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModel.swift diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CheckApplicantCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CheckApplicantCoordinatable.swift index f144273d..d4193255 100644 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CheckApplicantCoordinatable.swift +++ b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/CheckApplicantCoordinatable.swift @@ -10,4 +10,5 @@ import Foundation public protocol CheckApplicantCoordinatable: ParentCoordinator { func taskFinished() + func showWorkerProfileScreen(profileId: String) } From abceca2b7c02ae3963006c1f481ba18f03c01d4b Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 14 Aug 2024 11:27:20 +0900 Subject: [PATCH 16/20] =?UTF-8?q?[IDLE-141]=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=20=ED=83=88=EC=B6=9C=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 화면전환을 ViewModel에서 처리하도록 수정하였습니다. --- .../Profile/WorkerProfileCoordinator.swift | 2 ++ .../Profile/WorkerProfileViewController.swift | 18 +++++------- .../Profile/WorkerMyProfileViewModel.swift | 16 +++++++++- .../Profile/WorkerProfileViewModel.swift | 29 ++++++++++++++++++- 4 files changed, 52 insertions(+), 13 deletions(-) diff --git a/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift b/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift index 5a9cde69..aa9a5d8f 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift @@ -51,10 +51,12 @@ public class WorkerProfileCoordinator: ChildCoordinator { switch profileMode { case .myProfile: vm = WorkerMyProfileViewModel( + coordinator: self, workerProfileUseCase: workerProfileUseCase ) case .otherProfile(let id): vm = WorkerProfileViewModel( + coordinator: self, workerProfileUseCase: workerProfileUseCase, workerId: id ) diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift index 0b4c9b3c..5443be63 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift @@ -13,15 +13,6 @@ import DSKit import Entity import BaseFeature -public protocol WorkerProfileViewModelable { - - // Input - var viewWillAppear: PublishRelay { get } - - // Output - var profileRenderObject: Driver? { get } -} - public class WorkerProfileViewController: DisposableViewController { private var viewModel: (any WorkerProfileViewModelable)? @@ -172,7 +163,6 @@ public class WorkerProfileViewController: DisposableViewController { setApearance() setAutoLayout() - setObservable() } public required init?(coder: NSCoder) { @@ -362,6 +352,7 @@ public class WorkerProfileViewController: DisposableViewController { self.viewModel = viewModel + // Input profileEditButton .eventPublisher .observe(on: MainScheduler.instance) @@ -373,13 +364,18 @@ public class WorkerProfileViewController: DisposableViewController { }) .disposed(by: disposeBag) - // Input + navigationBar + .eventPublisher + .bind(to: viewModel.exitButtonClicked) + .disposed(by: disposeBag) + self.rx .viewWillAppear .filter { $0 } .map { _ in () } .bind(to: viewModel.viewWillAppear) .disposed(by: disposeBag) + // Output viewModel diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerMyProfileViewModel.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerMyProfileViewModel.swift index 474bf458..5fe8605a 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerMyProfileViewModel.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerMyProfileViewModel.swift @@ -15,6 +15,8 @@ import UseCaseInterface public class WorkerMyProfileViewModel: WorkerProfileEditViewModelable { + public weak var coordinator: WorkerProfileCoordinator? + let workerProfileUseCase: WorkerProfileUseCase // Input(Editing) @@ -29,6 +31,8 @@ public class WorkerMyProfileViewModel: WorkerProfileEditViewModelable { // Input(Rendering) public var viewWillAppear: PublishRelay = .init() + public var exitButtonClicked: RxRelay.PublishRelay = .init() + // Output var uploadSuccess: Driver? public var alert: Driver? @@ -43,8 +47,12 @@ public class WorkerMyProfileViewModel: WorkerProfileEditViewModelable { let disposbag: DisposeBag = .init() - public init(workerProfileUseCase: WorkerProfileUseCase) { + public init( + coordinator: WorkerProfileCoordinator?, + workerProfileUseCase: WorkerProfileUseCase + ) { + self.coordinator = coordinator self.workerProfileUseCase = workerProfileUseCase // Input(Rendering) @@ -80,6 +88,12 @@ public class WorkerMyProfileViewModel: WorkerProfileEditViewModelable { .bind(to: rederingState) .disposed(by: disposbag) + exitButtonClicked + .subscribe(onNext: { [weak self] in + self?.coordinator?.coordinatorDidFinish() + }) + .disposed(by: disposbag) + // Edit Input let imageValidationResult = editingImage diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModel.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModel.swift index e523327c..c76b63ab 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModel.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModel.swift @@ -13,8 +13,23 @@ import DSKit import Entity import UseCaseInterface +public protocol WorkerProfileViewModelable { + + var coordinator: WorkerProfileCoordinator? { get } + + // Input + var viewWillAppear: PublishRelay { get } + var exitButtonClicked: PublishRelay { get } + + // Output + var profileRenderObject: Driver? { get } +} + + public class WorkerProfileViewModel: WorkerProfileViewModelable { + public weak var coordinator: WorkerProfileCoordinator? + let workerProfileUseCase: WorkerProfileUseCase // Init @@ -22,6 +37,7 @@ public class WorkerProfileViewModel: WorkerProfileViewModelable { // Input(Rendering) public var viewWillAppear: PublishRelay = .init() + public var exitButtonClicked: RxRelay.PublishRelay = .init() // Output var uploadSuccess: Driver? @@ -37,8 +53,13 @@ public class WorkerProfileViewModel: WorkerProfileViewModelable { let disposbag: DisposeBag = .init() - public init(workerProfileUseCase: WorkerProfileUseCase, workerId: String) { + public init( + coordinator: WorkerProfileCoordinator?, + workerProfileUseCase: WorkerProfileUseCase, + workerId: String + ) { + self.coordinator = coordinator self.workerProfileUseCase = workerProfileUseCase self.workerId = workerId @@ -66,6 +87,12 @@ public class WorkerProfileViewModel: WorkerProfileViewModelable { return vo } + exitButtonClicked + .subscribe(onNext: { [weak self] in + self?.coordinator?.coordinatorDidFinish() + }) + .disposed(by: disposbag) + fetchedProfileVOSuccess .asObservable() .map({ vo in From 9f5b3fc1d683ed4a96cf890167bdee3387fc5fca Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 14 Aug 2024 13:18:42 +0900 Subject: [PATCH 17/20] =?UTF-8?q?[IDLE-141]=20=EC=9A=94=EC=96=91=EB=B3=B4?= =?UTF-8?q?=ED=98=B8=EC=82=AC=20=ED=94=84=EB=A1=9C=ED=95=84=EB=B7=B0=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EB=B7=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Profile/WorkerProfileCoordinator.swift | 8 +- .../EditWorkerProfileViewController.swift | 14 -- .../Profile/WorkerProfileViewController.swift | 154 ++++++++++++++---- .../WorkerProfileRenderObject.swift | 4 + .../Profile/WorkerMyProfileViewModel.swift | 30 +++- .../Profile/WorkerProfileViewModel.swift | 26 +-- .../Profile/WorkerProfileViewModelable.swift | 24 +++ .../Sources/Extensions/String+Extension.swift | 15 ++ 8 files changed, 205 insertions(+), 70 deletions(-) create mode 100644 project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModelable.swift create mode 100644 project/Projects/Presentation/PresentationCore/Sources/Extensions/String+Extension.swift diff --git a/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift b/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift index aa9a5d8f..0c775251 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/Coordinator/Profile/WorkerProfileCoordinator.swift @@ -45,23 +45,23 @@ public class WorkerProfileCoordinator: ChildCoordinator { } public func start() { - var vm: WorkerProfileViewModelable! let vc = WorkerProfileViewController() switch profileMode { case .myProfile: - vm = WorkerMyProfileViewModel( + let vm = WorkerMyProfileViewModel( coordinator: self, workerProfileUseCase: workerProfileUseCase ) + vc.bind(vm) case .otherProfile(let id): - vm = WorkerProfileViewModel( + let vm = WorkerProfileViewModel( coordinator: self, workerProfileUseCase: workerProfileUseCase, workerId: id ) + vc.bind(vm) } - vc.bind(vm) viewControllerRef = vc navigationController.pushViewController(vc, animated: true) } diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/EditWorkerProfileViewController.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/EditWorkerProfileViewController.swift index a6b4640a..c7dcc7a0 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/EditWorkerProfileViewController.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/EditWorkerProfileViewController.swift @@ -13,20 +13,6 @@ import DSKit import Entity import BaseFeature -protocol WorkerProfileEditViewModelable: WorkerProfileViewModelable { - - var requestUpload: PublishRelay { get } - var editingImage: PublishRelay { get } - var editingIsJobFinding: PublishRelay { get } - var editingExpYear: PublishRelay { get } - var editingAddress: PublishRelay { get } - var editingIntroduce: PublishRelay { get } - var editingSpecialty: PublishRelay { get } - - var uploadSuccess: Driver? { get } - var alert: Driver? { get } -} - public class EditWorkerProfileViewController: BaseViewController { // Navigation bar diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift index 5443be63..9f1897c6 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift @@ -57,6 +57,16 @@ public class WorkerProfileViewController: DisposableViewController { return imageView }() + // 즐겨찾기 버튼 +// public let starButton: IconWithColorStateButton = { +// let button = IconWithColorStateButton( +// representImage: DSKitAsset.Icons.subscribeStar.image, +// normalColor: DSKitAsset.Colors.gray200.color, +// accentColor: DSKitAsset.Colors.orange300.color +// ) +// return button +// }() + // 구인중 / 휴식중 let workingTag: TagLabel = { let label = TagLabel( @@ -116,6 +126,21 @@ public class WorkerProfileViewController: DisposableViewController { return label }() + // 통화하기 버튼 + // 통화하기, 채팅하기 + lazy var contactButtonContainer: HStack = .init( + [phoneCallButton], + spacing: 8, + distribution: .fillEqually + ) + let phoneCallButton: IdleSecondaryButton = { + let button = IdleSecondaryButton(level: .medium) + button.label.textString = "통화하기" + return button + }() + + // MARK: 상세정보 + // 주소(Label + Content) let addressTitleLabel: IdleLabel = { let label = IdleLabel(typography: .Subtitle4) @@ -170,13 +195,7 @@ public class WorkerProfileViewController: DisposableViewController { } private func setApearance() { - view.backgroundColor = .white - view.layoutMargins = .init( - top: 0, - left: 20, - bottom: 0, - right: 20 - ) + view.backgroundColor = DSKitAsset.Colors.gray050.color } private func setAutoLayout() { @@ -254,6 +273,7 @@ public class WorkerProfileViewController: DisposableViewController { } } + // MARK: Divider // 요양보호사 인적정보 / 요양보호사 구직정보 디바이더 let divider = UIView() divider.backgroundColor = DSKitAsset.Colors.gray050.color @@ -283,57 +303,107 @@ public class WorkerProfileViewController: DisposableViewController { spacing: 28, alignment: .leading) - // view hierarchy + let contentView = UIView() + contentView.backgroundColor = DSKitAsset.Colors.gray0.color + contentView.layoutMargins = .init( + top: 0, + left: 20, + bottom: 54, + right: 20 + ) + [ grayBackgrounnd, - navigationStack, profileImageContainer, +// starButton, tagNameStack, humanInfoStack, + contactButtonContainer, divider, employeeInfoTitleLabel, employeeInfoStack ].forEach { $0.translatesAutoresizingMaskIntoConstraints = false - view.addSubview($0) + contentView.addSubview($0) } - grayBackgrounnd.layer.zPosition = 0.0 - navigationStack.layer.zPosition = 1.0 NSLayoutConstraint.activate([ - grayBackgrounnd.topAnchor.constraint(equalTo: view.topAnchor), - grayBackgrounnd.leadingAnchor.constraint(equalTo: view.leadingAnchor), - grayBackgrounnd.trailingAnchor.constraint(equalTo: view.trailingAnchor), - grayBackgrounnd.heightAnchor.constraint(equalToConstant: 196), - - navigationStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 21), - navigationStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 12), - navigationStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + grayBackgrounnd.topAnchor.constraint(equalTo: contentView.topAnchor), + grayBackgrounnd.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + grayBackgrounnd.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + grayBackgrounnd.heightAnchor.constraint(equalToConstant: 80), profileImageContainer.widthAnchor.constraint(equalToConstant: 96), profileImageContainer.heightAnchor.constraint(equalTo: profileImageContainer.widthAnchor), - profileImageContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor), + profileImageContainer.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), profileImageContainer.centerYAnchor.constraint(equalTo: grayBackgrounnd.bottomAnchor), +// starButton.widthAnchor.constraint(equalToConstant: 24), +// starButton.heightAnchor.constraint(equalTo: starButton.widthAnchor), +// starButton.topAnchor.constraint(equalTo: grayBackgrounnd.bottomAnchor, constant: 20), +// starButton.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20), + tagNameStack.topAnchor.constraint(equalTo: profileImageContainer.bottomAnchor, constant: 16), - tagNameStack.centerXAnchor.constraint(equalTo: view.centerXAnchor), + tagNameStack.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), humanInfoStack.topAnchor.constraint(equalTo: tagNameStack.bottomAnchor, constant: 16), - humanInfoStack.centerXAnchor.constraint(equalTo: view.centerXAnchor), + humanInfoStack.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), + + contactButtonContainer.topAnchor.constraint(equalTo: humanInfoStack.bottomAnchor, constant: 24), + contactButtonContainer.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 36), + contactButtonContainer.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -36), - divider.topAnchor.constraint(equalTo: humanInfoStack.bottomAnchor, constant: 24), - divider.leadingAnchor.constraint(equalTo: view.leadingAnchor), - divider.trailingAnchor.constraint(equalTo: view.trailingAnchor), + divider.topAnchor.constraint(equalTo: contactButtonContainer.bottomAnchor, constant: 24), + divider.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + divider.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), divider.heightAnchor.constraint(equalToConstant: 8), employeeInfoTitleLabel.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 24), - employeeInfoTitleLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + employeeInfoTitleLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), employeeInfoStack.topAnchor.constraint(equalTo: employeeInfoTitleLabel.bottomAnchor, constant: 20), - employeeInfoStack.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), - employeeInfoStack.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + employeeInfoStack.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + employeeInfoStack.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), + employeeInfoStack.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), + ]) + + let scrollView = UIScrollView() + scrollView.delaysContentTouches = false + let contentGuide = scrollView.contentLayoutGuide + let frameGuide = scrollView.frameLayoutGuide + + scrollView.addSubview(contentView) + contentView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + contentView.topAnchor.constraint(equalTo: contentGuide.topAnchor), + contentView.leftAnchor.constraint(equalTo: contentGuide.leftAnchor), + contentView.rightAnchor.constraint(equalTo: contentGuide.rightAnchor), + contentView.bottomAnchor.constraint(equalTo: contentGuide.bottomAnchor), + + contentView.widthAnchor.constraint(equalTo: frameGuide.widthAnchor), + ]) + + + [ + navigationStack, + scrollView, + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + view.addSubview($0) + } + + NSLayoutConstraint.activate([ + + navigationStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 21), + navigationStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 12), + navigationStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + + scrollView.topAnchor.constraint(equalTo: navigationStack.bottomAnchor), + scrollView.leftAnchor.constraint(equalTo: view.leftAnchor), + scrollView.rightAnchor.constraint(equalTo: view.rightAnchor), + scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) } @@ -348,7 +418,22 @@ public class WorkerProfileViewController: DisposableViewController { .disposed(by: disposeBag) } - public func bind(_ viewModel: any WorkerProfileViewModelable) { + /// 다른 프로필 확인용 바인딩 입니다. + public func bind(_ viewModel: OtherWorkerProfileViewModelable) { + bind(viewModel as WorkerProfileViewModelable) + + phoneCallButton + .rx.tap + .bind(to: viewModel.phoneCallButtonClicked) + .disposed(by: disposeBag) + } + + /// 내프로필 접속용 바인딩 입니다. + public func bind(_ viewModel: WorkerProfileEditViewModelable) { + bind(viewModel as WorkerProfileViewModelable) + } + + private func bind(_ viewModel: any WorkerProfileViewModelable) { self.viewModel = viewModel @@ -387,14 +472,17 @@ public class WorkerProfileViewController: DisposableViewController { // UI 업데이트 navigationBar.navigationTitle = ro.navigationTitle profileEditButton.isHidden = !ro.showEditButton + contactButtonContainer.isHidden = !ro.showContactButton +// starButton.isHidden = !ro.showStarButton workingTag.textString = ro.stateText nameLabel.textString = ro.nameText ageLabel.textString = ro.ageText genderLabel.textString = ro.genderText expLabel.textString = ro.expText + addressLabel.textString = ro.address - introductionLabel.textString = ro.oneLineIntroduce - abilityLabel.textString = ro.specialty + introductionLabel.textString = ro.oneLineIntroduce.emptyDefault("-") + abilityLabel.textString = ro.specialty.emptyDefault("-") if let imageUrl = ro.imageUrl { workerProfileImage.setImage(url: imageUrl) @@ -408,6 +496,8 @@ public class WorkerProfileViewController: DisposableViewController { } } + + @available(iOS 17.0, *) #Preview("Preview", traits: .defaultLayout) { diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RenderObject/WorkerProfileRenderObject.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RenderObject/WorkerProfileRenderObject.swift index 66b0531c..ab991549 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RenderObject/WorkerProfileRenderObject.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RenderObject/WorkerProfileRenderObject.swift @@ -13,6 +13,8 @@ public struct WorkerProfileRenderObject { let navigationTitle: String let showEditButton: Bool + let showContactButton: Bool +// let showStarButton: Bool let isJobFinding: Bool let stateText: String let nameText: String @@ -30,6 +32,8 @@ public struct WorkerProfileRenderObject { .init( navigationTitle: isMyProfile ? "내 프로필" : "요양보호사 프로필", showEditButton: isMyProfile, + showContactButton: !isMyProfile, +// showStarButton: !isMyProfile, isJobFinding: vo.isLookingForJob, stateText: vo.isLookingForJob ? "구인중" : "휴식중", nameText: vo.nameText, diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerMyProfileViewModel.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerMyProfileViewModel.swift index 5fe8605a..5cb5a7d3 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerMyProfileViewModel.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerMyProfileViewModel.swift @@ -13,6 +13,20 @@ import DSKit import Entity import UseCaseInterface +public protocol WorkerProfileEditViewModelable: WorkerProfileViewModelable { + + var requestUpload: PublishRelay { get } + var editingImage: PublishRelay { get } + var editingIsJobFinding: PublishRelay { get } + var editingExpYear: PublishRelay { get } + var editingAddress: PublishRelay { get } + var editingIntroduce: PublishRelay { get } + var editingSpecialty: PublishRelay { get } + + var uploadSuccess: Driver? { get } + var alert: Driver? { get } +} + public class WorkerMyProfileViewModel: WorkerProfileEditViewModelable { public weak var coordinator: WorkerProfileCoordinator? @@ -20,13 +34,13 @@ public class WorkerMyProfileViewModel: WorkerProfileEditViewModelable { let workerProfileUseCase: WorkerProfileUseCase // Input(Editing) - var requestUpload: PublishRelay = .init() - var editingImage: PublishRelay = .init() - var editingIsJobFinding: PublishRelay = .init() - var editingExpYear: PublishRelay = .init() - var editingAddress: PublishRelay = .init() - var editingIntroduce: PublishRelay = .init() - var editingSpecialty: PublishRelay = .init() + public var requestUpload: PublishRelay = .init() + public var editingImage: PublishRelay = .init() + public var editingIsJobFinding: PublishRelay = .init() + public var editingExpYear: PublishRelay = .init() + public var editingAddress: PublishRelay = .init() + public var editingIntroduce: PublishRelay = .init() + public var editingSpecialty: PublishRelay = .init() // Input(Rendering) public var viewWillAppear: PublishRelay = .init() @@ -34,7 +48,7 @@ public class WorkerMyProfileViewModel: WorkerProfileEditViewModelable { public var exitButtonClicked: RxRelay.PublishRelay = .init() // Output - var uploadSuccess: Driver? + public var uploadSuccess: Driver? public var alert: Driver? public var profileRenderObject: Driver? diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModel.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModel.swift index c76b63ab..0a07f9be 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModel.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModel.swift @@ -13,20 +13,13 @@ import DSKit import Entity import UseCaseInterface -public protocol WorkerProfileViewModelable { +/// 자신의 프로필을 확인하는 경우가 아닌 센터측에서 요양보호사를 보는 경우 +public protocol OtherWorkerProfileViewModelable: WorkerProfileViewModelable { - var coordinator: WorkerProfileCoordinator? { get } - - // Input - var viewWillAppear: PublishRelay { get } - var exitButtonClicked: PublishRelay { get } - - // Output - var profileRenderObject: Driver? { get } + var phoneCallButtonClicked: PublishRelay { get } } - -public class WorkerProfileViewModel: WorkerProfileViewModelable { +public class WorkerProfileViewModel: OtherWorkerProfileViewModelable { public weak var coordinator: WorkerProfileCoordinator? @@ -37,7 +30,8 @@ public class WorkerProfileViewModel: WorkerProfileViewModelable { // Input(Rendering) public var viewWillAppear: PublishRelay = .init() - public var exitButtonClicked: RxRelay.PublishRelay = .init() + public var exitButtonClicked: PublishRelay = .init() + public var phoneCallButtonClicked: PublishRelay = .init() // Output var uploadSuccess: Driver? @@ -93,6 +87,14 @@ public class WorkerProfileViewModel: WorkerProfileViewModelable { }) .disposed(by: disposbag) + phoneCallButtonClicked + .subscribe(onNext: { _ in + + // 안심번호 전화연결 + + }) + .disposed(by: disposbag) + fetchedProfileVOSuccess .asObservable() .map({ vo in diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModelable.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModelable.swift new file mode 100644 index 00000000..c2b7a687 --- /dev/null +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/Profile/WorkerProfileViewModelable.swift @@ -0,0 +1,24 @@ +// +// File.swift +// WorkerFeature +// +// Created by choijunios on 8/14/24. +// + +import UIKit +import RxSwift +import RxCocoa +import Entity + + +public protocol WorkerProfileViewModelable { + + var coordinator: WorkerProfileCoordinator? { get } + + // Input + var viewWillAppear: PublishRelay { get } + var exitButtonClicked: PublishRelay { get } + + // Output + var profileRenderObject: Driver? { get } +} diff --git a/project/Projects/Presentation/PresentationCore/Sources/Extensions/String+Extension.swift b/project/Projects/Presentation/PresentationCore/Sources/Extensions/String+Extension.swift new file mode 100644 index 00000000..51dab598 --- /dev/null +++ b/project/Projects/Presentation/PresentationCore/Sources/Extensions/String+Extension.swift @@ -0,0 +1,15 @@ +// +// String+Extension.swift +// PresentationCore +// +// Created by choijunios on 8/14/24. +// + +import Foundation + +public extension String { + + func emptyDefault(_ str: String) -> String { + self.isEmpty ? str : self + } +} From fd12a1cb64c1c69a0187b8654802454fefeb4aea Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 14 Aug 2024 13:32:18 +0900 Subject: [PATCH 18/20] =?UTF-8?q?[IDLE-141]=20IdleNavigationBar=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Navigation/IdleNavigationBar.swift | 106 ++++++++++++++++++ .../Profile/WorkerProfileViewController.swift | 28 ++--- 2 files changed, 116 insertions(+), 18 deletions(-) create mode 100644 project/Projects/Presentation/DSKit/Sources/CommonUI/Navigation/IdleNavigationBar.swift diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Navigation/IdleNavigationBar.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Navigation/IdleNavigationBar.swift new file mode 100644 index 00000000..73cdefee --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Navigation/IdleNavigationBar.swift @@ -0,0 +1,106 @@ +// +// IdleNavigationBar.swift +// DSKit +// +// Created by choijunios on 8/14/24. +// + +import UIKit +import RxSwift +import RxCocoa + +public class IdleNavigationBar: UIView { + + // Init parameters + + // View + public let backButton: UIButton = { + + let btn = UIButton() + + let image = DSKitAsset.Icons.back.image + + let imageView = UIImageView(image: image) + btn.setImage(image, for: .normal) + btn.imageView?.contentMode = .scaleAspectFit + + return btn + }() + + public lazy var titleLabel: IdleLabel = { + + let label = IdleLabel(typography: .Subtitle1) + label.textAlignment = .left + return label + }() + + private let disposeBag = DisposeBag() + + public init( + titleText: String = "", + innerViews: [UIView] + ) { + super.init(frame: .zero) + + self.titleLabel.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: 12, + bottom: 12, + right: 20 + ) + + let mainStack = HStack( + [ + [ + backButton, + Spacer(width: 4), + titleLabel, + Spacer(), + ], + innerViews + ].flatMap { $0 }, + alignment: .center, + distribution: .fill + ) + + [ + mainStack + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + self.addSubview($0) + } + + NSLayoutConstraint.activate([ + backButton.widthAnchor.constraint(equalToConstant: 32), + backButton.heightAnchor.constraint(equalToConstant: 32), + + 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), + ]) + + } +} + +@available(iOS 17.0, *) +#Preview("Preview", traits: .defaultLayout) { + let innerView = Spacer(width: 50, height: 50) + innerView.backgroundColor = .red + let bar = IdleNavigationBar(titleText: "테스트", innerViews: [innerView]) + return bar +} diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift index 9f1897c6..2572f87e 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/Profile/WorkerProfileViewController.swift @@ -18,8 +18,8 @@ public class WorkerProfileViewController: DisposableViewController { private var viewModel: (any WorkerProfileViewModelable)? // 네비게이션 바 - let navigationBar: NavigationBarType1 = { - let bar = NavigationBarType1(navigationTitle: "") + lazy var navigationBar: IdleNavigationBar = { + let bar = IdleNavigationBar(titleText: "", innerViews: [profileEditButton]) return bar }() @@ -200,14 +200,6 @@ public class WorkerProfileViewController: DisposableViewController { private func setAutoLayout() { - // 상단 네비게이션바 세팅 - let navigationStack = HStack([ - navigationBar, - profileEditButton, - ]) - navigationStack.distribution = .equalSpacing - navigationStack.backgroundColor = .clear - // 흑색 바탕 let grayBackgrounnd = UIView() grayBackgrounnd.backgroundColor = DSKitAsset.Colors.gray050.color @@ -387,7 +379,7 @@ public class WorkerProfileViewController: DisposableViewController { [ - navigationStack, + navigationBar, scrollView, ].forEach { $0.translatesAutoresizingMaskIntoConstraints = false @@ -396,11 +388,11 @@ public class WorkerProfileViewController: DisposableViewController { NSLayoutConstraint.activate([ - navigationStack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 21), - navigationStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 12), - navigationStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), + navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), - scrollView.topAnchor.constraint(equalTo: navigationStack.bottomAnchor), + scrollView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), scrollView.leftAnchor.constraint(equalTo: view.leftAnchor), scrollView.rightAnchor.constraint(equalTo: view.rightAnchor), scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), @@ -410,7 +402,7 @@ public class WorkerProfileViewController: DisposableViewController { private func setObservable() { navigationBar - .eventPublisher + .backButton.rx.tap .observe(on: MainScheduler.instance) .subscribe { [weak self] _ in self?.navigationController?.popViewController(animated: true) @@ -450,7 +442,7 @@ public class WorkerProfileViewController: DisposableViewController { .disposed(by: disposeBag) navigationBar - .eventPublisher + .backButton.rx.tap .bind(to: viewModel.exitButtonClicked) .disposed(by: disposeBag) @@ -470,7 +462,7 @@ public class WorkerProfileViewController: DisposableViewController { guard let self else { return } // UI 업데이트 - navigationBar.navigationTitle = ro.navigationTitle + navigationBar.titleLabel.textString = ro.navigationTitle profileEditButton.isHidden = !ro.showEditButton contactButtonContainer.isHidden = !ro.showContactButton // starButton.isHidden = !ro.showStarButton From e3e4492e81f329502dab9d64317154c183e9f0e8 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 14 Aug 2024 14:05:45 +0900 Subject: [PATCH 19/20] =?UTF-8?q?[IDLE-146]=20=EC=8A=A4=ED=83=80=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=A3=BC=EC=84=9D=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MVP이후에 추가로 작업할 예정입니다. --- .../Worker/ApplyScreen/ApplicantCard.swift | 46 +++++++++++-------- .../ApplyScreen/ApplicantCardCell.swift | 12 ++--- .../Overview/PostOverviewVC.swift | 2 +- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCard.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCard.swift index e23fa599..f0c0af45 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCard.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCard.swift @@ -17,16 +17,24 @@ public struct ApplicantCardRO { public let profileUrl: URL? public let isJobFinding: Bool - public let isStared: Bool +// public let isStared: Bool public let name: String public let ageText: String public let genderText: String public let expText: String - public init(profileUrl: URL?, isJobFinding: Bool, isStared: Bool, name: String, ageText: String, genderText: String, expText: String) { + public init( + profileUrl: URL?, + isJobFinding: Bool, +// isStared: Bool, + name: String, + ageText: String, + genderText: String, + expText: String + ) { self.profileUrl = profileUrl self.isJobFinding = isJobFinding - self.isStared = isStared +// self.isStared = isStared self.name = name self.ageText = ageText self.genderText = genderText @@ -36,7 +44,7 @@ public struct ApplicantCardRO { public static let mock: ApplicantCardRO = .init( profileUrl: URL(string: "https://dummyimage.com/600x400/00ffbf/0011ff&text=worker+profile"), isJobFinding: false, - isStared: false, +// isStared: false, name: "홍길동", ageText: "51세", genderText: "여성", @@ -47,7 +55,7 @@ public struct ApplicantCardRO { .init( profileUrl: vo.profileUrl, isJobFinding: vo.isJobFinding, - isStared: vo.isStared, +// isStared: vo.isStared, name: vo.name, ageText: "\(vo.age)세", genderText: vo.gender.twoLetterKoreanWord, @@ -91,14 +99,14 @@ public class ApplicantCard: UIView { return imageView }() // Star - public let starButton: IconWithColorStateButton = { - let button = IconWithColorStateButton( - representImage: DSKitAsset.Icons.subscribeStar.image, - normalColor: DSKitAsset.Colors.gray200.color, - accentColor: DSKitAsset.Colors.orange300.color - ) - return button - }() +// public let starButton: IconWithColorStateButton = { +// let button = IconWithColorStateButton( +// representImage: DSKitAsset.Icons.subscribeStar.image, +// normalColor: DSKitAsset.Colors.gray200.color, +// accentColor: DSKitAsset.Colors.orange300.color +// ) +// return button +// }() // Row1 let workingTag: TagLabel = { @@ -211,13 +219,13 @@ public class ApplicantCard: UIView { profileImageContainer, labelStack, Spacer(), - starButton +// starButton ], spacing: 16, alignment: .top) - NSLayoutConstraint.activate([ - starButton.widthAnchor.constraint(equalToConstant: 22), - starButton.heightAnchor.constraint(equalTo: starButton.widthAnchor), - ]) +// NSLayoutConstraint.activate([ +// starButton.widthAnchor.constraint(equalToConstant: 22), +// starButton.heightAnchor.constraint(equalTo: starButton.widthAnchor), +// ]) let buttonStack = HStack([ showProfileButton, employButton @@ -255,7 +263,7 @@ public class ApplicantCard: UIView { } workingTag.textString = ro.isJobFinding ? "구직중" : "휴식중" - starButton.setState(ro.isStared ? .accent : .normal) +// starButton.setState(ro.isStared ? .accent : .normal) nameLabel.textString = ro.name infoLabel.textString = "\(ro.ageText) \(ro.genderText)" expLabel.textString = "\(ro.expText)" diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift index c3cd9a30..c5af8cb9 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Card/Post/Worker/ApplyScreen/ApplicantCardCell.swift @@ -71,12 +71,12 @@ public class ApplicantCardCell: UITableViewCell { }), // Input - cardView - .starButton.eventPublisher - .map { state in - state == .accent - } - .bind(to: viewModel.staredThisWorker), +// cardView +// .starButton.eventPublisher +// .map { state in +// state == .accent +// } +// .bind(to: viewModel.staredThisWorker), cardView .showProfileButton.rx.tap 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 68bb72b7..eb81b020 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 @@ -313,7 +313,7 @@ public class PostOverviewVC: BaseViewController { .disposed(by: disposeBag) - // 앞전가지 입력한 정보를 저장합니다. + // 앞전까지 입력한 정보를 저장합니다. viewModel.updateToState() // 화면이 등장할 때마다 유효한 상태를 불러옵니다. From 7c6cecda0d0200e41a0455d284ced8788d1de9c7 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 14 Aug 2024 14:11:21 +0900 Subject: [PATCH 20/20] =?UTF-8?q?[IDLE-146]=20=EC=98=88=EC=8B=9C=EC=95=B1?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Worker/ExampleApp/Sources/SceneDelegate.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift index 642cc560..f4d784fc 100644 --- a/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/Worker/ExampleApp/Sources/SceneDelegate.swift @@ -32,7 +32,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { repository: DefaultUserProfileRepository(store) ) - let vm = WorkerMyProfileViewModel(workerProfileUseCase: useCase) + let vm = WorkerMyProfileViewModel( + coordinator: nil, + workerProfileUseCase: useCase + ) let vc = WorkerProfileViewController()