Created
December 3, 2017 16:25
-
-
Save soxjke/97e3c1e6c909ccabc914b5daf5ca3b85 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
enum UIState { | |
case current | |
case forecast(page: Int) | |
} | |
enum UIEvent { | |
case turnCurrent | |
case turnForecast | |
case turnLeft | |
case turnRight | |
} | |
class ViewModel { | |
class UIStore: Store<UIState, UIEvent> { | |
fileprivate init() { | |
super.init(state: .current, reducers: [uistore_reducer]) | |
} | |
required init(state: UIState, reducers: [UIStore.Reducer]) { | |
fatalError("init(state:reducers:) cannot be called on type UIStore. Use `shared` accessor") | |
} | |
} | |
private let uiStore = UIStore() | |
private let appStore = AppStore.shared | |
private (set) lazy var uiAction: Action<UIEvent, (), NoError> = createUIAction() | |
private (set) lazy var reloadAction: Action<(), (), NoError> = createReloadAction() | |
private (set) lazy var locateAction: Action<(), (), NoError> = createLocateAction() | |
private (set) lazy var weatherFeatures: SignalProducer<[WeatherFeatureCellKind], NoError> = self.weatherFeaturesProducer() | |
private (set) lazy var title: SignalProducer<String, NoError> = self.appStore.producer.map(ViewModel.geopositionString).skipRepeats() | |
private (set) lazy var isLoading: SignalProducer<Bool, NoError> = self.appStore.producer.map(ViewModel.isLoading).skipRepeats() | |
init() { | |
uiStore.producer.logEvents().start() | |
} | |
func isEnabledControl(for events: Set<UIEvent>) -> SignalProducer<Bool, NoError> { | |
return uiStore.producer | |
.combineLatest(with: appStore.producer) | |
.map { (uiState, appState) -> Bool in | |
guard case .success(_, let forecast) = appState.weather.weatherRequestState else { | |
return false | |
} | |
var allowedEvents = Set<UIEvent>([.turnCurrent, .turnForecast]) | |
if case .forecast(let page) = uiState { | |
if page > 0 { | |
allowedEvents = allowedEvents.union([.turnLeft]) | |
} | |
if page < forecast.count - 1 { | |
allowedEvents = allowedEvents.union([.turnRight]) | |
} | |
} | |
return allowedEvents.isSuperset(of: events) | |
} | |
.skipRepeats() | |
} | |
} | |
extension ViewModel { | |
fileprivate func createUIAction() -> Action<UIEvent, (), NoError> { | |
return Action { (event) in | |
return SignalProducer { [weak self] in | |
self?.uiStore.consume(event: event) | |
} | |
} | |
} | |
func createButtonAction(for event: UIEvent) -> Action<(), (), NoError> { | |
return Action(enabledIf: Property(initial: false, then: isEnabledControl(for: Set([event]))) ) { | |
return SignalProducer { [weak self] in | |
self?.uiStore.consume(event: event) | |
} | |
} | |
} | |
fileprivate func createReloadAction() -> Action<(), (), NoError> { | |
return Action { | |
return SignalProducer { [weak self] in | |
self?.appStore.consume(event: .weatherRequest) | |
} | |
} | |
} | |
fileprivate func createLocateAction() -> Action<(), (), NoError> { | |
let enabledProducer = appStore.producer.map { (appState) in | |
return !(appState.location.locationRequestState.isUpdating || appState.weather.geopositionRequestState.isUpdating) | |
} | |
return Action(enabledIf: Property(initial: false, then: enabledProducer)) { | |
return SignalProducer { [weak self] in | |
self?.appStore.consume(event: .locationRequest) | |
} | |
} | |
} | |
fileprivate func weatherFeaturesProducer() -> SignalProducer<[WeatherFeatureCellKind], NoError> { | |
return uiStore.producer | |
.combineLatest(with: appStore.producer.filter { $0.weather.weatherRequestState.isSuccess } ) | |
.map(ViewModel.toWeatherFeatures) | |
} | |
fileprivate static func toWeatherFeatures(uiState: UIState, appState: AppState) -> [WeatherFeatureCellKind] { | |
guard case .success(let currentWeather, let forecast) = appState.weather.weatherRequestState else { | |
return [] | |
} | |
switch uiState { | |
case .current: return currentWeather.toWeatherFeatures() | |
case .forecast(let page): return page < forecast.count ? forecast[page].toWeatherFeatures() : [] | |
} | |
} | |
fileprivate static func geopositionString(appState: AppState) -> String { | |
if case .success(let geoposition) = appState.weather.geopositionRequestState { | |
return "\(geoposition.englishName), \(geoposition.englishRegion) \(geoposition.country)" | |
} | |
return "Locating.." | |
} | |
fileprivate static func isLoading(appState: AppState) -> Bool { | |
return appState.weather.weatherRequestState.isUpdating || | |
appState.weather.geopositionRequestState.isUpdating || | |
appState.location.locationRequestState.isUpdating | |
} | |
} | |
func uistore_reducer(state: UIState, event: UIEvent) -> UIState { | |
switch event { | |
case .turnCurrent: return .current | |
case .turnForecast: return .forecast(page: 0) | |
case .turnLeft: if case .forecast(let page) = state { return .forecast(page: page - 1) } else { return state } | |
case .turnRight: if case .forecast(let page) = state { return .forecast(page: page + 1) } else { return state } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment