Commit 72a1b8d8 authored by Romain Bertozzi's avatar Romain Bertozzi

wizard: complete add account pipe

This patch completes the pipe of creating a dummy account using
RxSwift and MVVM.

The AccountsService now exposes a public shared responseStream that
observers can listen to.
Callers will call this service's methods directly and wait for any
interesting response on the stream.

Tuleap: #1391
Change-Id: I2eb5abac9eb012892a806ee2fb73df8730edba87
parent 545255d0
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
0273C3081E0C68BF00CF00BA /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0273C3071E0C68BF00CF00BA /* RoundedButton.swift */; }; 0273C3081E0C68BF00CF00BA /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0273C3071E0C68BF00CF00BA /* RoundedButton.swift */; };
02AED8191DD4C4B100F740BA /* librestbed.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 02AED8181DD4C4B100F740BA /* librestbed.a */; }; 02AED8191DD4C4B100F740BA /* librestbed.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 02AED8181DD4C4B100F740BA /* librestbed.a */; };
02B22DFC1DF755BB000358C9 /* AccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B22DFA1DF755BB000358C9 /* AccountModel.swift */; }; 02B22DFC1DF755BB000358C9 /* AccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B22DFA1DF755BB000358C9 /* AccountModel.swift */; };
02B22DFD1DF755BB000358C9 /* AccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B22DFB1DF755BB000358C9 /* AccountViewModel.swift */; }; 02B22DFD1DF755BB000358C9 /* CreateRingAccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B22DFB1DF755BB000358C9 /* CreateRingAccountViewModel.swift */; };
02B22DFF1DF755DB000358C9 /* AccountsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B22DFE1DF755DB000358C9 /* AccountsService.swift */; }; 02B22DFF1DF755DB000358C9 /* AccountsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B22DFE1DF755DB000358C9 /* AccountsService.swift */; };
02B22E011DF755E5000358C9 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B22E001DF755E5000358C9 /* MainTabBarViewController.swift */; }; 02B22E011DF755E5000358C9 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B22E001DF755E5000358C9 /* MainTabBarViewController.swift */; };
02B22E031DF755F7000358C9 /* WalkthroughStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 02B22E021DF755F7000358C9 /* WalkthroughStoryboard.storyboard */; }; 02B22E031DF755F7000358C9 /* WalkthroughStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 02B22E021DF755F7000358C9 /* WalkthroughStoryboard.storyboard */; };
...@@ -134,7 +134,7 @@ ...@@ -134,7 +134,7 @@
028568301DF610A9003A8D8D /* RingTests-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RingTests-Bridging-Header.h"; sourceTree = "<group>"; }; 028568301DF610A9003A8D8D /* RingTests-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RingTests-Bridging-Header.h"; sourceTree = "<group>"; };
02AED8181DD4C4B100F740BA /* librestbed.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = librestbed.a; path = ../DEPS/x86_64/lib/librestbed.a; sourceTree = "<group>"; }; 02AED8181DD4C4B100F740BA /* librestbed.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = librestbed.a; path = ../DEPS/x86_64/lib/librestbed.a; sourceTree = "<group>"; };
02B22DFA1DF755BB000358C9 /* AccountModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AccountModel.swift; path = Account/AccountModel.swift; sourceTree = "<group>"; }; 02B22DFA1DF755BB000358C9 /* AccountModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AccountModel.swift; path = Account/AccountModel.swift; sourceTree = "<group>"; };
02B22DFB1DF755BB000358C9 /* AccountViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AccountViewModel.swift; path = Account/AccountViewModel.swift; sourceTree = "<group>"; }; 02B22DFB1DF755BB000358C9 /* CreateRingAccountViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CreateRingAccountViewModel.swift; path = Account/CreateRingAccountViewModel.swift; sourceTree = "<group>"; };
02B22DFE1DF755DB000358C9 /* AccountsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AccountsService.swift; path = Services/AccountsService.swift; sourceTree = "<group>"; }; 02B22DFE1DF755DB000358C9 /* AccountsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AccountsService.swift; path = Services/AccountsService.swift; sourceTree = "<group>"; };
02B22E001DF755E5000358C9 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MainTabBarViewController.swift; path = MainTabBar/MainTabBarViewController.swift; sourceTree = "<group>"; }; 02B22E001DF755E5000358C9 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MainTabBarViewController.swift; path = MainTabBar/MainTabBarViewController.swift; sourceTree = "<group>"; };
02B22E021DF755F7000358C9 /* WalkthroughStoryboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = WalkthroughStoryboard.storyboard; path = Walkthrough/WalkthroughStoryboard.storyboard; sourceTree = "<group>"; }; 02B22E021DF755F7000358C9 /* WalkthroughStoryboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = WalkthroughStoryboard.storyboard; path = Walkthrough/WalkthroughStoryboard.storyboard; sourceTree = "<group>"; };
...@@ -365,7 +365,7 @@ ...@@ -365,7 +365,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
02B22DFA1DF755BB000358C9 /* AccountModel.swift */, 02B22DFA1DF755BB000358C9 /* AccountModel.swift */,
02B22DFB1DF755BB000358C9 /* AccountViewModel.swift */, 02B22DFB1DF755BB000358C9 /* CreateRingAccountViewModel.swift */,
); );
name = Account; name = Account;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -719,7 +719,7 @@ ...@@ -719,7 +719,7 @@
02B22DFC1DF755BB000358C9 /* AccountModel.swift in Sources */, 02B22DFC1DF755BB000358C9 /* AccountModel.swift in Sources */,
043866331D22CE8C00E06CE2 /* MeViewController.swift in Sources */, 043866331D22CE8C00E06CE2 /* MeViewController.swift in Sources */,
04399AAE1D1C304300E99CD9 /* Utils.mm in Sources */, 04399AAE1D1C304300E99CD9 /* Utils.mm in Sources */,
02B22DFD1DF755BB000358C9 /* AccountViewModel.swift in Sources */, 02B22DFD1DF755BB000358C9 /* CreateRingAccountViewModel.swift in Sources */,
043999FA1D1C2D9D00E99CD9 /* Ring.xcdatamodeld in Sources */, 043999FA1D1C2D9D00E99CD9 /* Ring.xcdatamodeld in Sources */,
0438663B1D2313B700E06CE2 /* AccountDetailsViewController.swift in Sources */, 0438663B1D2313B700E06CE2 /* AccountDetailsViewController.swift in Sources */,
0273C3081E0C68BF00CF00BA /* RoundedButton.swift in Sources */, 0273C3081E0C68BF00CF00BA /* RoundedButton.swift in Sources */,
......
...@@ -21,44 +21,63 @@ ...@@ -21,44 +21,63 @@
import RxSwift import RxSwift
/** /**
A structure representing the ViewModel (MVVM) of the accounts managed by Ring. A class representing the ViewModel (MVVM) of the accounts managed by Ring.
Its responsabilities: Its responsabilities:
- expose to the Views a public API for its interactions concerning the Accounts, - expose to the Views a public API for its interactions concerning the Accounts,
- react to the Views user events concerning the Accounts (add an account...) - react to the Views user events concerning the Accounts (add an account...)
*/ */
struct AccountViewModel { class CreateRingAccountViewModel {
/** /**
Dispose bag that contains the Disposable objects of the ViewModel, and managing their disposes. Dispose bag that contains the Disposable objects of the ViewModel, and managing their disposes.
*/ */
fileprivate let disposeBag = DisposeBag() fileprivate let disposeBag = DisposeBag()
/**
Retains the currently active stream adding an account.
Useful to dispose it before starting a new one.
*/
fileprivate var addAccountDisposable: Disposable?
/** /**
Create the observers to the streams passed in parameters. Create the observers to the streams passed in parameters.
It will allow this ViewModel to react to other entities' events. It will allow this ViewModel to react to other entities' events.
- Parameter observable: An observable stream to subscribe on. - Parameter observable: An observable stream to subscribe on.
Any observed event on this stream will trigger the action of creating an account. Any observed event on this stream will trigger the action of creating an account.
- Parameter onStart: Closure that will be triggered when the action will begin. - Parameter onStartCallback: Closure that will be triggered when the action will begin.
- Parameter onError: Closure that will be triggered in case of error. - Parameter onSuccessCallback: Closure that will be triggered when the action will succeed.
*/ - Parameter onErrorCallback: Closure that will be triggered in case of error.
*/
func configureAddAccountObservers(observable: Observable<Void>, func configureAddAccountObservers(observable: Observable<Void>,
onStart: ((() -> Void)?), onStartCallback: ((() -> Void)?),
onSuccess: ((() -> Void)?), onSuccessCallback: ((() -> Void)?),
onError: (((Error?) -> Void)?)) { onErrorCallback: (((Error?) -> Void)?)) {
_ = observable _ = observable
.subscribe(onNext: { .subscribe(
if onStart != nil { onNext: { [weak self] in
onStart!() //~ Let the caller know that the action has just begun.
} onStartCallback?()
AccountsService.sharedInstance.addAccount()
}, onError: { (error) in //~ Dispose any previously running stream. There is only one add account action
if onError != nil { //~ simultaneously authorized.
onError!(error) self?.addAccountDisposable?.dispose()
} //~ Subscribe on the AccountsService responseStream to get results.
}, onCompleted: { self?.addAccountDisposable = AccountsService.sharedInstance
//~ Nothing to do. .sharedResponseStream
}, onDisposed: { .subscribe(onNext:{ (event) in
//~ Nothing to do. if event == AccountRxEvent.AccountChanged {
onSuccessCallback?()
}
}, onError: { error in
onErrorCallback?(error)
})
self?.addAccountDisposable?.addDisposableTo((self?.disposeBag)!)
//~ Launch the action.
AccountsService.sharedInstance.addAccount()
},
onError: { (error) in
onErrorCallback?(error)
}) })
.addDisposableTo(disposeBag) .addDisposableTo(disposeBag)
} }
......
...@@ -22,6 +22,15 @@ ...@@ -22,6 +22,15 @@
import RxCocoa import RxCocoa
import RxSwift import RxSwift
/**
Events that can be sent to the response stream
- AccountChanged: the accounts have been changed daemon-side
*/
enum AccountRxEvent {
case AccountChanged
}
class AccountsService: AccountAdapterDelegate { class AccountsService: AccountAdapterDelegate {
// MARK: Private members // MARK: Private members
/** /**
...@@ -38,6 +47,18 @@ class AccountsService: AccountAdapterDelegate { ...@@ -38,6 +47,18 @@ class AccountsService: AccountAdapterDelegate {
*/ */
fileprivate var accountList: Array<AccountModel> fileprivate var accountList: Array<AccountModel>
fileprivate let disposeBag = DisposeBag()
/**
PublishSubject forwarding AccountRxEvent events.
This stream is used strictly inside this service.
External observers should use the public shared responseStream.
- SeeAlso: `AccountRxEvent`
- SeeAlso: `sharedResponseStream`
*/
fileprivate let responseStream = PublishSubject<AccountRxEvent>()
// MARK: - Public members // MARK: - Public members
/** /**
Accounts list public interface. Accounts list public interface.
...@@ -53,15 +74,29 @@ class AccountsService: AccountAdapterDelegate { ...@@ -53,15 +74,29 @@ class AccountsService: AccountAdapterDelegate {
} }
} }
/**
Public shared stream forwarding the events of the responseStream.
External observers must subscribe to this stream to get results.
- SeeAlso: `responseStream`
- SeeAlso: `AccountRxEvent`
*/
var sharedResponseStream: Observable<AccountRxEvent>
// MARK: - Singleton // MARK: - Singleton
static let sharedInstance = AccountsService() static let sharedInstance = AccountsService()
fileprivate init() { fileprivate init() {
accountList = [] self.accountList = []
self.responseStream.addDisposableTo(disposeBag)
//~ Create a shared stream based on the responseStream one.
self.sharedResponseStream = responseStream.share()
//~ Registering to the AccountConfigurationManagerAdaptator with self as delegate in order //~ Registering to the AccountConfigurationManagerAdaptator with self as delegate in order
//~ to receive delegation callbacks. //~ to receive delegation callbacks.
confAdapter.delegate = self self.confAdapter.delegate = self
} }
// MARK: - Methods // MARK: - Methods
...@@ -100,5 +135,6 @@ class AccountsService: AccountAdapterDelegate { ...@@ -100,5 +135,6 @@ class AccountsService: AccountAdapterDelegate {
// MARK: - AccountAdapterDelegate // MARK: - AccountAdapterDelegate
func accountsChanged() { func accountsChanged() {
print("Accounts changed.") print("Accounts changed.")
self.responseStream.onNext(.AccountChanged)
} }
} }
...@@ -25,7 +25,7 @@ import RxSwift ...@@ -25,7 +25,7 @@ import RxSwift
class CreateRingAccountViewController: UIViewController { class CreateRingAccountViewController: UIViewController {
var mAccountViewModel: AccountViewModel = AccountViewModel() var mAccountViewModel = CreateRingAccountViewModel()
@IBOutlet weak var mCreateAccountButton: RoundedButton! @IBOutlet weak var mCreateAccountButton: RoundedButton!
...@@ -48,14 +48,14 @@ class CreateRingAccountViewController: UIViewController { ...@@ -48,14 +48,14 @@ class CreateRingAccountViewController: UIViewController {
mAccountViewModel.configureAddAccountObservers( mAccountViewModel.configureAddAccountObservers(
observable: createAccountObservable, observable: createAccountObservable,
onStart: { [weak self] in onStartCallback: { [weak self] in
self?.setCreateAccountAsLoading() self?.setCreateAccountAsLoading()
}, },
onSuccess: { [weak self] in onSuccessCallback: { [weak self] in
print("Account created.") print("Account created.")
self?.setCreateAccountAsIdle() self?.setCreateAccountAsIdle()
}, },
onError: { [weak self] (error) in onErrorCallback: { [weak self] (error) in
print("Error creating account...") print("Error creating account...")
if error != nil { if error != nil {
print(error!) print(error!)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment