Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ target 'WordPress', :exclusive => true do
pod 'WPMediaPicker', '~> 0.7.3'
pod 'ReactiveCocoa', '~> 2.4.7'
pod 'FormatterKit', '~> 1.8.0'
pod 'RxSwift', '~> 2.1.0'
pod 'RxCocoa', '~> 2.1.0'
end

target 'WordPressTodayWidget', :exclusive => true do
Expand All @@ -53,6 +55,7 @@ target :WordPressTest, :exclusive => true do
pod 'Specta', '1.0.5'
pod 'Expecta', '0.3.2'
pod 'Nimble', '~> 3.0.0'
pod 'RxSwift', '~> 2.1.0'
end

target 'UITests', :exclusive => true do
Expand Down
7 changes: 7 additions & 0 deletions Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ PODS:
- ReactiveCocoa/no-arc (2.4.7)
- ReactiveCocoa/UI (2.4.7):
- ReactiveCocoa/Core
- RxCocoa (2.1.0):
- RxSwift (~> 2.0)
- RxSwift (2.1.0)
- Simperium (0.8.10):
- Simperium/DiffMatchPach (= 0.8.10)
- Simperium/JRSwizzle (= 0.8.10)
Expand Down Expand Up @@ -205,6 +208,8 @@ DEPENDENCIES:
- OHHTTPStubs/Swift (~> 4.6.0)
- Reachability (= 3.2)
- ReactiveCocoa (~> 2.4.7)
- RxCocoa (~> 2.1.0)
- RxSwift (~> 2.1.0)
- Simperium (= 0.8.10)
- Specta (= 1.0.5)
- SVProgressHUD (~> 1.1.3)
Expand Down Expand Up @@ -275,6 +280,8 @@ SPEC CHECKSUMS:
PDKTZipArchive: 81c4824eb5587131e422fc58b92c043c4aa96466
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
ReactiveCocoa: eb38dee0a0e698f73a9b25e5c1faea2bb4c79240
RxCocoa: 79b5feb8378545336e756a0a33fcf5e95050b71c
RxSwift: 110fb07f81c17c2c3b3254d168363057b1880d18
Simperium: f507d9b400c499048a98fe728a0b2b9956fd14c1
Specta: ac94d110b865115fe60ff2c6d7281053c6f8e8a2
SVProgressHUD: 748080e4f36e603f6c02aec292664239df5279c1
Expand Down
3 changes: 3 additions & 0 deletions WordPress/Classes/Models/ManagedAccountSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,6 @@ enum AccountSettingsChange {
}
}
}

typealias AccountSettingsChangeWithString = String -> AccountSettingsChange
typealias AccountSettingsChangeWithInt = Int -> AccountSettingsChange
51 changes: 51 additions & 0 deletions WordPress/Classes/Services/AccountService+Rx.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Foundation
import RxSwift
import RxCocoa

extension AccountService {
/// Observable that emits new values when the default account is set
///
/// - warning: This should only be observed from the main thread, otherwise behavior is undefined
var defaultAccountObjectID: Observable<NSManagedObjectID?> {
return NSNotificationCenter.defaultCenter()
.rx_notification(WPAccountDefaultWordPressComAccountChangedNotification)
.map({ ($0.object as! WPAccount?)?.objectID })
.startWith(defaultWordPressComAccount()?.objectID)
}

/// Observable that emits values when there is a change in the default account.
/// This can be that the default account is set or removed, or one of its properties changes.
///
/// - warning: This should only be observed from the main thread, otherwise behavior is undefined
var defaultAccountChanged: Observable<WPAccount?> {
// Keep a reference to the context to avoid having to reference self
// within the closure
let context = managedObjectContext

return defaultAccountObjectID
// When the default account is set return the values of this new signal
.flatMapLatest({ (objectID) -> Observable<WPAccount?> in
if let objectID = objectID,
let account = try? context.existingObjectWithID(objectID) as? WPAccount {

return NSNotificationCenter.defaultCenter()
.rx_notification(NSManagedObjectContextObjectsDidChangeNotification, object: context)
// Transform the notifications into the changed account if it changed
.map({ (note) -> WPAccount? in
guard let updatedObjects = note.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject> else {
return nil
}

let matchingObject = updatedObjects.filter({ $0.objectID == objectID }).first
return matchingObject as? WPAccount
})
// A nil value here means the change didn't affect the current account
.filter({ $0 != nil })
.startWith(account)
} else {
// If the default account was removed, just send a nil value
return Observable.just(nil)
}
})
}
}
12 changes: 10 additions & 2 deletions WordPress/Classes/Services/AccountService.m
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,22 @@ - (void)setDefaultWordPressComAccount:(WPAccount *)account
[[NSUserDefaults standardUserDefaults] synchronize];

NSManagedObjectID *accountID = account.objectID;
dispatch_async(dispatch_get_main_queue(), ^{
void (^notifyAccountChange)() = ^{
NSManagedObjectContext *mainContext = [[ContextManager sharedInstance] mainContext];
NSManagedObject *accountInContext = [mainContext existingObjectWithID:accountID error:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:WPAccountDefaultWordPressComAccountChangedNotification object:accountInContext];

[[PushNotificationsManager sharedInstance] registerForRemoteNotifications];
[[InteractiveNotificationsHandler sharedInstance] registerForUserNotifications];
});
};
if ([NSThread isMainThread]) {
// This is meant to help with testing account observers.
// Short version: dispatch_async and XCTest asynchronous helpers don't play nice with each other
// Long version: see the comment in https://github.com/wordpress-mobile/WordPress-iOS/blob/2f9a2100ca69d8f455acec47a1bbd6cbc5084546/WordPress/WordPressTest/AccountServiceRxTests.swift#L7
notifyAccountChange();
} else {
dispatch_async(dispatch_get_main_queue(), notifyAccountChange);
}
}

/**
Expand Down
63 changes: 19 additions & 44 deletions WordPress/Classes/Services/AccountSettingsService.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Foundation
import RxCocoa
import RxSwift

let AccountSettingsServiceChangeSaveFailedNotification = "AccountSettingsServiceChangeSaveFailed"

Expand Down Expand Up @@ -28,13 +30,6 @@ struct AccountSettingsService {
})
}

func subscribeSettings(next: AccountSettings? -> Void) -> AccountSettingsSubscription {
return AccountSettingsSubscription(userID: userID, context: context, changed: { (managedSettings) -> Void in
let settings = managedSettings.map({ AccountSettings(managed: $0) })
next(settings)
})
}

func saveChange(change: AccountSettingsChange) {
guard let reverse = try? applyChange(change) else {
return
Expand All @@ -53,8 +48,18 @@ struct AccountSettingsService {
}
}

var settingsObserver: Observable<AccountSettings?> {
let notificationCenter = NSNotificationCenter.defaultCenter()
let notificationObserver = notificationCenter.rx_notification(NSManagedObjectContextDidSaveNotification, object: context)
return notificationObserver.map(getSettings).startWith(getSettings())
}

private func getSettings(_: Any? = nil) -> AccountSettings? {
return accountSettingsWithID(self.userID)
}

private func applyChange(change: AccountSettingsChange) throws -> AccountSettingsChange {
guard let settings = accountSettingsWithID(userID) else {
guard let settings = managedAccountSettingsWithID(userID) else {
DDLogSwift.logError("Tried to apply a change to nonexistent settings (ID: \(userID)")
throw Errors.NotFound
}
Expand All @@ -67,7 +72,7 @@ struct AccountSettingsService {
}

private func updateSettings(settings: AccountSettings) {
if let managedSettings = accountSettingsWithID(userID) {
if let managedSettings = managedAccountSettingsWithID(userID) {
managedSettings.updateWith(settings)
} else {
createAccountSettings(userID, settings: settings)
Expand All @@ -76,7 +81,11 @@ struct AccountSettingsService {
ContextManager.sharedInstance().saveContext(context)
}

private func accountSettingsWithID(userID: Int) -> ManagedAccountSettings? {
private func accountSettingsWithID(userID: Int) -> AccountSettings? {
return managedAccountSettingsWithID(userID).map(AccountSettings.init)
}

private func managedAccountSettingsWithID(userID: Int) -> ManagedAccountSettings? {
let request = NSFetchRequest(entityName: ManagedAccountSettings.entityName)
request.predicate = NSPredicate(format: "account.userID = %d", userID)
request.fetchLimit = 1
Expand All @@ -100,37 +109,3 @@ struct AccountSettingsService {
case NotFound
}
}

class AccountSettingsSubscription {
private var subscription: NSObjectProtocol? = nil

init(userID: Int, context: NSManagedObjectContext, changed: ManagedAccountSettings? -> Void) {
subscription = NSNotificationCenter.defaultCenter().addObserverForName(NSManagedObjectContextDidSaveNotification, object: context, queue: NSOperationQueue.mainQueue()) {
[unowned self]
notification in
// FIXME: Inspect changed objects in notification instead of fetching for performance (@koke 2015-11-23)
let account = self.fetchAccount(userID, context: context)
changed(account)
}

let initial = fetchAccount(userID, context: context)
dispatch_async(dispatch_get_main_queue()) {
changed(initial)
}
}

private func fetchAccount(userID: Int, context: NSManagedObjectContext) -> ManagedAccountSettings? {
let request = NSFetchRequest(entityName: ManagedAccountSettings.entityName)
request.predicate = NSPredicate(format: "account.userID = %d", userID)
request.fetchLimit = 1
let results = (try? context.executeFetchRequest(request) as! [ManagedAccountSettings]) ?? []
return results.first
}

deinit {
if let subscription = subscription {
NSNotificationCenter.defaultCenter().removeObserver(subscription)
}
}
}

6 changes: 3 additions & 3 deletions WordPress/Classes/Utility/ImmuTable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
struct DestructiveButtonRow: ImmuTableRow {
static let cell = ImmuTableCell.Class(UITableViewCell.self)
let title: String
let action: ImmuTableActionType?
let action: ImmuTableAction?

func configureCell(cell: UITableViewCell) {
cell.textLabel?.text = title
Expand Down Expand Up @@ -137,7 +137,7 @@ public protocol ImmuTableRow {
}

*/
var action: ImmuTableActionType? { get }
var action: ImmuTableAction? { get }

/// This method is called when an associated cell needs to be configured.
/// - precondition: You can assume that the passed cell is of the type defined
Expand Down Expand Up @@ -297,7 +297,7 @@ public class ImmuTableViewHandler: NSObject, UITableViewDataSource, UITableViewD
// MARK: - Type aliases


public typealias ImmuTableActionType = (ImmuTableRow) -> Void
public typealias ImmuTableAction = (ImmuTableRow) -> Void


// MARK: - Internal testing helpers
Expand Down
74 changes: 74 additions & 0 deletions WordPress/Classes/Utility/ImmuTableViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import UIKit
import RxSwift
import WordPressShared

typealias ImmuTableRowControllerGenerator = ImmuTableRow -> UIViewController

protocol ImmuTablePresenter: AnyObject {
func push(controllerGenerator: ImmuTableRowControllerGenerator) -> ImmuTableAction
}

extension ImmuTablePresenter where Self: UIViewController {
func push(controllerGenerator: ImmuTableRowControllerGenerator) -> ImmuTableAction {
return {
[unowned self] in
let controller = controllerGenerator($0)
self.navigationController?.pushViewController(controller, animated: true)
}
}
}

/// Generic view controller to present ImmuTable-based tables
///
/// Instead of subclassing the view controller, this is designed to be used from
/// a "controller" class that handles all the logic, and updates the view
/// controller, like you would update a view.
final class ImmuTableViewController: UITableViewController, ImmuTablePresenter {
private lazy var handler: ImmuTableViewHandler = {
return ImmuTableViewHandler(takeOver: self)
}()

private var willAppearSubject: PublishSubject<Void> {
return willAppear as! PublishSubject<Void>
}

// MARK: - Table View Controller

init() {
super.init(style: .Grouped)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()

WPStyleGuide.resetReadableMarginsForTableView(tableView)
WPStyleGuide.configureColorsForView(view, andTableView: tableView)
}

override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
willAppearSubject.onNext()
}

// MARK: - Inputs

/// Sets the view model for the view controller
func bindViewModel(viewModel: ImmuTable) {
handler.viewModel = viewModel
}

/// Registers custom rows
/// - seealso: ImmuTable.registerRows(_:tableView)
func registerRows(rows: [ImmuTableRow.Type]) {
ImmuTable.registerRows(rows, tableView: tableView)
}

// MARK: - Outputs

/// Emits a value every time viewWillAppear is called
let willAppear: Observable<Void> = PublishSubject()
}
Loading