Skip to content

Commit

Permalink
πŸ”— :: (#218) οΏ½CompanySearch 개발
Browse files Browse the repository at this point in the history
  • Loading branch information
juyeong525 authored Apr 3, 2024
2 parents ee10575 + 1ae0d2f commit 38cc151
Show file tree
Hide file tree
Showing 10 changed files with 324 additions and 1 deletion.
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 @@ -237,6 +237,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))
$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
)
}
}

0 comments on commit 38cc151

Please sign in to comment.