multi-device: add ability to export Ring account
This patch adds a Devices panel for Ring accounts.
This panel contains the list of devices linked with this Ring account,
and the possibility to export the Ring account on the DHT to setup a new
device
Change-Id: I7281b03d4376fbfc2d74c4e520b8cd0726b9166d
Tuleap: #959
diff --git a/src/AccDevicesVC.h b/src/AccDevicesVC.h
new file mode 100644
index 0000000..3ae4c2d
--- /dev/null
+++ b/src/AccDevicesVC.h
@@ -0,0 +1,26 @@
+/*
+ * 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 <account.h>
+@interface AccDevicesVC : NSViewController
+
+@property Account* account;
+
+@end
diff --git a/src/AccDevicesVC.mm b/src/AccDevicesVC.mm
new file mode 100644
index 0000000..a712e88
--- /dev/null
+++ b/src/AccDevicesVC.mm
@@ -0,0 +1,134 @@
+/*
+ * 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 "AccDevicesVC.h"
+
+//Qt
+#import <qitemselectionmodel.h>
+
+//LRC
+#import <accountmodel.h>
+#import <ringdevicemodel.h>
+#import <account.h>
+
+#import "QNSTreeController.h"
+#import "ExportPasswordWC.h"
+
+@interface AccDevicesVC () <ExportPasswordDelegate>
+
+@property QNSTreeController* devicesTreeController;
+@property ExportPasswordWC* passwordWC;
+
+@property (unsafe_unretained) IBOutlet NSOutlineView* deviceDetailsView;
+
+@end
+
+@implementation AccDevicesVC
+
+@synthesize passwordWC;
+
+NSInteger const TAG_NAME = 100;
+NSInteger const TAG_DEVICE_IDS = 200;
+
+- (void)awakeFromNib
+{
+ NSLog(@"INIT Devices VC");
+
+ QObject::connect(AccountModel::instance().selectionModel(),
+ &QItemSelectionModel::currentChanged,
+ [=](const QModelIndex ¤t, const QModelIndex &previous) {
+ if(!current.isValid())
+ return;
+ [self loadAccount];
+ });
+}
+
+
+- (void)loadAccount
+{
+ auto account = AccountModel::instance().selectedAccount();
+ self.devicesTreeController = [[QNSTreeController alloc] initWithQModel:(QAbstractItemModel*)account->ringDeviceModel()];
+ [self.devicesTreeController setAvoidsEmptySelection:NO];
+ [self.devicesTreeController setChildrenKeyPath:@"children"];
+
+ [self.deviceDetailsView bind:@"content" toObject:self.devicesTreeController withKeyPath:@"arrangedObjects" options:nil];
+ [self.deviceDetailsView bind:@"sortDescriptors" toObject:self.devicesTreeController withKeyPath:@"sortDescriptors" options:nil];
+ [self.deviceDetailsView bind:@"selectionIndexPaths" toObject:self.devicesTreeController withKeyPath:@"selectionIndexPaths" options:nil];
+}
+
+- (IBAction)startExportOnRing:(id)sender
+{
+ NSButton* btbAdd = (NSButton *) sender;
+
+ self.account = AccountModel::instance().selectedAccount();
+ [self showPasswordPrompt];
+}
+#pragma mark - Export methods
+
+- (void)showPasswordPrompt
+{
+ auto account = AccountModel::instance().selectedAccount();
+ passwordWC = [[ExportPasswordWC alloc] initWithDelegate:self actionCode:1];
+ [passwordWC setAccount: account];
+#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_9
+ [self.view.window beginSheet:passwordWC.window completionHandler:nil];
+#else
+ [NSApp beginSheet: passwordWC.window
+ modalForWindow: self.view.window
+ modalDelegate: self
+ didEndSelector: nil
+ contextInfo: nil];
+#endif
+}
+
+
+#pragma mark - NSOutlineViewDelegate methods
+
+- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
+{
+ return YES;
+}
+
+- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
+{
+ return [outlineView makeViewWithIdentifier:@"HoverRowView" owner:nil];
+}
+
+- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
+{
+ NSTableView* result = [outlineView makeViewWithIdentifier:@"DeviceView" owner:self];
+
+ QModelIndex qIdx = [self.devicesTreeController toQIdx:((NSTreeNode*)item)];
+ if(!qIdx.isValid())
+ return result;
+
+ NSTextField* nameLabel = [result viewWithTag:TAG_NAME];
+ NSTextField* deviceIDLabel = [result viewWithTag:TAG_DEVICE_IDS];
+
+ auto account = AccountModel::instance().selectedAccount();
+
+ NSString* string = account->ringDeviceModel()->data(qIdx,Qt::DisplayRole).toString().toNSString();
+
+ [nameLabel setStringValue:account->alias().toNSString()];
+ [deviceIDLabel setStringValue:string];
+
+ return result;
+}
+
+@end
diff --git a/src/AccountsVC.mm b/src/AccountsVC.mm
index c6d14e5..1557915 100644
--- a/src/AccountsVC.mm
+++ b/src/AccountsVC.mm
@@ -36,6 +36,7 @@
#import "AccAdvancedVC.h"
#import "AccSecurityVC.h"
#import "AccRingVC.h"
+#import "AccDevicesVC.h"
#import "PathPasswordWC.h"
@interface AccountsVC () <PathPasswordDelegate>
@@ -48,6 +49,7 @@
@property (retain) IBOutlet NSTabViewItem *advancedTabItem;
@property (retain) IBOutlet NSTabViewItem *securityTabItem;
@property (retain) IBOutlet NSTabViewItem *ringTabItem;
+@property (retain) IBOutlet NSTabViewItem *ringDevicesTabItem;
@property QNSTreeController *treeController;
@property (assign) IBOutlet NSOutlineView *accountsListView;
@@ -55,6 +57,7 @@
@property (unsafe_unretained) IBOutlet NSButton* exportAccountButton;
@property AccRingVC* ringVC;
+@property AccDevicesVC* devicesVC;
@property AccGeneralVC* generalVC;
@property AccMediaVC* audioVC;
@property AccAdvancedVC* advancedVC;
@@ -71,6 +74,7 @@
@synthesize advancedTabItem;
@synthesize securityTabItem;
@synthesize ringTabItem;
+@synthesize ringDevicesTabItem;
@synthesize accountsListView;
@synthesize accountDetailsView;
@synthesize treeController;
@@ -153,6 +157,11 @@
[[self.ringVC view] setFrame:[self.ringTabItem.view frame]];
[[self.ringVC view] setBounds:[self.ringTabItem.view bounds]];
[self.ringTabItem setView:self.ringVC.view];
+
+ self.devicesVC = [[AccDevicesVC alloc] initWithNibName:@"AccDevices" bundle:nil];
+ [[self.devicesVC view] setFrame:[self.ringDevicesTabItem.view frame]];
+ [[self.devicesVC view] setBounds:[self.ringDevicesTabItem.view bounds]];
+ [self.ringDevicesTabItem setView:self.devicesVC.view];
}
- (IBAction)addAccount:(id)sender {
@@ -193,8 +202,9 @@
}
[configPanels insertTabViewItem:ringTabItem atIndex:0];
- [configPanels insertTabViewItem:mediaTabItem atIndex:1];
- [configPanels insertTabViewItem:advancedTabItem atIndex:2];
+ [configPanels insertTabViewItem:ringDevicesTabItem atIndex:1];
+ [configPanels insertTabViewItem:mediaTabItem atIndex:2];
+ [configPanels insertTabViewItem:advancedTabItem atIndex:3];
}
- (IBAction)exportAccount:(id)sender
diff --git a/src/ExportPasswordWC.h b/src/ExportPasswordWC.h
new file mode 100644
index 0000000..3a6560c
--- /dev/null
+++ b/src/ExportPasswordWC.h
@@ -0,0 +1,45 @@
+/*
+ * 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 <account.h>
+
+#import "LoadingWCDelegate.h"
+#import "AbstractLoadingWC.h"
+
+@protocol ExportPasswordDelegate <LoadingWCDelegate>
+
+@optional
+- (void)didCompleteWithPin:(NSString*) path Password:(NSString*) password;
+- (void)didStartWithPassword:(NSString*) password;
+
+@end
+
+@interface ExportPasswordWC : AbstractLoadingWC
+
+/**
+ * password string contained in passwordField.
+ * This is a KVO method to bind the text with the OK Button
+ * if password.length is > 0, button is enabled, otherwise disabled
+ */
+@property (retain) NSString* password;
+@property (assign) Account* account;
+
+@end
diff --git a/src/ExportPasswordWC.mm b/src/ExportPasswordWC.mm
new file mode 100644
index 0000000..f9b914c
--- /dev/null
+++ b/src/ExportPasswordWC.mm
@@ -0,0 +1,138 @@
+/*
+ * 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 "ExportPasswordWC.h"
+
+//LRC
+#import <account.h>
+
+//Ring
+#import "views/ITProgressIndicator.h"
+@interface ExportPasswordWC() <NSTextFieldDelegate>{
+ __unsafe_unretained IBOutlet NSSecureTextField* passwordField;
+ __unsafe_unretained IBOutlet NSTextField* resultField;
+ __unsafe_unretained IBOutlet NSTextField* errorField;
+ __unsafe_unretained IBOutlet ITProgressIndicator* progressIndicator;
+}
+@end
+
+@implementation ExportPasswordWC {
+ struct {
+ unsigned int didStart:1;
+ unsigned int didComplete:1;
+ } delegateRespondsTo;
+}
+
+@synthesize account;
+QMetaObject::Connection accountConnection;
+
+
+#pragma mark - Initialize
+- (id)initWithDelegate:(id <ExportPasswordDelegate>) del actionCode:(NSInteger) code
+{
+ return [super initWithWindowNibName:@"ExportPasswordWindow" delegate:del actionCode:code];
+}
+
+- (void)windowDidLoad
+{
+ [super windowDidLoad];
+}
+
+- (void)setDelegate:(id <ExportPasswordDelegate>)aDelegate
+{
+ if (super.delegate != aDelegate) {
+ [super setDelegate: aDelegate];
+ delegateRespondsTo.didStart = [aDelegate respondsToSelector:@selector(didStartWithPassword:)];
+ delegateRespondsTo.didComplete = [aDelegate respondsToSelector:@selector(didCompleteWithPin:Password:)];
+ }
+}
+
+- (void)showError:(NSString*) errorMessage
+{
+ [errorField setStringValue:errorMessage];
+ [super showError];
+}
+
+- (void)showLoading
+{
+ [progressIndicator setNumberOfLines:30];
+ [progressIndicator setWidthOfLine:2];
+ [progressIndicator setLengthOfLine:5];
+ [progressIndicator setInnerMargin:20];
+ [super showLoading];
+}
+
+#pragma mark - Events Handlers
+- (IBAction)completeAction:(id)sender
+{
+ // Check to avoid exporting an old account (not supported by daemon)
+ if (account->needsMigration()) {
+ [self showError:NSLocalizedString(@"You have to migrate your account before exporting", @"Error shown to user")];
+ } else {
+ NSString* password = passwordField.stringValue;
+ [self showLoading];
+ QObject::disconnect(accountConnection);
+ accountConnection = QObject::connect(account,
+ &Account::exportOnRingEnded,
+ [=](Account::ExportOnRingStatus status,const QString &pin) {
+ NSLog(@"Export ended!");
+ switch (status) {
+ case Account::ExportOnRingStatus::SUCCESS:{
+ NSString *nsPin = pin.toNSString();
+ NSLog(@"Export ended with Success, pin is %@",nsPin);
+ [resultField setAttributedStringValue:[self formatPinMessage:nsPin]];
+ [self showFinal];
+ }
+ break;
+ case Account::ExportOnRingStatus::WRONG_PASSWORD:{
+ NSLog(@"Export ended with Wrong Password");
+ [self showError:NSLocalizedString(@"Export ended with Wrong Password", @"Error shown to the user" )];
+ }
+ break;
+ case Account::ExportOnRingStatus::NETWORK_ERROR:{
+ NSLog(@"Export ended with NetworkError!");
+ [self showError:NSLocalizedString(@"A network error occured during the export", @"Error shown to the user" )];
+ }
+ break;
+ default:{
+ NSLog(@"Export ended with Unknown status!");
+ [self showError:NSLocalizedString(@"An error occured during the export", @"Error shown to the user" )];
+ }
+ break;
+ }
+ });
+ account->exportOnRing(QString::fromNSString(password));
+ }
+}
+
+//TODO: Move String formatting to a dedicated Utility Classes
+- (NSAttributedString *)formatPinMessage:(NSString*) pin
+{
+ NSMutableAttributedString* hereIsThePin = [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"Your generated pin:","Title shown to user to concat with Pin")];
+ NSMutableAttributedString* thePin = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@" %@\n", pin]];
+ [thePin beginEditing];
+ NSRange range = NSMakeRange(0, [thePin length]);
+ [thePin addAttribute:NSFontAttributeName value:[NSFont fontWithName:@"Helvetica-Bold" size:12.0] range:range];
+ [hereIsThePin appendAttributedString:thePin];
+ NSMutableAttributedString* infos = [[NSMutableAttributedString alloc] initWithString:NSLocalizedString(@"This pin and the account password should be entered on your new device within 5 minutes. On most client, this is done from \"Existing Ring account\" menu. You may generate a new pin at any moment.","Infos on how to use the pin")];
+ [hereIsThePin appendAttributedString:infos];
+
+ return hereIsThePin;
+}
+
+@end