From 45f1f548fbb133f0292b0403bef8c03d057dec0d Mon Sep 17 00:00:00 2001
From: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
Date: Thu, 25 Aug 2016 15:16:17 -0400
Subject: [PATCH] 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
---
 CMakeLists.txt                             |   8 +-
 src/AccDevicesVC.h                         |  26 +++
 src/AccDevicesVC.mm                        | 134 ++++++++++++
 src/AccountsVC.mm                          |  14 +-
 src/ExportPasswordWC.h                     |  45 ++++
 src/ExportPasswordWC.mm                    | 138 ++++++++++++
 ui/Base.lproj/AccDevices.xib               | 123 +++++++++++
 ui/Base.lproj/Accounts.xib                 |  13 +-
 ui/Base.lproj/ExportPasswordWindow.strings |  45 ++++
 ui/Base.lproj/ExportPasswordWindow.xib     | 241 +++++++++++++++++++++
 ui/en.lproj/ExportPasswordWindow.strings   |  39 ++++
 11 files changed, 820 insertions(+), 6 deletions(-)
 create mode 100644 src/AccDevicesVC.h
 create mode 100644 src/AccDevicesVC.mm
 create mode 100644 src/ExportPasswordWC.h
 create mode 100644 src/ExportPasswordWC.mm
 create mode 100644 ui/Base.lproj/AccDevices.xib
 create mode 100644 ui/Base.lproj/ExportPasswordWindow.strings
 create mode 100644 ui/Base.lproj/ExportPasswordWindow.xib
 create mode 100644 ui/en.lproj/ExportPasswordWindow.strings

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 37fc1843..5ce87048 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -140,6 +140,10 @@ SET(ringclient_CONTROLLERS
    src/LoadingWCDelegate.h
    src/AbstractLoadingWC.h
    src/AbstractLoadingWC.mm
+   src/AccDevicesVC.mm
+   src/AccDevicesVC.h
+   src/ExportPasswordWC.mm
+   src/ExportPasswordWC.h
 )
 
 SET(ringclient_BACKENDS
@@ -196,9 +200,11 @@ SET(ringclient_XIBS
    RingWizard
    CertificateWindow
    PathPasswordWindow
+   ExportPasswordWindow
    PersonLinker
    Broker
-   Conversation)
+   Conversation
+   AccDevices)
 
 # Icons
 # This part tells CMake where to find and install the file itself
diff --git a/src/AccDevicesVC.h b/src/AccDevicesVC.h
new file mode 100644
index 00000000..3ae4c2d3
--- /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 00000000..a712e88b
--- /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 &current, 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 c6d14e5c..1557915a 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 @@ NSInteger const TAG_TYPE        =   400;
     [[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 @@ NSInteger const TAG_TYPE        =   400;
     }
 
     [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 00000000..3a6560cd
--- /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 00000000..f9b914c6
--- /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
diff --git a/ui/Base.lproj/AccDevices.xib b/ui/Base.lproj/AccDevices.xib
new file mode 100644
index 00000000..5db46b33
--- /dev/null
+++ b/ui/Base.lproj/AccDevices.xib
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11201" systemVersion="15G1004" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/>
+    </dependencies>
+    <objects>
+        <customObject id="-2" userLabel="File's Owner" customClass="AccDevicesVC">
+            <connections>
+                <outlet property="deviceDetailsView" destination="20b-Ji-RbX" id="A3A-w9-0m5"/>
+                <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
+            </connections>
+        </customObject>
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+        <customView id="Hz6-mo-xeY">
+            <rect key="frame" x="0.0" y="0.0" width="576" height="389"/>
+            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+            <subviews>
+                <scrollView fixedFrame="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="57" horizontalPageScroll="10" verticalLineScroll="57" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Cn2-3f-RfE">
+                    <rect key="frame" x="20" y="20" width="536" height="219"/>
+                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+                    <clipView key="contentView" ambiguous="YES" id="5RD-uP-uce">
+                        <rect key="frame" x="0.0" y="0.0" width="536" height="219"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" autosaveColumns="NO" rowHeight="55" rowSizeStyle="automatic" viewBased="YES" outlineTableColumn="Sv0-5z-dC2" id="20b-Ji-RbX" customClass="RingOutlineView">
+                                <rect key="frame" x="0.0" y="0.0" width="536" height="219"/>
+                                <autoresizingMask key="autoresizingMask"/>
+                                <size key="intercellSpacing" width="3" height="2"/>
+                                <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+                                <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
+                                <tableColumns>
+                                    <tableColumn width="533" minWidth="40" maxWidth="1000" id="Sv0-5z-dC2">
+                                        <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
+                                            <font key="font" metaFont="smallSystem"/>
+                                            <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+                                            <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
+                                        </tableHeaderCell>
+                                        <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" id="eNP-Qg-jM1">
+                                            <font key="font" metaFont="system"/>
+                                            <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+                                            <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+                                        </textFieldCell>
+                                        <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+                                        <prototypeCellViews>
+                                            <tableCellView identifier="DeviceView" id="V2l-Lf-B1r">
+                                                <rect key="frame" x="1" y="1" width="533" height="50"/>
+                                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                                                <subviews>
+                                                    <textField verticalHuggingPriority="750" tag="200" translatesAutoresizingMaskIntoConstraints="NO" id="mif-hy-cnh" userLabel="Status label">
+                                                        <rect key="frame" x="38" y="6" width="38" height="14"/>
+                                                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" placeholderString="Status" id="mAD-1Z-aYB">
+                                                            <font key="font" metaFont="smallSystem"/>
+                                                            <color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
+                                                            <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                                                        </textFieldCell>
+                                                    </textField>
+                                                    <textField verticalHuggingPriority="750" tag="100" translatesAutoresizingMaskIntoConstraints="NO" id="UbS-VO-lii" userLabel="Name label">
+                                                        <rect key="frame" x="38" y="26" width="208" height="21"/>
+                                                        <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" placeholderString="Name" id="w62-Jz-2tu">
+                                                            <font key="font" metaFont="system" size="17"/>
+                                                            <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                                                            <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                                                        </textFieldCell>
+                                                    </textField>
+                                                </subviews>
+                                                <constraints>
+                                                    <constraint firstItem="UbS-VO-lii" firstAttribute="top" secondItem="V2l-Lf-B1r" secondAttribute="top" constant="3" id="1UM-xz-ZNR"/>
+                                                    <constraint firstItem="mif-hy-cnh" firstAttribute="leading" secondItem="V2l-Lf-B1r" secondAttribute="leading" constant="40" id="CQT-Fu-bgB"/>
+                                                    <constraint firstAttribute="trailing" secondItem="UbS-VO-lii" secondAttribute="trailing" constant="289" id="YkQ-XF-ydv"/>
+                                                    <constraint firstItem="UbS-VO-lii" firstAttribute="top" secondItem="V2l-Lf-B1r" secondAttribute="top" constant="3" id="hCv-J7-Ivx"/>
+                                                    <constraint firstItem="mif-hy-cnh" firstAttribute="top" secondItem="UbS-VO-lii" secondAttribute="bottom" constant="6" id="lKI-61-ssq"/>
+                                                    <constraint firstItem="mif-hy-cnh" firstAttribute="top" secondItem="UbS-VO-lii" secondAttribute="bottom" constant="6" id="vWW-7c-2Hk"/>
+                                                    <constraint firstItem="UbS-VO-lii" firstAttribute="leading" secondItem="V2l-Lf-B1r" secondAttribute="leading" constant="40" id="yh9-rt-7e7"/>
+                                                </constraints>
+                                            </tableCellView>
+                                            <customView identifier="HoverRowView" id="lBd-rL-ixN" customClass="HoverTableRowView">
+                                                <rect key="frame" x="1" y="53" width="533" height="55"/>
+                                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+                                            </customView>
+                                        </prototypeCellViews>
+                                    </tableColumn>
+                                </tableColumns>
+                                <connections>
+                                    <outlet property="delegate" destination="-2" id="Q8m-cE-QXK"/>
+                                </connections>
+                            </outlineView>
+                        </subviews>
+                    </clipView>
+                    <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="xKj-nZ-7Mx">
+                        <rect key="frame" x="0.0" y="-16" width="0.0" height="16"/>
+                        <autoresizingMask key="autoresizingMask"/>
+                    </scroller>
+                    <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="eiD-YY-HVN">
+                        <rect key="frame" x="-16" y="0.0" width="16" height="0.0"/>
+                        <autoresizingMask key="autoresizingMask"/>
+                    </scroller>
+                </scrollView>
+                <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="K8a-3i-BoE">
+                    <rect key="frame" x="18" y="310" width="540" height="54"/>
+                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+                    <textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Use the same Ring account on multiple devices." id="kMd-iv-UAy">
+                        <font key="font" metaFont="system"/>
+                        <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                        <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                    </textFieldCell>
+                </textField>
+                <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="hbO-OT-jpc">
+                    <rect key="frame" x="220" y="274" width="136" height="32"/>
+                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+                    <buttonCell key="cell" type="push" title="Add new device" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="uDY-qB-G0I">
+                        <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                        <font key="font" metaFont="system"/>
+                    </buttonCell>
+                    <connections>
+                        <action selector="startExportOnRing:" target="-2" id="6cr-RZ-7Wi"/>
+                    </connections>
+                </button>
+            </subviews>
+            <point key="canvasLocation" x="377" y="270.5"/>
+        </customView>
+    </objects>
+</document>
diff --git a/ui/Base.lproj/Accounts.xib b/ui/Base.lproj/Accounts.xib
index ea72abea..ee7b8906 100644
--- a/ui/Base.lproj/Accounts.xib
+++ b/ui/Base.lproj/Accounts.xib
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
     <dependencies>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
     </dependencies>
     <objects>
         <customObject id="-2" userLabel="File's Owner" customClass="AccountsVC">
@@ -14,6 +14,7 @@
                 <outlet property="generalTabItem" destination="tPR-Ac-N5Y" id="39S-pz-1Xs"/>
                 <outlet property="mediaTabItem" destination="lxr-my-vH8" id="BhJ-cS-yVi"/>
                 <outlet property="protocolList" destination="rZv-qd-BGe" id="yU0-6C-Vt1"/>
+                <outlet property="ringDevicesTabItem" destination="cT1-A5-rbj" id="Sql-e4-mD8"/>
                 <outlet property="ringTabItem" destination="1HC-kF-Jun" id="FJZ-2g-Y1i"/>
                 <outlet property="securityTabItem" destination="Vp5-yV-ScC" id="FDx-0T-3t9"/>
                 <outlet property="view" destination="Hz6-mo-xeY" id="eBn-rZ-84z"/>
@@ -59,6 +60,12 @@
                                 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                             </view>
                         </tabViewItem>
+                        <tabViewItem label="Devices" identifier="" id="cT1-A5-rbj">
+                            <view key="view" id="Ey1-5D-Zou">
+                                <rect key="frame" x="10" y="33" width="576" height="565"/>
+                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                            </view>
+                        </tabViewItem>
                     </tabViewItems>
                     <connections>
                         <outlet property="delegate" destination="-2" id="hfK-WK-DJT"/>
@@ -71,7 +78,7 @@
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
                             <outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" autosaveColumns="NO" rowHeight="55" rowSizeStyle="automatic" viewBased="YES" outlineTableColumn="eOe-f3-q88" id="jXv-6I-P9R" customClass="RingOutlineView">
-                                <rect key="frame" x="0.0" y="0.0" width="250" height="500"/>
+                                <rect key="frame" x="0.0" y="0.0" width="250" height="0.0"/>
                                 <autoresizingMask key="autoresizingMask"/>
                                 <size key="intercellSpacing" width="3" height="2"/>
                                 <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
diff --git a/ui/Base.lproj/ExportPasswordWindow.strings b/ui/Base.lproj/ExportPasswordWindow.strings
new file mode 100644
index 00000000..10cd6259
--- /dev/null
+++ b/ui/Base.lproj/ExportPasswordWindow.strings
@@ -0,0 +1,45 @@
+
+/* Class = "BindingConnection"; ibShadowedIsNilPlaceholder = "Password..."; ObjectID = "5cX-yS-4Sl"; */
+"5cX-yS-4Sl.ibShadowedIsNilPlaceholder" = "Password...";
+
+/* Class = "BindingConnection"; ibShadowedMultipleValuesPlaceholder = "Password..."; ObjectID = "5cX-yS-4Sl"; */
+"5cX-yS-4Sl.ibShadowedMultipleValuesPlaceholder" = "Password...";
+
+/* Class = "BindingConnection"; ibShadowedNoSelectionPlaceholder = "Password..."; ObjectID = "5cX-yS-4Sl"; */
+"5cX-yS-4Sl.ibShadowedNoSelectionPlaceholder" = "Password...";
+
+/* Class = "BindingConnection"; ibShadowedNotApplicablePlaceholder = "Password..."; ObjectID = "5cX-yS-4Sl"; */
+"5cX-yS-4Sl.ibShadowedNotApplicablePlaceholder" = "Password...";
+
+/* Class = "NSTextFieldCell"; title = "To add a new device to your Ring account, you export your account on the Ring. This will generate a pin that must be entered on your new device within 5 minutes of its generation."; ObjectID = "859-10-HEb"; */
+"859-10-HEb.title" = "To add a new device to your Ring account, you export your account on the Ring. This will generate a pin that must be entered on your new device within 5 minutes of its generation.";
+
+/* Class = "NSWindow"; title = "Window"; ObjectID = "QvC-M9-y7g"; */
+"QvC-M9-y7g.title" = "Window";
+
+/* Class = "NSTextFieldCell"; placeholderString = "error label"; ObjectID = "Ua9-fG-r6k"; */
+"Ua9-fG-r6k.placeholderString" = "error label";
+
+/* Class = "NSButtonCell"; title = "OK"; ObjectID = "VN1-A3-RIh"; */
+"VN1-A3-RIh.title" = "OK";
+
+/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "cSU-aD-OwX"; */
+"cSU-aD-OwX.title" = "Cancel";
+
+/* Class = "NSButtonCell"; title = "OK"; ObjectID = "cre-OL-lZy"; */
+"cre-OL-lZy.title" = "OK";
+
+/* Class = "NSTextFieldCell"; placeholderString = "error label"; ObjectID = "e7n-Ev-bK7"; */
+"e7n-Ev-bK7.placeholderString" = "error label";
+
+/* Class = "NSButtonCell"; title = "OK"; ObjectID = "rW5-Il-5YD"; */
+"rW5-Il-5YD.title" = "OK";
+
+/* Class = "NSTextFieldCell"; title = "Password"; ObjectID = "vwh-K9-3O9"; */
+"vwh-K9-3O9.title" = "Password";
+
+/* Class = "NSTextFieldCell"; title = "Adding new device"; ObjectID = "wmv-sA-Mlh"; */
+"wmv-sA-Mlh.title" = "Adding new device";
+
+/* Class = "NSSecureTextFieldCell"; placeholderString = "Password..."; ObjectID = "xqz-Uz-hqU"; */
+"xqz-Uz-hqU.placeholderString" = "Password...";
diff --git a/ui/Base.lproj/ExportPasswordWindow.xib b/ui/Base.lproj/ExportPasswordWindow.xib
new file mode 100644
index 00000000..c2631f2e
--- /dev/null
+++ b/ui/Base.lproj/ExportPasswordWindow.xib
@@ -0,0 +1,241 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11201" systemVersion="15G1004" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/>
+        <capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
+    </dependencies>
+    <objects>
+        <customObject id="-2" userLabel="File's Owner" customClass="ExportPasswordWC">
+            <connections>
+                <outlet property="errorContainer" destination="ty1-sj-tT6" id="eEy-Cr-yiw"/>
+                <outlet property="errorField" destination="G1N-th-ZtP" id="3Qf-fy-ie3"/>
+                <outlet property="finalContainer" destination="srJ-1d-Sbs" id="BP0-xw-uwV"/>
+                <outlet property="initialContainer" destination="xUT-yB-g8Q" id="CZQ-jg-fnH"/>
+                <outlet property="passwordField" destination="vej-Z8-dOm" id="Ff0-Rb-Al6"/>
+                <outlet property="progressContainer" destination="AGJ-1n-ZWv" id="1i0-bR-eog"/>
+                <outlet property="progressIndicator" destination="Ovf-4O-7LZ" id="3ak-Ju-PkY"/>
+                <outlet property="resultField" destination="M0M-Fg-Znl" id="Y5c-jF-Tij"/>
+                <outlet property="window" destination="QvC-M9-y7g" id="bos-rN-Jgz"/>
+            </connections>
+        </customObject>
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+        <window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g">
+            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
+            <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
+            <rect key="contentRect" x="196" y="240" width="410" height="191"/>
+            <rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
+            <view key="contentView" id="EiT-Mj-1SZ">
+                <rect key="frame" x="0.0" y="0.0" width="410" height="191"/>
+                <autoresizingMask key="autoresizingMask"/>
+                <subviews>
+                    <customView translatesAutoresizingMaskIntoConstraints="NO" id="AGJ-1n-ZWv">
+                        <rect key="frame" x="15" y="15" width="380" height="166"/>
+                        <subviews>
+                            <customView hidden="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ovf-4O-7LZ" customClass="ITProgressIndicator">
+                                <rect key="frame" x="155" y="48" width="70" height="70"/>
+                                <constraints>
+                                    <constraint firstAttribute="width" constant="70" id="CYq-9V-MRO"/>
+                                    <constraint firstAttribute="height" constant="70" id="oZI-2d-W12"/>
+                                </constraints>
+                            </customView>
+                        </subviews>
+                        <constraints>
+                            <constraint firstItem="Ovf-4O-7LZ" firstAttribute="centerY" secondItem="AGJ-1n-ZWv" secondAttribute="centerY" id="4ka-be-3Bb"/>
+                            <constraint firstItem="Ovf-4O-7LZ" firstAttribute="width" secondItem="AGJ-1n-ZWv" secondAttribute="height" multiplier="35:83" id="Ntl-ue-3m3"/>
+                            <constraint firstItem="Ovf-4O-7LZ" firstAttribute="centerX" secondItem="AGJ-1n-ZWv" secondAttribute="centerX" id="teu-Ra-LR8"/>
+                        </constraints>
+                    </customView>
+                    <customView translatesAutoresizingMaskIntoConstraints="NO" id="xUT-yB-g8Q">
+                        <rect key="frame" x="15" y="10" width="380" height="171"/>
+                        <subviews>
+                            <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bcr-Pl-Fz9">
+                                <rect key="frame" x="45" y="39" width="63" height="17"/>
+                                <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Password" id="vwh-K9-3O9">
+                                    <font key="font" metaFont="system"/>
+                                    <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                                    <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                                </textFieldCell>
+                            </textField>
+                            <secureTextField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="vej-Z8-dOm">
+                                <rect key="frame" x="113" y="36" width="210" height="22"/>
+                                <secureTextFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" placeholderString="Password..." drawsBackground="YES" usesSingleLineMode="YES" id="xqz-Uz-hqU">
+                                    <font key="font" metaFont="system"/>
+                                    <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
+                                    <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
+                                    <allowedInputSourceLocales>
+                                        <string>NSAllRomanInputSourcesLocaleIdentifier</string>
+                                    </allowedInputSourceLocales>
+                                </secureTextFieldCell>
+                                <connections>
+                                    <binding destination="-2" name="value" keyPath="self.password" id="5cX-yS-4Sl">
+                                        <dictionary key="options">
+                                            <bool key="NSContinuouslyUpdatesValue" value="YES"/>
+                                            <string key="NSMultipleValuesPlaceholder">Password...</string>
+                                            <string key="NSNoSelectionPlaceholder">Password...</string>
+                                            <string key="NSNotApplicablePlaceholder">Password...</string>
+                                            <string key="NSNullPlaceholder">Password...</string>
+                                        </dictionary>
+                                    </binding>
+                                    <outlet property="delegate" destination="-2" id="UEj-AI-SPU"/>
+                                </connections>
+                            </secureTextField>
+                            <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YR8-e7-f8B">
+                                <rect key="frame" x="45" y="142" width="280" height="17"/>
+                                <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Adding new device" id="wmv-sA-Mlh">
+                                    <font key="font" metaFont="systemBold"/>
+                                    <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                                    <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                                </textFieldCell>
+                            </textField>
+                            <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oXB-Be-LaB">
+                                <rect key="frame" x="327" y="-7" width="59" height="32"/>
+                                <buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="rW5-Il-5YD">
+                                    <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                                    <font key="font" metaFont="system"/>
+                                    <string key="keyEquivalent" base64-UTF8="YES">
+DQ
+</string>
+                                </buttonCell>
+                                <connections>
+                                    <action selector="completeAction:" target="-2" id="4me-jZ-4mk"/>
+                                    <binding destination="-2" name="enabled" keyPath="self.password.length" id="KN4-nF-wwM"/>
+                                </connections>
+                            </button>
+                            <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Qx0-KE-jEl">
+                                <rect key="frame" x="18" y="66" width="344" height="68"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="68" id="g34-vr-Zas"/>
+                                </constraints>
+                                <textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="justified" id="859-10-HEb">
+                                    <font key="font" metaFont="system"/>
+                                    <string key="title">To add a new device to your Ring account, you export your account on the Ring. This will generate a pin that must be entered on your new device within 5 minutes of its generation.</string>
+                                    <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                                    <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                                </textFieldCell>
+                            </textField>
+                            <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Yf1-T3-fOf">
+                                <rect key="frame" x="237" y="-7" width="82" height="32"/>
+                                <buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="cSU-aD-OwX">
+                                    <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                                    <font key="font" metaFont="system"/>
+                                    <string key="keyEquivalent" base64-UTF8="YES">
+Gw
+</string>
+                                </buttonCell>
+                                <connections>
+                                    <action selector="cancelPressed:" target="-2" id="bIl-jp-gRS"/>
+                                </connections>
+                            </button>
+                        </subviews>
+                        <constraints>
+                            <constraint firstItem="vej-Z8-dOm" firstAttribute="top" secondItem="Qx0-KE-jEl" secondAttribute="bottom" constant="8" id="12Q-Te-DMO"/>
+                            <constraint firstItem="YR8-e7-f8B" firstAttribute="leading" secondItem="xUT-yB-g8Q" secondAttribute="leading" constant="47" id="86W-iT-c6E"/>
+                            <constraint firstItem="YR8-e7-f8B" firstAttribute="trailing" secondItem="vej-Z8-dOm" secondAttribute="trailing" id="97f-vZ-ehW"/>
+                            <constraint firstAttribute="trailing" secondItem="Qx0-KE-jEl" secondAttribute="trailing" constant="20" id="FV8-cW-4Ua"/>
+                            <constraint firstItem="Qx0-KE-jEl" firstAttribute="leading" secondItem="xUT-yB-g8Q" secondAttribute="leading" constant="20" id="GL9-b9-rHs"/>
+                            <constraint firstItem="oXB-Be-LaB" firstAttribute="leading" secondItem="Yf1-T3-fOf" secondAttribute="trailing" constant="20" id="Ghk-zq-b9T"/>
+                            <constraint firstItem="Bcr-Pl-Fz9" firstAttribute="baseline" secondItem="vej-Z8-dOm" secondAttribute="baseline" id="MM1-gt-2Ln"/>
+                            <constraint firstItem="Qx0-KE-jEl" firstAttribute="top" secondItem="YR8-e7-f8B" secondAttribute="bottom" constant="8" id="OPG-g5-b8f"/>
+                            <constraint firstItem="YR8-e7-f8B" firstAttribute="top" secondItem="xUT-yB-g8Q" secondAttribute="top" constant="12" id="T0q-sr-B1c"/>
+                            <constraint firstItem="vej-Z8-dOm" firstAttribute="leading" secondItem="Bcr-Pl-Fz9" secondAttribute="trailing" constant="7" id="ibU-NT-KTl"/>
+                            <constraint firstAttribute="bottom" secondItem="Yf1-T3-fOf" secondAttribute="bottom" id="kKt-h2-PSf"/>
+                            <constraint firstItem="Bcr-Pl-Fz9" firstAttribute="top" secondItem="Qx0-KE-jEl" secondAttribute="bottom" constant="10" id="md2-cC-e45"/>
+                            <constraint firstAttribute="trailing" secondItem="oXB-Be-LaB" secondAttribute="trailing" id="nc0-bc-HZy"/>
+                            <constraint firstItem="Qx0-KE-jEl" firstAttribute="top" secondItem="YR8-e7-f8B" secondAttribute="bottom" constant="8" symbolic="YES" id="sQ9-yl-Dxe"/>
+                            <constraint firstAttribute="trailing" secondItem="YR8-e7-f8B" secondAttribute="trailing" constant="57" id="slS-SE-ulf"/>
+                            <constraint firstAttribute="bottom" secondItem="oXB-Be-LaB" secondAttribute="bottom" id="upQ-xH-MIe"/>
+                            <constraint firstItem="YR8-e7-f8B" firstAttribute="leading" secondItem="Bcr-Pl-Fz9" secondAttribute="leading" id="wF5-CT-zPE"/>
+                        </constraints>
+                    </customView>
+                    <customView hidden="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ty1-sj-tT6">
+                        <rect key="frame" x="15" y="10" width="380" height="171"/>
+                        <subviews>
+                            <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="G1N-th-ZtP">
+                                <rect key="frame" x="-2" y="0.0" width="384" height="132"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+                                <textFieldCell key="cell" controlSize="mini" sendsActionOnEndEditing="YES" alignment="left" placeholderString="error label" id="e7n-Ev-bK7">
+                                    <font key="font" metaFont="system"/>
+                                    <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                                    <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                                </textFieldCell>
+                            </textField>
+                            <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cxh-K7-1eh">
+                                <rect key="frame" x="327" y="-7" width="59" height="32"/>
+                                <buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="VN1-A3-RIh">
+                                    <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                                    <font key="font" metaFont="system"/>
+                                    <string key="keyEquivalent" base64-UTF8="YES">
+DQ
+</string>
+                                </buttonCell>
+                                <connections>
+                                    <action selector="cancelPressed:" target="-2" id="JTp-N4-Tb8"/>
+                                </connections>
+                            </button>
+                        </subviews>
+                        <constraints>
+                            <constraint firstAttribute="trailing" secondItem="cxh-K7-1eh" secondAttribute="trailing" id="OrO-VW-3Xw"/>
+                            <constraint firstAttribute="bottom" secondItem="cxh-K7-1eh" secondAttribute="bottom" id="tLl-Ru-JDw"/>
+                            <constraint firstItem="cxh-K7-1eh" firstAttribute="width" secondItem="ty1-sj-tT6" secondAttribute="height" multiplier="47:171" id="xxl-WJ-uWd"/>
+                        </constraints>
+                    </customView>
+                    <customView hidden="YES" translatesAutoresizingMaskIntoConstraints="NO" id="srJ-1d-Sbs">
+                        <rect key="frame" x="15" y="10" width="380" height="171"/>
+                        <subviews>
+                            <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="M0M-Fg-Znl">
+                                <rect key="frame" x="-2" y="0.0" width="384" height="132"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+                                <textFieldCell key="cell" controlSize="mini" sendsActionOnEndEditing="YES" alignment="left" placeholderString="finish label" id="ezj-X9-NQd">
+                                    <font key="font" metaFont="system"/>
+                                    <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                                    <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                                </textFieldCell>
+                            </textField>
+                            <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="I5i-KQ-7yI">
+                                <rect key="frame" x="327" y="-7" width="59" height="32"/>
+                                <buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="dfD-AK-f3R">
+                                    <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                                    <font key="font" metaFont="system"/>
+                                    <string key="keyEquivalent" base64-UTF8="YES">
+DQ
+</string>
+                                </buttonCell>
+                                <connections>
+                                    <action selector="cancelPressed:" target="-2" id="NWs-SH-M68"/>
+                                </connections>
+                            </button>
+                        </subviews>
+                        <constraints>
+                            <constraint firstItem="I5i-KQ-7yI" firstAttribute="width" secondItem="srJ-1d-Sbs" secondAttribute="height" multiplier="47:171" id="1kj-gX-F60"/>
+                            <constraint firstAttribute="trailing" secondItem="I5i-KQ-7yI" secondAttribute="trailing" id="L4m-3f-iAH"/>
+                            <constraint firstAttribute="bottom" secondItem="I5i-KQ-7yI" secondAttribute="bottom" id="OA3-eH-jPg"/>
+                        </constraints>
+                    </customView>
+                </subviews>
+                <constraints>
+                    <constraint firstAttribute="trailing" secondItem="xUT-yB-g8Q" secondAttribute="trailing" constant="15" id="0cz-Qg-8O3"/>
+                    <constraint firstAttribute="bottom" secondItem="ty1-sj-tT6" secondAttribute="bottom" constant="10" id="1bi-Qk-WJf"/>
+                    <constraint firstItem="xUT-yB-g8Q" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="10" id="80V-3H-yhe"/>
+                    <constraint firstItem="ty1-sj-tT6" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="15" id="JfP-nv-HzG"/>
+                    <constraint firstItem="srJ-1d-Sbs" firstAttribute="leading" secondItem="xUT-yB-g8Q" secondAttribute="leading" id="LX2-ln-cMG"/>
+                    <constraint firstItem="ty1-sj-tT6" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="10" id="WRY-iS-AnI"/>
+                    <constraint firstItem="srJ-1d-Sbs" firstAttribute="trailing" secondItem="xUT-yB-g8Q" secondAttribute="trailing" id="X7E-dC-Irw"/>
+                    <constraint firstItem="AGJ-1n-ZWv" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="15" id="fQD-sb-Ef1"/>
+                    <constraint firstItem="xUT-yB-g8Q" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="15" id="hyR-9O-NC2"/>
+                    <constraint firstAttribute="trailing" secondItem="ty1-sj-tT6" secondAttribute="trailing" constant="15" id="kMD-cN-UU3"/>
+                    <constraint firstAttribute="bottom" secondItem="xUT-yB-g8Q" secondAttribute="bottom" constant="10" id="p1f-f1-8dd"/>
+                    <constraint firstAttribute="trailing" secondItem="AGJ-1n-ZWv" secondAttribute="trailing" constant="15" id="pRc-7V-4dD"/>
+                    <constraint firstItem="srJ-1d-Sbs" firstAttribute="top" secondItem="xUT-yB-g8Q" secondAttribute="top" id="uGQ-Kt-GUW"/>
+                    <constraint firstItem="AGJ-1n-ZWv" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="10" id="xBv-N5-u4V"/>
+                    <constraint firstAttribute="bottom" secondItem="AGJ-1n-ZWv" secondAttribute="bottom" constant="15" id="xtD-fH-ddM"/>
+                </constraints>
+            </view>
+            <connections>
+                <outlet property="delegate" destination="-2" id="3wn-SC-48D"/>
+            </connections>
+            <point key="canvasLocation" x="-420" y="-196.5"/>
+        </window>
+        <userDefaultsController representsSharedInstance="YES" id="2A5-Q9-edp"/>
+    </objects>
+</document>
diff --git a/ui/en.lproj/ExportPasswordWindow.strings b/ui/en.lproj/ExportPasswordWindow.strings
new file mode 100644
index 00000000..4ee23625
--- /dev/null
+++ b/ui/en.lproj/ExportPasswordWindow.strings
@@ -0,0 +1,39 @@
+
+/* Class = "BindingConnection"; ibShadowedIsNilPlaceholder = "Password..."; ObjectID = "5cX-yS-4Sl"; */
+"5cX-yS-4Sl.ibShadowedIsNilPlaceholder" = "Password...";
+
+/* Class = "BindingConnection"; ibShadowedMultipleValuesPlaceholder = "Password..."; ObjectID = "5cX-yS-4Sl"; */
+"5cX-yS-4Sl.ibShadowedMultipleValuesPlaceholder" = "Password...";
+
+/* Class = "BindingConnection"; ibShadowedNoSelectionPlaceholder = "Password..."; ObjectID = "5cX-yS-4Sl"; */
+"5cX-yS-4Sl.ibShadowedNoSelectionPlaceholder" = "Password...";
+
+/* Class = "BindingConnection"; ibShadowedNotApplicablePlaceholder = "Password..."; ObjectID = "5cX-yS-4Sl"; */
+"5cX-yS-4Sl.ibShadowedNotApplicablePlaceholder" = "Password...";
+
+/* Class = "NSTextFieldCell"; title = "To add a new device to your Ring account, you export your account on the Ring. This will generate a pin that must be entered on your new device within 5 minutes of its generation."; ObjectID = "859-10-HEb"; */
+"859-10-HEb.title" = "To add a new device to your Ring account, you export your account on the Ring. This will generate a pin that must be entered on your new device within 5 minutes of its generation.";
+
+/* Class = "NSWindow"; title = "Window"; ObjectID = "QvC-M9-y7g"; */
+"QvC-M9-y7g.title" = "Window";
+
+/* Class = "NSButtonCell"; title = "OK"; ObjectID = "VN1-A3-RIh"; */
+"VN1-A3-RIh.title" = "OK";
+
+/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "cSU-aD-OwX"; */
+"cSU-aD-OwX.title" = "Cancel";
+
+/* Class = "NSTextFieldCell"; placeholderString = "error label"; ObjectID = "e7n-Ev-bK7"; */
+"e7n-Ev-bK7.placeholderString" = "error label";
+
+/* Class = "NSButtonCell"; title = "OK"; ObjectID = "rW5-Il-5YD"; */
+"rW5-Il-5YD.title" = "OK";
+
+/* Class = "NSTextFieldCell"; title = "Password"; ObjectID = "vwh-K9-3O9"; */
+"vwh-K9-3O9.title" = "Password";
+
+/* Class = "NSTextFieldCell"; title = "Adding new device"; ObjectID = "wmv-sA-Mlh"; */
+"wmv-sA-Mlh.title" = "Adding new device";
+
+/* Class = "NSSecureTextFieldCell"; placeholderString = "Password..."; ObjectID = "xqz-Uz-hqU"; */
+"xqz-Uz-hqU.placeholderString" = "Password...";
-- 
GitLab