blob: 493993c58403956d3324a8f9799a34551fc1736e [file] [log] [blame]
Loïc Siretfcb4ca62016-09-21 17:12:09 -04001/*
2 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
3 * Author: Loïc Siret <loic.siret@savoirfairelinux.com>
Kateryna Kostiukecaa3952018-07-13 16:00:34 -04004 * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
Loïc Siretfcb4ca62016-09-21 17:12:09 -04005 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21#import "RingWizardNewAccountVC.h"
22
23
24//Cocoa
Loïc Siretfcb4ca62016-09-21 17:12:09 -040025#import <Quartz/Quartz.h>
Kateryna Kostiuk91b44e32018-09-28 17:08:02 -040026#import <AVFoundation/AVFoundation.h>
Loïc Siretfcb4ca62016-09-21 17:12:09 -040027
28//Qt
29#import <QUrl>
30#import <QPixmap>
Kateryna Kostiukecaa3952018-07-13 16:00:34 -040031#import <QSize>
32#import <QtMacExtras/qmacfunctions.h>
Loïc Siretfcb4ca62016-09-21 17:12:09 -040033
34//LRC
Kateryna Kostiukecaa3952018-07-13 16:00:34 -040035#import <api/lrc.h>
36#import <api/newaccountmodel.h>
Loïc Siretfcb4ca62016-09-21 17:12:09 -040037#import <account.h>
Kateryna Kostiukecaa3952018-07-13 16:00:34 -040038#import <interfaces/pixmapmanipulatori.h>
Loïc Siretfcb4ca62016-09-21 17:12:09 -040039
Loïc Siretfcb4ca62016-09-21 17:12:09 -040040#import "Constants.h"
41#import "views/NSImage+Extensions.h"
42#import "views/NSColor+RingTheme.h"
Andreas Traczyk92c101e2018-07-03 14:43:53 -040043#import "utils.h"
Loïc Siretfcb4ca62016-09-21 17:12:09 -040044
45@interface RingWizardNewAccountVC ()
46@end
47
48@implementation RingWizardNewAccountVC
49{
Loïc Siret3652cfb2016-10-27 10:12:07 -040050 __unsafe_unretained IBOutlet NSView* loadingView;
51 __unsafe_unretained IBOutlet NSView* creationView;
52
Loïc Siretfcb4ca62016-09-21 17:12:09 -040053 __unsafe_unretained IBOutlet NSButton* photoView;
Alexandre Lision882289b2016-10-31 16:10:39 -040054 __unsafe_unretained IBOutlet NSTextField* displayNameField;
55 __unsafe_unretained IBOutlet NSTextField* registeredNameField;
Loïc Siretfcb4ca62016-09-21 17:12:09 -040056 __unsafe_unretained IBOutlet NSSecureTextField* passwordField;
Loïc Siret3652cfb2016-10-27 10:12:07 -040057 __unsafe_unretained IBOutlet NSSecureTextField* passwordRepeatField;
Loïc Siret3652cfb2016-10-27 10:12:07 -040058 __unsafe_unretained IBOutlet NSImageView* passwordCheck;
59 __unsafe_unretained IBOutlet NSImageView* passwordRepeatCheck;
Kateryna Kostiuk87ae2bf2018-05-04 13:46:17 -040060 __unsafe_unretained IBOutlet NSImageView* addProfilePhotoImage;
Loïc Siret3652cfb2016-10-27 10:12:07 -040061
62 __unsafe_unretained IBOutlet NSProgressIndicator* progressBar;
Alexandre Lision882289b2016-10-31 16:10:39 -040063
Alexandre Lision882289b2016-10-31 16:10:39 -040064 __unsafe_unretained IBOutlet NSImageView* ivLookupResult;
65 __unsafe_unretained IBOutlet NSProgressIndicator* indicatorLookupResult;
66
67 __unsafe_unretained IBOutlet NSPopover* helpBlockchainContainer;
68 __unsafe_unretained IBOutlet NSPopover* helpPasswordContainer;
Kateryna Kostiuk23222fe2018-11-16 14:28:02 -050069 __unsafe_unretained IBOutlet NSLayoutConstraint* buttonTopConstraint;
70 __unsafe_unretained IBOutlet NSBox* passwordBox;
71 __unsafe_unretained IBOutlet NSButton* passwordButton;
Alexandre Lision882289b2016-10-31 16:10:39 -040072
Alexandre Lision882289b2016-10-31 16:10:39 -040073 QMetaObject::Connection registeredNameFound;
Kateryna Kostiukecaa3952018-07-13 16:00:34 -040074 QMetaObject::Connection accountCreated;
75 QMetaObject::Connection accountRemoved;
Alexandre Lision882289b2016-10-31 16:10:39 -040076
77 BOOL lookupQueued;
78 NSString* usernameWaitingForLookupResult;
Kateryna Kostiukecaa3952018-07-13 16:00:34 -040079 std::string accountToCreate;
Loïc Siretfcb4ca62016-09-21 17:12:09 -040080}
81
Alexandre Lision882289b2016-10-31 16:10:39 -040082NSInteger const DISPLAY_NAME_TAG = 1;
83NSInteger const BLOCKCHAIN_NAME_TAG = 2;
Loïc Siretfcb4ca62016-09-21 17:12:09 -040084
Loïc Siret3652cfb2016-10-27 10:12:07 -040085//ERROR CODE for textfields validations
86NSInteger const ERROR_PASSWORD_TOO_SHORT = -1;
87NSInteger const ERROR_REPEAT_MISMATCH = -2;
88
Kateryna Kostiukecaa3952018-07-13 16:00:34 -040089@synthesize accountModel;
90
Kateryna Kostiuk23222fe2018-11-16 14:28:02 -050091#define heightWithCancelAndAdvanced 468
92#define defaultHeight 408
93
Kateryna Kostiukecaa3952018-07-13 16:00:34 -040094-(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil accountmodel:(lrc::api::NewAccountModel*) accountModel {
95 if (self = [self initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
96 {
97 self.accountModel = accountModel;
98 }
99 return self;
100}
101
Loïc Siret3652cfb2016-10-27 10:12:07 -0400102- (BOOL)produceError:(NSError**)error withCode:(NSInteger)code andMessage:(NSString*)message
103{
104 if (error != NULL){
105 NSDictionary *errorDetail = @{NSLocalizedDescriptionKey: message};
106 *error = [NSError errorWithDomain:@"Input" code:code userInfo:errorDetail];
107 }
108 return NO;
109}
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400110
Alexandre Lision882289b2016-10-31 16:10:39 -0400111- (IBAction)showBlockchainHelp:(id)sender
112{
113 [helpBlockchainContainer showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxYEdge];
114}
115
116- (IBAction)showPasswordHelp:(id)sender
117{
118 [helpPasswordContainer showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxYEdge];
119}
120
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400121- (void)show
122{
Alexandre Lision882289b2016-10-31 16:10:39 -0400123 [displayNameField setTag:DISPLAY_NAME_TAG];
124 [registeredNameField setTag:BLOCKCHAIN_NAME_TAG];
125 [displayNameField setStringValue: NSFullUserName()];
126 [self controlTextDidChange:[NSNotification notificationWithName:@"PlaceHolder" object:displayNameField]];
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400127 [photoView setWantsLayer: YES];
128 photoView.layer.cornerRadius = photoView.frame.size.width / 2;
129 photoView.layer.masksToBounds = YES;
Alexandre Lisionc2ad6392016-11-08 11:20:34 -0500130 self.signUpBlockchainState = YES;
131 [self toggleSignupRing:nil];
Kateryna Kostiuk87ae2bf2018-05-04 13:46:17 -0400132 [addProfilePhotoImage setWantsLayer: YES];
133 [photoView setBordered:YES];
Kateryna Kostiuk23222fe2018-11-16 14:28:02 -0500134 [passwordButton setState: NSControlStateValueOff];
135 NSRect viewFrame = creationView.frame;
136 viewFrame.size.height = defaultHeight;
137 creationView.frame = viewFrame;
138
139 buttonTopConstraint.constant = 25;
140 [passwordBox setHidden: YES];
141 self.registeredName = @"";
142 self.password = @"";
143 self.repeatPassword = @"";
Loïc Siret3652cfb2016-10-27 10:12:07 -0400144
145 [self display:creationView];
146}
147
148- (void)removeSubviews
149{
150 while ([self.view.subviews count] > 0){
151 [[self.view.subviews firstObject] removeFromSuperview];
152 }
153}
154
155- (void)display:(NSView *)view
156{
157 [self.delegate showView:view];
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400158}
159
160- (IBAction)editPhoto:(id)sender
161{
Loïc Siret3652cfb2016-10-27 10:12:07 -0400162 auto pictureTaker = [IKPictureTaker pictureTaker];
Kateryna Kostiuk91b44e32018-09-28 17:08:02 -0400163 if (@available(macOS 10.14, *)) {
164 AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
165 if(authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied)
166 {
167 [pictureTaker setValue:0 forKey:IKPictureTakerAllowsVideoCaptureKey];
168 }
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400169
Kateryna Kostiuk91b44e32018-09-28 17:08:02 -0400170 if(authStatus == AVAuthorizationStatusNotDetermined)
171 {
172 [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
173 if(!granted){
174 [pictureTaker setValue:0 forKey:IKPictureTakerAllowsVideoCaptureKey];
175 }
176 }];
177 }
178 }
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400179 [pictureTaker beginPictureTakerSheetForWindow:[self.delegate window]
180 withDelegate:self
181 didEndSelector:@selector(pictureTakerDidEnd:returnCode:contextInfo:)
182 contextInfo:nil];
183
184}
185
186- (void)pictureTakerDidEnd:(IKPictureTaker *) picker
Alexandre Lision882289b2016-10-31 16:10:39 -0400187 returnCode:(NSInteger) code
188 contextInfo:(void*) contextInfo
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400189{
Kateryna Kostiuk256814e2018-09-04 14:47:33 -0400190 //do nothing when editing canceled
191 if (code == 0) {
192 return;
193 }
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400194 if (auto outputImage = [picker outputImage]) {
Kateryna Kostiuk87ae2bf2018-05-04 13:46:17 -0400195 [photoView setBordered:NO];
Kateryna Kostiuk1f8c1252018-07-30 18:18:57 -0400196 auto image = [picker inputImage];
197 CGFloat newSize = MIN(image.size.height, image.size.width);
198 outputImage = [outputImage cropImageToSize:CGSizeMake(newSize, newSize)];
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400199 [photoView setImage:outputImage];
Kateryna Kostiuk87ae2bf2018-05-04 13:46:17 -0400200 [addProfilePhotoImage setHidden:YES];
201 } else if(!photoView.image) {
202 [photoView setBordered:YES];
203 [addProfilePhotoImage setHidden:NO];
Alexandre Lisionc2ad6392016-11-08 11:20:34 -0500204 }
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400205}
206
Loïc Siret3652cfb2016-10-27 10:12:07 -0400207#pragma mark - Input validation
Alexandre Lision882289b2016-10-31 16:10:39 -0400208
Loïc Siret3652cfb2016-10-27 10:12:07 -0400209- (BOOL)isPasswordValid
210{
211 return self.password.length >= 6;
212}
213
214- (BOOL)isRepeatPasswordValid
215{
Anthony Léonard24110e82017-09-15 16:29:11 -0400216 return [self.password isEqualToString:self.repeatPassword] || ([self.password length] == 0 && [self.repeatPassword length] == 0);
Loïc Siret3652cfb2016-10-27 10:12:07 -0400217}
218
219- (BOOL)validateRepeatPassword:(NSError **)error
220{
221 if (!self.isRepeatPasswordValid){
222 return [self produceError:error
223 withCode:ERROR_REPEAT_MISMATCH
224 andMessage:NSLocalizedString(@"Passwords don't match",
Alexandre Lision882289b2016-10-31 16:10:39 -0400225 @"Indication for user")];
Loïc Siret3652cfb2016-10-27 10:12:07 -0400226 }
227 return YES;
228}
229
230- (BOOL)validatePassword:(NSError **)error
231{
232 if (!self.isRepeatPasswordValid){
233 return [self produceError:error
234 withCode:ERROR_PASSWORD_TOO_SHORT
235 andMessage:NSLocalizedString(@"Password is too short",
Alexandre Lision882289b2016-10-31 16:10:39 -0400236 @"Indication for user")];
Loïc Siret3652cfb2016-10-27 10:12:07 -0400237 }
238 return YES;
239}
240
241- (BOOL)validateUserInputPassword:(NSError **)error
242{
243 return [self validatePassword:error] && [self validateRepeatPassword:error];
244}
245
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400246- (IBAction)createRingAccount:(id)sender
247{
Loïc Siret3652cfb2016-10-27 10:12:07 -0400248 NSError *error = nil;
249 if (![self validateUserInputPassword:&error]){
250 NSAlert* alert = [NSAlert alertWithMessageText:[error localizedDescription]
251 defaultButton:NSLocalizedString(@"Revise Input",
252 @"Button title")
253 alternateButton:nil
254 otherButton:nil
255 informativeTextWithFormat:@"%@",error];
256
257 [alert beginSheetModalForWindow:passwordField.window
258 modalDelegate:nil
259 didEndSelector:NULL
260 contextInfo:NULL];
261
262 return;
263 }
264
Kateryna Kostiukecaa3952018-07-13 16:00:34 -0400265 QObject::disconnect(accountCreated);
266 QObject::disconnect(accountRemoved);
267 accountCreated = QObject::connect(self.accountModel,
268 &lrc::api::NewAccountModel::accountAdded,
269 [self] (const std::string& accountID) {
270 if(accountID.compare(accountToCreate) != 0) {
271 return;
272 }
273 QObject::disconnect(accountCreated);
274 QObject::disconnect(accountRemoved);
275 //set account avatar
276 if([photoView image]) {
277 NSImage *avatarImage = [photoView image];
278 auto imageToBytes = QByteArray::fromNSData([avatarImage TIFFRepresentation]).toBase64();
279 std::string imageToString = std::string(imageToBytes.constData(), imageToBytes.length());
280 self.accountModel->setAvatar(accountID, imageToString);
281 }
282 //register username
283 if (self.registeredName && ![self.registeredName isEqualToString:@""]) {
284 NSString *passwordString = self.password ? self.password: @"";
285 NSString *usernameString = self.registeredName;
286 self.accountModel->registerName(accountID, [passwordString UTF8String], [usernameString UTF8String]);
287 }
288
289 [self registerDefaultPreferences];
290 [self.delegate didCreateAccountWithSuccess:YES];
291 });
292 //if account creation failed remove loading view
293 accountRemoved = QObject::connect(self.accountModel,
294 &lrc::api::NewAccountModel::accountRemoved,
295 [self] (const std::string& accountID) {
296 if(accountID.compare(accountToCreate) != 0) {
297 return;
298 }
299 QObject::disconnect(accountCreated);
300 QObject::disconnect(accountRemoved);
301 [self.delegate didCreateAccountWithSuccess:NO];
302 });
Loïc Siret3652cfb2016-10-27 10:12:07 -0400303 [self display:loadingView];
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400304 [progressBar startAnimation:nil];
Loïc Siret3652cfb2016-10-27 10:12:07 -0400305
Kateryna Kostiuk1f8c1252018-07-30 18:18:57 -0400306 accountToCreate = self.accountModel->createNewAccount(lrc::api::profile::Type::RING, [displayNameField.stringValue UTF8String],"",[passwordField.stringValue UTF8String], "");
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400307}
308
309/**
310 * Set default values for preferences
311 */
312- (void)registerDefaultPreferences
313{
Andreas Traczyk92c101e2018-07-03 14:43:53 -0400314 if (!appSandboxed()) {
315 // enable AutoStartup
316 LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
317 if (loginItemsRef == nil) return;
318 CFURLRef appUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
319 LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, appUrl, NULL, NULL);
320 if (itemRef) CFRelease(itemRef);
321 }
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400322
323 // enable Notifications
324 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:Preferences::Notifications];
325}
326
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400327- (IBAction)cancel:(id)sender
328{
329 [self.delegate didCreateAccountWithSuccess:NO];
330}
331
Alexandre Lision882289b2016-10-31 16:10:39 -0400332#pragma mark - UserNameRegistration delegate methods
333
334- (IBAction)toggleSignupRing:(id)sender
335{
336 if (self.withBlockchain) {
337 [self lookupUserName];
338 }
339}
340
Kateryna Kostiuk23222fe2018-11-16 14:28:02 -0500341- (IBAction)togglePasswordButton:(NSButton *)sender
342{
343 NSRect viewFrame = creationView.frame;
344 if([sender state] == NSControlStateValueOn) {
345 viewFrame.size.height = heightWithCancelAndAdvanced;
346 [self.delegate updateFrame: heightWithCancelAndAdvanced];
347 creationView.frame = viewFrame;
348 buttonTopConstraint.constant = 85;
349 [passwordBox setHidden: NO];
350 } else {
351 buttonTopConstraint.priority = 100;
352 viewFrame.size.height = defaultHeight;
353 [self.delegate updateFrame: defaultHeight];
354 creationView.frame = viewFrame;
355 buttonTopConstraint.constant = 25;
356 buttonTopConstraint.priority = 999;
357 [passwordBox setHidden: YES];
358 }
359}
360
Alexandre Lision882289b2016-10-31 16:10:39 -0400361- (BOOL)withBlockchain
362{
363 return self.signUpBlockchainState == NSOnState;
364}
365
366- (BOOL)userNameAvailableORNotBlockchain
367{
368 return !self.withBlockchain || (self.registeredName.length > 0 && self.isUserNameAvailable);
369}
370
371- (void)showLookUpAvailable:(BOOL)available andText:(NSString *)message
372{
373 [ivLookupResult setImage:[NSImage imageNamed:(available?@"ic_action_accept":@"ic_action_cancel")]] ;
374 [ivLookupResult setHidden:NO];
375 [ivLookupResult setToolTip:message];
376}
377
378- (void)onUsernameAvailabilityChangedWithNewAvailability:(BOOL)newAvailability
379{
380 self.isUserNameAvailable = newAvailability;
381}
382
383- (void)hideLookupSpinner
384{
385 [indicatorLookupResult setHidden:YES];
386}
387
388- (void)showLookupSpinner
389{
390 [ivLookupResult setHidden:YES];
391 [indicatorLookupResult setHidden:NO];
392 [indicatorLookupResult startAnimation:nil];
393}
394
395- (BOOL)lookupUserName
396{
397 [self showLookupSpinner];
398 QObject::disconnect(registeredNameFound);
399 registeredNameFound = QObject::connect(
400 &NameDirectory::instance(),
401 &NameDirectory::registeredNameFound,
402 [=] ( const Account* account, NameDirectory::LookupStatus status, const QString& address, const QString& name) {
403 NSLog(@"Name lookup ended");
404 lookupQueued = NO;
405 //If this is the username we are waiting for, we can disconnect.
406 if (name.compare(QString::fromNSString(usernameWaitingForLookupResult)) == 0) {
407 QObject::disconnect(registeredNameFound);
408 } else {
409 //Keep waiting...
410 return;
411 }
412
413 //We may now stop the spinner
414 [self hideLookupSpinner];
415
416 BOOL isAvailable = NO;
417 NSString* message;
418 switch(status)
419 {
420 case NameDirectory::LookupStatus::SUCCESS:
421 {
422 message = NSLocalizedString(@"The entered username is not available",
423 @"Text shown to user when his username is already registered");
424 isAvailable = NO;
425 break;
426 }
427 case NameDirectory::LookupStatus::NOT_FOUND:
428 {
429 message = NSLocalizedString(@"The entered username is available",
430 @"Text shown to user when his username is available to be registered");
431 isAvailable = YES;
432 break;
433 }
434 case NameDirectory::LookupStatus::INVALID_NAME:
435 {
Alexandre Lision5dc5d312016-11-10 10:41:37 -0500436 message = NSLocalizedString(@"The entered username is invalid. It must have at least 3 characters and contain only lowercase alphanumeric characters.",
Alexandre Lision882289b2016-10-31 16:10:39 -0400437 @"Text shown to user when his username is invalid to be registered");
438 isAvailable = NO;
439 break;
440 }
441 case NameDirectory::LookupStatus::ERROR:
442 default:
443 {
444 message = NSLocalizedString(@"Failed to perform lookup",
445 @"Text shown to user when an error occur at registration");
446 isAvailable = NO;
447 break;
448 }
449 }
450 [self showLookUpAvailable:isAvailable andText: message];
451 [self onUsernameAvailabilityChangedWithNewAvailability:isAvailable];
452
453 });
454
455 //Start the lookup in a second so that the UI dosen't seem to freeze
456 BOOL result = NameDirectory::instance().lookupName(nullptr, QString(), QString::fromNSString(usernameWaitingForLookupResult));
457
458}
459
Alexandre Lision5dc5d312016-11-10 10:41:37 -0500460#pragma mark - NSTextFieldDelegate delegate methods
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400461
Alexandre Lision882289b2016-10-31 16:10:39 -0400462- (void)controlTextDidChange:(NSNotification *)notif
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400463{
464 NSTextField* textField = [notif object];
Alexandre Lision882289b2016-10-31 16:10:39 -0400465 if (textField.tag != BLOCKCHAIN_NAME_TAG) {
466 return;
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400467 }
Alexandre Lision882289b2016-10-31 16:10:39 -0400468 NSString* alias = textField.stringValue;
469
470 [self showLookupSpinner];
471 [self onUsernameAvailabilityChangedWithNewAvailability:NO];
472 [NSObject cancelPreviousPerformRequestsWithTarget:self];
473 [self performSelector:@selector(lookUp:) withObject:alias afterDelay:0.5];
474}
475
476- (void) lookUp:(NSString*) name
477{
478 if (self.withBlockchain && !lookupQueued) {
479 usernameWaitingForLookupResult = name;
480 lookupQueued = YES;
481 [self lookupUserName];
482 }
483}
484
485
486+ (NSSet *)keyPathsForValuesAffectingUserNameAvailableORNotBlockchain
487{
488 return [NSSet setWithObjects: NSStringFromSelector(@selector(signUpBlockchainState)),
489 NSStringFromSelector(@selector(isUserNameAvailable)),
490 nil];
491}
492
493+ (NSSet *)keyPathsForValuesAffectingWithBlockchain
494{
495 return [NSSet setWithObjects: NSStringFromSelector(@selector(signUpBlockchainState)),
496 nil];
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400497}
498
Loïc Siret3652cfb2016-10-27 10:12:07 -0400499+ (NSSet *)keyPathsForValuesAffectingIsPasswordValid
500{
501 return [NSSet setWithObjects:@"password", nil];
502}
503
504+ (NSSet *)keyPathsForValuesAffectingIsRepeatPasswordValid
505{
506 return [NSSet setWithObjects:@"password", @"repeatPassword", nil];
507}
Loïc Siretfcb4ca62016-09-21 17:12:09 -0400508@end