Skip to content

Instantly share code, notes, and snippets.

@ndelitski
Created September 28, 2016 13:09
Show Gist options
  • Save ndelitski/90dac45200067c655efdad8758ff8329 to your computer and use it in GitHub Desktop.
Save ndelitski/90dac45200067c655efdad8758ff8329 to your computer and use it in GitHub Desktop.
RX+MVVM.swift
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