Skip to content

Commit

Permalink
Merge branch 'edit-task-refactoring' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
reloni committed Jul 2, 2017
2 parents f86d72c + c875f57 commit 62baac1
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 63 deletions.
44 changes: 21 additions & 23 deletions Aika/Controllers/Tasks/EditTaskController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SnapKit
import Material
import RxSwift
import RxDataFlow
import RxGesture

final class EditTaskController : UIViewController {
enum DatePickerExpandMode {
Expand Down Expand Up @@ -73,6 +74,7 @@ final class EditTaskController : UIViewController {
picker.borderColor = Theme.Colors.romanSilver
picker.borderWidth = 0.5
picker.layoutMargins = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
picker.date = nil

return picker
}()
Expand Down Expand Up @@ -119,13 +121,11 @@ final class EditTaskController : UIViewController {
return text
}()

let saveSubject = PublishSubject<Void>()

init(viewModel: EditTaskViewModel) {
self.viewModel = viewModel

descriptionTextField.text = viewModel.taskDescription.value
notesTextField.text = viewModel.taskNotes.value
targetDatePickerView.date = viewModel.taskTargetDate.value

super.init(nibName: nil, bundle: nil)
}

Expand Down Expand Up @@ -153,10 +153,6 @@ final class EditTaskController : UIViewController {
updateViewConstraints()

bind()

if viewModel.task == nil {
descriptionTextField.becomeFirstResponder()
}
}

func bind() {
Expand All @@ -170,22 +166,24 @@ final class EditTaskController : UIViewController {
self?.scrollView.updatecontentInsetFor(keyboardHeight: 0)
}).disposed(by: bag)

descriptionTextField.rx.didChange.subscribe(onNext: { [weak self] _ in
self?.viewModel.taskDescription.value = self?.descriptionTextField.text ?? ""
}).disposed(by: bag)

notesTextField.rx.didChange.subscribe(onNext: { [weak self] _ in
self?.viewModel.taskNotes.value = self?.notesTextField.text
}).disposed(by: bag)

targetDatePickerView.currentDate.bind(to: viewModel.taskTargetDate).disposed(by: bag)
let state = viewModel.state.shareReplay(1)

viewModel.datePickerExpanded.skip(1).subscribe(onNext: { [weak self] isExpanded in self?.switchDatePickerExpandMode(isExpanded) }).disposed(by: bag)
let datePickerExpanded = targetDateView.calendarButton.rx.tap
.withLatestFrom(state.map { !$0.datePickerExpanded })

viewModel.taskTargetDateChanged.subscribe(onNext: { [weak self] next in self?.targetDatePickerView.date = next }).disposed(by: bag)
viewModel.subscribe(taskDescription: descriptionTextField.rx.didChange.map { [weak self] _ in return self?.descriptionTextField.text ?? "" }.distinctUntilChanged(),
taskNotes: notesTextField.rx.didChange.map { [weak self] _ in return self?.notesTextField.text }.distinctUntilChanged { $0.0 == $0.1 },
taskTargetDate: targetDatePickerView.currentDate.skip(1).distinctUntilChanged { $0.0 == $0.1 },
datePickerExpanded: datePickerExpanded,
clearTargetDate: targetDateView.clearButton.rx.tap.flatMap { Observable<Void>.just() },
saveChanges: saveSubject.asObservable())
.forEach { bag.insert($0) }

targetDateView.calendarButton.rx.tap.subscribe(onNext: { [weak self] _ in self?.viewModel.switchDatePickerExpansion() }).disposed(by: bag)
targetDateView.clearButton.rx.tap.subscribe(onNext: { [weak self] _ in self?.viewModel.clearTargetDate() }).disposed(by: bag)
state.take(1).map { $0.description }.bind(to: descriptionTextField.rx.text).disposed(by: bag)
state.take(1).map { $0.notes }.bind(to: notesTextField.rx.text).disposed(by: bag)
state.map { $0.targetDate }.distinctUntilChanged({ $0.0 == $0.1 }).do(onNext: { [weak self] in self?.targetDatePickerView.date = $0 }).subscribe().disposed(by: bag)
state.map { $0.datePickerExpanded }.distinctUntilChanged().do(onNext: { [weak self] in self?.switchDatePickerExpandMode($0) }).subscribe().disposed(by: bag)
state.take(1).map { $0.currentTask == nil }.filter { $0 }.do(onNext: { [weak self] _ in self?.descriptionTextField.becomeFirstResponder() }).subscribe().disposed(by: bag)

targetDatePickerView.currentDate.map { $0?.toString(withSpelling: false) ?? "" }.bind(to: targetDateView.textField.rx.text).disposed(by: bag)

Expand All @@ -195,7 +193,7 @@ final class EditTaskController : UIViewController {

func targetDateTextFieldTapped(_ gesture: UITapGestureRecognizer) {
guard gesture.state == .ended else { return }
viewModel.switchDatePickerExpansion()
targetDateView.calendarButton.sendActions(for: .touchUpInside)
}

func switchDatePickerExpandMode(_ expand: Bool) {
Expand All @@ -220,7 +218,7 @@ final class EditTaskController : UIViewController {
}

func done() {
viewModel.save()
saveSubject.onNext()
}

override func updateViewConstraints() {
Expand Down
197 changes: 157 additions & 40 deletions Aika/ViewModels/EditTaskViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,70 +11,187 @@ import RxSwift
import RxDataFlow

final class EditTaskViewModel: ViewModelType {
private let _datePickerExpanded = Variable(false)
private let _taskTargetDateSubject = PublishSubject<TaskDate?>()

let flowController: RxDataFlowController<RootReducer>
let task: Task?
struct State: RxStateType {
let description: String
let notes: String?
let targetDate: TaskDate?
let datePickerExpanded: Bool
let currentTask: Task?

func new(description: String? = nil, notes: String? = nil, targetDate: TaskDate?? = nil, datePickerExpanded: Bool? = nil) -> State {
return State(description: description ?? self.description,
notes: notes ?? self.notes,
targetDate: targetDate ?? self.targetDate,
datePickerExpanded: datePickerExpanded ?? self.datePickerExpanded,
currentTask: self.currentTask)
}
}

lazy var taskDescription: Variable<String> = { return Variable<String>(self.task?.description ?? "") }()
lazy var taskNotes: Variable<String?> = { return Variable<String?>(self.task?.notes) }()
lazy var taskTargetDate: Variable<TaskDate?> = { return Variable<TaskDate?>(self.task?.targetDate) }()
let title: String

lazy var datePickerExpanded: Observable<Bool> = { self._datePickerExpanded.asObservable() }()
lazy var taskTargetDateChanged: Observable<TaskDate?> = { self._taskTargetDateSubject.asObservable() }()
let flowController: RxDataFlowController<RootReducer>

let localStateSubject: BehaviorSubject<State>
var state: Observable<State> { return localStateSubject.asObservable() }

init(task: Task?, flowController: RxDataFlowController<RootReducer>) {
self.task = task
self.flowController = flowController

let initialState = State(description: task?.description ?? "",
notes: task?.notes ?? "",
targetDate: task?.targetDate,
datePickerExpanded: false,
currentTask: task)
localStateSubject = BehaviorSubject(value: initialState)

title = {
if let desc = task?.description {
return "Edit \(desc)"
} else {
return "New task"
}
}()
}

lazy var title: String = {
if let desc = self.task?.description {
return "Edit \(desc)"
} else {
return "New task"
}
}()

func clearTargetDate() {
_taskTargetDateSubject.onNext(nil)
_datePickerExpanded.value = false
}

func switchDatePickerExpansion() {
_datePickerExpanded.value = !_datePickerExpanded.value
if _datePickerExpanded.value, taskTargetDate.value == nil {
_taskTargetDateSubject.onNext(TaskDate(date: Date(), includeTime: true))
}
func subscribe(taskDescription: Observable<String>,
taskNotes: Observable<String?>,
taskTargetDate: Observable<TaskDate?>,
datePickerExpanded: Observable<Bool>,
clearTargetDate: Observable<Void>,
saveChanges: Observable<Void>) -> [Disposable] {
let currentState = localStateSubject.asObservable().shareReplay(1)
return [
taskDescription.withLatestFrom(currentState) { return ($0.1, $0.0) }
.do(onNext: { [weak localStateSubject] in localStateSubject?.onNext($0.0.new(description: $0.1)) })
.subscribe(),
taskNotes.withLatestFrom(currentState) { return ($0.1, $0.0) }
.do(onNext: { [weak localStateSubject] in localStateSubject?.onNext($0.0.new(notes: $0.1)) })
.subscribe(),
taskTargetDate.withLatestFrom(currentState) { return ($0.1, $0.0) }
.do(onNext: { [weak localStateSubject] in localStateSubject?.onNext($0.0.new(targetDate: $0.1)) })
.subscribe(),
datePickerExpanded.withLatestFrom(currentState) { return ($0.1, $0.0) }
.do(onNext: { [weak localStateSubject] in
if $0.1, $0.0.targetDate == nil {
localStateSubject?.onNext($0.0.new(targetDate: TaskDate(date: Date(), includeTime: true), datePickerExpanded: $0.1))
} else {
localStateSubject?.onNext($0.0.new(datePickerExpanded: $0.1))
}
})
.subscribe(),
clearTargetDate.withLatestFrom(currentState) { return ($0.1, $0.0) }
.do(onNext: { [weak localStateSubject] in
localStateSubject?.onNext($0.0.new(targetDate: Optional<TaskDate?>.some(Optional<TaskDate>.none), datePickerExpanded: false))
})
.subscribe(),
saveChanges.withLatestFrom(currentState) { return ($0.1, $0.0) }
.do(onNext: { [weak self] in self?.save(state: $0.0) })
.subscribe()
]
}

private func createTask() -> [RxActionType] {
private func createTask(state: State) -> [RxActionType] {
let action = RxCompositeAction(actions: [UIAction.dismisEditTaskController,
SynchronizationAction.addTask(Task(uuid: UniqueIdentifier(),
completed: false,
description: taskDescription.value,
notes: taskNotes.value,
targetDate: taskTargetDate.value))])
description: state.description,
notes: state.notes,
targetDate: state.targetDate))])

return [action, RxCompositeAction.synchronizationAction]
}

private func update(task: Task) -> [RxActionType] {
let newTask = Task(uuid: task.uuid, completed: false, description: taskDescription.value, notes: taskNotes.value, targetDate: taskTargetDate.value)
private func update(task: Task, state: State) -> [RxActionType] {
let newTask = Task(uuid: task.uuid, completed: false, description: state.description, notes: state.notes, targetDate: state.targetDate)
let action = RxCompositeAction(actions: [UIAction.dismisEditTaskController,
SynchronizationAction.updateTask(newTask)])

return [action, RxCompositeAction.synchronizationAction]
}

func save() {
guard taskDescription.value.characters.count > 0 else { return }
guard let task = task else {
createTask().forEach { flowController.dispatch($0) }
private func save(state: State) {
guard state.description.characters.count > 0 else { return }
guard let task = state.currentTask else {
createTask(state: state).forEach { flowController.dispatch($0) }
return
}

update(task: task).forEach { flowController.dispatch($0) }
update(task: task, state: state).forEach { flowController.dispatch($0) }
}
}
//
//final class EditTaskViewModel: ViewModelType {
// struct State {
// let description: String
// let notes: String
// let targetDate: TaskDate?
// let datePickerExpanded: Bool
// }
//
// private let _datePickerExpanded = Variable(false)
// private let _taskTargetDateSubject = PublishSubject<TaskDate?>()
//
// let flowController: RxDataFlowController<RootReducer>
// let task: Task?
//
// lazy var taskDescription: Variable<String> = { return Variable<String>(self.task?.description ?? "") }()
// lazy var taskNotes: Variable<String?> = { return Variable<String?>(self.task?.notes) }()
// lazy var taskTargetDate: Variable<TaskDate?> = { return Variable<TaskDate?>(self.task?.targetDate) }()
//
// lazy var datePickerExpanded: Observable<Bool> = { self._datePickerExpanded.asObservable() }()
// lazy var taskTargetDateChanged: Observable<TaskDate?> = { self._taskTargetDateSubject.asObservable() }()
//
// init(task: Task?, flowController: RxDataFlowController<RootReducer>) {
// self.task = task
// self.flowController = flowController
// }
//
// lazy var title: String = {
// if let desc = self.task?.description {
// return "Edit \(desc)"
// } else {
// return "New task"
// }
// }()
//
// func clearTargetDate() {
// _taskTargetDateSubject.onNext(nil)
// _datePickerExpanded.value = false
// }
//
// func switchDatePickerExpansion() {
// _datePickerExpanded.value = !_datePickerExpanded.value
// if _datePickerExpanded.value, taskTargetDate.value == nil {
// _taskTargetDateSubject.onNext(TaskDate(date: Date(), includeTime: true))
// }
// }
//
// private func createTask() -> [RxActionType] {
// let action = RxCompositeAction(actions: [UIAction.dismisEditTaskController,
// SynchronizationAction.addTask(Task(uuid: UniqueIdentifier(),
// completed: false,
// description: taskDescription.value,
// notes: taskNotes.value,
// targetDate: taskTargetDate.value))])
//
// return [action, RxCompositeAction.synchronizationAction]
// }
//
// private func update(task: Task) -> [RxActionType] {
// let newTask = Task(uuid: task.uuid, completed: false, description: taskDescription.value, notes: taskNotes.value, targetDate: taskTargetDate.value)
// let action = RxCompositeAction(actions: [UIAction.dismisEditTaskController,
// SynchronizationAction.updateTask(newTask)])
//
// return [action, RxCompositeAction.synchronizationAction]
// }
//
// func save() {
// guard taskDescription.value.characters.count > 0 else { return }
// guard let task = task else {
// createTask().forEach { flowController.dispatch($0) }
// return
// }
//
// update(task: task).forEach { flowController.dispatch($0) }
// }
//}

0 comments on commit 62baac1

Please sign in to comment.