blob: a67e13d1f0e275dcb6ae27034e7dcb9884316db1 [file] [log] [blame]
/*
* Copyright (C) 2016 Savoir-faire Linux Inc.
*
* 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
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import UIKit
import RxCocoa
import RxSwift
import PKHUD
import SwiftyBeaver
fileprivate enum CreateRingAccountCellType {
case registerPublicUsername
case usernameField
case passwordNotice
case newPasswordField
case repeatPasswordField
}
class CreateRingAccountViewController: UITableViewController {
/**
logguer
*/
private let log = SwiftyBeaver.self
var accountViewModel = CreateRingAccountViewModel(withAccountService: AppDelegate.accountService,
nameService: AppDelegate.nameService)
@IBOutlet weak var createAccountButton: DesignableButton!
@IBOutlet weak var createAccountTitleLabel: UILabel!
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
self.registerCells()
self.bindViews()
self.setupUI()
}
func registerCells() {
self.tableView.register(cellType: SwitchCell.self)
self.tableView.register(cellType: TextFieldCell.self)
self.tableView.register(cellType: TextCell.self)
}
/**
Bind all the necessary of this View to its ViewModel.
That allows to build the binding part of the MVVM pattern.
*/
fileprivate func bindViews() {
//Add Account button action
self.createAccountButton
.rx
.tap
.takeUntil(self.rx.deallocated)
.subscribe(onNext: {
self.accountViewModel.createAccount()
})
.disposed(by: self.disposeBag)
//Add Account Registration state
self.accountViewModel.accountCreationState.observeOn(MainScheduler.instance).subscribe(
onNext: { [unowned self] state in
switch state {
case .started:
self.setCreateAccountAsLoading()
case .success:
self.setCreateAccountAsIdle()
self.showDeviceAddedAlert()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "MainStoryboard") as UIViewController
self.dismiss(animated: true, completion: nil)
self.present(vc, animated: true, completion: nil)
default:
return
}
},
onError: { [unowned self] error in
if let error = error as? AccountCreationError {
self.showErrorAlert(error)
}
self.setCreateAccountAsIdle()
}).disposed(by: disposeBag)
//Show or hide user name field
self.accountViewModel.registerUsername.asObservable()
.subscribe(onNext: { [weak self] showUsernameField in
self?.toggleRegisterSwitch(showUsernameField)
}).disposed(by: disposeBag)
//Enables create account button
self.accountViewModel.canCreateAccount
.bind(to: self.createAccountButton.rx.isEnabled)
.disposed(by: disposeBag)
}
/**
Customize the views
*/
fileprivate func setupUI() {
self.tableView.estimatedRowHeight = 44.0
self.tableView.rowHeight = UITableViewAutomaticDimension
self.createAccountTitleLabel.text = L10n.Createaccount.createAccountFormTitle.smartString
}
fileprivate func setCreateAccountAsLoading() {
log.debug("Creating account...")
self.createAccountButton.setTitle(L10n.Createaccount.loading.smartString, for: .normal)
self.createAccountButton.isUserInteractionEnabled = false
HUD.show(.labeledProgress(title: L10n.Createaccount.waitCreateAccountTitle.smartString, subtitle: nil))
}
fileprivate func setCreateAccountAsIdle() {
self.createAccountButton.setTitle(L10n.Welcome.createAccount.smartString, for: .normal)
self.createAccountButton.isUserInteractionEnabled = true
HUD.hide()
}
fileprivate func showDeviceAddedAlert() {
HUD.flash(.labeledSuccess(title: L10n.Alerts.accountAddedTitle.smartString, subtitle: nil), delay: Durations.alertFlashDuration.value)
}
fileprivate func showErrorAlert(_ error: AccountCreationError) {
let alert = UIAlertController.init(title: error.title,
message: error.message,
preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: L10n.Global.ok.smartString, style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
/**
Show or hide the username field cell in function of the switch state
*/
func toggleRegisterSwitch(_ show: Bool) {
let usernameFieldCellIndex = 1
if show && !cells.contains(.usernameField) {
self.cells.insert(.usernameField, at: usernameFieldCellIndex)
self.tableView.insertRows(at: [IndexPath(row: usernameFieldCellIndex, section: 0)],
with: .automatic)
} else if !show && cells.contains(.usernameField) {
self.cells.remove(at: usernameFieldCellIndex)
self.tableView.deleteRows(at: [IndexPath(row: usernameFieldCellIndex, section: 0)],
with: .automatic)
}
}
// MARK: TableView datasource
fileprivate var cells: [CreateRingAccountCellType] = [.registerPublicUsername,
.passwordNotice,
.newPasswordField,
.repeatPasswordField]
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cells.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let currentCellType = cells[indexPath.row]
if currentCellType == .registerPublicUsername {
let cell = tableView.dequeueReusableCell(for: indexPath, cellType: SwitchCell.self)
cell.titleLabel.text = L10n.Createaccount.registerPublicUsername.smartString
cell.titleLabel.textColor = .white
cell.registerSwitch.rx.value.bind(to: self.accountViewModel.registerUsername).disposed(by: disposeBag)
return cell
} else if currentCellType == .usernameField {
let cell = tableView.dequeueReusableCell(for: indexPath, cellType: TextFieldCell.self)
cell.textField.isSecureTextEntry = false
cell.textField.placeholder = L10n.Createaccount.enterNewUsernamePlaceholder.smartString
//Binds the username field value to the ViewModel
cell.textField.rx.text.orEmpty
.throttle(Durations.textFieldThrottlingDuration.value, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.bind(to: self.accountViewModel.username)
.disposed(by: disposeBag)
//Switch to new password cell when return button is touched
cell.textField.rx.controlEvent(.editingDidEndOnExit).subscribe(onNext: {
self.switchToCell(withType: .newPasswordField)
}).disposed(by: disposeBag)
self.accountViewModel.usernameValidationMessage.bind(to: cell.errorMessageLabel.rx.text).disposed(by: disposeBag)
return cell
} else if currentCellType == .passwordNotice {
let cell = tableView.dequeueReusableCell(for: indexPath, cellType: TextCell.self)
cell.label.text = L10n.Createaccount.chooseStrongPassword.smartString
return cell
} else if currentCellType == .newPasswordField {
let cell = tableView.dequeueReusableCell(for: indexPath, cellType: TextFieldCell.self)
cell.textField.isSecureTextEntry = true
cell.textField.placeholder = L10n.Createaccount.newPasswordPlaceholder.smartString
cell.errorMessageLabel.text = L10n.Createaccount.passwordCharactersNumberError.smartString
//Binds the password field value to the ViewModel
cell.textField.rx.text.orEmpty.bind(to: self.accountViewModel.password).disposed(by: disposeBag)
//Binds the observer to show the error label if the field is not empty
self.accountViewModel.hidePasswordError.bind(to: cell.errorMessageLabel.rx.isHidden).disposed(by: disposeBag)
//Switch to the repeat pasword cell when return button is touched
cell.textField.rx.controlEvent(.editingDidEndOnExit)
.subscribe(onNext: {
self.switchToCell(withType: .repeatPasswordField)
}).disposed(by: disposeBag)
return cell
} else {
let cell = tableView.dequeueReusableCell(for: indexPath, cellType: TextFieldCell.self)
cell.textField.isSecureTextEntry = true
cell.textField.placeholder = L10n.Createaccount.repeatPasswordPlaceholder.smartString
cell.errorMessageLabel.text = L10n.Createaccount.passwordNotMatchingError.smartString
//Binds the repeat password field value to the ViewModel
cell.textField.rx.text.orEmpty.bind(to: self.accountViewModel.repeatPassword).disposed(by: disposeBag)
//Binds the observer to the text field 'hidden' property
self.accountViewModel.hideRepeatPasswordError.bind(to: cell.errorMessageLabel.rx.isHidden).disposed(by: disposeBag)
return cell
}
}
fileprivate func switchToCell(withType cellType: CreateRingAccountCellType) {
if let cellIndex = self.cells.index(of: cellType) {
if let cell = tableView.cellForRow(at: IndexPath(row: cellIndex, section: 0))
as? TextFieldCell {
cell.textField.becomeFirstResponder()
}
self.tableView.scrollToRow(at: IndexPath(row: cellIndex, section: 0),
at: .bottom, animated: false)
}
}
}