Last active
April 19, 2016 16:08
-
-
Save ivanbruel/1227bf321161c46bb7a3ed804413ebce to your computer and use it in GitHub Desktop.
DressDetailsAddToBagViewModel
This file contains hidden or 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
// | |
// DressDetailsAddToBagViewModel.swift | |
// ChicByChoice | |
// | |
// Created by Ivan Bruel on 18/04/16. | |
// Copyright © 2016 Chic by Choice. All rights reserved. | |
// | |
import Foundation | |
import RxSwift | |
import RxCocoa | |
import Maya | |
import Device | |
class DressDetailsAddToBagViewModel: DressDetailsViewModel, ErrorAlertable { | |
private let _size: Variable<DressSize?> | |
private let _secondSize: Variable<DressSize?> | |
private let _cut: Variable<DressCut?> | |
private let _rentalPeriod: Variable<OrderPeriod> | |
private let _eventDate: Variable<MayaDate?> | |
private let _tryOnDate: Variable<MayaDate?> | |
private let _eventDateAvailabilities: Variable<[DressDetailsAvailabilityViewModel]> | |
private let _tryOnDateAvailabilities: Variable<[DressDetailsAvailabilityViewModel]> | |
var ctaViewModel: CTAAlertViewModel { | |
return CTAAlertViewModel(message: tr(.CTAMessageCall), image: UIImage(asset: .PhoneIcon), | |
buttonTitle: tr(.CTAButtonDismiss)) | |
} | |
override func initViewModels(dress: Dress) -> [DressDetailsType] { | |
let size = DressDetailsAddToBag.Size(size: self.size, availableSizes: availableSizes, | |
selected: selectedSize) | |
let secondSize = DressDetailsAddToBag.SecondSize(secondSize: self.secondSize, | |
availableSecondSizes: availableSecondSizes, | |
secondSizeInformation: secondSizeInformation, | |
selected: selectedSecondSize) | |
let cut = DressDetailsAddToBag.Cut(cut: self.cut, availableCuts: availableCuts, | |
selected: selectedCut) | |
let rentalPeriod = DressDetailsAddToBag | |
.RentalPeriod(rentalPeriod: self.rentalPeriod, availableRentalPeriods: avaiableRentalPeriods, | |
selected: selectedRentalPeriod) | |
let deliveryDate = DressDetailsAddToBag | |
.DeliveryDate(deliveryDateText: deliveryDateText, | |
eventDateAvailability: eventDateAvailability, | |
eventDateAvailabilities: eventDateAvailabilities, maxDate: eventMaxDate, | |
load: loadEventDateAvailability, | |
selected: selectedEventDate) | |
let tryOnDate = DressDetailsAddToBag | |
.TryOnDate(tryOnDateText: tryOnDateText, tryOnDateAvailability: tryOnDateAvailability, | |
tryOnDateAvailabilities: tryOnAvailabilities, tryOnInformation: tryOnPricing, | |
maxDate: tryOnMaxDate, load: loadTryOnDateAvailability, | |
selected: selectedTryOnDate) | |
return [size, secondSize, cut, rentalPeriod, deliveryDate, tryOnDate].map { $0.viewModel } | |
} | |
// MARK: Size | |
var size: Observable<String?> { | |
return _size.asObservable() | |
.map { $0?.name(self.user.country) } | |
} | |
var availableSizes: Observable<[String]> { | |
return dress.asObservable().map { $0.sizes ?? [] } | |
.map { dressSizes in | |
dressSizes | |
.sort { $0.rawValue < $1.rawValue } | |
.map { $0.name(self.user.country) } | |
} | |
} | |
// MARK: Second Size | |
var secondSize: Observable<String?> { | |
return _secondSize.asObservable() | |
.map { $0?.name(self.user.country) } | |
} | |
var availableSecondSizes: Observable<[String]> { | |
let availableSizes: Observable<[DressSize]> = dress.asObservable().map({ $0.sizes ?? [] }) | |
let size: Observable<DressSize?> = _size.asObservable() | |
return Observable.combineLatest(size, availableSizes) { ($0, $1) } | |
.map { (dressSize, availableSizes) in | |
guard let dressSize = dressSize, | |
secondDressSize = DressSize(rawValue: dressSize.rawValue + 1) else { | |
return [] | |
} | |
return availableSizes.contains(secondDressSize) ? | |
[secondDressSize.name(self.user.country)] : [] | |
} | |
} | |
var secondSizeInformation: Observable<String?> { | |
return Observable.combineLatest(size, availableSecondSizes, eventDate, eventDateAvailability) { | |
($0, $1, $2, $3) | |
}.map { (size, availableSecondSizes, eventDate, eventDateAvailability) in | |
guard size != nil else { | |
return tr(.DressDetailsLabelSelectSize) | |
} | |
guard eventDate != nil else { | |
return availableSecondSizes.count > 0 ? nil : tr(.DressDetailsLabelNotAvailable) | |
} | |
return eventDateAvailability != nil ? nil : tr(.DressDetailsLabelNotAvailable) | |
} | |
} | |
// MARK: Cut | |
var cut: Observable<String?> { | |
return _cut.asObservable().map { $0?.name } | |
} | |
var availableCuts: Observable<[String]> { | |
return Observable.combineLatest(_size.asObservable(), dress.asObservable()) { ($0, $1) } | |
.map { (size, dress) in | |
guard let cuts = dress.cutsForSize(size) | |
where cuts.count > 1 else { | |
return [] | |
} | |
return cuts.map { $0.name } | |
} | |
} | |
// MARK: RentalPeriod | |
var rentalPeriod: Observable<String?> { | |
return _rentalPeriod.asObservable() | |
.map { $0.name } | |
} | |
var avaiableRentalPeriods: Observable<[String]> { | |
let orderPeriodNames = OrderPeriod.all().map { $0.name } | |
return Observable.just(orderPeriodNames) | |
} | |
// MARK: DeliveryDate | |
var eventDate: Observable<MayaDate?> { | |
return _eventDate.asObservable() | |
} | |
var deliveryDate: Observable<MayaDate?> { | |
return DressDetailsAddToBagViewModel | |
.deliveryDateObservable(_eventDate.asObservable(), availabilities: eventDateAvailabilities) | |
} | |
var deliveryDateText: Observable<String?> { | |
return deliveryDate.map { DressDetailsAddToBagViewModel.dateName($0) } | |
} | |
var eventMaxDate: Observable<MayaDate> { | |
return Observable.just(MayaMonth(mayaDate: MayaDate.today).monthWithOffset(11).lastDate) | |
} | |
var eventDateAvailabilities: Observable<[DressDetailsAvailabilityViewModel]> { | |
return _eventDateAvailabilities.asObservable() | |
.map { availabilities in | |
return availabilities.filter { $0.sizeAvailable } | |
} | |
} | |
var eventDateAvailability: Observable<DressDetailsAvailabilityViewModel?> { | |
return Observable.combineLatest(eventDateAvailabilities, eventDate) { ($0, $1) } | |
.map { (availabilities, eventDate) in | |
guard let eventDate = eventDate else { | |
return nil | |
} | |
return availabilities.filter { $0.date == eventDate }.first | |
} | |
} | |
// MARK: TryOnDate | |
var tryOnDate: Observable<MayaDate?> { | |
return DressDetailsAddToBagViewModel.deliveryDateObservable(_tryOnDate.asObservable(), | |
availabilities: tryOnAvailabilities) | |
} | |
var tryOnDateText: Observable<String?> { | |
return _tryOnDate.asObservable().map { DressDetailsAddToBagViewModel.dateName($0) } | |
} | |
var tryOnAvailabilities: Observable<[DressDetailsAvailabilityViewModel]> { | |
return _tryOnDateAvailabilities.asObservable() | |
.map { availabilities in | |
return availabilities.filter { $0.sizeAvailable } | |
} | |
} | |
var tryOnMaxDate: Observable<MayaDate> { | |
return eventDate.map { eventDate in | |
guard let eventDate = eventDate else { | |
return MayaMonth(mayaDate: MayaDate.today).monthWithOffset(11).lastDate | |
} | |
return eventDate | |
} | |
} | |
var tryOnDateAvailability: Observable<DressDetailsAvailabilityViewModel?> { | |
return Observable.combineLatest(tryOnAvailabilities, tryOnDate) { ($0, $1) } | |
.map { (availabilities, eventDate) in | |
guard let eventDate = eventDate else { | |
return nil | |
} | |
return availabilities.filter { $0.date == eventDate }.first | |
} | |
} | |
var tryOnPricing: Observable<String?> { | |
return Observable.just(user.tryOnServicePrice) | |
} | |
init(user: User, dress: Dress, failedToLoad: (() -> Void)? = nil, | |
oldGallery: DressPhotoGalleryViewModel? = nil) { | |
_size = Variable(DressDetailsAddToBagViewModel.sizeFromUser(user, forDress: dress)) | |
_secondSize = Variable(nil) | |
_cut = Variable(nil) | |
_rentalPeriod = Variable(.FourDays) | |
_eventDate = Variable(nil) | |
_tryOnDate = Variable(nil) | |
_eventDateAvailabilities = Variable([]) | |
_tryOnDateAvailabilities = Variable([]) | |
super.init(user: user, confirmButtonTitle: tr(.DressDetailsButtonAddToBag), dress: dress, | |
failedToLoad: failedToLoad, oldGallery: oldGallery) | |
setupViewModels() | |
} | |
private func setupViewModels() { | |
setupSecondSizeObservable() | |
setupCutObservable() | |
setupEventDate() | |
} | |
private func setupSecondSizeObservable() { | |
let availableSizes: Observable<[DressSize]> = dress.asObservable().map({ $0.sizes ?? [] }) | |
let size: Observable<DressSize?> = _size.asObservable() | |
.distinctUntilChanged { $0 == $1 } | |
Observable.combineLatest(size, availableSizes) { ($0, $1) } | |
.bindNext { (size, availableSizes) in | |
guard let size = size, | |
secondDressSize = DressSize(rawValue: size.rawValue + 1) else { | |
self._secondSize.value = nil | |
return | |
} | |
self._secondSize.value = availableSizes.contains(secondDressSize) ? secondDressSize : nil | |
}.addDisposableTo(rx_disposeBag) | |
} | |
private func setupCutObservable() { | |
_size.asObservable() | |
.distinctUntilChanged { $0 == $1 } | |
.bindNext { size in | |
guard let size = size else { | |
self._cut.value = nil | |
return | |
} | |
self._cut.value = self.dress.value.cutsForSize(size)?.first | |
}.addDisposableTo(rx_disposeBag) | |
} | |
private func setupEventDate() { | |
let size = _size.asObservable().distinctUntilChanged { $0 == $1 } | |
let secondSize = _secondSize.asObservable().distinctUntilChanged { $0 == $1 } | |
let rentalPeriod = _rentalPeriod.asObservable().distinctUntilChanged { $0 == $1 } | |
Observable.combineLatest(size, secondSize, rentalPeriod) { ($0, $1, $2) } | |
.bindNext { (size, secondSize, rentalPeriod) in | |
guard let size = size else { | |
return | |
} | |
self.loadAvailability(size, secondSize: secondSize, hirePeriod: rentalPeriod, | |
date: NSDate()) | |
}.addDisposableTo(rx_disposeBag) | |
_eventDate.asObservable() | |
.distinctUntilChanged { $0 == $1 } | |
.bindNext { eventDate in | |
self._tryOnDate.value = nil | |
}.addDisposableTo(rx_disposeBag) | |
} | |
} | |
// MARK: Selection | |
extension DressDetailsAddToBagViewModel { | |
func selectedSize(size: String?) { | |
QL1("Selected size \(size)") | |
_size.value = DressSize.sizeFromString(size, country: user.country) | |
} | |
func selectedSecondSize(secondSize: String?) { | |
QL1("Selected second size \(secondSize)") | |
_secondSize.value = DressSize.sizeFromString(secondSize, country: user.country) | |
} | |
func selectedCut(cut: String?) { | |
QL1("Selected cut \(cut)") | |
_cut.value = DressCut.cutFromString(cut) | |
} | |
func selectedRentalPeriod(rentalPeriod: String?) { | |
QL1("Selected rental period \(rentalPeriod)") | |
_rentalPeriod.value = OrderPeriod.orderPeriodFromString(rentalPeriod) ?? .FourDays | |
} | |
func selectedEventDate(eventDate: MayaDate?) -> Bool { | |
QL1("Selected event date \(eventDate?.date)") | |
_eventDate.value = eventDate | |
return true | |
} | |
func selectedTryOnDate(tryOnDate: MayaDate?) -> Bool { | |
QL1("Selected try on date \(tryOnDate?.date)") | |
_tryOnDate.value = tryOnDate | |
return true | |
} | |
} | |
// MARK: Availability | |
extension DressDetailsAddToBagViewModel { | |
func loadEventDateAvailability(date: MayaDate) { | |
guard let size = _size.value else { | |
return | |
} | |
loadAvailability(size, secondSize: _secondSize.value, hirePeriod: _rentalPeriod.value, | |
date: date.date) | |
} | |
func loadTryOnDateAvailability(date: MayaDate) { | |
guard let size = _size.value else { | |
return | |
} | |
loadAvailability(size, secondSize: _secondSize.value, hirePeriod: .TwoDays, | |
date: date.date) | |
} | |
} | |
// MARK: Networking | |
extension DressDetailsAddToBagViewModel { | |
func addToBag() -> Observable<Order> { | |
self.isPerformingARequest.value = true | |
guard let dressSize = _size.value else { | |
self.isPerformingARequest.value = false | |
return .error(AddToBagError.EmptyFields(tr(.DressDetailsErrorSizeNotPicked))) | |
} | |
let freeSecondSize = _secondSize.value | |
let cut: DressCut? = _cut.value | |
let rentalPeriod: OrderPeriod = _rentalPeriod.value | |
guard let deliveryDate = DressDetailsAddToBagViewModel | |
.deliveryDate(_eventDate.value, availabilities: _eventDateAvailabilities.value)?.date else { | |
self.isPerformingARequest.value = false | |
return .error(AddToBagError.EmptyFields(tr(.DressDetailsErrorDeliveryDateNotPicked))) | |
} | |
guard let returnDate = DressDetailsAddToBagViewModel | |
.returnDate(_eventDate.value, availabilities: _eventDateAvailabilities.value)?.date else { | |
self.isPerformingARequest.value = false | |
return .error(AddToBagError.FetchingReturnDate) | |
} | |
let tryOnService = _tryOnDate.value?.date | |
return Network.provider.request(API.ShoppingBagAdd(dress.value, dressSize, freeSecondSize, cut, | |
rentalPeriod, deliveryDate, returnDate, tryOnService)) | |
.filterSuccessfulStatusCodes() | |
.observeOn(SerialDispatchQueueScheduler(globalConcurrentQueueQOS: .Background)) | |
.mapObject(Order) | |
.observeOn(MainScheduler.instance) | |
.doOn { _ in | |
self.isPerformingARequest.value = false | |
}.doOnNext { order in | |
self.user.shoppingBag = order | |
self.user.saveEventually() | |
} | |
} | |
func loadAvailability(size: DressSize, secondSize: DressSize?, hirePeriod: OrderPeriod, | |
date: NSDate) { | |
guard let size = _size.value else { | |
return | |
} | |
let freeSecondSize = _secondSize.value | |
QL1("Loading availability \(size.rawValue) \(freeSecondSize?.rawValue) \(date) \(hirePeriod.rawValue)") | |
Network.provider.request(API.DressAvailability(self.dress.value.identifier, | |
size.rawValue, freeSecondSize?.rawValue, date, hirePeriod.rawValue)) | |
.filterSuccessfulStatusCodes() | |
.observeOn(SerialDispatchQueueScheduler(globalConcurrentQueueQOS: .Background)) | |
.mapArray(DressAvailability) | |
.observeOn(MainScheduler.instance) | |
.bindNext { availabilityArray in | |
let availabilityViewModelArray = availabilityArray | |
.filter { $0.deliveryDate != nil } | |
.filter { MayaDate(date: $0.date).compare(MayaDate.today) == .OrderedDescending } | |
.map { DressDetailsAvailabilityViewModel(dressAvailability: $0) } | |
if hirePeriod == .TwoDays { | |
self._tryOnDateAvailabilities.value += availabilityViewModelArray | |
} else { | |
self._eventDateAvailabilities.value += availabilityViewModelArray | |
} | |
}.addDisposableTo(rx_disposeBag) | |
} | |
} | |
// MARK: Helpers | |
extension DressDetailsAddToBagViewModel { | |
private class func deliveryDateObservable( | |
date: Observable<MayaDate?>, | |
availabilities: Observable<[DressDetailsAvailabilityViewModel]>) -> Observable<MayaDate?> { | |
return Observable.combineLatest(date, availabilities) { ($0, $1) } | |
.map { (date, availabilities) in | |
return deliveryDate(date, availabilities: availabilities) | |
} | |
} | |
private class func deliveryDate( | |
date: MayaDate?, | |
availabilities: [DressDetailsAvailabilityViewModel]) -> MayaDate? { | |
guard let availability = availabilities.filter({ $0.date == date }).first, | |
deliveryDate = availability.deliveryDate else { | |
return nil | |
} | |
return deliveryDate | |
} | |
private class func returnDate(date: MayaDate?, | |
availabilities: [DressDetailsAvailabilityViewModel]) -> MayaDate? { | |
guard let availability = availabilities.filter({ $0.date == date }).first else { | |
return nil | |
} | |
return availability.returnDate | |
} | |
private class func sizeFromUser(user: User, forDress dress: Dress) -> DressSize? { | |
guard let sizes = dress.sizes else { return nil } | |
if let size = user.session.filters.value.sizes?.first where sizes.contains(size) { | |
return size | |
} | |
if let size = user.size where sizes.contains(size) { | |
return size | |
} | |
return nil | |
} | |
private class func dateName(date: MayaDate?) -> String? { | |
return Device.isSmallerThanScreenSize(.Screen4_7Inch) ? date?.date.dateValue : | |
date?.date.localizedLongValue | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment