-
Notifications
You must be signed in to change notification settings - Fork 3
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
Add Basic Food Table View #66
Changes from 10 commits
c08377e
b8b54f6
8d32dfa
3c38a7c
5555e88
24e1ba3
bce7ee5
bf6279f
3bba1d2
c0da432
fd55730
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,11 +4,12 @@ import RxExtensions | |
import Presentations | ||
import Action | ||
|
||
class DetailViewModel: ViewModel, SelectionPresentingViewModel { | ||
class DetailViewModel: ViewModel, SelectionPresentingViewModel, FoodInfoPresentingViewModel { | ||
|
||
let isActive = BehaviorRelay(value: false) | ||
|
||
weak var selectionPresenter: SelectionPresenter? | ||
weak var foodInfoPresenter: FoodInfoPresenter? | ||
|
||
let title = Property(L10n.Detail.title) | ||
|
||
|
@@ -20,12 +21,9 @@ class DetailViewModel: ViewModel, SelectionPresentingViewModel { | |
let foodListTitle = Property(L10n.Detail.FoodList.title) | ||
let foodInfoButtonTitle = Property(L10n.Detail.FoodButton.title) | ||
|
||
let presentContents = CocoaAction { _ in | ||
print("Content button pressed") | ||
return .empty() | ||
} | ||
private(set) lazy var presentFoodInfo = makePresentFoodInfo(withFactory: factory) | ||
|
||
let foods: BehaviorRelay<[Food]> = BehaviorRelay(value: [.beans, .greens, .potatoes, .tomatoes]) | ||
private let foods: Property<[Food]> | ||
|
||
private(set) lazy var foodListText: Property<String> = { | ||
let observable = foods.map { foods -> String in | ||
|
@@ -37,7 +35,7 @@ class DetailViewModel: ViewModel, SelectionPresentingViewModel { | |
}() | ||
|
||
private(set) lazy var presentSelection = makePresentSelection( | ||
withFactory: selectionFactory, | ||
withFactory: factory, | ||
defaultValue: { [weak self] in | ||
return self?.selectionResult.value | ||
}, | ||
|
@@ -50,24 +48,34 @@ class DetailViewModel: ViewModel, SelectionPresentingViewModel { | |
.disposed(by: self.disposeBag) | ||
}) | ||
|
||
init(selectionFactory: SelectionViewModelFactoryProtocol) { | ||
self.selectionFactory = selectionFactory | ||
typealias Factory = SelectionViewModelFactoryProtocol & FoodInfoViewModelFactoryProtocol | ||
|
||
init(foods: Property<[Food]>, factory: Factory) { | ||
self.foods = foods | ||
self.factory = factory | ||
} | ||
|
||
private let selectionResultRelay = BehaviorRelay<String?>(value: nil) | ||
private let selectionFactory: SelectionViewModelFactoryProtocol | ||
private let factory: Factory | ||
private let disposeBag = DisposeBag() | ||
|
||
} | ||
|
||
protocol DetailViewModelFactoryProtocol: SelectionViewModelFactoryProtocol { | ||
protocol DetailViewModelFactoryProtocol: SelectionViewModelFactoryProtocol, FoodInfoViewModelFactoryProtocol { | ||
var foods: Property<[Food]> { get } | ||
|
||
func makeDetailViewModel() -> DetailViewModel | ||
} | ||
|
||
extension DetailViewModelFactoryProtocol { | ||
|
||
// This can be backed by a datastore. For now, use a computed property to represent persistent data | ||
var foods: Property<[Food]> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps this should just be defined on the class that implements
I believe one of the Stubs would also need to be updated. |
||
return Property([.beans, .greens, .potatoes, .tomatoes]) | ||
} | ||
|
||
func makeDetailViewModel() -> DetailViewModel { | ||
return DetailViewModel(selectionFactory: self) | ||
return DetailViewModel(foods: foods, factory: self) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import UIKit | ||
import Presentations | ||
import RxSwift | ||
import Action | ||
import Core | ||
|
||
class FoodInfoViewController: UITableViewController, ViewController { | ||
|
||
let viewModel: FoodInfoViewModel | ||
|
||
let themeProvider: ThemeProvider | ||
|
||
required init(viewModel: FoodInfoViewModel, themeProvider: ThemeProvider) { | ||
self.viewModel = viewModel | ||
self.themeProvider = themeProvider | ||
|
||
super.init(nibName: nil, bundle: nil) | ||
} | ||
|
||
override func viewDidLoad() { | ||
super.viewDidLoad() | ||
|
||
let cellIdentifier = "FoodCell" | ||
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier) | ||
|
||
viewModel.foods | ||
.bind(to: tableView.rx.items(cellIdentifier: cellIdentifier)) { _, food, cell in | ||
cell.textLabel?.text = food.name | ||
} | ||
.disposed(by: disposeBag) | ||
|
||
rx.isAppeared | ||
.bind(to: viewModel.isActive) | ||
.disposed(by: disposeBag) | ||
|
||
themeProvider.bindToStyleable(self) { FoodInfoViewControllerStyle(theme: $0) } | ||
} | ||
|
||
@available(*, unavailable) | ||
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { fatalError("\(#function) not implemented.") } | ||
|
||
@available(*, unavailable) | ||
required init?(coder aDecoder: NSCoder) { fatalError("\(#function) not implemented.") } | ||
|
||
private let disposeBag = DisposeBag() | ||
|
||
} | ||
|
||
protocol FoodInfoViewControllerFactoryProtocol { | ||
var themeProvider: ThemeProvider { get } | ||
|
||
func makeFoodInfoViewController(viewModel: FoodInfoViewModel) -> FoodInfoViewController | ||
} | ||
|
||
extension FoodInfoViewControllerFactoryProtocol { | ||
|
||
func makeFoodInfoViewController(viewModel: FoodInfoViewModel) -> FoodInfoViewController { | ||
return FoodInfoViewController(viewModel: viewModel, themeProvider: themeProvider) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import Themer | ||
import UIKit | ||
import Core | ||
|
||
struct FoodInfoViewControllerStyle: Style { | ||
|
||
let theme: Theme | ||
|
||
init(theme: Theme) { | ||
self.theme = theme | ||
} | ||
|
||
func apply(to styleable: FoodInfoViewController) { | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import RxSwift | ||
import RxCocoa | ||
import RxExtensions | ||
import Presentations | ||
import Action | ||
|
||
class FoodInfoViewModel: ViewModel { | ||
|
||
let isActive = BehaviorRelay(value: false) | ||
let foods: Property<[Food]> | ||
|
||
init(with foods: Property<[Food]>) { | ||
self.foods = foods | ||
foods | ||
.asObservable() | ||
.logValue(.info, .application) { "FOODS: \($0)" } | ||
.subscribe() | ||
.disposed(by: disposeBag) | ||
} | ||
|
||
private let disposeBag = DisposeBag() | ||
|
||
} | ||
|
||
protocol FoodInfoViewModelFactoryProtocol { | ||
var foods: Property<[Food]> { get } | ||
|
||
func makeFoodInfoViewModel() -> FoodInfoViewModel | ||
} | ||
|
||
extension FoodInfoViewModelFactoryProtocol { | ||
|
||
func makeFoodInfoViewModel() -> FoodInfoViewModel { | ||
return FoodInfoViewModel(with: foods) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import RxSwift | ||
import RxCocoa | ||
import Action | ||
import Presentations | ||
|
||
protocol FoodInfoPresentingViewModel: AnyObject, PresentingViewModel { | ||
var foodInfoPresenter: FoodInfoPresenter? { get set } | ||
var presentFoodInfo: Action<Bool, FoodInfoViewModel> { get } | ||
} | ||
|
||
extension FoodInfoPresentingViewModel { | ||
|
||
/// Makes an action that is suitable to be set as the presentFoodInfo action. | ||
/// | ||
/// This action should be executed with a Bool indicating whether the presentation should be animated. | ||
/// | ||
/// - Parameter factory: A factory to be used to generate the presented view model. | ||
/// - Parameter setupViewModel: This closure will be called with the presented view model when a present action | ||
/// is executed. Consumers can use this to observe changes to the presented view model if necessary. | ||
func makePresentFoodInfo( | ||
withFactory factory: FoodInfoViewModelFactoryProtocol, | ||
setupViewModel: ((FoodInfoViewModel) -> Void)? = nil | ||
) -> Action<Bool, FoodInfoViewModel> { | ||
return makePresentAction { [weak self] animated -> DismissablePresentationContext<FoodInfoViewModel>? in | ||
guard | ||
let self = self, | ||
let presenter = self.foodInfoPresenter else { | ||
return nil | ||
} | ||
|
||
let viewModel = factory.makeFoodInfoViewModel() | ||
|
||
setupViewModel?(viewModel) | ||
|
||
let presentation = presenter.foodInfoPresentation(of: viewModel) | ||
|
||
return DismissablePresentationContext(presentation: presentation, viewModel: viewModel, presentAnimated: animated) | ||
} | ||
} | ||
|
||
} | ||
|
||
protocol FoodInfoPresenter: AnyObject { | ||
func foodInfoPresentation(of viewModel: FoodInfoViewModel) -> DismissablePresentation | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe this is needed. This function should be implemented by the protocol extension.