struct MenuViewModelInput { let fetchMenus: Observable let clearSelections: Observable let makeOrder: Observable let increaseMenuCount: Observable<(menu: ViewMenu, inc: Int)> } struct MenuViewModelOutput { let activated: Observable let errorMessage: Observable let allMenus: Observable<[ViewMenu]> let totalSelectedCountText: Observable let totalPriceText: Observable let showOrderPage: Observable<[ViewMenu]> } func menuViewModel(fetchMenus: Observable<[Thing]>) -> (MenuViewModelInput) -> MenuViewModelOutput { { input in let errorSubject = PublishSubject() enum Action { case reset([ViewMenu]) case adjustCount(menu: ViewMenu, inc: Int) } let resetMenus = input.fetchMenus .flatMap { fetchMenus .catch { error in errorSubject.onNext(error) return .empty() } } .map { Action.reset($0.map { ViewMenu($0) }) } let allMenus = Observable.merge( resetMenus, input.increaseMenuCount.map(Action.adjustCount(menu:inc:)) ) .scan(into: [ViewMenu]()) { state, action in switch action { case let .reset(viewMenus): state = viewMenus case .adjustCount(menu: let menu, inc: let inc): guard let index = state.firstIndex(where: { $0.name == menu.name }) else { break } state[index] = state[index].countUpdated(inc) } } .share(replay: 1) return MenuViewModelOutput( activated: Observable.merge( input.fetchMenus.map { true }, resetMenus.map { _ in false } ) .startWith(false), errorMessage: errorSubject.asObservable(), allMenus: allMenus, totalSelectedCountText: allMenus .map { $0.map { $0.count }.reduce(0, +) } .map { "\($0)" }, totalPriceText: allMenus .map { $0.map { $0.price * $0.count }.reduce(0, +) } .map { $0.currencyKR() }, showOrderPage: input.makeOrder .withLatestFrom(allMenus) .map { $0.filter { $0.count > 0 } } .do(onNext: { items in if items.count == 0 { let err = NSError(domain: "No Orders", code: -1, userInfo: nil) errorSubject.onNext(err) } }) .filter { $0.count > 0 } ) } }