blockchain: register name in account settings
- Show field of blockchain server. No edition possible yet since this
feature has not been tested well enough
- Display register name window
Change-Id: I2b19ce83e40de9a6cda0e0ec601516b75c01702b
Tuleap: #1158
diff --git a/src/AccRingVC.mm b/src/AccRingVC.mm
index 4b9dc1d..1705e57 100644
--- a/src/AccRingVC.mm
+++ b/src/AccRingVC.mm
@@ -21,26 +21,34 @@
#import <accountmodel.h>
#import <qitemselectionmodel.h>
-@interface AccRingVC ()
+#import "RegisterNameWC.h"
-@property (assign) IBOutlet NSTextField *aliasTextField;
-@property (assign) IBOutlet NSTextField *bootstrapField;
-@property (assign) IBOutlet NSTextField *hashField;
+@interface AccRingVC () <RegisterNameDelegate>
-@property (assign) IBOutlet NSButton *upnpButton;
-@property (assign) IBOutlet NSButton *autoAnswerButton;
-@property (assign) IBOutlet NSButton *userAgentButton;
-@property (assign) IBOutlet NSTextField *userAgentTextField;
+@property (unsafe_unretained) IBOutlet NSTextField *aliasTextField;
+@property (unsafe_unretained) IBOutlet NSTextField *bootstrapField;
+@property (unsafe_unretained) IBOutlet NSTextField *blockchainField;
+@property (unsafe_unretained) IBOutlet NSTextField *ringIDField;
+@property (unsafe_unretained) IBOutlet NSButton *registerBlockchainNameButton;
+@property (unsafe_unretained) IBOutlet NSTextField *registeredNameField;
+
+@property (unsafe_unretained) IBOutlet NSButton *upnpButton;
+@property (unsafe_unretained) IBOutlet NSButton *autoAnswerButton;
+@property (unsafe_unretained) IBOutlet NSButton *userAgentButton;
+@property (unsafe_unretained) IBOutlet NSTextField *userAgentTextField;
@property (unsafe_unretained) IBOutlet NSButton *allowUnknown;
@property (unsafe_unretained) IBOutlet NSButton *allowHistory;
@property (unsafe_unretained) IBOutlet NSButton *allowContacts;
+@property AbstractLoadingWC* accountModal;
+
@end
@implementation AccRingVC
@synthesize bootstrapField;
-@synthesize hashField;
+@synthesize ringIDField;
@synthesize aliasTextField;
+@synthesize blockchainField;
@synthesize upnpButton;
@synthesize autoAnswerButton;
@synthesize userAgentButton;
@@ -51,6 +59,7 @@
ALIAS = 0,
HOSTNAME,
USERAGENT,
+ BLOCKCHAIN,
};
- (void)awakeFromNib
@@ -59,6 +68,7 @@
[aliasTextField setTag:TagViews::ALIAS];
[userAgentTextField setTag:TagViews::USERAGENT];
[bootstrapField setTag:TagViews::HOSTNAME];
+ [blockchainField setTag:TagViews::BLOCKCHAIN];
QObject::connect(AccountModel::instance().selectionModel(),
&QItemSelectionModel::currentChanged,
@@ -90,13 +100,37 @@
[userAgentTextField setStringValue:account->userAgent().toNSString()];
[bootstrapField setStringValue:account->hostname().toNSString()];
+ [blockchainField setStringValue:account->nameServiceURL().toNSString()];
- if([account->username().toNSString() isEqualToString:@""])
- [hashField setStringValue:NSLocalizedString(@"Reopen account to see your hash",
+ if([account->username().toNSString() isEqualToString:@""]) {
+ [ringIDField setStringValue:NSLocalizedString(@"Reopen account to see your hash",
@"Show advice to user")];
- else
- [hashField setStringValue:account->username().toNSString()];
+ } else {
+ [ringIDField setStringValue:account->username().toNSString()];
+ }
+ [self refreshRegisteredName:account];
+}
+
+- (void) refreshRegisteredName:(Account*) account
+{
+ [self.registerBlockchainNameButton setHidden:!account->registeredName().isEmpty()];
+ [self.registeredNameField setStringValue:account->registeredName().toNSString()];
+}
+
+- (IBAction)startNameRegistration:(id)sender
+{
+ auto registerWC = [[RegisterNameWC alloc] initWithDelegate:self];
+#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_9
+ [self.view.window beginSheet:registerWC.window completionHandler:nil];
+#else
+ [NSApp beginSheet: registerWC.window
+ modalForWindow: self.view.window
+ modalDelegate: self
+ didEndSelector: nil
+ contextInfo: nil];
+#endif
+ self.accountModal = registerWC;
}
- (IBAction)toggleUpnp:(NSButton *)sender {
@@ -146,9 +180,18 @@
case TagViews::USERAGENT:
AccountModel::instance().selectedAccount()->setUserAgent([[textField stringValue] UTF8String]);
break;
+ case TagViews::BLOCKCHAIN:
+ AccountModel::instance().selectedAccount()->setNameServiceURL([[textField stringValue] UTF8String]);
+ break;
default:
break;
}
}
+- (void) didRegisterNameWithSuccess
+{
+ [self.accountModal close];
+ [self refreshRegisteredName:AccountModel::instance().selectedAccount()];
+}
+
@end
diff --git a/src/RegisterNameWC.h b/src/RegisterNameWC.h
new file mode 100644
index 0000000..ae54e29
--- /dev/null
+++ b/src/RegisterNameWC.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 Savoir-faire Linux Inc.
+ * Author: Alexandre Lision <alexandre.lision@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 <Cocoa/Cocoa.h>
+
+#import "AbstractLoadingWC.h"
+#import "LoadingWCDelegate.h"
+
+@protocol RegisterNameDelegate <LoadingWCDelegate>
+
+@optional
+
+- (void) didRegisterNameWithSuccess;
+
+@end
+
+@interface RegisterNameWC : AbstractLoadingWC
+
+- (id)initWithDelegate:(id <LoadingWCDelegate>) del;
+
+@property (nonatomic, weak) NSWindowController <RegisterNameDelegate>* delegate;
+
+/**
+ * KVO with the registeredNameField
+ */
+@property (nonatomic, weak)NSString* registeredName;
+
+/**
+ * KVO with the passwordField
+ */
+@property (nonatomic, weak)NSString* password;
+
+/**
+ * KVO validators for the UI
+ */
+@property (readonly)BOOL isPasswordValid;
+@property (assign)BOOL isUserNameAvailable;
+
+@end
diff --git a/src/RegisterNameWC.mm b/src/RegisterNameWC.mm
new file mode 100644
index 0000000..063018f
--- /dev/null
+++ b/src/RegisterNameWC.mm
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2016 Savoir-faire Linux Inc.
+ * Author: Alexandre Lision <alexandre.lision@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 "RegisterNameWC.h"
+
+
+//Cocoa
+#import <AddressBook/AddressBook.h>
+
+//LRC
+#import <accountmodel.h>
+#import <QItemSelectionModel>
+#import <account.h>
+
+#import "AppDelegate.h"
+
+@interface RegisterNameWC ()
+@end
+
+@implementation RegisterNameWC
+{
+ __unsafe_unretained IBOutlet NSTextField* registeredNameField;
+ __unsafe_unretained IBOutlet NSSecureTextField* passwordField;
+ __unsafe_unretained IBOutlet NSImageView* ivLookupResult;
+ __unsafe_unretained IBOutlet NSProgressIndicator* indicatorLookupResult;
+
+ __unsafe_unretained IBOutlet NSProgressIndicator *registrationProgress;
+
+ QMetaObject::Connection registrationEnded;
+ QMetaObject::Connection registeredNameFound;
+
+ BOOL lookupQueued;
+ NSString* usernameWaitingForLookupResult;
+}
+
+NSInteger const BLOCKCHAIN_NAME_TAG = 2;
+
+- (id)initWithDelegate:(id <LoadingWCDelegate>) del
+{
+ return [self initWithDelegate:del actionCode:0];
+}
+
+- (id)initWithDelegate:(id <RegisterNameDelegate>) del actionCode:(NSInteger) code
+{
+ return [super initWithWindowNibName:@"RegisterNameWindow" delegate:del actionCode:code];
+}
+
+- (void)windowDidLoad
+{
+ [super windowDidLoad];
+ [registeredNameField setTag:BLOCKCHAIN_NAME_TAG];
+ self.registeredName = @"";
+ [ivLookupResult setHidden:YES];
+ [indicatorLookupResult setHidden:YES];
+}
+
+#pragma mark - Input validation
+
+- (BOOL)isPasswordValid
+{
+ return self.password.length >= 6;
+}
+
+#pragma mark - Username validation delegate methods
+
+- (BOOL)userNameAvailable
+{
+ return (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));
+
+}
+
+- (void)controlTextDidChange:(NSNotification *)notif
+{
+ NSTextField* textField = [notif object];
+ if (textField.tag != BLOCKCHAIN_NAME_TAG) {
+ return;
+ }
+ 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 (!lookupQueued) {
+ usernameWaitingForLookupResult = name;
+ lookupQueued = YES;
+ [self lookupUserName];
+ }
+}
+
+#pragma mark - Registration process
+
+- (IBAction)registerUsername:(id)sender
+{
+ [registrationProgress startAnimation:nil];
+ [self showLoading];
+ [self setCallback];
+
+ self.isUserNameAvailable = AccountModel::instance().selectedAccount()->registerName(QString::fromNSString(self.password),
+ QString::fromNSString(self.registeredName));
+ if (!self.isUserNameAvailable) {
+ NSLog(@"Could not initialize registerName operation");
+ QObject::disconnect(registrationEnded);
+ }
+}
+
+- (void)setCallback
+{
+ QObject::disconnect(registrationEnded);
+ registrationEnded = QObject::connect(AccountModel::instance().selectedAccount(),
+ &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 showError];
+ break;
+ case NameDirectory::RegisterNameStatus::SUCCESS:
+ [self.delegate didRegisterNameWithSuccess];
+ break;
+ }
+ });
+}
+
+
++ (NSSet *)keyPathsForValuesAffectingUserNameAvailableORNotBlockchain
+{
+ return [NSSet setWithObjects: NSStringFromSelector(@selector(isUserNameAvailable)), nil];
+}
+
++ (NSSet *)keyPathsForValuesAffectingIsPasswordValid
+{
+ return [NSSet setWithObjects:@"password", nil];
+}
+
+@end