Commit 0f44b05a authored by Romain Bertozzi's avatar Romain Bertozzi

link new device with new accounts service

Change-Id: Idecf7406a7ac59f6738b72d972deae8c333d57c2
parent 1c39f962
......@@ -64,7 +64,6 @@ final class AppCoordinator: Coordinator, StateableResponsive {
self.injectionBag = injectionBag
self.navigationController.setNavigationBarHidden(true, animated: false)
self.prepareMainInterface()
self.stateSubject.subscribe(onNext: { [unowned self] (state) in
guard let state = state as? AppState else { return }
......@@ -74,6 +73,7 @@ final class AppCoordinator: Coordinator, StateableResponsive {
case .needToOnboard:
self.showWalkthrough()
case .allSet:
self.prepareMainInterface()
self.showMainInterface()
}
}).disposed(by: self.disposeBag)
......
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="2dj-eG-xeW">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="2dj-eG-xeW">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13527"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
......
......@@ -2,6 +2,7 @@
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
* Author: Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -18,25 +19,25 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import Foundation
import Reusable
import RxSwift
import PKHUD
class LinkNewDeviceViewController: UIViewController, StoryboardBased, ViewModelBased {
final class LinkNewDeviceViewController: UIViewController, StoryboardBased, ViewModelBased {
@IBOutlet weak var titleLable: UILabel!
@IBOutlet weak var passwordField: UITextField!
@IBOutlet weak var okButton: UIButton!
@IBOutlet weak var cancelButton: UIButton!
@IBOutlet weak var pinLabel: UILabel!
@IBOutlet weak var explanationMessage: UILabel!
@IBOutlet weak var errorMessage: UILabel!
@IBOutlet weak var background: UIImageView!
@IBOutlet weak var containerView: UIView!
@IBOutlet private weak var titleLable: UILabel!
@IBOutlet private weak var passwordField: UITextField!
@IBOutlet private weak var okButton: UIButton!
@IBOutlet private weak var cancelButton: UIButton!
@IBOutlet private weak var pinLabel: UILabel!
@IBOutlet private weak var explanationMessage: UILabel!
@IBOutlet private weak var errorMessage: UILabel!
@IBOutlet private weak var background: UIImageView!
@IBOutlet private weak var containerView: UIView!
var viewModel: LinkNewDeviceViewModel!
let disposeBag = DisposeBag()
private let disposeBag = DisposeBag()
override func viewDidLoad() {
......@@ -46,21 +47,21 @@ class LinkNewDeviceViewController: UIViewController, StoryboardBased, ViewModelB
// initial state
self.viewModel.isInitialState
.bind(to: self.titleLable.rx.isHidden)
.addDisposableTo(self.disposeBag)
.disposed(by: self.disposeBag)
self.viewModel.isInitialState.bind(to: self.passwordField.rx.isHidden)
.addDisposableTo(self.disposeBag)
.disposed(by: self.disposeBag)
self.viewModel.isInitialState.bind(to: self.cancelButton.rx.isHidden)
.addDisposableTo(self.disposeBag)
.disposed(by: self.disposeBag)
// error state
self.viewModel.isErrorState.bind(to: self.errorMessage.rx.isVisible)
.addDisposableTo(self.disposeBag)
.disposed(by: self.disposeBag)
// success state
self.viewModel.isSuccessState
.bind(to: self.explanationMessage.rx.isVisible)
.addDisposableTo(self.disposeBag)
.disposed(by: self.disposeBag)
self.viewModel.isSuccessState
.bind(to: self.pinLabel.rx.isVisible)
.addDisposableTo(self.disposeBag)
.disposed(by: self.disposeBag)
self.viewModel.observableState
.observeOn(MainScheduler.instance)
......@@ -77,7 +78,7 @@ class LinkNewDeviceViewController: UIViewController, StoryboardBased, ViewModelB
default:
break
}
}).addDisposableTo(self.disposeBag)
}).disposed(by: self.disposeBag)
cancelButton.rx.tap.subscribe(onNext: { [unowned self] in
self.dismiss(animated: true, completion: nil)
......@@ -109,4 +110,3 @@ class LinkNewDeviceViewController: UIViewController, StoryboardBased, ViewModelB
self.explanationMessage.text = self.viewModel.explanationMessage
}
}
......@@ -2,6 +2,7 @@
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
* Author: Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -18,7 +19,6 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import Foundation
import RxSwift
import RxDataSources
......@@ -28,23 +28,6 @@ enum ExportAccountResponse: Int {
case networkProblem = 2
}
enum PinError {
case passwordError
case networkError
case defaultError
var description: String {
switch self {
case .passwordError:
return L10n.Linkdevice.passwordError
case .networkError:
return L10n.Linkdevice.networkError
case .defaultError:
return L10n.Linkdevice.defaultError
}
}
}
enum GeneratingPinState {
case initial
......@@ -66,7 +49,6 @@ enum GeneratingPinState {
}
func isStateOfType(type: String) -> Bool {
return self.rawValue == type
}
}
......@@ -108,17 +90,16 @@ class LinkNewDeviceViewModel: ViewModel, Stateable {
}
}().share()
let accountService: AccountsService
private let accountsService: NewAccountsService
let disposeBag = DisposeBag()
private let disposeBag = DisposeBag()
// MARK: L10n
let linkDeviceTitleTitle = L10n.Linkdevice.title
let explanationMessage = L10n.Linkdevice.explanationMessage
required init(with injectionBag: InjectionBag) {
self.accountService = injectionBag.accountService
self.accountsService = injectionBag.newAccountsService
}
func linkDevice(with password: String?) {
......@@ -127,42 +108,21 @@ class LinkNewDeviceViewModel: ViewModel, Stateable {
self.generatingState.value = GeneratingPinState.error(error: PinError.passwordError)
return
}
self.accountService.exportOnRing(withPassword: password).subscribe(onCompleted: {
if let account = self.accountService.currentAccount {
let accountHelper = AccountModelHelper(withAccount: account)
let uri = accountHelper.ringId
self.accountService.sharedResponseStream
.filter({ exportComplitedEvent in
return exportComplitedEvent.eventType == ServiceEventType.exportOnRingEnded
&& exportComplitedEvent.getEventInput(.uri) == uri
})
.subscribe(onNext: { [unowned self] exportComplitedEvent in
if let state: Int = exportComplitedEvent.getEventInput(.state) {
switch state {
case ExportAccountResponse.success.rawValue:
if let pin: String = exportComplitedEvent.getEventInput(.pin) {
self.generatingState.value = GeneratingPinState.success(pin: pin)
} else {
self.generatingState.value = GeneratingPinState.error(error: PinError.defaultError)
}
case ExportAccountResponse.wrongPassword.rawValue:
self.generatingState.value = GeneratingPinState.error(error: PinError.passwordError)
case ExportAccountResponse.networkProblem.rawValue:
self.generatingState.value = GeneratingPinState.error(error: PinError.networkError)
default:
self.generatingState.value = GeneratingPinState.error(error: PinError.defaultError)
}
}
})
.disposed(by: self.disposeBag)
} else {
self.generatingState.value = GeneratingPinState.error(error: PinError.defaultError)
self.accountsService.currentAccount().asObservable()
.flatMap { [unowned self] (account) -> Observable<String> in
return self.accountsService.exportAccountOnRing(account, withPassword: password)
}
})
{ error in
self.generatingState.value = GeneratingPinState.error(error: PinError.passwordError)
}.addDisposableTo(self.disposeBag)
.subscribe(onNext: { [weak self] (pin) in
self?.generatingState.value = GeneratingPinState.success(pin: pin)
}, onError: { [weak self] (error) in
if let pinError = error as? PinError {
self?.generatingState.value = GeneratingPinState.error(error: pinError)
} else {
self?.generatingState.value = GeneratingPinState.error(error: PinError.defaultError)
}
})
.disposed(by: self.disposeBag)
}
func refresh() {
......
......@@ -84,7 +84,7 @@ final class MeViewModel: ViewModel, Stateable {
private let nameService: NameService
private let log = SwiftyBeaver.self
private let accountUsername = Variable<String>("")
lazy var accountUsernameObservable: Observable<String> = {
return self.accountUsername.asObservable()
......@@ -104,6 +104,14 @@ final class MeViewModel: ViewModel, Stateable {
self.accountService = injectionBag.newAccountsService
self.nameService = injectionBag.nameService
self.refresh()
}
func linkDevice() {
self.stateSubject.onNext(MeState.linkNewDevice)
}
func refresh() {
self.accountService.currentAccount()
.do(onNext: { [weak self] (account) in
let accountUsernameKey = ConfigKeyModel(withKey: ConfigKey.accountUsername)
......@@ -125,9 +133,9 @@ final class MeViewModel: ViewModel, Stateable {
} else {
self?.accountSettings.value = [addNewDevice]
}
}, onError: { [weak self] (error) in
self?.accountRingId.value = "No RingId found"
self?.log.error("No RingId found - \(error.localizedDescription)")
}, onError: { [weak self] (error) in
self?.accountRingId.value = "No RingId found"
self?.log.error("No RingId found - \(error.localizedDescription)")
})
.flatMap { (account) -> PrimitiveSequence<SingleTrait, String> in
let registeredNameKey = ConfigKeyModel(withKey: ConfigKey.accountRegisteredName)
......@@ -140,14 +148,10 @@ final class MeViewModel: ViewModel, Stateable {
}
.subscribe(onSuccess: { [weak self] (username) in
self?.accountUsername.value = username
}, onError: { [weak self] (error) in
self?.accountUsername.value = "No username found"
self?.log.error("No username found - \(error.localizedDescription)")
}, onError: { [weak self] (error) in
self?.accountUsername.value = "No username found"
self?.log.error("No username found - \(error.localizedDescription)")
})
.disposed(by: self.disposeBag)
}
func linkDevice() {
self.stateSubject.onNext(MeState.linkNewDevice)
}
}
......@@ -25,7 +25,7 @@ import RxSwift
///
/// - meDetail: user want its account detail
/// -linkDevice: link new device to account
public enum MeState: State {
enum MeState: State {
case meDetail
case linkNewDevice
}
......
......@@ -32,6 +32,27 @@ enum AccountError: Error {
case unknownError
}
enum ExportAccountError: Error {
case unknownError
}
enum PinError: Error {
case passwordError
case networkError
case defaultError
var description: String {
switch self {
case .passwordError:
return L10n.Linkdevice.passwordError
case .networkError:
return L10n.Linkdevice.networkError
case .defaultError:
return L10n.Linkdevice.defaultError
}
}
}
/// The New Accounts Service, with no model duplication from the daemon.
final class NewAccountsService {
......@@ -45,6 +66,8 @@ final class NewAccountsService {
/// Stream for daemon signal, inaccessible from the outside
fileprivate let daemonSignals = PublishSubject<ServiceEvent>()
fileprivate let disposeBag = DisposeBag()
// MARK: - Members
lazy var daemonSignalsObservable: Observable<ServiceEvent> = {
return self.daemonSignals.asObservable()
......@@ -207,6 +230,44 @@ final class NewAccountsService {
})
}
func exportAccountOnRing(_ account: AccountModel, withPassword password: String) -> Observable<String> {
let export = self.exportAccount(account, withPassword: password)
let filteredDaemonSignals = self.daemonSignals.filter { (serviceEvent) -> Bool in
if serviceEvent.getEventInput(ServiceEventInput.state) == ErrorGeneric {
throw AccountCreationError.linkError
} else if serviceEvent.getEventInput(ServiceEventInput.state) == ErrorNetwork {
throw AccountCreationError.network
}
return serviceEvent.eventType == ServiceEventType.exportOnRingEnded
}.asObservable()
return Observable
.combineLatest(export, filteredDaemonSignals) { (_, serviceEvent) -> String in
let accountModelHelper = AccountModelHelper(withAccount: account)
guard let uri = accountModelHelper.ringId, uri == serviceEvent.getEventInput(.uri) else {
throw ExportAccountError.unknownError
}
if let state: Int = serviceEvent.getEventInput(.state) {
switch state {
case ExportAccountResponse.success.rawValue:
guard let pin: String = serviceEvent.getEventInput(.pin) else {
throw PinError.defaultError
}
return pin
case ExportAccountResponse.wrongPassword.rawValue:
throw PinError.passwordError
case ExportAccountResponse.networkProblem.rawValue:
throw PinError.networkError
default:
throw PinError.defaultError
}
}
throw PinError.defaultError
}
}
}
// MARK: - Private daemon wrappers
......@@ -305,6 +366,19 @@ extension NewAccountsService {
return accountModel
}
fileprivate func exportAccount(_ account: AccountModel, withPassword password: String) -> Observable<Void> {
return Observable.create { [unowned self] observable in
let export = self.accountAdapter.export(onRing: account.id, password: password)
if export {
observable.onNext()
observable.onCompleted()
} else {
observable.onError(LinkNewDeviceError.unknownError)
}
return Disposables.create()
}
}
}
// MARK: - AccountAdapterDelegate
......@@ -332,6 +406,25 @@ extension NewAccountsService: AccountAdapterDelegate {
func exportOnRingEnded(for account: String, state: Int, pin: String) {
log.debug("Export on Ring ended.")
self.getAccount(fromAccountId: account)
.subscribe(onSuccess: { [unowned self] (account) in
let accountHelper = AccountModelHelper(withAccount: account)
if let uri = accountHelper.ringId {
var event = ServiceEvent(withEventType: .exportOnRingEnded)
event.addEventInput(.uri, value: uri)
event.addEventInput(.state, value: state)
event.addEventInput(.pin, value: pin)
self.daemonSignals.onNext(event)
}
}, onError: { [unowned self] (error) in
self.log.error("Account not found")
var event = ServiceEvent(withEventType: .exportOnRingEnded)
event.addEventInput(.state, value: state)
event.addEventInput(.pin, value: pin)
self.daemonSignals.onNext(event)
})
.disposed(by: self.disposeBag)
}
}
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