diff --git a/Projects/Data/Sources/DTO/Applications/TotalPassStudentResponseDTO.swift b/Projects/Data/Sources/DTO/Applications/TotalPassStudentResponseDTO.swift
index 5d6dfa26..c9f24c55 100644
--- a/Projects/Data/Sources/DTO/Applications/TotalPassStudentResponseDTO.swift
+++ b/Projects/Data/Sources/DTO/Applications/TotalPassStudentResponseDTO.swift
@@ -19,7 +19,8 @@ public struct TotalPassStudentResponseDTO: Codable {
public extension TotalPassStudentResponseDTO {
func toDomain() -> TotalPassStudentEntity {
- TotalPassStudentEntity(
+ let totalStudentCount = totalStudentCount > 0 ? totalStudentCount: 1
+ return TotalPassStudentEntity(
totalStudentCount: totalStudentCount,
passedCount: passedCount,
approvedCount: approvedCount
diff --git a/Projects/Domain/Sources/UseCases/Recruitments/FetchRecruitmentListUseCase.swift b/Projects/Domain/Sources/UseCases/Recruitments/FetchRecruitmentListUseCase.swift
index a86e9127..f9358d6f 100644
--- a/Projects/Domain/Sources/UseCases/Recruitments/FetchRecruitmentListUseCase.swift
+++ b/Projects/Domain/Sources/UseCases/Recruitments/FetchRecruitmentListUseCase.swift
@@ -8,7 +8,7 @@ public struct FetchRecruitmentListUseCase {
private let recruitmentsRepository: RecruitmentsRepository
public func execute(
- page: Int, jobCode: String?, techCode: [String]?, name: String?
+ page: Int, jobCode: String? = nil, techCode: [String]? = nil, name: String? = nil
) -> Single<[RecruitmentEntity]> {
recruitmentsRepository.fetchRecruitmentList(page: page, jobCode: jobCode, techCode: techCode, name: name)
}
diff --git a/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/bookmarkOff.imageset/Bookmark-1.svg b/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Bookmark Off.imageset/Bookmark-1.svg
similarity index 100%
rename from Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/bookmarkOff.imageset/Bookmark-1.svg
rename to Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Bookmark Off.imageset/Bookmark-1.svg
diff --git a/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/bookmarkOff.imageset/Contents.json b/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Bookmark Off.imageset/Contents.json
similarity index 100%
rename from Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/bookmarkOff.imageset/Contents.json
rename to Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Bookmark Off.imageset/Contents.json
diff --git a/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/bookmarkOn.imageset/Bookmark.svg b/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Bookmark On.imageset/Bookmark.svg
similarity index 100%
rename from Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/bookmarkOn.imageset/Bookmark.svg
rename to Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Bookmark On.imageset/Bookmark.svg
diff --git a/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/bookmarkOn.imageset/Contents.json b/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Bookmark On.imageset/Contents.json
similarity index 100%
rename from Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/bookmarkOn.imageset/Contents.json
rename to Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Bookmark On.imageset/Contents.json
diff --git a/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/searchIcon.imageset/Contents.json b/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Search Icon.imageset/Contents.json
similarity index 100%
rename from Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/searchIcon.imageset/Contents.json
rename to Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Search Icon.imageset/Contents.json
diff --git a/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/searchIcon.imageset/search.svg b/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Search Icon.imageset/search.svg
similarity index 100%
rename from Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/searchIcon.imageset/search.svg
rename to Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Search Icon.imageset/search.svg
diff --git a/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Sound.imageset/Contents.json b/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Sound.imageset/Contents.json
new file mode 100644
index 00000000..6f2dd8dd
--- /dev/null
+++ b/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Sound.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "fluent_speaker-2-48-regular.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Sound.imageset/fluent_speaker-2-48-regular.svg b/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Sound.imageset/fluent_speaker-2-48-regular.svg
new file mode 100644
index 00000000..e407be70
--- /dev/null
+++ b/Projects/Modules/DesignSystem/Resources/Images/Icons.xcassets/Sound.imageset/fluent_speaker-2-48-regular.svg
@@ -0,0 +1,3 @@
+
diff --git a/Projects/Modules/DesignSystem/Sources/Image/JobisIcon.swift b/Projects/Modules/DesignSystem/Sources/Image/JobisIcon.swift
index 06aee370..8972a827 100644
--- a/Projects/Modules/DesignSystem/Sources/Image/JobisIcon.swift
+++ b/Projects/Modules/DesignSystem/Sources/Image/JobisIcon.swift
@@ -29,6 +29,7 @@ public enum JobisIcon {
case currentPageControl
case defaultPageControl
case pieChart
+ case sound
case trash
case emptyBookmark
@@ -100,6 +101,9 @@ public enum JobisIcon {
case .pieChart:
return dsIcons.pieChart.image
+ case .sound:
+ return dsIcons.sound.image
+
case .trash:
return dsIcons.trash.image
diff --git a/Projects/Presentation/Sources/Base/BaseBottomSheetViewController.swift b/Projects/Presentation/Sources/Base/BaseBottomSheetViewController.swift
new file mode 100644
index 00000000..78b5dee9
--- /dev/null
+++ b/Projects/Presentation/Sources/Base/BaseBottomSheetViewController.swift
@@ -0,0 +1,251 @@
+import UIKit
+import Then
+import SnapKit
+import RxGesture
+import RxSwift
+import RxCocoa
+import DesignSystem
+
+public enum BottomSheetViewState {
+ case normal
+ case custom(height: CGFloat)
+}
+
+public class BaseBottomSheetViewController: UIViewController,
+ ViewControllable,
+ LifeCyclePublishable,
+ HasDisposeBag,
+ AddViewable,
+ SetLayoutable,
+ Bindable,
+ ViewControllerConfigurable,
+ NavigationConfigurable {
+ public let viewModel: ViewModel
+ public var disposeBag = DisposeBag()
+ public var viewDidLoadPublisher = PublishRelay()
+ public var viewWillAppearPublisher = PublishRelay()
+ public var viewDidAppearPublisher = PublishRelay()
+ public var viewWillDisappearPublisher = PublishRelay()
+ public var viewDidDisappearPublisher = PublishRelay()
+ private lazy var dimmedView = UIView().then {
+ $0.backgroundColor = .black.withAlphaComponent(0.5)
+ $0.alpha = 0
+ $0.isUserInteractionEnabled = true
+ }
+ private let bottomSheetView = UIView().then {
+ $0.backgroundColor = .clear
+ }
+ private let dragIndicatorView = UIView().then {
+ $0.backgroundColor = .white
+ $0.layer.cornerRadius = 2
+ }
+ public let contentView = UIView().then {
+ $0.backgroundColor = .GrayScale.gray30
+ $0.layer.cornerRadius = 16
+ $0.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
+ $0.clipsToBounds = true
+ }
+ private let state: BottomSheetViewState
+ private let bottomSheetPanMinTopInset: CGFloat = 50
+ private let dragHeight = 28.0
+ private var defaultHeight: CGFloat = 500
+ private lazy var maxTopInset = (
+ view.safeAreaInsets.bottom + view.safeAreaLayoutGuide.layoutFrame.height
+ )
+ private lazy var bottomSheetViewTopInset: CGFloat = maxTopInset
+ private lazy var bottomSheetPanStartingTopInset: CGFloat = bottomSheetPanMinTopInset
+
+ public init(_ viewModel: ViewModel, state: BottomSheetViewState = .normal) {
+ self.viewModel = viewModel
+ self.state = state
+ super .init(nibName: nil, bundle: nil)
+ self.modalPresentationStyle = .overFullScreen
+ }
+
+ required public init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func viewWillLayoutSubviews() {
+ super.viewWillLayoutSubviews()
+ self.addView()
+ self.setLayout()
+ }
+
+ public override func viewDidLoad() {
+ super.viewDidLoad()
+ self.bind()
+ self.configureNavigation()
+ self.configureViewController()
+ }
+
+ public override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ self.viewWillAppearPublisher.accept(())
+ }
+
+ public override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+ self.viewDidAppearPublisher.accept(())
+ self.showBottomSheet(atState: state)
+ }
+
+ public override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ self.viewWillDisappearPublisher.accept(())
+ }
+
+ public override func viewDidDisappear(_ animated: Bool) {
+ super.viewDidDisappear(animated)
+ self.viewDidDisappearPublisher.accept(())
+ }
+
+ public override func touchesBegan(_ touches: Set, with event: UIEvent?) {
+ super.touchesBegan(touches, with: event)
+ self.view.endEditing(true)
+ }
+
+ public func addView() {
+ [
+ dimmedView,
+ bottomSheetView
+ ].forEach(view.addSubview(_:))
+ [
+ dragIndicatorView,
+ contentView
+ ].forEach(bottomSheetView.addSubview(_:))
+ }
+
+ public func setLayout() {
+ dimmedView.snp.makeConstraints {
+ $0.edges.equalToSuperview()
+ }
+ bottomSheetView.snp.updateConstraints {
+ $0.leading.trailing.bottom.equalToSuperview()
+ $0.top.equalTo(view.safeAreaLayoutGuide).inset(bottomSheetViewTopInset)
+ }
+ dragIndicatorView.snp.makeConstraints {
+ $0.width.lessThanOrEqualTo(64)
+ $0.height.lessThanOrEqualTo(4)
+ $0.centerX.equalToSuperview()
+ $0.top.lessThanOrEqualToSuperview().inset(12)
+ }
+ contentView.snp.makeConstraints {
+ $0.top.equalTo(dragIndicatorView.snp.bottom).offset(12)
+ $0.leading.trailing.bottom.equalToSuperview()
+ }
+ }
+
+ public func bind() {}
+
+ public func configureViewController() {
+ dimmedView.rx.tapGesture()
+ .when(.recognized)
+ .bind { [weak self] _ in
+ self?.dismissBottomSheet()
+ }
+ .disposed(by: disposeBag)
+
+ bottomSheetView.rx.panGesture()
+ .skip(1)
+ .bind(with: self, onNext: { owner, gesture in
+ let translation = gesture.translation(in: owner.view)
+ let defaultPadding = owner.maxTopInset - owner.defaultHeight
+
+ switch gesture.state {
+ case .began:
+ owner.bottomSheetPanStartingTopInset = owner.bottomSheetViewTopInset
+
+ case .changed:
+ if translation.y > 0 {
+ owner.bottomSheetViewTopInset = owner
+ .bottomSheetPanStartingTopInset + translation.y
+ }
+ owner.bottomSheetView.snp.updateConstraints {
+ $0.top.equalTo(owner.view.safeAreaLayoutGuide)
+ .inset(owner.bottomSheetViewTopInset)
+ }
+
+ case .ended:
+ let nearestValue = owner.nearest(
+ to: owner.bottomSheetViewTopInset,
+ inValues: [defaultPadding, owner.maxTopInset]
+ )
+
+ if nearestValue == defaultPadding {
+ owner.showBottomSheet(atState: owner.state)
+ } else {
+ owner.dismissBottomSheet()
+ }
+
+ default:
+ return
+ }
+ })
+ .disposed(by: disposeBag)
+ }
+
+ public func configureNavigation() {}
+}
+
+extension BaseBottomSheetViewController {
+ private func showBottomSheet(atState: BottomSheetViewState) {
+ switch atState {
+ case .normal:
+ bottomSheetViewTopInset = maxTopInset - defaultHeight - dragHeight
+ case let .custom(customHegiht):
+ defaultHeight = customHegiht
+ let topInset = maxTopInset - customHegiht - dragHeight
+ if topInset > 0 {
+ bottomSheetViewTopInset = topInset
+ } else {
+ bottomSheetViewTopInset = bottomSheetPanMinTopInset
+ defaultHeight = maxTopInset - bottomSheetPanMinTopInset
+ }
+ }
+
+ bottomSheetView.snp.remakeConstraints {
+ $0.leading.trailing.bottom.equalToSuperview()
+ $0.top.equalTo(view.safeAreaLayoutGuide).inset(bottomSheetViewTopInset)
+ }
+
+ UIView.animate(
+ withDuration: 0.2,
+ delay: 0,
+ options: .curveEaseInOut
+ ) { self.dimmedView.alpha = 1 }
+
+ UIView.animate(
+ withDuration: 0.5,
+ delay: 0,
+ usingSpringWithDamping: 0.76,
+ initialSpringVelocity: 0.0
+ ) { self.view.layoutIfNeeded() }
+ }
+
+ public func dismissBottomSheet() {
+ bottomSheetViewTopInset = maxTopInset
+ bottomSheetView.snp.remakeConstraints {
+ $0.leading.trailing.bottom.equalToSuperview()
+ $0.top.equalTo(view.safeAreaLayoutGuide).inset(bottomSheetViewTopInset)
+ }
+
+ UIView.animate(
+ withDuration: 0.2,
+ delay: 0,
+ options: .curveEaseInOut
+ ) {
+ self.dimmedView.alpha = 0
+ self.view.layoutIfNeeded()
+ } completion: { _ in
+ self.dismiss(animated: false)
+ }
+ }
+
+ // 가까이 있는 숫자 반환해주는 함수
+ private func nearest(to number: CGFloat, inValues values: [CGFloat]) -> CGFloat {
+ guard let nearestVal = values.min(by: { abs(number - $0) < abs(number - $1) })
+ else { return number }
+ return nearestVal
+ }
+}
diff --git a/Projects/Presentation/Sources/Base/BaseTabBarController.swift b/Projects/Presentation/Sources/Base/BaseTabBarController.swift
index bdd31960..eae294bb 100644
--- a/Projects/Presentation/Sources/Base/BaseTabBarController.swift
+++ b/Projects/Presentation/Sources/Base/BaseTabBarController.swift
@@ -9,7 +9,6 @@ public class BaseTabBarController: UITabBarController,
private let stroke = UIView().then {
$0.backgroundColor = .GrayScale.gray30
}
- private let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
public override func viewDidLoad() {
super.viewDidLoad()
@@ -33,10 +32,6 @@ public class BaseTabBarController: UITabBarController,
$0.height.equalTo(1)
}
}
-
- public override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
- self.impactFeedbackGenerator.impactOccurred()
- }
}
extension BaseTabBarController: UITabBarControllerDelegate {
diff --git a/Projects/Presentation/Sources/DI/PresentationAssembly.swift b/Projects/Presentation/Sources/DI/PresentationAssembly.swift
index 771ff1fb..8cba348a 100644
--- a/Projects/Presentation/Sources/DI/PresentationAssembly.swift
+++ b/Projects/Presentation/Sources/DI/PresentationAssembly.swift
@@ -31,8 +31,12 @@ public final class PresentationAssembly: Assembly {
container.register(RecruitmentViewController.self) { resolver in
RecruitmentViewController(resolver.resolve(RecruitmentViewModel.self)!)
}
- container.register(RecruitmentViewModel.self) { _ in
- RecruitmentViewModel()
+
+ container.register(RecruitmentViewModel.self) { resolver in
+ RecruitmentViewModel(
+ fetchRecruitmentListUseCase: resolver.resolve(FetchRecruitmentListUseCase.self)!,
+ bookmarkUseCase: resolver.resolve(BookmarkUseCase.self)!
+ )
}
container.register(BookmarkViewController.self) { resolver in
diff --git a/Projects/Presentation/Sources/MyPage/Components/Cell/SectionTableViewCell.swift b/Projects/Presentation/Sources/MyPage/Components/Cell/SectionTableViewCell.swift
index 8f9aaea8..374c28a0 100644
--- a/Projects/Presentation/Sources/MyPage/Components/Cell/SectionTableViewCell.swift
+++ b/Projects/Presentation/Sources/MyPage/Components/Cell/SectionTableViewCell.swift
@@ -1,22 +1,25 @@
import UIKit
import DesignSystem
-final class SectionTableViewCell: UITableViewCell {
+typealias SectionType = (String, UIImage)
+final class SectionTableViewCell: BaseTableViewCell {
static let identifier = "SectionTableViewCell"
private let sectionImageView = UIImageView()
private let titleLabel = UILabel()
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
super.setHighlighted(highlighted, animated: animated)
- self.titleLabel.textColor = self.isHighlighted ? .GrayScale.gray90.withAlphaComponent(0.1) : .GrayScale.gray90
+ self.titleLabel.textColor = self.isHighlighted ? .GrayScale.gray50 : .GrayScale.gray90
}
- override func layoutSubviews() {
+ override func addView() {
[
sectionImageView,
titleLabel
].forEach { self.addSubview($0) }
+ }
+ override func setLayout() {
sectionImageView.snp.makeConstraints {
$0.top.bottom.equalToSuperview().inset(12)
$0.width.equalTo(28)
@@ -28,9 +31,9 @@ final class SectionTableViewCell: UITableViewCell {
}
}
- func setCell(image: UIImage, title: String) {
- self.sectionImageView.image = image
- self.titleLabel.setJobisText(title, font: .body, color: .GrayScale.gray90)
+ override func adapt(model: SectionType) {
+ self.sectionImageView.image = model.1
+ self.titleLabel.setJobisText(model.0, font: .body, color: .GrayScale.gray90)
self.selectionStyle = .none
}
}
diff --git a/Projects/Presentation/Sources/MyPage/Components/HelpSectionView.swift b/Projects/Presentation/Sources/MyPage/Components/HelpSectionView.swift
new file mode 100644
index 00000000..24f794cc
--- /dev/null
+++ b/Projects/Presentation/Sources/MyPage/Components/HelpSectionView.swift
@@ -0,0 +1,32 @@
+import UIKit
+import SnapKit
+import Then
+import RxSwift
+import RxCocoa
+import DesignSystem
+
+final class HelpSectionView: BaseView {
+ enum HelpSectionType: Int {
+ case announcement
+ }
+ private let helpSectionView = SectionView(
+ menuText: "도움말",
+ items: [
+ ("공지사항", .jobisIcon(.sound))
+ ]
+ )
+
+ override func addView() {
+ self.addSubview(helpSectionView)
+ }
+
+ override func setLayout() {
+ helpSectionView.snp.makeConstraints {
+ $0.edges.equalToSuperview()
+ }
+ }
+
+ func getSelectedItem(type: HelpSectionType) -> Observable {
+ self.helpSectionView.getSelectedItem(index: type.rawValue)
+ }
+}
diff --git a/Projects/Presentation/Sources/MyPage/Components/SectionView.swift b/Projects/Presentation/Sources/MyPage/Components/SectionView.swift
index 7b4322f3..b2e9d5ec 100644
--- a/Projects/Presentation/Sources/MyPage/Components/SectionView.swift
+++ b/Projects/Presentation/Sources/MyPage/Components/SectionView.swift
@@ -64,7 +64,8 @@ extension SectionView: UITableViewDataSource {
withIdentifier: SectionTableViewCell.identifier,
for: indexPath
) as? SectionTableViewCell else { return UITableViewCell() }
- cell.setCell(image: items[indexPath.row].icon, title: items[indexPath.row].title)
+ cell.adapt(model: items[indexPath.row])
+
return cell
}
}
diff --git a/Projects/Presentation/Sources/MyPage/MyPageViewController.swift b/Projects/Presentation/Sources/MyPage/MyPageViewController.swift
index 9c52cfc0..259c1ff4 100644
--- a/Projects/Presentation/Sources/MyPage/MyPageViewController.swift
+++ b/Projects/Presentation/Sources/MyPage/MyPageViewController.swift
@@ -19,6 +19,7 @@ public final class MyPageViewController: BaseViewController {
private let reviewNavigateStackView = ReviewNavigateStackView()
private let accountSectionView = AccountSectionView()
private let bugSectionView = BugSectionView()
+ private let helpSectionView = HelpSectionView()
public override func addView() {
self.view.addSubview(scrollView)
@@ -27,6 +28,7 @@ public final class MyPageViewController: BaseViewController {
studentInfoView,
editButton,
reviewNavigateStackView,
+ helpSectionView,
accountSectionView,
bugSectionView
].forEach { self.contentView.addSubview($0) }
@@ -36,31 +38,43 @@ public final class MyPageViewController: BaseViewController {
scrollView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
+
contentView.snp.makeConstraints {
$0.edges.equalTo(scrollView.contentLayoutGuide)
$0.top.width.equalToSuperview()
- $0.bottom.equalTo(bugSectionView)
+ $0.bottom.equalTo(bugSectionView).offset(60)
}
+
studentInfoView.snp.makeConstraints {
$0.top.equalToSuperview()
$0.leading.trailing.equalToSuperview()
}
+
editButton.snp.makeConstraints {
$0.centerY.equalTo(studentInfoView)
$0.trailing.equalToSuperview().offset(-28)
}
+
reviewNavigateStackView.snp.updateConstraints {
$0.leading.trailing.equalToSuperview().inset(24)
$0.top.equalTo(studentInfoView.snp.bottom)
}
- accountSectionView.snp.makeConstraints {
+
+ helpSectionView.snp.makeConstraints {
$0.top.equalTo(reviewNavigateStackView.snp.bottom)
$0.leading.trailing.equalToSuperview()
}
+
+ accountSectionView.snp.makeConstraints {
+ $0.top.equalTo(helpSectionView.snp.bottom)
+ $0.leading.trailing.equalToSuperview()
+ }
+
bugSectionView.snp.makeConstraints {
$0.top.equalTo(accountSectionView.snp.bottom)
$0.leading.trailing.equalToSuperview()
}
+
}
public override func bind() {
diff --git a/Projects/Presentation/Sources/Recruitment/RecruitmentTableViewCell.swift b/Projects/Presentation/Sources/Recruitment/Cell/RecruitmentTableViewCell.swift
similarity index 54%
rename from Projects/Presentation/Sources/Recruitment/RecruitmentTableViewCell.swift
rename to Projects/Presentation/Sources/Recruitment/Cell/RecruitmentTableViewCell.swift
index 7b4a553c..cb02fabb 100644
--- a/Projects/Presentation/Sources/Recruitment/RecruitmentTableViewCell.swift
+++ b/Projects/Presentation/Sources/Recruitment/Cell/RecruitmentTableViewCell.swift
@@ -1,18 +1,30 @@
import UIKit
+import Domain
import DesignSystem
import SnapKit
import Then
import RxSwift
import RxCocoa
-final class RecruitmentTableViewCell: UITableViewCell {
+final class RecruitmentTableViewCell: BaseTableViewCell {
static let identifier = "RecruitmentTableViewCell"
-
+ public var bookmarkButtonDidTap: (()->(Void))?
+ public var recruitmentID = 0
private var disposeBag = DisposeBag()
- private var isBookmarked: Bool = false
+ private var isBookmarked = false {
+ didSet {
+ var bookmarkImage: JobisIcon {
+ isBookmarked ? .bookmarkOn: .bookmarkOff
+ }
+ bookmarkButton.setImage(
+ .jobisIcon(bookmarkImage)
+ .resize(size: 28), for: .normal
+ )
+ }
+ }
private let companyProfileImageView = UIImageView().then {
- $0.backgroundColor = .blue
$0.layer.cornerRadius = 8
+ $0.clipsToBounds = true
}
private let fieldTypeLabel = UILabel().then {
$0.setJobisText(
@@ -20,6 +32,8 @@ final class RecruitmentTableViewCell: UITableViewCell {
font: .subHeadLine,
color: UIColor.GrayScale.gray90
)
+ $0.numberOfLines = 1
+ $0.lineBreakMode = .byTruncatingTail
}
private let benefitsLabel = UILabel().then {
$0.setJobisText(
@@ -35,46 +49,11 @@ final class RecruitmentTableViewCell: UITableViewCell {
color: UIColor.GrayScale.gray70
)
}
- private let bookmarkButton = UIButton().then {
- $0.setImage(
- .jobisIcon(.bookmarkOff).resize(size: 28),
- for: .normal
- )
- }
-
- override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
- super.init(style: style, reuseIdentifier: reuseIdentifier)
- backgroundColor = UIColor.GrayScale.gray10
- addView()
- layout()
- attribute()
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
+ public let bookmarkButton = UIButton().then {
+ $0.setImage(.jobisIcon(.bookmarkOff).resize(size: 28), for: .normal)
}
- private func attribute() {
- bookmarkButton.rx.tap.asObservable()
- .subscribe(onNext: { [weak self] in
- guard let self else { return }
- bookmark()
- })
- .disposed(by: disposeBag)
- }
-
- private func bookmark() {
- var bookmarkImage: JobisIcon {
- isBookmarked ? .bookmarkOn: .bookmarkOff
- }
- bookmarkButton.setImage(
- .jobisIcon(bookmarkImage).resize(size: 28),
- for: .normal
- )
- isBookmarked.toggle()
- }
-
- private func addView() {
+ override func addView() {
[
companyProfileImageView,
fieldTypeLabel,
@@ -86,15 +65,20 @@ final class RecruitmentTableViewCell: UITableViewCell {
}
}
- private func layout() {
+ override func setLayout() {
companyProfileImageView.snp.makeConstraints {
$0.top.equalToSuperview().inset(12)
$0.left.equalToSuperview().inset(24)
$0.width.height.equalTo(48)
}
+ bookmarkButton.snp.makeConstraints {
+ $0.top.equalToSuperview().inset(12)
+ $0.right.equalToSuperview().inset(24)
+ }
fieldTypeLabel.snp.makeConstraints {
$0.top.equalToSuperview().inset(12)
$0.left.equalTo(companyProfileImageView.snp.right).offset(12)
+ $0.right.equalToSuperview().inset(52)
}
benefitsLabel.snp.makeConstraints {
$0.top.equalTo(fieldTypeLabel.snp.bottom).offset(4)
@@ -104,9 +88,39 @@ final class RecruitmentTableViewCell: UITableViewCell {
$0.top.equalTo(benefitsLabel.snp.bottom).offset(4)
$0.left.equalTo(companyProfileImageView.snp.right).offset(12)
}
- bookmarkButton.snp.makeConstraints {
- $0.top.equalToSuperview().inset(12)
- $0.right.equalToSuperview().inset(24)
- }
+ }
+
+ override func configureView() {
+ companyProfileImageView.layer.cornerRadius = 8
+ bookmarkButton.rx.tap
+ .bind(onNext: { [weak self] in
+ self?.bookmarkButtonDidTap?()
+ self?.isBookmarked.toggle()
+ })
+ .disposed(by: disposeBag)
+ }
+
+ override func adapt(model: RecruitmentEntity) {
+ companyProfileImageView.setJobisImage(
+ urlString: model.companyProfileURL
+ )
+ fieldTypeLabel.setJobisText(
+ model.hiringJobs,
+ font: .subHeadLine,
+ color: .GrayScale.gray90
+ )
+ let militarySupport = model.militarySupport ? "O": "X"
+ benefitsLabel.setJobisText(
+ "병역특례 \(militarySupport) · 실습 수당 \(model.trainPay)만원",
+ font: .subBody,
+ color: .GrayScale.gray70
+ )
+ companyLabel.setJobisText(
+ model.companyName,
+ font: .description,
+ color: .GrayScale.gray70
+ )
+ recruitmentID = model.recruitID
+ isBookmarked = model.bookmarked
}
}
diff --git a/Projects/Presentation/Sources/Recruitment/RecruitmentViewController.swift b/Projects/Presentation/Sources/Recruitment/RecruitmentViewController.swift
index 45f8d45b..508336f9 100644
--- a/Projects/Presentation/Sources/Recruitment/RecruitmentViewController.swift
+++ b/Projects/Presentation/Sources/Recruitment/RecruitmentViewController.swift
@@ -1,4 +1,5 @@
import UIKit
+import Domain
import RxSwift
import RxCocoa
import SnapKit
@@ -7,75 +8,82 @@ import Core
import DesignSystem
public final class RecruitmentViewController: BaseViewController {
- private let tableView = UITableView().then {
- $0.register(RecruitmentTableViewCell.self, forCellReuseIdentifier: RecruitmentTableViewCell.identifier)
+ private let bookmarkButtonDidClicked = PublishRelay()
+ private let pageCount = PublishRelay()
+ private let recruitmentTableView = UITableView().then {
+ $0.register(
+ RecruitmentTableViewCell.self,
+ forCellReuseIdentifier: RecruitmentTableViewCell.identifier
+ )
$0.separatorStyle = .none
$0.rowHeight = 96
+ $0.showsVerticalScrollIndicator = false
}
- private let navigateToFilterButton = UIButton().then {
+ private let filterButton = UIButton().then {
$0.setImage(.jobisIcon(.filterIcon), for: .normal)
}
- private let navigateToSearchButton = UIButton().then {
+ private let searchButton = UIButton().then {
$0.setImage(.jobisIcon(.searchIcon), for: .normal)
}
public override func addView() {
- self.view.addSubview(tableView)
+ self.view.addSubview(recruitmentTableView)
}
public override func setLayout() {
- tableView.snp.makeConstraints {
+ recruitmentTableView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
- public override func configureViewController() {
- tableView.dataSource = self
- tableView.delegate = self
+ public override func bind() {
+ let input = RecruitmentViewModel.Input(
+ viewAppear: self.viewWillAppearPublisher,
+ bookMarkButtonDidTap: bookmarkButtonDidClicked,
+ pageChange: pageCount
+ )
+
+ let output = viewModel.transform(input)
- navigateToSearchButton.rx.tap
- .subscribe(onNext: { _ in
- print("hello")
- })
+ output.recruitmentData
+ .bind(
+ to: recruitmentTableView.rx.items(
+ cellIdentifier: RecruitmentTableViewCell.identifier,
+ cellType: RecruitmentTableViewCell.self
+ )) { _, element, cell in
+ cell.adapt(model: element)
+ cell.bookmarkButtonDidTap = {
+ self.bookmarkButtonDidClicked.accept(cell.recruitmentID)
+ }
+ }
+ .disposed(by: disposeBag)
+ }
+
+ public override func configureViewController() {
+ recruitmentTableView.delegate = self
+ searchButton.rx.tap
+ .subscribe(onNext: { _ in })
.disposed(by: disposeBag)
}
public override func configureNavigation() {
navigationItem.rightBarButtonItems = [
- UIBarButtonItem(customView: navigateToFilterButton),
- UIBarButtonItem(customView: navigateToSearchButton)
+ UIBarButtonItem(customView: filterButton),
+ UIBarButtonItem(customView: searchButton)
]
setLargeTitle(title: "모집의뢰서")
}
}
-extension RecruitmentViewController: UITableViewDataSource, UITableViewDelegate {
- public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return 10
- }
- public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- guard let cell = tableView.dequeueReusableCell(
- withIdentifier: RecruitmentTableViewCell.identifier,
- for: indexPath
- ) as? RecruitmentTableViewCell else { return UITableViewCell() }
-
- return cell
- }
-
- public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- if let cell = tableView.cellForRow(at: indexPath) {
- cell.contentView.backgroundColor = UIColor.GrayScale.gray20
- // 다른 동작 수행 가능
- // 예: 특정 셀을 선택했을 때의 동작 처리
-
- // 지연 작업을 통해 일정 시간 후에 원래 색으로 돌아가도록 함
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
- UIView.animate(withDuration: 0.1) {
- cell.contentView.backgroundColor = UIColor.GrayScale.gray10
- }
- }
+extension RecruitmentViewController: UITableViewDelegate {
+ public func tableView(
+ _ tableView: UITableView,
+ willDisplay cell: UITableViewCell,
+ forRowAt indexPath: IndexPath
+ ) {
+ let lastRowIndex = tableView.numberOfRows(inSection: indexPath.section) - 1
+ if indexPath.row == lastRowIndex {
+ pageCount.accept(indexPath.row)
}
-
- tableView.deselectRow(at: indexPath, animated: false)
}
}
diff --git a/Projects/Presentation/Sources/Recruitment/RecruitmentViewModel.swift b/Projects/Presentation/Sources/Recruitment/RecruitmentViewModel.swift
index 139c258c..00468963 100644
--- a/Projects/Presentation/Sources/Recruitment/RecruitmentViewModel.swift
+++ b/Projects/Presentation/Sources/Recruitment/RecruitmentViewModel.swift
@@ -6,17 +6,68 @@ import Core
import Domain
public final class RecruitmentViewModel: BaseViewModel, Stepper {
- public var steps = PublishRelay()
-
+ public let steps = PublishRelay()
private let disposeBag = DisposeBag()
+ private let fetchRecruitmentListUseCase: FetchRecruitmentListUseCase
+ private let bookmarkUseCase: BookmarkUseCase
+ private var recruitmentData = BehaviorRelay<[RecruitmentEntity]>(value: [])
+ private var pageCount: Int = 1
+
+ init(
+ fetchRecruitmentListUseCase: FetchRecruitmentListUseCase,
+ bookmarkUseCase: BookmarkUseCase
+ ) {
+ self.fetchRecruitmentListUseCase = fetchRecruitmentListUseCase
+ self.bookmarkUseCase = bookmarkUseCase
+ }
public struct Input {
+ let viewAppear: PublishRelay
+ let bookMarkButtonDidTap: PublishRelay
+ var pageChange: PublishRelay
}
public struct Output {
+ var recruitmentData = BehaviorRelay<[RecruitmentEntity]>(value: [])
}
public func transform(_ input: Input) -> Output {
- return Output()
+ input.viewAppear.asObservable()
+ .flatMap {
+ self.pageCount = 1
+ return self.fetchRecruitmentListUseCase.execute(page: self.pageCount)
+ }
+ .bind(onNext: {
+ self.recruitmentData.accept([])
+ var currentElements = self.recruitmentData.value
+ currentElements.append(contentsOf: $0)
+ self.recruitmentData.accept(currentElements)
+ })
+ .disposed(by: disposeBag)
+
+ input.pageChange.asObservable()
+ .flatMap { value in
+ if value == self.recruitmentData.value.count-1 {
+ self.pageCount += 1
+ return self.fetchRecruitmentListUseCase.execute(page: self.pageCount)
+ } else {
+ return Single.just([])
+ }
+ }
+ .bind(onNext: {
+ var currentElements = self.recruitmentData.value
+ currentElements.append(contentsOf: $0)
+ self.recruitmentData.accept(currentElements)
+ })
+ .disposed(by: disposeBag)
+
+ input.bookMarkButtonDidTap.asObservable()
+ .flatMap { id in
+ self.bookmarkUseCase.execute(id: id)
+ }.subscribe(onCompleted: {
+ print("bookmark!")
+ }).disposed(by: disposeBag)
+
+ return Output(recruitmentData: recruitmentData)
}
}