blockchain: register name at account creation
- Add signup with Blockchain checkbox on new Ring Account view
- Add indicator next to Username field to check availability
Tuleap: #1158
Change-Id: I52ba5fa32068c39f9059a6f4307c2ead89d4526a
diff --git a/src/RingWizardNewAccountVC.mm b/src/RingWizardNewAccountVC.mm
index 254e544..9d0929b 100644
--- a/src/RingWizardNewAccountVC.mm
+++ b/src/RingWizardNewAccountVC.mm
@@ -53,23 +53,34 @@
__unsafe_unretained IBOutlet NSView* creationView;
__unsafe_unretained IBOutlet NSButton* photoView;
- __unsafe_unretained IBOutlet NSTextField* nicknameField;
+ __unsafe_unretained IBOutlet NSTextField* displayNameField;
+ __unsafe_unretained IBOutlet NSTextField* registeredNameField;
__unsafe_unretained IBOutlet NSSecureTextField* passwordField;
__unsafe_unretained IBOutlet NSSecureTextField* passwordRepeatField;
- __unsafe_unretained IBOutlet NSTextField* passwordLabel;
- __unsafe_unretained IBOutlet NSTextField* passwordRepeatLabel;
__unsafe_unretained IBOutlet NSImageView* passwordCheck;
__unsafe_unretained IBOutlet NSImageView* passwordRepeatCheck;
- __unsafe_unretained IBOutlet NSButton* createButton;
- __unsafe_unretained IBOutlet NSButton* cancelButton;
__unsafe_unretained IBOutlet NSProgressIndicator* progressBar;
+
+ __unsafe_unretained IBOutlet NSButton* cbSignupRing;
+ __unsafe_unretained IBOutlet NSImageView* ivLookupResult;
+ __unsafe_unretained IBOutlet NSProgressIndicator* indicatorLookupResult;
+
+ __unsafe_unretained IBOutlet NSPopover* helpBlockchainContainer;
+ __unsafe_unretained IBOutlet NSPopover* helpPasswordContainer;
+
Account* accountToCreate;
NSTimer* errorTimer;
QMetaObject::Connection stateChanged;
+ QMetaObject::Connection registrationEnded;
+ QMetaObject::Connection registeredNameFound;
+
+ BOOL lookupQueued;
+ NSString* usernameWaitingForLookupResult;
}
-NSInteger const NICKNAME_TAG = 1;
+NSInteger const DISPLAY_NAME_TAG = 1;
+NSInteger const BLOCKCHAIN_NAME_TAG = 2;
//ERROR CODE for textfields validations
NSInteger const ERROR_PASSWORD_TOO_SHORT = -1;
@@ -85,12 +96,23 @@
return NO;
}
+- (IBAction)showBlockchainHelp:(id)sender
+{
+ [helpBlockchainContainer showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxYEdge];
+}
+
+- (IBAction)showPasswordHelp:(id)sender
+{
+ [helpPasswordContainer showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxYEdge];
+}
+
- (void)show
{
AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
- [nicknameField setTag:NICKNAME_TAG];
- [nicknameField setStringValue:NSFullUserName()];
- [self controlTextDidChange:[NSNotification notificationWithName:@"PlaceHolder" object:nicknameField]];
+ [displayNameField setTag:DISPLAY_NAME_TAG];
+ [registeredNameField setTag:BLOCKCHAIN_NAME_TAG];
+ [displayNameField setStringValue: NSFullUserName()];
+ [self controlTextDidChange:[NSNotification notificationWithName:@"PlaceHolder" object:displayNameField]];
NSData* imgData = [[[ABAddressBook sharedAddressBook] me] imageData];
if (imgData != nil) {
@@ -129,8 +151,8 @@
}
- (void)pictureTakerDidEnd:(IKPictureTaker *) picker
- returnCode:(NSInteger) code
- contextInfo:(void*) contextInfo
+ returnCode:(NSInteger) code
+ contextInfo:(void*) contextInfo
{
if (auto outputImage = [picker outputImage]) {
[photoView setImage:outputImage];
@@ -139,6 +161,7 @@
}
#pragma mark - Input validation
+
- (BOOL)isPasswordValid
{
return self.password.length >= 6;
@@ -155,7 +178,7 @@
return [self produceError:error
withCode:ERROR_REPEAT_MISMATCH
andMessage:NSLocalizedString(@"Passwords don't match",
- @"Indication for user")];
+ @"Indication for user")];
}
return YES;
}
@@ -166,7 +189,7 @@
return [self produceError:error
withCode:ERROR_PASSWORD_TOO_SHORT
andMessage:NSLocalizedString(@"Password is too short",
- @"Indication for user")];
+ @"Indication for user")];
}
return YES;
}
@@ -198,17 +221,17 @@
[self display:loadingView];
[progressBar startAnimation:nil];
-
- if ([self.alias isEqualToString:@""]) {
- self.alias = NSLocalizedString(@"Unknown", @"Name used when user leave field empty");
+ NSString* displayName = displayNameField.stringValue;
+ if ([displayName isEqualToString:@""]) {
+ displayName = NSLocalizedString(@"Unknown", @"Name used when user leave field empty");
}
- accountToCreate = AccountModel::instance().add(QString::fromNSString(self.alias), Account::Protocol::RING);
- accountToCreate->setAlias([self.alias UTF8String]);
- accountToCreate->setDisplayName([self.alias UTF8String]);
+ accountToCreate = AccountModel::instance().add(QString::fromNSString(displayName), Account::Protocol::RING);
+ accountToCreate->setAlias([displayName UTF8String]);
+ accountToCreate->setDisplayName([displayName UTF8String]);
if (auto profile = ProfileModel::instance().selectedProfile()) {
- profile->person()->setFormattedName([self.alias UTF8String]);
+ profile->person()->setFormattedName([displayName UTF8String]);
QPixmap p;
auto smallImg = [NSImage imageResize:[photoView image] newSize:{100,100}];
if (p.loadFromData(QByteArray::fromNSData([smallImg TIFFRepresentation]))) {
@@ -259,28 +282,62 @@
selector:@selector(didCreateFailed) userInfo:nil
repeats:NO];
stateChanged = QObject::connect(&AccountModel::instance(),
- &AccountModel::accountStateChanged,
- [=](Account *account, const Account::RegistrationState state) {
- switch(state){
- case Account::RegistrationState::READY:
- case Account::RegistrationState::TRYING:
- case Account::RegistrationState::UNREGISTERED:{
- accountToCreate<< Account::EditAction::RELOAD;
- QObject::disconnect(stateChanged);
- [self.delegate didCreateAccountWithSuccess:YES];
- break;
- }
- case Account::RegistrationState::ERROR:
- QObject::disconnect(stateChanged);
- [self.delegate didCreateAccountWithSuccess:NO];
- break;
- case Account::RegistrationState::INITIALIZING:
- case Account::RegistrationState::COUNT__:{
- //DO Nothing
- break;
- }
- }
- });
+ &AccountModel::accountStateChanged,
+ [=](Account *account, const Account::RegistrationState state) {
+ switch(state){
+ case Account::RegistrationState::READY:
+ case Account::RegistrationState::TRYING:
+ case Account::RegistrationState::UNREGISTERED:{
+ accountToCreate << Account::EditAction::RELOAD;
+ QObject::disconnect(stateChanged);
+ //try to register username
+ if (self.signUpBlockchainState == NSOnState){
+ [self startNameRegistration:account];
+ } else {
+ [self.delegate didCreateAccountWithSuccess:YES];
+ }
+ break;
+ }
+ case Account::RegistrationState::ERROR:
+ QObject::disconnect(stateChanged);
+ [self.delegate didCreateAccountWithSuccess:NO];
+ break;
+ case Account::RegistrationState::INITIALIZING:
+ case Account::RegistrationState::COUNT__:{
+ //Do Nothing
+ break;
+ }
+ }
+ });
+}
+
+- (void) startNameRegistration:(Account*) account
+{
+ registrationEnded = QObject::connect(account,
+ &Account::nameRegistrationEnded,
+ [=] (NameDirectory::RegisterNameStatus status, const QString& name) {
+ QObject::disconnect(registrationEnded);
+ switch(status) {
+ case NameDirectory::RegisterNameStatus::WRONG_PASSWORD:
+ case NameDirectory::RegisterNameStatus::ALREADY_TAKEN:
+ case NameDirectory::RegisterNameStatus::NETWORK_ERROR: {
+ [self couldNotRegisterUsername];
+ break;
+ }
+ case NameDirectory::RegisterNameStatus::SUCCESS: {
+ break;
+ }
+ }
+
+ [self.delegate didCreateAccountWithSuccess:YES];
+ });
+ self.isUserNameAvailable = account->registerName(QString::fromNSString(self.password),
+ QString::fromNSString(self.registeredName));
+ if (!self.isUserNameAvailable){
+ NSLog(@"Could not initialize registerName operation");
+ QObject::disconnect(registrationEnded);
+ [self.delegate didCreateAccountWithSuccess:YES];
+ }
}
- (void)didCreateFailed
@@ -293,6 +350,119 @@
[self.delegate didCreateAccountWithSuccess:NO];
}
+#pragma mark - UserNameRegistration delegate methods
+
+- (IBAction)toggleSignupRing:(id)sender
+{
+ if (self.withBlockchain) {
+ [self lookupUserName];
+ }
+}
+
+- (void)couldNotRegisterUsername
+{
+ // Do nothing
+}
+
+- (BOOL)withBlockchain
+{
+ return self.signUpBlockchainState == NSOnState;
+}
+
+- (BOOL)userNameAvailableORNotBlockchain
+{
+ return !self.withBlockchain || (self.registeredName.length > 0 && self.isUserNameAvailable);
+}
+
+- (void)showLookUpAvailable:(BOOL)available andText:(NSString *)message
+{
+ [ivLookupResult setImage:[NSImage imageNamed:(available?@"ic_action_accept":@"ic_action_cancel")]] ;
+ [ivLookupResult setHidden:NO];
+ [ivLookupResult setToolTip:message];
+}
+
+- (void)onUsernameAvailabilityChangedWithNewAvailability:(BOOL)newAvailability
+{
+ self.isUserNameAvailable = newAvailability;
+}
+
+- (void)hideLookupSpinner
+{
+ [indicatorLookupResult setHidden:YES];
+}
+
+- (void)showLookupSpinner
+{
+ [ivLookupResult setHidden:YES];
+ [indicatorLookupResult setHidden:NO];
+ [indicatorLookupResult startAnimation:nil];
+}
+
+- (BOOL)lookupUserName
+{
+ [self showLookupSpinner];
+ QObject::disconnect(registeredNameFound);
+ registeredNameFound = QObject::connect(
+ &NameDirectory::instance(),
+ &NameDirectory::registeredNameFound,
+ [=] ( const Account* account, NameDirectory::LookupStatus status, const QString& address, const QString& name) {
+ NSLog(@"Name lookup ended");
+ lookupQueued = NO;
+ //If this is the username we are waiting for, we can disconnect.
+ if (name.compare(QString::fromNSString(usernameWaitingForLookupResult)) == 0) {
+ QObject::disconnect(registeredNameFound);
+ } else {
+ //Keep waiting...
+ return;
+ }
+
+ //We may now stop the spinner
+ [self hideLookupSpinner];
+
+ BOOL isAvailable = NO;
+ NSString* message;
+ switch(status)
+ {
+ case NameDirectory::LookupStatus::SUCCESS:
+ {
+ message = NSLocalizedString(@"The entered username is not available",
+ @"Text shown to user when his username is already registered");
+ isAvailable = NO;
+ break;
+ }
+ case NameDirectory::LookupStatus::NOT_FOUND:
+ {
+ message = NSLocalizedString(@"The entered username is available",
+ @"Text shown to user when his username is available to be registered");
+ isAvailable = YES;
+ break;
+ }
+ case NameDirectory::LookupStatus::INVALID_NAME:
+ {
+ message = NSLocalizedString(@"The entered username is invalid. It must have at leat 3 characters and contains only lowercase alphanumeric characters.",
+ @"Text shown to user when his username is invalid to be registered");
+ isAvailable = NO;
+ break;
+ }
+ case NameDirectory::LookupStatus::ERROR:
+ default:
+ {
+ message = NSLocalizedString(@"Failed to perform lookup",
+ @"Text shown to user when an error occur at registration");
+ isAvailable = NO;
+ break;
+ }
+ }
+ [self showLookUpAvailable:isAvailable andText: message];
+ [self onUsernameAvailabilityChangedWithNewAvailability:isAvailable];
+
+ });
+
+ //Start the lookup in a second so that the UI dosen't seem to freeze
+ BOOL result = NameDirectory::instance().lookupName(nullptr, QString(), QString::fromNSString(usernameWaitingForLookupResult));
+
+}
+
#pragma mark - NSOpenSavePanelDelegate delegate methods
- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError **)outError
@@ -300,15 +470,41 @@
return YES;
}
--(void)controlTextDidChange:(NSNotification *)notif
+- (void)controlTextDidChange:(NSNotification *)notif
{
NSTextField* textField = [notif object];
- // else it is NICKNAME_TAG field
- NSString* alias = textField.stringValue;
- if ([alias isEqualToString:@""]) {
- alias = NSLocalizedString(@"Unknown", @"Name used when user leave field empty");
+ if (textField.tag != BLOCKCHAIN_NAME_TAG) {
+ return;
}
- self.alias = alias;
+ NSString* alias = textField.stringValue;
+
+ [self showLookupSpinner];
+ [self onUsernameAvailabilityChangedWithNewAvailability:NO];
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+ [self performSelector:@selector(lookUp:) withObject:alias afterDelay:0.5];
+}
+
+- (void) lookUp:(NSString*) name
+{
+ if (self.withBlockchain && !lookupQueued) {
+ usernameWaitingForLookupResult = name;
+ lookupQueued = YES;
+ [self lookupUserName];
+ }
+}
+
+
++ (NSSet *)keyPathsForValuesAffectingUserNameAvailableORNotBlockchain
+{
+ return [NSSet setWithObjects: NSStringFromSelector(@selector(signUpBlockchainState)),
+ NSStringFromSelector(@selector(isUserNameAvailable)),
+ nil];
+}
+
++ (NSSet *)keyPathsForValuesAffectingWithBlockchain
+{
+ return [NSSet setWithObjects: NSStringFromSelector(@selector(signUpBlockchainState)),
+ nil];
}
+ (NSSet *)keyPathsForValuesAffectingIsPasswordValid
@@ -320,5 +516,4 @@
{
return [NSSet setWithObjects:@"password", @"repeatPassword", nil];
}
-
@end