Created
September 28, 2016 13:09
-
-
Save ndelitski/90dac45200067c655efdad8758ff8329 to your computer and use it in GitHub Desktop.
RX+MVVM.swift
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
import Foundation | |
import RxSwift | |
import GoogleSignIn | |
import RxViewModel | |
import RxCocoa | |
import RealmSwift | |
class InboxDetailViewModel: RxViewModel { | |
// Input | |
struct Input { | |
let selectedAuthor: Driver<MailParticipant> | |
let selectedCategory: Driver<MailThreadCategory> | |
let cardDidSwipeTrigger: Driver<CardDidSwipeAtIndexTuple> | |
} | |
// Output | |
var authors: Driver<[MailParticipant]> = Driver.never() | |
var authorsSet: Driver<Set<MailParticipant>> = Driver.never() | |
var threads: Driver<[MailThread]> = Driver.never() | |
var categoryCount: Driver<Int> = Driver.never() | |
var title: Driver<String?> = Driver.never() | |
var currentAuthorIndex = Driver<Int?>.never() | |
// I-O | |
private let currentSelectedAuthor = ReplaySubject<MailParticipant>.create(bufferSize: 1) | |
let activityIndicator = ActivityIndicator() | |
let cardAtIndexDidArchived = PublishSubject<Int>() | |
let cardAtIndexDidTodo = PublishSubject<Int>() | |
// Private | |
private let disposeBag = DisposeBag() | |
init(input: Input) { | |
super.init() | |
input.selectedAuthor | |
.asObservable() | |
.subscribeNext { self.currentSelectedAuthor.on(.Next($0))} | |
let categoryThreads = input.selectedCategory.asObservable() | |
.debug("vm:inbox-detail:threads:read-realm") | |
.flatMapLatest { category -> Observable<[MailThread]> in | |
try! Realm() | |
.objects(MailThread.self) | |
.filter("stateRawValue == nil AND categoryString == %@", category.rawValue) | |
.asObservableArray() | |
} | |
threads = Observable.combineLatest( | |
categoryThreads, | |
currentSelectedAuthor.asObservable().distinctUntilChanged() | |
) { (xs: [MailThread], author:MailParticipant) -> [MailThread] in | |
return xs.filter { t in | |
Array(t.messages).some {$0.from! == author} | |
} + xs.filter { t in | |
Array(t.messages).all {$0.from! != author} | |
} | |
} | |
.debug("vm:inbox-detail:threads:sort-author-first") | |
.asDriver(onErrorRecover: { err in | |
debugPrint(err) | |
return Driver.just([MailThread]()) | |
}) | |
authorsSet = threads | |
.debug("vm:inbox-detail:authors") | |
.map { | |
threads in | |
threads.flatMap { t in | |
t.messages.flatMap { $0.from! } | |
}.asSet | |
} | |
authors = authorsSet.map { Array($0) } | |
currentAuthorIndex = Observable.combineLatest( | |
authors.asObservable(), | |
currentSelectedAuthor.asObservable(), | |
didBecomeActive | |
) { [unowned self] | |
xs, x, _ in | |
if let index = xs.indexOf(x) { | |
return index | |
} else if xs.count > 0 { | |
self.currentSelectedAuthor.onNext(xs.first!) | |
return 0 | |
} else { | |
return nil | |
} | |
} | |
.filter { $0 != nil } | |
.debug("vm:inbox-detail:current-author-index") | |
.asDriver(onErrorJustReturn: nil) | |
categoryCount = threads | |
.map { $0.count } | |
.debug("vm:inbox-detail:category-count") | |
title = input.selectedCategory | |
.map { $0.rawValue + "s" } // brute plural form | |
_ = input.cardDidSwipeTrigger | |
.filter { $0.swipe == DeckSwipeDirection.Left } | |
.asObservable() | |
.withLatestFrom(threads.asObservable()) { swipeInfo, threads -> String in | |
return threads[swipeInfo.index].id | |
} | |
// .debug("vm:inbox-detail:did-swipe-right:gmail-archive") | |
// .flatMapFirst { threadId -> Observable<MailThread.Id> in | |
// GmailService.rx_create() | |
// .flatMap { $0.archiveThread(threadId) } | |
// } | |
.debug("vm:inbox-detail:did-swipe-left:realm-save") | |
.subscribeNext { threadId in | |
let realm = try! Realm() | |
try! realm.write { | |
realm.create(MailThread.self, value: ["id": threadId, "stateRawValue": MailThreadState.Archived.rawValue], update: true) | |
} | |
} | |
.addDisposableTo(disposeBag) | |
_ = input.cardDidSwipeTrigger | |
.filter { $0.swipe == .Right } | |
.asObservable() | |
.withLatestFrom(threads.asObservable()) { swipeInfo, threads -> String in | |
return threads[swipeInfo.index].id | |
} | |
.debug("vm:inbox-detail:did-swipe-right:gmail-todo") | |
.flatMapFirst { threadId -> Observable<MailThread.Id> in | |
GmailService.rx_create() | |
.flatMap { $0.todoThread(threadId) } | |
} | |
.observeOn(MainScheduler.instance) | |
.debug("vm:inbox-detail:did-swipe-right:realm-save") | |
.catchError { error in | |
return DefaultWireframe.promptFor(String(error), title: "Error",cancelAction: "OK", actions: []) | |
.map { _ in | |
return error | |
} | |
.flatMap { error in | |
return Observable.error(error) | |
} | |
} | |
.subscribeNext { threadId in | |
let realm = try! Realm() | |
try! realm.write { | |
realm.create(MailThread.self, value: ["id": threadId, "stateRawValue": MailThreadState.Doing.rawValue], update: true) | |
} | |
} | |
// .asResult() | |
// .flatMap { result -> Observable<Bool> in | |
// if case .Failure(let err) = result { | |
// return DefaultWireframe.promptFor("Todo action failed", title: "action failed", cancelAction: "Ok", actions: ["Retry"]) | |
// .flatMap { _ -> Observable<Bool> in | |
// return Observable.just(false) | |
// } | |
// } else { | |
// return Observable.just(true) | |
// } | |
// } | |
// .subscribeNext { _ in | |
// | |
// } | |
} | |
typealias CardViewModelFactory = (CardView, MailThread, Int) -> InboxCardViewModel | |
func cardViewModelFactory() -> CardViewModelFactory { | |
return { [unowned self] cardView, thread, positionInDeck in | |
let threadId = thread.id | |
let input = InboxCardViewModel.Input( | |
threadObservable: Driver.just(threadId).debug("vm:card:input.thread"), | |
messagesSortObservable: self.currentSelectedAuthor.map { MessagesSortOrder.ByParticipantId($0.id) }.asDriver(onErrorJustReturn: MessagesSortOrder.NewestFirst).debug("vm:card:input.sort"), | |
needBePresentedTrigger: self.threads.asDriver().map { $0.indexOf(thread) <= 1 }.debug("vm:card:input.is-card-visible"), | |
willArchiveTrigger: cardView.rx_tapArchive.asDriver().debug("vm:card:input.will-archive"), | |
willToDoTrigger: cardView.rx_tapTodo.asDriver().debug("vm:card:input.will-todo") | |
) | |
let vm = InboxCardViewModel(input: input) | |
_ = vm.threadDidArchived | |
.withLatestFrom(self.threads) { threadId, xs in xs.findIndex{$0.id == threadId}! } | |
.takeUntil(vm.rx_deallocated) | |
.subscribeNext { [unowned self] in | |
self.cardAtIndexDidArchived.onNext($0) | |
} | |
_ = vm.threadDidTodo | |
.withLatestFrom(self.threads) { threadId, xs in xs.findIndex{$0.id == threadId}! } | |
.takeUntil(vm.rx_deallocated) | |
.subscribeNext { [unowned self] in | |
self.cardAtIndexDidTodo.onNext($0) | |
} | |
return vm | |
} | |
} | |
deinit { | |
debugPrint("vm:inbox-detail:deinit()") | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment