diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/AppliedAndLikedBoardCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/AppliedAndLikedBoardCoordinator.swift index f7fb2469..40062b20 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/AppliedAndLikedBoardCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/AppliedAndLikedBoardCoordinator.swift @@ -74,7 +74,8 @@ extension AppliedAndLikedBoardCoordinator { parent: self, navigationController: navigationController, recruitmentPostUseCase: injector.resolve(RecruitmentPostUseCase.self), - workerProfileUseCase: injector.resolve(WorkerProfileUseCase.self) + workerProfileUseCase: injector.resolve(WorkerProfileUseCase.self), + centerProfileUseCase: injector.resolve(CenterProfileUseCase.self) ) ) addChildCoordinator(coodinator) @@ -92,4 +93,6 @@ extension AppliedAndLikedBoardCoordinator { coordinator.parent = self coordinator.start() } + + func showWorkerProfile() { } } diff --git a/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerRecruitmentBoardCoordinator.swift b/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerRecruitmentBoardCoordinator.swift index b4006799..5111c7b1 100644 --- a/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerRecruitmentBoardCoordinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Main/Worker/SubCoordinator/WorkerRecruitmentBoardCoordinator.swift @@ -63,7 +63,8 @@ extension WorkerRecruitmentBoardCoordinator { parent: self, navigationController: navigationController, recruitmentPostUseCase: injector.resolve(RecruitmentPostUseCase.self), - workerProfileUseCase: injector.resolve(WorkerProfileUseCase.self) + workerProfileUseCase: injector.resolve(WorkerProfileUseCase.self), + centerProfileUseCase: injector.resolve(CenterProfileUseCase.self) ) ) addChildCoordinator(coodinator) @@ -81,5 +82,18 @@ extension WorkerRecruitmentBoardCoordinator { coordinator.parent = self coordinator.start() } + + public func showWorkerProfile() { + let coordinator = WorkerProfileCoordinator( + dependency: .init( + profileMode: .myProfile, + navigationController: navigationController, + workerProfileUseCase: injector.resolve(WorkerProfileUseCase.self) + ) + ) + addChildCoordinator(coordinator) + coordinator.parent = self + coordinator.start() + } } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/Button/PhoneCSButton.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/Button/PhoneCSButton.swift index 8dbc5005..66776653 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/Button/PhoneCSButton.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/Button/PhoneCSButton.swift @@ -107,6 +107,16 @@ public class PhoneCSButton: TappableUIView { self.nameLabel.textString = nameText self.phoneNumberLabel.textString = phoneNumberText + + self + .rx.tap + .subscribe(onNext: { + + if let phoneURL = URL(string: "tel://\(phoneNumberText)"), UIApplication.shared.canOpenURL(phoneURL) { + UIApplication.shared.open(phoneURL, options: [:], completionHandler: nil) + } + }) + .disposed(by: disposeBag) } } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/PostDetailForWorkerCoodinator.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/PostDetailForWorkerCoodinator.swift index 204d0555..469e3499 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/PostDetailForWorkerCoodinator.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/Coordinator/PostDetailForWorkerCoodinator.swift @@ -19,6 +19,7 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { let navigationController: UINavigationController let recruitmentPostUseCase: RecruitmentPostUseCase let workerProfileUseCase: WorkerProfileUseCase + let centerProfileUseCase: CenterProfileUseCase public init( postType: RecruitmentPostType, @@ -26,7 +27,8 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { parent: WorkerRecruitmentBoardCoordinatable? = nil, navigationController: UINavigationController, recruitmentPostUseCase: RecruitmentPostUseCase, - workerProfileUseCase: WorkerProfileUseCase + workerProfileUseCase: WorkerProfileUseCase, + centerProfileUseCase: CenterProfileUseCase ) { self.postType = postType self.postId = postId @@ -34,6 +36,7 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { self.navigationController = navigationController self.recruitmentPostUseCase = recruitmentPostUseCase self.workerProfileUseCase = workerProfileUseCase + self.centerProfileUseCase = centerProfileUseCase } } @@ -45,6 +48,7 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { public let navigationController: UINavigationController let recruitmentPostUseCase: RecruitmentPostUseCase let workerProfileUseCase: WorkerProfileUseCase + let centerProfileUseCase: CenterProfileUseCase public init( dependency: Dependency @@ -55,6 +59,7 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { self.navigationController = dependency.navigationController self.recruitmentPostUseCase = dependency.recruitmentPostUseCase self.workerProfileUseCase = dependency.workerProfileUseCase + self.centerProfileUseCase = dependency.centerProfileUseCase } deinit { @@ -72,7 +77,8 @@ public class PostDetailForWorkerCoodinator: ChildCoordinator { postId: postId, coordinator: self, recruitmentPostUseCase: recruitmentPostUseCase, - workerProfileUseCase: workerProfileUseCase + workerProfileUseCase: workerProfileUseCase, + centerProfileUseCase: centerProfileUseCase ) nativeDetailVC.bind(viewModel: vm) vc = nativeDetailVC diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/NativePostDetailForWorkerVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/NativePostDetailForWorkerVC.swift index dfd8de35..768c6bd5 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/NativePostDetailForWorkerVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/Postdetail/NativePostDetailForWorkerVC.swift @@ -35,6 +35,7 @@ public class NativePostDetailForWorkerVC: BaseViewController { let applyButton: IdlePrimaryButton = { let btn = IdlePrimaryButton(level: .medium) btn.label.textString = "지원하기" + btn.setEnabled(false) return btn }() @@ -47,7 +48,6 @@ public class NativePostDetailForWorkerVC: BaseViewController { public override func viewDidLoad() { setAppearance() setLayout() - setObservable() } private func setAppearance() { @@ -105,19 +105,7 @@ public class NativePostDetailForWorkerVC: BaseViewController { buttonStack.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16), ]) } - - private func setObservable() { - - // '문의하기'버튼 클릭시 - csButton.rx.tap - .subscribe { [weak self] _ in - let vc = SelectCSTypeVC() - vc.modalPresentationStyle = .overFullScreen - self?.present(vc, animated: false) - } - .disposed(by: disposeBag) - } - + public func bind(viewModel: NativePostDetailForWorkerViewModelable) { super.bind(viewModel: viewModel) @@ -135,6 +123,14 @@ public class NativePostDetailForWorkerVC: BaseViewController { contentView.cardView.bind(ro: cardRO) + if bundle.applyDate != nil { + // 지원한 공고인 경우 + applyButton.setEnabled(false) + } else { + // 지원하지 않은 공고인 경우 + applyButton.setEnabled(true) + } + // 근무 조건 contentView.workConditionView.bind( workTimeAndPayStateObject: bundle.workTimeAndPay, @@ -203,10 +199,28 @@ public class NativePostDetailForWorkerVC: BaseViewController { }) .disposed(by: disposeBag) + // 문의하기 버튼 + if let centerInfo = viewModel.centerInfoForCS?.asObservable() { + csButton.rx.tap + .withLatestFrom(centerInfo.asObservable()) + .subscribe (onNext: { [weak self] info in + let vc = SelectCSTypeVC() + vc.phoneCSButton.bind( + nameText: info.name, + phoneNumberText: info.phoneNumber + ) + vc.modalPresentationStyle = .overFullScreen + self?.present(vc, animated: false) + }) + .disposed(by: disposeBag) + } + + // 지원성공시 비활성화 viewModel - .alertDriver? - .drive(onNext: { [weak self] alertVO in - self?.showAlert(vo: alertVO) + .applySuccess? + .drive(onNext: { [weak self] in + guard let self else { return } + self.applyButton.setEnabled(false) }) .disposed(by: disposeBag) @@ -246,6 +260,7 @@ public class NativePostDetailForWorkerVC: BaseViewController { .rx.tap .bind(to: viewModel.backButtonClicked) .disposed(by: disposeBag) + } } diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/SelectCSTypeVC.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/SelectCSTypeVC.swift index af1cf62f..cf6d59c7 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/SelectCSTypeVC.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/View/SelectCSTypeVC.swift @@ -12,21 +12,21 @@ import RxSwift import Entity import DSKit -public class SelectCSTypeVC: IdleBottomSheetVC { +class SelectCSTypeVC: IdleBottomSheetVC { // Init // View let phoneCSButton: PhoneCSButton = .init() - public override init() { + override init() { super.init() setObservable() } - public required init?(coder: NSCoder) { fatalError() } + required init?(coder: NSCoder) { fatalError() } - public override func viewDidLoad() { + override func viewDidLoad() { super.viewDidLoad() setLayout() @@ -51,7 +51,6 @@ public class SelectCSTypeVC: IdleBottomSheetVC { private func setObservable() { } - } @available(iOS 17.0, *) diff --git a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift index 110e354d..a5d44281 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/View/View/RecruitmentPost/Worker/Detail/ViewModel/NativePostDetailForWorkerVM.swift @@ -15,11 +15,15 @@ import DSKit public protocol NativePostDetailForWorkerViewModelable: BaseViewModel { + typealias CenterInfoForCS = (name: String, phoneNumber: String) + // Output var postForWorkerBundle: Driver? { get } var locationInfo: Driver? { get } var idleAlertVM: Driver? { get } var starButtonRequestResult: Driver? { get } + var applySuccess: Driver? { get } + var centerInfoForCS: Driver? { get } // Input var viewWillAppear: PublishRelay { get } @@ -38,12 +42,15 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork private let postId: String private let recruitmentPostUseCase: RecruitmentPostUseCase private let workerProfileUseCase: WorkerProfileUseCase + private let centerProfileUseCase: CenterProfileUseCase // Ouput public var postForWorkerBundle: RxCocoa.Driver? public var locationInfo: RxCocoa.Driver? public var idleAlertVM: Driver? public var starButtonRequestResult: Driver? + public var applySuccess: RxCocoa.Driver? + public var centerInfoForCS: Driver? // Input public var backButtonClicked: RxRelay.PublishRelay = .init() @@ -52,26 +59,24 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork public var centerCardClicked: RxRelay.PublishRelay = .init() public var viewWillAppear: RxRelay.PublishRelay = .init() + private var centerInfoForCSCache: CenterInfoForCS? public init( postId: String, coordinator: PostDetailForWorkerCoodinator?, recruitmentPostUseCase: RecruitmentPostUseCase, - workerProfileUseCase: WorkerProfileUseCase + workerProfileUseCase: WorkerProfileUseCase, + centerProfileUseCase: CenterProfileUseCase ) { self.postId = postId self.coordinator = coordinator self.recruitmentPostUseCase = recruitmentPostUseCase self.workerProfileUseCase = workerProfileUseCase + self.centerProfileUseCase = centerProfileUseCase super.init() - // MARK: 로딩 옵저버블 - var loadingStartObservables: [Observable] = [] - var loadingEndObservables: [Observable] = [] - - let getPostDetailResult = viewWillAppear .flatMap { [recruitmentPostUseCase] _ in recruitmentPostUseCase @@ -131,6 +136,44 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork } .asDriver(onErrorRecover: { _ in fatalError() }) + // MARK: 센터전화번호 가져오기 + let centerInfoCache = viewWillAppear.compactMap { [weak self] _ in + self?.centerInfoForCSCache + } + + let centerProfileRequestResult = getPostDetailSuccess + .filter { [weak self] _ in + self?.centerInfoForCSCache == nil + } + .flatMap { [centerProfileUseCase] bundle in + + let centerId = bundle.centerInfo.centerId + + return centerProfileUseCase + .getProfile(mode: .otherProfile(id: centerId)) + } + .share() + + let centerProfileRequestSuccess = centerProfileRequestResult.compactMap { $0.value } + let centerProfileRequestFailure = centerProfileRequestResult.compactMap { $0.error } + + let centerInfoForCS = centerProfileRequestSuccess + .map { [weak self] profile in + + let info = (profile.centerName, profile.officeNumber) as CenterInfoForCS + self?.centerInfoForCSCache = info + + return info + } + + self.centerInfoForCS = Observable + .merge( + centerInfoForCS, + centerInfoCache + ) + .asDriver(onErrorDriveWith: .never()) + + // MARK: 버튼 처리 backButtonClicked .subscribe(onNext: { [weak self] _ in @@ -153,25 +196,29 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork } } .asDriver(onErrorDriveWith: .never()) - - // 로딩 시작 - loadingStartObservables.append(applyRequest.map { _ in }) - let applyRequestResult = applyRequest + let applyRequestResult = mapEndLoading(mapStartLoading(applyRequest.asObservable()) .flatMap { [recruitmentPostUseCase] _ in // 리스트화면에서는 앱내 지원만 지원합니다. - return recruitmentPostUseCase + recruitmentPostUseCase .applyToPost(postId: postId, method: .app) - } + }) .share() - // 로딩 종료 - loadingEndObservables.append(applyRequestResult.map { _ in }) - let applyRequestSuccess = applyRequestResult.compactMap { $0.value } let applyRequestFailure = applyRequestResult.compactMap { $0.error } + self.applySuccess = applyRequestSuccess + .asDriver(onErrorDriveWith: .never()) + + applyRequestSuccess + .subscribe { [weak self] _ in + guard let self else { return } + self.snackBar.onNext(.init(titleText: "지원이 완료되었어요.")) + } + .disposed(by: disposeBag) + let applyRequestFailureAlert = applyRequestFailure .map { error in DefaultAlertContentVO( @@ -195,6 +242,14 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork message: error.message ) } + + let centerProfileRequestFailureAlert = centerProfileRequestFailure + .map { error in + DefaultAlertContentVO( + title: "센터정보 불러오기 실패", + message: error.message + ) + } // MARK: 즐겨찾기 starButtonRequestResult = starButtonClicked @@ -224,31 +279,14 @@ public class NativePostDetailForWorkerVM: BaseViewModel ,NativePostDetailForWork .merge( getPostDetailFailureAlert, applyRequestFailureAlert, - requestWorkerLocationFailureAlert + requestWorkerLocationFailureAlert, + centerProfileRequestFailureAlert ) .subscribe(onNext: { [weak self] alertVO in guard let self else { return } alert.onNext(alertVO) }) .disposed(by: disposeBag) - - // MARK: 로딩 - Observable - .merge(loadingStartObservables) - .subscribe(onNext: { [weak self] _ in - guard let self else { return } - showLoading.onNext(()) - }) - .disposed(by: disposeBag) - - Observable - .merge(loadingEndObservables) - .delay(.milliseconds(500), scheduler: MainScheduler.instance) - .subscribe(onNext: { [weak self] _ in - guard let self else { return } - dismissLoading.onNext(()) - }) - .disposed(by: disposeBag) } public func setPostFavoriteState(isFavoriteRequest: Bool, postId: String, postType: Entity.RecruitmentPostType) -> RxSwift.Single { diff --git a/project/Projects/Presentation/Feature/Base/Sources/ViewModelType/BaseViewModel.swift b/project/Projects/Presentation/Feature/Base/Sources/ViewModelType/BaseViewModel.swift index e641e0bc..74569bb7 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/ViewModelType/BaseViewModel.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/ViewModelType/BaseViewModel.swift @@ -77,6 +77,7 @@ open class BaseViewModel { public func mapStartLoading(_ target: Observable) -> Observable { target + .throttle(.milliseconds(500), scheduler: MainScheduler.instance) .map { [weak self] item in self?.showLoading.onNext(()) diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerBoardEmptyView.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerBoardEmptyView.swift new file mode 100644 index 00000000..8588e1f5 --- /dev/null +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerBoardEmptyView.swift @@ -0,0 +1,71 @@ +// +// WorkerBoardEmptyView.swift +// WorkerFeature +// +// Created by choijunios on 9/12/24. +// + +import UIKit +import DSKit + +import RxSwift +import RxCocoa + +class WorkerBoardEmptyView: UIView { + + let titleLabel: IdleLabel = { + let label = IdleLabel(typography: .Heading2) + label.textString = "아직 해당 지역의 공고가 없어요." + label.textAlignment = .center + return label + }() + + let descriptionLabel: IdleLabel = { + let label = IdleLabel(typography: .Body3) + label.attrTextColor = DSColor.gray300.color + label.numberOfLines = 3 + label.textString = "나의 위치를 근처 다른 지역으로 바꿔\n새로운 공고를 탐색해보세요.\n나의 위치는 추후에 다시 변경할 수 있어요." + label.textAlignment = .center + return label + }() + + let editProfile: IdleThirdinaryButton = { + let button = IdleThirdinaryButton(level: .medium) + button.label.textString = "내 프로필 수정" + return button + }() + + init() { + super.init(frame: .zero) + + setAppearance() + setLayout() + } + required init?(coder: NSCoder) { nil } + + func setAppearance() { + self.backgroundColor = .clear + } + + func setLayout() { + + let mainStack = VStack([ + titleLabel, + Spacer(height: 8), + descriptionLabel, + Spacer(height: 20), + editProfile, + ]) + + self.addSubview(mainStack) + mainStack.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + + editProfile.widthAnchor.constraint(equalToConstant: 165), + + mainStack.centerXAnchor.constraint(equalTo: self.centerXAnchor), + mainStack.centerYAnchor.constraint(equalTo: self.centerYAnchor), + ]) + } +} diff --git a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift index ee8ae4e9..6866201f 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/View/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVC.swift @@ -25,6 +25,11 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { }() let postTableView = UITableView() let tableHeader = BoardSortigHeaderView() + let emptyScreen: WorkerBoardEmptyView = { + let view = WorkerBoardEmptyView() + view.isHidden = true + return view + }() // Paging var isPaging = true @@ -61,7 +66,9 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { .postBoardData? .drive(onNext: { [weak self] (isRefreshed: Bool, postData) in guard let self else { return } + self.postData = postData + postTableView.reloadData() isPaging = false @@ -70,6 +77,16 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { self?.postTableView.setContentOffset(.zero, animated: false) } } + + postTableView.layoutIfNeeded() + if self.checkScrollViewHasSpace() { + // 빈공간이 있는 경우 바로 다음요청 + requestNextPage.accept(()) + } + + + // 공고가 없을 경우 + emptyScreen.isHidden = (postData.count != 0) }) .disposed(by: disposeBag) @@ -81,6 +98,11 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { .disposed(by: disposeBag) // Input + self.emptyScreen + .editProfile.rx.tap + .bind(to: viewModel.editProfileButtonClicked) + .disposed(by: disposeBag) + self.rx.viewDidLoad .bind(to: viewModel.requestWorkerLocation) .disposed(by: disposeBag) @@ -119,7 +141,8 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { [ topContainer, - postTableView + postTableView, + emptyScreen, ].forEach { $0.translatesAutoresizingMaskIntoConstraints = false view.addSubview($0) @@ -127,19 +150,28 @@ public class WorkerRecruitmentPostBoardVC: BaseViewController { NSLayoutConstraint.activate([ topContainer.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - topContainer.leftAnchor.constraint(equalTo: view.leftAnchor), - topContainer.rightAnchor.constraint(equalTo: view.rightAnchor), + topContainer.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), + topContainer.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor), postTableView.topAnchor.constraint(equalTo: topContainer.bottomAnchor), - postTableView.leftAnchor.constraint(equalTo: view.leftAnchor), - postTableView.rightAnchor.constraint(equalTo: view.rightAnchor), + postTableView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), + postTableView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor), postTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + + emptyScreen.topAnchor.constraint(equalTo: topContainer.bottomAnchor), + emptyScreen.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), + emptyScreen.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor), + emptyScreen.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) } private func setObservable() { } + + func checkScrollViewHasSpace() -> Bool { + postTableView.contentSize.height < postTableView.frame.height + } } extension WorkerRecruitmentPostBoardVC: UITableViewDataSource, UITableViewDelegate { diff --git a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift index c8c74b5c..ae45e355 100644 --- a/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift +++ b/project/Projects/Presentation/Feature/Worker/Sources/ViewModel/RecruitmentPost/OnGoingPostBoard/WorkerRecruitmentPostBoardVM.swift @@ -45,6 +45,9 @@ public protocol WorkerRecruitmentPostBoardVMable: WorkerAppliablePostBoardVMable /// 요양보호사 위치정보를 요청합니다. var requestWorkerLocation: PublishRelay { get } + /// 프로필 수정버튼이 눌린 경우 + var editProfileButtonClicked: PublishRelay { get } + /// 요양보호사 위치 정보를 전달합니다. var workerLocationTitleText: Driver? { get } } @@ -56,7 +59,9 @@ public class WorkerRecruitmentPostBoardVM: BaseViewModel, WorkerRecruitmentPostB public var workerLocationTitleText: Driver? public var idleAlertVM: RxCocoa.Driver? + // Input + public var editProfileButtonClicked: PublishRelay = .init() public var requestInitialPageRequest: PublishRelay = .init() public var requestWorkerLocation: PublishRelay = .init() public var requestNextPage: PublishRelay = .init() @@ -89,9 +94,6 @@ public class WorkerRecruitmentPostBoardVM: BaseViewModel, WorkerRecruitmentPostB super.init() - var loadingStartObservables: [Observable] = [] - var loadingEndObservables: [Observable] = [] - // MARK: 상단 위치정보 불러오기 workerLocationTitleText = requestWorkerLocation .flatMap { [workerProfileUseCase] _ in @@ -162,7 +164,7 @@ public class WorkerRecruitmentPostBoardVM: BaseViewModel, WorkerRecruitmentPostB } // MARK: 공고리스트 처음부터 요청하기 - let initialRequest = requestInitialPageRequest + let initialRequest = mapEndLoading(mapStartLoading(requestInitialPageRequest.asObservable()) .flatMap { [weak self, recruitmentPostUseCase] request in self?.currentPostVO.accept([]) @@ -173,12 +175,9 @@ public class WorkerRecruitmentPostBoardVM: BaseViewModel, WorkerRecruitmentPostB request: .initial, postCount: 10 ) - } + }) .share() - // 로딩 시작 - loadingStartObservables.append(initialRequest.map { _ in }) - // MARK: 공고리스트 페이징 요청 let pagingRequest = requestNextPage .compactMap { [weak self] _ in @@ -198,9 +197,6 @@ public class WorkerRecruitmentPostBoardVM: BaseViewModel, WorkerRecruitmentPostB .merge(initialRequest, pagingRequest) .share() - // 로딩 종료 - loadingEndObservables.append(postPageReqeustResult.map { _ in }) - let requestPostListSuccess = postPageReqeustResult.compactMap { $0.value } let requestPostListFailure = postPageReqeustResult.compactMap { $0.error } @@ -267,6 +263,15 @@ public class WorkerRecruitmentPostBoardVM: BaseViewModel, WorkerRecruitmentPostB ) } + // MARK: 프로필 수정 + editProfileButtonClicked + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak self] in + guard let self else { return } + self.coordinator?.showWorkerProfile() + }) + .disposed(by: dispostBag) + Observable .merge( applyRequestFailureAlert, @@ -274,19 +279,6 @@ public class WorkerRecruitmentPostBoardVM: BaseViewModel, WorkerRecruitmentPostB ) .subscribe(self.alert) .disposed(by: dispostBag) - - // MARK: 로딩 - Observable - .merge(loadingStartObservables) - .subscribe(self.showLoading) - .disposed(by: dispostBag) - - - Observable - .merge(loadingEndObservables) - .delay(.milliseconds(300), scheduler: MainScheduler.instance) - .subscribe(self.dismissLoading) - .disposed(by: dispostBag) } func convertStringToLessThan3Words(target: String) -> String { diff --git a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Worker/WorkerRecruitmentBoardCoordinatable.swift b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Worker/WorkerRecruitmentBoardCoordinatable.swift index bfd1a779..8fd0e4e1 100644 --- a/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Worker/WorkerRecruitmentBoardCoordinatable.swift +++ b/project/Projects/Presentation/PresentationCore/Sources/ScreenCoordinating/Interface/RecruitmentPost/Worker/WorkerRecruitmentBoardCoordinatable.swift @@ -14,4 +14,7 @@ public protocol WorkerRecruitmentBoardCoordinatable: ParentCoordinator { /// 센터 프로필을 표시합니다. func showCenterProfile(centerId: String) + + /// 요양보호사의 프로필을 표시합니다. + func showWorkerProfile() }