diff --git a/src/js/controllers/addresses.js b/src/js/controllers/addresses.js
new file mode 100644
index 000000000..9b31e431f
--- /dev/null
+++ b/src/js/controllers/addresses.js
@@ -0,0 +1,199 @@
+'use strict';
+
+angular.module('copayApp.controllers').controller('addressesController', function($scope, $stateParams, $state, $timeout, $ionicHistory, $ionicPopover, $ionicScrollDelegate, configService, popupService, gettextCatalog, ongoingProcess, lodash, profileService, walletService, platformInfo) {
+ var UNUSED_ADDRESS_LIMIT = 5;
+ var BALANCE_ADDRESS_LIMIT = 5;
+ var MENU_ITEM_HEIGHT = 55;
+ var config;
+ var unitName;
+ var unitToSatoshi;
+ var satToUnit;
+ var unitDecimals;
+ var withBalance;
+ $scope.showInfo = false;
+ $scope.showMore = false;
+ $scope.allAddressesView = false;
+ $scope.isCordova = platformInfo.isCordova;
+ $scope.wallet = profileService.getWallet($stateParams.walletId);
+
+ function init() {
+ ongoingProcess.set('gettingAddresses', true);
+ walletService.getMainAddresses($scope.wallet, {}, function(err, addresses) {
+ if (err) {
+ ongoingProcess.set('gettingAddresses', false);
+ return popupService.showAlert(gettextCatalog.getString('Error'), err);
+ }
+
+ var allAddresses = addresses;
+
+ walletService.getBalance($scope.wallet, {}, function(err, resp) {
+ ongoingProcess.set('gettingAddresses', false);
+ if (err) {
+ return popupService.showAlert(gettextCatalog.getString('Error'), err);
+ }
+
+ withBalance = resp.byAddress;
+ var idx = lodash.indexBy(withBalance, 'address');
+ $scope.noBalance = lodash.reject(allAddresses, function(x) {
+ return idx[x.address];
+ });
+
+ processPaths($scope.noBalance);
+ processPaths(withBalance);
+
+ $scope.latestUnused = lodash.slice($scope.noBalance, 0, UNUSED_ADDRESS_LIMIT);
+ $scope.latestWithBalance = lodash.slice(withBalance, 0, BALANCE_ADDRESS_LIMIT);
+
+ lodash.each(withBalance, function(a) {
+ a.balanceStr = (a.amount * satToUnit).toFixed(unitDecimals) + ' ' + unitName;
+ });
+
+ $scope.viewAll = {
+ value: $scope.noBalance.length > UNUSED_ADDRESS_LIMIT || withBalance.length > BALANCE_ADDRESS_LIMIT
+ };
+ $scope.allAddresses = $scope.noBalance.concat(withBalance);
+ $scope.$digest();
+ });
+ });
+ };
+
+ function processPaths(list) {
+ lodash.each(list, function(n) {
+ n.path = n.path.replace(/^m/g, 'xpub');
+ });
+ };
+
+ $scope.newAddress = function() {
+ if ($scope.gapReached) return;
+
+ ongoingProcess.set('generatingNewAddress', true);
+ walletService.getAddress($scope.wallet, true, function(err, addr) {
+ if (err) {
+ ongoingProcess.set('generatingNewAddress', false);
+ $scope.gapReached = true;
+ $timeout(function() {
+ $scope.$digest();
+ });
+ return;
+ }
+
+ walletService.getMainAddresses($scope.wallet, {
+ limit: 1
+ }, function(err, _addr) {
+ ongoingProcess.set('generatingNewAddress', false);
+ if (err) return popupService.showAlert(gettextCatalog.getString('Error'), err);
+ if (addr != _addr[0].address) return popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('New address could not be generated. Please try again.'));
+
+ $scope.noBalance = [_addr[0]].concat($scope.noBalance);
+ $scope.latestUnused = lodash.slice($scope.noBalance, 0, UNUSED_ADDRESS_LIMIT);
+ $scope.viewAll = {
+ value: $scope.noBalance.length > UNUSED_ADDRESS_LIMIT
+ };
+ $scope.$digest();
+ });
+ });
+ };
+
+ $scope.viewAllAddresses = function() {
+ $state.go('tabs.receive.allAddresses', {
+ walletId: $scope.wallet.id
+ });
+ };
+
+ $scope.showInformation = function() {
+ $timeout(function() {
+ $scope.showInfo = !$scope.showInfo;
+ $ionicScrollDelegate.resize();
+ });
+ };
+
+ $scope.readMore = function() {
+ $timeout(function() {
+ $scope.showMore = !$scope.showMore;
+ $ionicScrollDelegate.resize();
+ });
+ };
+
+ $scope.showMenu = function(allAddresses, $event) {
+ var scanObj = {
+ text: gettextCatalog.getString('Scan addresses for funds'),
+ action: scan,
+ };
+
+ var sendAddressesObj = {
+ text: gettextCatalog.getString('Send addresses by email'),
+ action: sendByEmail,
+ }
+
+ $scope.items = allAddresses ? [sendAddressesObj] : [scanObj];
+ $scope.height = $scope.items.length * MENU_ITEM_HEIGHT;
+
+ $ionicPopover.fromTemplateUrl('views/includes/menu-popover.html', {
+ scope: $scope
+ }).then(function(popover) {
+ $scope.menu = popover;
+ $scope.menu.show($event);
+ });
+ };
+
+ var scan = function() {
+ walletService.startScan($scope.wallet);
+ $scope.menu.hide();
+ $ionicHistory.clearHistory();
+ $state.go('tabs.home');
+ };
+
+ var sendByEmail = function() {
+ function formatDate(ts) {
+ var dateObj = new Date(ts * 1000);
+ if (!dateObj) {
+ $log.debug('Error formating a date');
+ return 'DateError';
+ }
+ if (!dateObj.toJSON()) {
+ return '';
+ }
+ return dateObj.toJSON();
+ };
+
+ ongoingProcess.set('sendingByEmail', true);
+ $timeout(function() {
+ var body = 'Copay Wallet "' + $scope.walletName + '" Addresses\n Only Main Addresses are shown.\n\n';
+ body += "\n";
+ body += $scope.allAddresses.map(function(v) {
+ return ('* ' + v.address + ' ' + 'xpub' + v.path.substring(1) + ' ' + formatDate(v.createdOn));
+ }).join("\n");
+ ongoingProcess.set('sendingByEmail', false);
+
+ window.plugins.socialsharing.shareViaEmail(
+ body,
+ 'Copay Addresses',
+ null, // TO: must be null or an array
+ null, // CC: must be null or an array
+ null, // BCC: must be null or an array
+ null, // FILES: can be null, a string, or an array
+ function() {},
+ function() {}
+ );
+
+ $scope.menu.hide();
+ });
+ };
+
+ $scope.$on("$ionicView.beforeEnter", function(event, data) {
+ $scope.allAddressesView = data.stateName == 'tabs.receive.allAddresses' ? true : false;
+ $timeout(function() {
+ $scope.$apply();
+ });
+ });
+
+ $scope.$on("$ionicView.afterEnter", function(event, data) {
+ config = configService.getSync().wallet.settings;
+ unitToSatoshi = config.unitToSatoshi;
+ satToUnit = 1 / unitToSatoshi;
+ unitName = config.unitName;
+ unitDecimals = config.unitDecimals;
+
+ if (!$scope.allAddresses || $scope.allAddresses.length < 0) init();
+ });
+});
diff --git a/src/js/controllers/preferencesInformation.js b/src/js/controllers/preferencesInformation.js
index 2f2da8b00..983b9cef8 100644
--- a/src/js/controllers/preferencesInformation.js
+++ b/src/js/controllers/preferencesInformation.js
@@ -1,62 +1,15 @@
'use strict';
angular.module('copayApp.controllers').controller('preferencesInformation',
- function($scope, $log, $timeout, $ionicHistory, $ionicScrollDelegate, platformInfo, gettextCatalog, lodash, profileService, configService, $stateParams, walletService, $state) {
- var base = 'xpub';
+ function($scope, $log, $ionicHistory, platformInfo, lodash, profileService, configService, $stateParams, $state) {
var wallet = profileService.getWallet($stateParams.walletId);
var walletId = wallet.id;
-
var config = configService.getSync();
- var b = 1;
+ var colorCounter = 1;
+ var BLACK_WALLET_COLOR = '#202020';
$scope.isCordova = platformInfo.isCordova;
config.colorFor = config.colorFor || {};
- $scope.sendAddrs = function() {
- function formatDate(ts) {
- var dateObj = new Date(ts * 1000);
- if (!dateObj) {
- $log.debug('Error formating a date');
- return 'DateError';
- }
- if (!dateObj.toJSON()) {
- return '';
- }
- return dateObj.toJSON();
- };
-
- $timeout(function() {
- wallet.getMainAddresses({
- doNotVerify: true
- }, function(err, addrs) {
- if (err) {
- $log.warn(err);
- return;
- };
-
- var body = 'Copay Wallet "' + $scope.walletName + '" Addresses\n Only Main Addresses are shown.\n\n';
- body += "\n";
- body += addrs.map(function(v) {
- return ('* ' + v.address + ' ' + base + v.path.substring(1) + ' ' + formatDate(v.createdOn));
- }).join("\n");
-
- window.plugins.socialsharing.shareViaEmail(
- body,
- 'Copay Addresses',
- null, // TO: must be null or an array
- null, // CC: must be null or an array
- null, // BCC: must be null or an array
- null, // FILES: can be null, a string, or an array
- function() {},
- function() {}
- );
-
- $timeout(function() {
- $scope.$apply();
- }, 1000);
- });
- }, 100);
- };
-
$scope.saveBlack = function() {
function save(color) {
var opts = {
@@ -71,14 +24,8 @@ angular.module('copayApp.controllers').controller('preferencesInformation',
});
};
- if (b != 5) return b++;
- save('#202020');
- };
-
- $scope.scan = function() {
- walletService.startScan(wallet);
- $ionicHistory.removeBackView();
- $state.go('tabs.home');
+ if (colorCounter != 5) return colorCounter++;
+ save(BLACK_WALLET_COLOR);
};
$scope.$on("$ionicView.enter", function(event, data) {
@@ -95,29 +42,5 @@ angular.module('copayApp.controllers').controller('preferencesInformation',
$scope.M = c.m;
$scope.N = c.n;
$scope.pubKeys = lodash.pluck(c.publicKeyRing, 'xPubKey');
- $scope.addrs = null;
-
- wallet.getMainAddresses({
- doNotVerify: true
- }, function(err, addrs) {
- if (err) {
- $log.warn(err);
- return;
- };
- var last10 = [],
- i = 0,
- e = addrs.pop();
- while (i++ < 10 && e) {
- e.path = base + e.path.substring(1);
- last10.push(e);
- e = addrs.pop();
- }
- $scope.addrs = last10;
- $timeout(function() {
- $ionicScrollDelegate.resize();
- $scope.$apply();
- }, 10);
- });
});
-
});
diff --git a/src/js/controllers/tab-receive.js b/src/js/controllers/tab-receive.js
index ea8b9778a..cae89c917 100644
--- a/src/js/controllers/tab-receive.js
+++ b/src/js/controllers/tab-receive.js
@@ -48,6 +48,12 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
}, 100);
};
+ $scope.showAddresses = function() {
+ $state.transitionTo('tabs.receive.addresses', {
+ walletId: $scope.wallet.credentials.walletId
+ });
+ };
+
$scope.openBackupNeededModal = function() {
$ionicModal.fromTemplateUrl('views/includes/backupNeededPopup.html', {
scope: $scope,
diff --git a/src/js/routes.js b/src/js/routes.js
index cd935dda2..e00498966 100644
--- a/src/js/routes.js
+++ b/src/js/routes.js
@@ -616,6 +616,31 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
})
+ /*
+ *
+ * Addresses
+ *
+ */
+
+ .state('tabs.receive.addresses', {
+ url: '/addresses/:walletId',
+ views: {
+ 'tab-receive@tabs': {
+ controller: 'addressesController',
+ templateUrl: 'views/addresses.html'
+ }
+ }
+ })
+ .state('tabs.receive.allAddresses', {
+ url: '/allAddresses/:walletId',
+ views: {
+ 'tab-receive@tabs': {
+ controller: 'addressesController',
+ templateUrl: 'views/allAddresses.html'
+ }
+ }
+ })
+
/*
*
* Init backup flow
diff --git a/src/js/services/onGoingProcess.js b/src/js/services/onGoingProcess.js
index acede937d..0d6977173 100644
--- a/src/js/services/onGoingProcess.js
+++ b/src/js/services/onGoingProcess.js
@@ -34,6 +34,9 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
'validatingWords': gettext('Validating recovery phrase...'),
'loadingTxInfo': gettext('Loading transaction info...'),
'sendingFeedback': gettext('Sending feedback...'),
+ 'generatingNewAddress': gettext('Generating new address...'),
+ 'gettingAddresses': gettext('Getting addresses...'),
+ 'sendingByEmail': gettext('Preparing addresses...'),
};
root.clear = function() {
diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js
index 3e5da9097..b00722c76 100644
--- a/src/js/services/walletService.js
+++ b/src/js/services/walletService.js
@@ -773,6 +773,21 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
});
};
+ root.getMainAddresses = function(wallet, opts, cb) {
+ opts = opts || {};
+ opts.reverse = true;
+ wallet.getMainAddresses(opts, function(err, addresses) {
+ return cb(err, addresses);
+ });
+ };
+
+ root.getBalance = function(wallet, opts, cb) {
+ opts = opts || {};
+ wallet.getBalance(opts, function(err, resp) {
+ return cb(err, resp);
+ });
+ };
+
root.getAddress = function(wallet, forceNew, cb) {
storageService.getLastAddress(wallet.id, function(err, addr) {
if (err) return cb(err);
diff --git a/src/sass/views/addresses.scss b/src/sass/views/addresses.scss
new file mode 100644
index 000000000..5401945f7
--- /dev/null
+++ b/src/sass/views/addresses.scss
@@ -0,0 +1,89 @@
+#addresses {
+ .addr {
+ &-explanation, &-button-group {
+ padding: 0 1rem;
+ margin: 1rem 0;
+ }
+ &-description {
+ text-align: center;
+ font-size: 15px;
+ color: $mid-gray;
+ margin: 1rem 0;
+ a {
+ font-weight: bold;
+ cursor: pointer;
+ cursor: hand;
+ }
+ }
+ &-balance {
+ margin-top: 4px;
+ color: #5DD263;
+ }
+ &-path {
+ margin-top: 4px;
+ color: #B8B8B8;
+ }
+ }
+
+ .banner-icon {
+ margin-top: 25px;
+ i {
+ box-shadow: $hovering-box-shadow;
+ }
+ }
+
+ .addr-list {
+ .item {
+ color: $dark-gray;
+ padding-top: 1.3rem;
+ padding-bottom: 1.3rem;
+ &.has-addr-value {
+ padding-top: .65rem;
+ padding-bottom: .65rem;
+ }
+ &.item-divider {
+ color: $mid-gray;
+ padding-bottom: .5rem;
+ font-size: .9rem;
+ }
+ &.view-all {
+ margin: 20px 0px 20px 0px;
+ cursor: pointer;
+ cursor: hand;
+ i {
+ font-size: 35px;
+ margin-right: 5px;
+ color: #647ce8;
+ }
+ span {
+ color: #647ce8;
+ font-weight: bold;
+ }
+ }
+ i {
+ font-size: 35px;
+ margin-right: 2px;
+ }
+ }
+ .box-error {
+ padding: 25px;
+ background-color: #E65555;
+ color: #F4F4F4;
+ h5 {
+ margin: 5px;
+ color: #F4F4F4;
+ text-align: center;
+ font-weight: bold;
+ }
+ a {
+ font-weight: bold;
+ color: #F4F4F4;
+ cursor: pointer;
+ cursor: hand;
+ }
+ }
+ .item-note {
+ color: $light-gray;
+ }
+ }
+}
diff --git a/src/sass/views/includes/menu-popover.scss b/src/sass/views/includes/menu-popover.scss
new file mode 100644
index 000000000..93139b42d
--- /dev/null
+++ b/src/sass/views/includes/menu-popover.scss
@@ -0,0 +1,12 @@
+#menu-popover {
+ border-radius: 5px;
+ .list {
+ .item {
+ cursor: pointer;
+ cursor: hand;
+ &:hover {
+ background-color: #E4E2E2;
+ }
+ }
+ }
+}
diff --git a/src/sass/views/tab-receive.scss b/src/sass/views/tab-receive.scss
index ebb9e3ef2..4d36162a4 100644
--- a/src/sass/views/tab-receive.scss
+++ b/src/sass/views/tab-receive.scss
@@ -89,6 +89,8 @@
.bit-address {
font-size: .8rem;
// left:10%;
+ cursor: pointer;
+ cursor: hand;
position: absolute;
transition: all .15s ease;
width:100%;
@@ -117,7 +119,6 @@
.item {
padding-top: 5px;
padding-bottom: 5px;
- display: inline-block;
font-size: .7rem;
@media(min-width:350px) {
font-size:.9rem;
diff --git a/src/sass/views/views.scss b/src/sass/views/views.scss
index f67826f61..08bcddf5e 100644
--- a/src/sass/views/views.scss
+++ b/src/sass/views/views.scss
@@ -15,6 +15,7 @@
@import "bitpayCardIntro";
@import "bitpayCardPreferences";
@import "address-book";
+@import "addresses";
@import "wallet-backup-phrase";
@import "zero-state";
@import "onboarding/onboarding";
@@ -26,6 +27,7 @@
@import "export";
@import "import";
@import "join";
+@import "includes/menu-popover";
@import "includes/walletActivity";
@import "includes/wallets";
@import "includes/modals/modals";
diff --git a/www/views/addresses.html b/www/views/addresses.html
new file mode 100644
index 000000000..a4a3b6962
--- /dev/null
+++ b/www/views/addresses.html
@@ -0,0 +1,72 @@
+ The maximum number of consecutive unused addresses (20) has been reached. When one of your unused addresses receives a payment, a new address will be generated and shown in your Receive tab. Read more The restore process will stop when 20 addresses are generated in a row which contain no funds. To safely generate more addresses, make a payment to one of the unused addresses which has already been generated. Read lessUnused Addresses Limit
+