Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔗 :: (#218) CompanySearch 개발 #225

Merged
merged 16 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Projects/Core/Sources/Steps/CompanyStep.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import RxFlow
public enum CompanyStep: Step {
case companyIsRequired
case companyDetailIsRequired(id: Int)
case searchCompanyIsRequired
}
6 changes: 6 additions & 0 deletions Projects/Core/Sources/Steps/SearchCompanyStep.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import RxFlow

public enum SearchCompanyStep: Step {
case searchCompanyIsRequired
case companyDetailIsRequired(id: Int)
}
18 changes: 18 additions & 0 deletions Projects/Flow/Sources/Company/CompanyFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public final class CompanyFlow: Flow {
return navigateToCompany()
case let .companyDetailIsRequired(id):
return navigateToCompanyDetail(id)
case .searchCompanyIsRequired:
return navigateToSearchCompany()
}
}
}
Expand Down Expand Up @@ -53,4 +55,20 @@ private extension CompanyFlow {
withNextStepper: OneStepper(withSingleStep: CompanyDetailStep.companyDetailIsRequired)
))
}

func navigateToSearchCompany() -> FlowContributors {
let searchCompanyFlow = SearchCompanyFlow(container: container)

Flows.use(searchCompanyFlow, when: .created) { (root) in
let view = root as? SearchCompanyViewController
self.rootViewController.navigationController?.pushViewController(
view!, animated: true
)
}

return .one(flowContributor: .contribute(
withNextPresentable: searchCompanyFlow,
withNextStepper: OneStepper(withSingleStep: SearchCompanyStep.searchCompanyIsRequired)
))
}
}
57 changes: 57 additions & 0 deletions Projects/Flow/Sources/Company/SearchCompanyFlow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import UIKit
import Presentation
import Swinject
import RxFlow
import Core

public final class SearchCompanyFlow: Flow {
public let container: Container
private let rootViewController: SearchCompanyViewController
public var root: Presentable {
return rootViewController
}

public init(container: Container) {
self.container = container
self.rootViewController = container.resolve(SearchCompanyViewController.self)!
}

public func navigate(to step: Step) -> FlowContributors {
guard let step = step as? SearchCompanyStep else { return .none }

switch step {
case .searchCompanyIsRequired:
return navigateToSearchCompany()

case let .companyDetailIsRequired(id):
return navigateToCompanyDetail(id)
}
}
}

private extension SearchCompanyFlow {
func navigateToSearchCompany() -> FlowContributors {
return .one(flowContributor: .contribute(
withNextPresentable: rootViewController,
withNextStepper: rootViewController.viewModel
))
}

func navigateToCompanyDetail(_ companyId: Int) -> FlowContributors {
let companyDetailFlow = CompanyDetailFlow(container: container)

Flows.use(companyDetailFlow, when: .created) { (root) in
let view = root as? CompanyDetailViewController
view?.viewModel.companyID = companyId
view?.viewModel.type = .searchCompany
self.rootViewController.navigationController?.pushViewController(
view!, animated: true
)
}

return .one(flowContributor: .contribute(
withNextPresentable: companyDetailFlow,
withNextStepper: OneStepper(withSingleStep: CompanyDetailStep.companyDetailIsRequired)
))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Core
import DesignSystem

public class CompanyViewController: BaseViewController<CompanyViewModel> {
private let searchButtonDidTap = PublishRelay<Void>()
private let companyTableView = UITableView().then {
$0.register(
CompanyTableViewCell.self,
Expand Down Expand Up @@ -43,7 +44,8 @@ public class CompanyViewController: BaseViewController<CompanyViewModel> {
) - 1
},
companyTableViewCellDidTap: companyTableView.rx.modelSelected(CompanyEntity.self)
.map { $0.companyID }
.map { $0.companyID },
searchButtonDidTap: searchButtonDidTap
)

let output = viewModel.transform(input)
Expand All @@ -66,6 +68,12 @@ public class CompanyViewController: BaseViewController<CompanyViewModel> {
self.hideTabbar()
}
.disposed(by: disposeBag)

searchButton.rx.tap
.subscribe(onNext: { _ in
self.searchButtonDidTap.accept(())
})
.disposed(by: disposeBag)
}

public override func configureNavigation() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public final class CompanyViewModel: BaseViewModel, Stepper {
let viewAppear: PublishRelay<Void>
var pageChange: Observable<WillDisplayCellEvent>
let companyTableViewCellDidTap: Observable<Int>
let searchButtonDidTap: PublishRelay<Void>
}

public struct Output {
Expand Down Expand Up @@ -58,6 +59,11 @@ public final class CompanyViewModel: BaseViewModel, Stepper {
.bind(to: steps)
.disposed(by: disposeBag)

input.searchButtonDidTap.asObservable()
.map { CompanyStep.searchCompanyIsRequired }
.bind(to: steps)
.disposed(by: disposeBag)

return Output(companyList: companyList)
}
}
9 changes: 9 additions & 0 deletions Projects/Presentation/Sources/DI/PresentationAssembly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,15 @@ public final class PresentationAssembly: Assembly {
)
}

container.register(SearchCompanyViewController.self) { resolver in
SearchCompanyViewController(
resolver.resolve(SearchCompanyViewModel.self)!
)
}
container.register(SearchCompanyViewModel.self) { resolver in
SearchCompanyViewModel(fetchCompanyListUseCase: resolver.resolve(FetchCompanyListUseCase.self)!)
}

container.register(RejectReasonViewModel.self) { resolver in
RejectReasonViewModel(
fetchRejectionReasonUseCase: resolver.resolve(FetchRejectionReasonUseCase.self)!
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import UIKit
import Domain
import RxSwift
import RxCocoa
import SnapKit
import Then
import Core
import DesignSystem

public final class SearchCompanyViewController: BaseViewController<SearchCompanyViewModel> {
private let searchButtonDidTap = PublishRelay<String>()
private let emptySearchView = ListEmptyView().then {
$0.isHidden = true
$0.setEmptyView(
title: "검색어와 관련 된 회사를 못찾았어요",
subTitle: "제대로 입력했는지 다시 한번 확인해주세요"
)
}

private let searchImageView = UIImageView().then {
$0.image = .jobisIcon(.searchIcon)
}
private let searchTextField = UITextField().then {
$0.placeholder = "검색어를 입력해주세요"
$0.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 44, height: 0))
juyeong525 marked this conversation as resolved.
Show resolved Hide resolved
$0.rightView = UIView(frame: CGRect(x: 0, y: 0, width: 16, height: 0))
$0.leftViewMode = .always
$0.rightViewMode = .always
$0.backgroundColor = .GrayScale.gray30
$0.layer.cornerRadius = 12
}

private let searchTableView = UITableView().then {
$0.rowHeight = 72
$0.separatorStyle = .none
$0.register(
CompanyTableViewCell.self,
forCellReuseIdentifier: CompanyTableViewCell.identifier
)
$0.showsVerticalScrollIndicator = false
}
public override func addView() {
[
searchTextField,
searchImageView,
searchTableView
].forEach(self.view.addSubview(_:))
[
emptySearchView
].forEach(searchTableView.addSubview(_:))
}

public override func setLayout() {
searchTextField.snp.makeConstraints {
$0.top.equalTo(view.safeAreaInsets)
$0.trailing.leading.equalToSuperview().inset(24)
$0.height.equalTo(48)
}

searchImageView.snp.makeConstraints {
$0.centerY.equalTo(searchTextField)
$0.left.equalTo(searchTextField.snp.left).inset(16)
}

searchTableView.snp.makeConstraints {
$0.leading.trailing.bottom.equalToSuperview()
$0.top.equalTo(searchTextField.snp.bottom).offset(12)
}

emptySearchView.snp.makeConstraints {
$0.centerY.equalTo(view.safeAreaLayoutGuide)
$0.centerX.equalTo(view.safeAreaLayoutGuide)
}
}

public override func bind() {
let input = SearchCompanyViewModel.Input(
viewAppear: self.viewWillAppearPublisher,
pageChange: searchTableView.rx.willDisplayCell
.filter {
$0.indexPath.row == self.searchTableView.numberOfRows(inSection: $0.indexPath.section) - 1
}.asObservable(),
searchButtonDidTap: searchButtonDidTap,
searchTableViewDidTap: searchTableView.rx.itemSelected
)

let output = viewModel.transform(input)

output.companyListInfo
.skip(1)
.do(onNext: {
self.emptySearchView.isHidden = !$0.isEmpty
})
.bind(to: searchTableView.rx.items(
cellIdentifier: CompanyTableViewCell.identifier,
cellType: CompanyTableViewCell.self
)) { _, element, cell in
cell.adapt(model: element)
}
.disposed(by: disposeBag)

output.emptyViewIsHidden.asObservable()
.map {
self.emptySearchView.isHidden = $0
}
.subscribe()
.disposed(by: disposeBag)
}

public override func configureViewController() {
self.searchTextField.delegate = self
viewWillAppearPublisher.asObservable()
.bind {
self.hideTabbar()
self.navigationController?.navigationBar.prefersLargeTitles = false
}
.disposed(by: disposeBag)
}

public override func configureNavigation() { }
}

extension SearchCompanyViewController: UITextFieldDelegate {
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let title = textField.text
viewModel.searchText = title
searchButtonDidTap.accept(textField.text ?? "")
self.view.endEditing(true)
return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import UIKit
import RxSwift
import RxCocoa
import RxFlow
import Core
import Domain

public final class SearchCompanyViewModel: BaseViewModel, Stepper {
public let steps = PublishRelay<Step>()
public var searchText: String?
private let disposeBag = DisposeBag()
private let fetchCompanyListUseCase: FetchCompanyListUseCase
private var companyListInfo = BehaviorRelay<[CompanyEntity]>(value: [])
private var pageCount: Int = 1

init(
fetchCompanyListUseCase: FetchCompanyListUseCase
) {
self.fetchCompanyListUseCase = fetchCompanyListUseCase
}

public struct Input {
let viewAppear: PublishRelay<Void>
let pageChange: Observable<WillDisplayCellEvent>
let searchButtonDidTap: PublishRelay<String>
let searchTableViewDidTap: ControlEvent<IndexPath>
}

public struct Output {
let companyListInfo: BehaviorRelay<[CompanyEntity]>
let emptyViewIsHidden: PublishRelay<Bool>
}

public func transform(_ input: Input) -> Output {
let emptyViewIsHidden = PublishRelay<Bool>()
input.viewAppear.asObservable()
.skip(1)
.flatMap {
self.pageCount = 1
return self.fetchCompanyListUseCase.execute(page: self.pageCount, name: self.searchText)
}
.bind(onNext: {
self.companyListInfo.accept([])
self.companyListInfo.accept(self.companyListInfo.value + $0)
self.pageCount = 1
})
.disposed(by: disposeBag)

input.pageChange.asObservable()
.distinctUntilChanged({ $0.indexPath.row })
.flatMap { _ in
self.pageCount += 1
return self.fetchCompanyListUseCase.execute(page: self.pageCount, name: self.searchText)
}
.bind { self.companyListInfo.accept(self.companyListInfo.value + $0) }
.disposed(by: disposeBag)

input.searchButtonDidTap.asObservable()
.filter {
emptyViewIsHidden.accept(!$0.isEmpty)
return $0 != ""
}
.flatMap {
self.pageCount = 1
return self.fetchCompanyListUseCase.execute(page: self.pageCount, name: $0)
}
.bind(onNext: {
self.companyListInfo.accept([])
self.companyListInfo.accept(self.companyListInfo.value + $0)
})
.disposed(by: disposeBag)

input.searchTableViewDidTap.asObservable()
.map {
SearchCompanyStep.companyDetailIsRequired(
id: self.companyListInfo.value[$0.row].companyID
)
}
.bind(to: steps)
.disposed(by: disposeBag)

return Output(
companyListInfo: companyListInfo,
emptyViewIsHidden: emptyViewIsHidden
)
}
}
Loading