blob: 39633224a36c5da80a3735980e42e0f96cc39f03 [file] [log] [blame]
Kateryna Kostiuka44b2052017-08-11 15:25:05 -04001/*
Sébastien Blin77c737a2019-01-02 17:34:46 -05002 * Copyright (C) 2017-2019 Savoir-faire Linux Inc.
Kateryna Kostiuka44b2052017-08-11 15:25:05 -04003 *
4 * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
Quentin Muretc60c32c2018-12-14 17:30:14 -05005 * Author: Quentin Muret <quentin.muret@savoirfairelinux.com>
Kateryna Kostiuka44b2052017-08-11 15:25:05 -04006 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22import Foundation
23import RxSwift
24
25enum PasswordValidationState {
26 case validated
27 case error (message: String)
28
29 var isValidated: Bool {
30 switch self {
31 case .validated:
32 return true
33 default:
34 return false
35 }
36 }
37
38 var message: String {
39 switch self {
40 case .validated:
41 return ""
42 case .error(let message):
43 return message
44 }
45 }
46}
47
48enum UsernameValidationState {
49 case unknown
50 case available
51 case lookingForAvailibility(message: String)
52 case invalid(message: String)
53 case unavailable(message: String)
54
55 var isAvailable: Bool {
56 switch self {
57 case .available, .unknown:
58 return true
59 default:
60 return false
61 }
62 }
63
64 var message: String {
65 switch self {
66 case .available, .unknown:
67 return ""
68 case .lookingForAvailibility(let message):
69 return message
70 case .invalid(let message):
71 return message
72 case .unavailable(let message):
73 return message
74 }
75 }
76}
77
78enum AccountCreationState {
79 case unknown
80 case started
81 case success
82 case error(error: AccountCreationError)
83
84 var isInProgress: Bool {
85 switch self {
86 case .started:
87 return true
88 default:
89 return false
90 }
91 }
92
93 var message: String {
94 switch self {
95 case .error(let error):
96 return error.localizedDescription
97 default:
98 return ""
99 }
100 }
Kateryna Kostiuk90a90d82019-01-14 13:22:53 -0500101
Kateryna Kostiuk6d5900c2019-01-15 16:46:17 -0500102 static func == (lhs: AccountCreationState, rhs: AccountCreationState) -> Bool {
Kateryna Kostiuk90a90d82019-01-14 13:22:53 -0500103 switch (lhs, rhs) {
104 case (.unknown, .unknown):
105 return true
106 case (.started, .started):
107 return true
108 case (.success, .success):
109 return true
Kateryna Kostiuk6d5900c2019-01-15 16:46:17 -0500110 case ( .error, .error):
Kateryna Kostiuk90a90d82019-01-14 13:22:53 -0500111 return true
112 default:
113 return false
114 }
115 }
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400116}
117
118enum AccountCreationError: Error {
119 case generic
120 case network
121 case unknown
Kateryna Kostiuk85e4ab32017-10-25 11:16:32 -0400122 case linkError
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400123}
124
125extension AccountCreationError: LocalizedError {
126
127 var title: String {
128 switch self {
129 case .generic:
130 return L10n.Alerts.accountCannotBeFoundTitle
131 case .network:
132 return L10n.Alerts.accountNoNetworkTitle
Kateryna Kostiuk85e4ab32017-10-25 11:16:32 -0400133 case .linkError:
134 return L10n.Alerts.accountCannotBeFoundTitle
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400135 default:
136 return L10n.Alerts.accountDefaultErrorTitle
137 }
138 }
139
140 var message: String {
141 switch self {
142 case .generic:
143 return L10n.Alerts.accountDefaultErrorMessage
144 case .network:
145 return L10n.Alerts.accountNoNetworkMessage
Kateryna Kostiuk85e4ab32017-10-25 11:16:32 -0400146 case .linkError:
147 return L10n.Alerts.accountCannotBeFoundMessage
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400148 default:
149 return L10n.Alerts.accountDefaultErrorMessage
150 }
151 }
152}
153
154// swiftlint:disable opening_brace
155// swiftlint:disable closure_parameter_position
156class CreateAccountViewModel: Stateable, ViewModel {
157
158 // MARK: - Rx Stateable
159 private let stateSubject = PublishSubject<State>()
160 lazy var state: Observable<State> = {
161 return self.stateSubject.asObservable()
162 }()
163
164 private let disposeBag = DisposeBag()
165
166 // MARK: L10n
Kateryna Kostiukd752c4e2018-12-07 17:48:03 -0500167 let createAccountTitle = L10n.CreateAccount.createAccountFormTitle
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400168 let createAccountButton = L10n.Welcome.createAccount
Kateryna Kostiukd752c4e2018-12-07 17:48:03 -0500169 let usernameTitle = L10n.CreateAccount.enterNewUsernamePlaceholder
170 let passwordTitle = L10n.CreateAccount.newPasswordPlaceholder
171 let confirmPasswordTitle = L10n.CreateAccount.repeatPasswordPlaceholder
qmuret2ff3a262018-11-26 16:05:13 -0500172 let registerAUserNameTitle = L10n.CreateAccount.registerAUsername
173 let chooseAPasswordTitle = L10n.CreateAccount.chooseAPassword
174 let passwordInfoTitle = L10n.CreateAccount.passwordInformation
175 let enableNotificationsTitle = L10n.CreateAccount.enableNotifications
176 let recommendedTitle = L10n.CreateAccount.recommended
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400177
178 // MARK: - Low level services
179 private let accountService: AccountsService
180 private let nameService: NameService
181
182 // MARK: - Rx Variables for UI binding
183 private let accountCreationState = Variable<AccountCreationState>(.unknown)
184 lazy var createState: Observable<AccountCreationState> = {
185 return self.accountCreationState.asObservable()
186 }()
187 let username = Variable<String>("")
188 let password = Variable<String>("")
189 let confirmPassword = Variable<String>("")
190 let registerUsername = Variable<Bool>(true)
qmuret2ff3a262018-11-26 16:05:13 -0500191 let notificationSwitch = Variable<Bool>(true)
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400192 lazy var passwordValidationState: Observable<PasswordValidationState> = {
193 return Observable.combineLatest(self.password.asObservable(), self.confirmPassword.asObservable())
194 { (password: String, confirmPassword: String) -> PasswordValidationState in
Andreas Traczykfea7a912017-11-01 16:55:21 -0400195 if password.isEmpty && confirmPassword.isEmpty {
196 return .validated
197 }
198
Kateryna Kostiuk6d5900c2019-01-15 16:46:17 -0500199 if password.count < 6 {
Kateryna Kostiukd752c4e2018-12-07 17:48:03 -0500200 return .error(message: L10n.CreateAccount.passwordCharactersNumberError)
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400201 }
202
203 if password != confirmPassword {
Kateryna Kostiukd752c4e2018-12-07 17:48:03 -0500204 return .error(message: L10n.CreateAccount.passwordNotMatchingError)
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400205 }
206
207 return .validated
208 }
209 }()
210 lazy var usernameValidationState = Variable<UsernameValidationState>(.unknown)
211 lazy var canAskForAccountCreation: Observable<Bool> = {
212 return Observable.combineLatest(self.passwordValidationState.asObservable(),
213 self.usernameValidationState.asObservable(),
214 self.registerUsername.asObservable(),
Andreas Traczykfea7a912017-11-01 16:55:21 -0400215 self.username.asObservable(),
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400216 self.createState,
217 resultSelector:
218 { ( passwordValidationState: PasswordValidationState,
219 usernameValidationState: UsernameValidationState,
220 registerUsername: Bool,
Andreas Traczykfea7a912017-11-01 16:55:21 -0400221 username: String,
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400222 creationState: AccountCreationState) -> Bool in
223
224 var canAsk = true
225
226 if registerUsername {
Andreas Traczykfea7a912017-11-01 16:55:21 -0400227 canAsk = canAsk && usernameValidationState.isAvailable && !username.isEmpty
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400228 }
229
230 canAsk = canAsk && passwordValidationState.isValidated
231
232 canAsk = canAsk && !creationState.isInProgress
233
234 return canAsk
235 })
236 }()
237
238 required init (with injectionBag: InjectionBag) {
qmuret12173e72018-10-25 16:52:05 -0400239 var isPageDisplayed = false
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400240 self.accountService = injectionBag.accountService
241 self.nameService = injectionBag.nameService
242
243 //Loookup name request observer
244 self.username.asObservable().subscribe(onNext: { [unowned self] username in
245 self.nameService.lookupName(withAccount: "", nameserver: "", name: username)
246 }).disposed(by: disposeBag)
247
248 self.nameService.usernameValidationStatus.asObservable().subscribe(onNext: { [weak self] (status) in
249 switch status {
250 case .lookingUp:
Kateryna Kostiukd752c4e2018-12-07 17:48:03 -0500251 self?.usernameValidationState.value = .lookingForAvailibility(message: L10n.CreateAccount.lookingForUsernameAvailability)
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400252 case .invalid:
Kateryna Kostiukd752c4e2018-12-07 17:48:03 -0500253 self?.usernameValidationState.value = .invalid(message: L10n.CreateAccount.invalidUsername)
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400254 case .alreadyTaken:
Kateryna Kostiukd752c4e2018-12-07 17:48:03 -0500255 self?.usernameValidationState.value = .unavailable(message: L10n.CreateAccount.usernameAlreadyTaken)
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400256 default:
257 self?.usernameValidationState.value = .available
258 }
259 }).disposed(by: self.disposeBag)
260
261 //Name registration observer
262 self.accountService
263 .sharedResponseStream
264 .filter({ [unowned self] (event) in
265 return event.eventType == ServiceEventType.registrationStateChanged &&
266 event.getEventInput(ServiceEventInput.registrationState) == Unregistered &&
267 self.registerUsername.value
268 })
269 .subscribe(onNext: { [unowned self] _ in
270
271 //Launch the process of name registration
272 if let currentAccountId = self.accountService.currentAccount?.id {
273 self.nameService.registerName(withAccount: currentAccountId,
274 password: self.password.value,
275 name: self.username.value)
276 }
277 })
278 .disposed(by: disposeBag)
279
280 //Account creation state observer
281 self.accountService
282 .sharedResponseStream
283 .subscribe(onNext: { [unowned self] event in
284 if event.getEventInput(ServiceEventInput.registrationState) == Unregistered {
285 self.accountCreationState.value = .success
qmuret12173e72018-10-25 16:52:05 -0400286 if !isPageDisplayed {
287 DispatchQueue.main.async {
288 self.stateSubject.onNext(WalkthroughState.accountCreated)
289 }
290 isPageDisplayed = true
291 }
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400292 } else if event.getEventInput(ServiceEventInput.registrationState) == ErrorGeneric {
293 self.accountCreationState.value = .error(error: AccountCreationError.generic)
294 } else if event.getEventInput(ServiceEventInput.registrationState) == ErrorNetwork {
295 self.accountCreationState.value = .error(error: AccountCreationError.network)
296 }
297 }, onError: { [unowned self] _ in
298 self.accountCreationState.value = .error(error: AccountCreationError.unknown)
299 }).disposed(by: disposeBag)
300
301 }
302
303 func createAccount() {
304 self.accountCreationState.value = .started
305 self.accountService.addRingAccount(withUsername: self.username.value,
qmuret2ff3a262018-11-26 16:05:13 -0500306 password: self.password.value, enable: self.notificationSwitch.value)
307 self.enablePushNotifications(enable: self.notificationSwitch.value)
308 }
309
310 func enablePushNotifications(enable: Bool) {
Kateryna Kostiuk90a90d82019-01-14 13:22:53 -0500311 if !enable {
qmuret2ff3a262018-11-26 16:05:13 -0500312 return
313 }
Kateryna Kostiuk90a90d82019-01-14 13:22:53 -0500314 NotificationCenter.default.post(name: NSNotification.Name(rawValue: NotificationName.enablePushNotifications.rawValue), object: nil)
Kateryna Kostiuka44b2052017-08-11 15:25:05 -0400315 }
316}