Merge branch 'master' of https://github.com/bitpay/copay into feat/app-identity

This commit is contained in:
Andy Phillipson 2016-12-22 09:59:35 -05:00
commit 9b3a3aab9d
213 changed files with 12663 additions and 7021 deletions

View file

@ -36,6 +36,7 @@ bwcModule.provider("bwcService", function() {
var bwc = new Client({ var bwc = new Client({
baseUrl: opts.bwsurl || 'https://bws.bitpay.com/bws/api', baseUrl: opts.bwsurl || 'https://bws.bitpay.com/bws/api',
verbose: opts.verbose, verbose: opts.verbose,
timeout: 100000,
transports: ['polling'], transports: ['polling'],
}); });
if (walletData) if (walletData)

View file

@ -9,20 +9,20 @@
"nameNoSpace": "bitpay", "nameNoSpace": "bitpay",
"nameCase": "BitPay", "nameCase": "BitPay",
"nameCaseNoSpace": "BitPay", "nameCaseNoSpace": "BitPay",
"gitHubRepoName": "bitpay-wallet", "gitHubRepoName": "copay",
"gitHubRepoUrl": "git://github.com/bitpay/bitpay-wallet.git", "gitHubRepoUrl": "git://github.com/bitpay/copay.git",
"gitHubRepoBugs": "https://github.com/bitpay/bitpay-wallet/issues", "gitHubRepoBugs": "https://github.com/bitpay/copay/issues",
"disclaimerUrl": "", "disclaimerUrl": "",
"url": "https://bitpay.com", "url": "https://bitpay.com",
"appDescription": "Secure Bitcoin Wallet", "appDescription": "Secure Bitcoin Wallet",
"winAppName": "BitPayWallet", "winAppName": "BitPayWallet",
"wpPublisherId": "{}", "wpPublisherId": "{}",
"wpProductId": "{}", "wpProductId": "{}",
"windowsAppId": "", "windowsAppId": "2d1002d7-ee34-4f60-bd29-0c871ba0c195",
"pushSenderId": "1036948132229", "pushSenderId": "1036948132229",
"description": "Secure Bitcoin Wallet", "description": "Secure Bitcoin Wallet",
"version": "1.0.1", "version": "1.2.1",
"androidVersion": "1", "androidVersion": "12100",
"_extraCSS": null, "_extraCSS": null,
"_enabledExtensions": { "_enabledExtensions": {
"coinbase": true, "coinbase": true,

View file

@ -65,6 +65,7 @@
<variable name="SENDER_ID" value="*PUSHSENDERID*"/> <variable name="SENDER_ID" value="*PUSHSENDERID*"/>
</plugin> </plugin>
<plugin name="cordova-custom-config" spec="~3.0.5" /> <plugin name="cordova-custom-config" spec="~3.0.5" />
<plugin name="cordova-plugin-queries-schemes" spec="~0.1.5" />
<!-- Supported Platforms --> <!-- Supported Platforms -->
<engine name="ios" spec="~4.2.1" /> <engine name="ios" spec="~4.2.1" />

View file

@ -56,7 +56,7 @@
"bezier-easing": "^2.0.3", "bezier-easing": "^2.0.3",
"bhttp": "^1.2.1", "bhttp": "^1.2.1",
"bitauth": "^0.3.2", "bitauth": "^0.3.2",
"bitcore-wallet-client": "4.3.2", "bitcore-wallet-client": "4.4.0",
"bower": "^1.7.9", "bower": "^1.7.9",
"chai": "^3.5.0", "chai": "^3.5.0",
"cordova-android": "5.1.1", "cordova-android": "5.1.1",
@ -87,6 +87,7 @@
"start": "npm run build:www && ionic serve --nolivereload --nogulp -s", "start": "npm run build:www && ionic serve --nolivereload --nogulp -s",
"start:ios": "npm run build:www && npm run build:ios && npm run open:ios", "start:ios": "npm run build:www && npm run build:ios && npm run open:ios",
"start:android": "npm run build:www && npm run build:android && npm run run:android", "start:android": "npm run build:www && npm run build:android && npm run run:android",
"start:desktop": "npm start",
"watch": "grunt watch", "watch": "grunt watch",
"build:www": "grunt", "build:www": "grunt",
"build:www-release": "grunt prod", "build:www-release": "grunt prod",

View file

@ -5,7 +5,7 @@
#define MyAppVersion "*VERSION*" #define MyAppVersion "*VERSION*"
#define MyAppPublisher "BitPay" #define MyAppPublisher "BitPay"
#define MyAppURL "*URL*" #define MyAppURL "*URL*"
#define MyAppExeName "*PACKAGENAME*.exe" #define MyAppExeName "*USERVISIBLENAME*.exe"
#define AppId "*WINDOWSAPPID*" #define AppId "*WINDOWSAPPID*"
[Setup] [Setup]
@ -19,7 +19,7 @@ AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL} AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\{#MyAppName} DefaultDirName={pf}\{#MyAppName}
DefaultGroupName={#MyAppName} DefaultGroupName={#MyAppName}
OutputBaseFilename=*PACKAGENAME*-win OutputBaseFilename=*USERVISIBLENAME*-win
OutputDir=./ OutputDir=./
Compression=lzma Compression=lzma
SolidCompression=yes SolidCompression=yes
@ -33,9 +33,9 @@ Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl"
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files] [Files]
Source: "*PACKAGENAME*\win64\*PACKAGENAME*.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "*USERVISIBLENAME*\win64\*USERVISIBLENAME*.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "*PACKAGENAME*\win64\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "*USERVISIBLENAME*\win64\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "../www/img/icons/favicon.ico"; DestDir: "{app}"; DestName: "icon.ico"; Flags: ignoreversion Source: "../www/img/app/favicon.ico"; DestDir: "{app}"; DestName: "icon.ico"; Flags: ignoreversion
; NOTE: Don't use "Flags: ignoreversion" on any shared system files ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons] [Icons]

View file

@ -19,7 +19,7 @@ Copay is a Multisig HD Wallet. Copay app holds the extended private keys for the
### Wallet Recovery Scope ### Wallet Recovery Scope
* Basic Recovery: Wallet access is restored. It is possible to see wallet balance and past transactions. It is possible to send and receive payments. * Basic Recovery: Wallet access is restored. It is possible to see wallet balance and past transactions. It is possible to send and receive payments.
* Full Recovery: All the features of Partial Recovery + wallet name, copayer names are recovered, past payment proposal metadata (who signed, and notes) are recoved. * Full Recovery: All the features of Partial Recovery + wallet name, copayer names are recovered, past payment proposal metadata (who signed, and notes) are recovered.
## Wallet Restore Scenarios ## Wallet Restore Scenarios

View file

@ -40,7 +40,7 @@ var local_file3 = fs.createReadStream(local_file_name3)
// obtain the crowdin api key // obtain the crowdin api key
var crowdin_api_key = fs.readFileSync(path.join(__dirname, 'crowdin_api_key.txt')) var crowdin_api_key = fs.readFileSync(path.join(__dirname, 'crowdin_api_key.txt'))
//console.log('api key: ' + crowdin_api_key); //console.log('api key: ' + crowdin_api_key);
if (crowdin_api_key != '') { if (crowdin_api_key != '') {
@ -51,7 +51,8 @@ if (crowdin_api_key != '') {
}; };
bhttp.post('https://api.crowdin.com/api/project/' + crowdin_identifier + '/update-file?key=' + crowdin_api_key, payload, {}, function(err, response) { bhttp.post('https://api.crowdin.com/api/project/' + crowdin_identifier + '/update-file?key=' + crowdin_api_key, payload, {}, function(err, response) {
console.log('\nResponse from update file call:\n', response.body.toString()); if (!err) console.log('\nResponse from update file call:\n', response.body.toString());
else console.log('\nError from update file call:\n', err.toString());
// This call will tell the server to generate a new zip file for you based on most recent translations. // This call will tell the server to generate a new zip file for you based on most recent translations.
https.get('https://api.crowdin.com/api/project/' + crowdin_identifier + '/export?key=' + crowdin_api_key, function(res) { https.get('https://api.crowdin.com/api/project/' + crowdin_identifier + '/export?key=' + crowdin_api_key, function(res) {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -26,7 +26,10 @@ angular.module('copayApp.controllers').controller('activityController',
$scope.openNotificationModal = function(n) { $scope.openNotificationModal = function(n) {
if (n.txid) { if (n.txid) {
openTxModal(n); $state.transitionTo('tabs.wallet.tx-details', {
txid: n.txid,
walletId: n.walletId
});
} else { } else {
var txp = lodash.find($scope.txps, { var txp = lodash.find($scope.txps, {
id: n.txpId id: n.txpId
@ -46,35 +49,4 @@ angular.module('copayApp.controllers').controller('activityController',
} }
} }
}; };
var openTxModal = function(n) {
var wallet = profileService.getWallet(n.walletId);
ongoingProcess.set('loadingTxInfo', true);
walletService.getTx(wallet, n.txid, function(err, tx) {
ongoingProcess.set('loadingTxInfo', false);
if (err) {
$log.error(err);
return popupService.showAlert(gettextCatalog.getString('Error'), err);
}
if (!tx) {
$log.warn('No tx found');
return popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Transaction not found'));
}
$scope.wallet = wallet;
$scope.btx = lodash.cloneDeep(tx);
$state.transitionTo('tabs.wallet.tx-details', {
txid: $scope.btx.txid,
walletId: $scope.walletId
});
walletService.getTxNote(wallet, n.txid, function(err, note) {
if (err) $log.warn('Could not fetch transaction note: ' + err);
$scope.btx.note = note;
});
});
};
}); });

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('addressbookListController', function($scope, $log, $timeout, addressbookService, lodash, popupService) { angular.module('copayApp.controllers').controller('addressbookListController', function($scope, $log, $timeout, addressbookService, lodash, popupService, gettextCatalog) {
var contacts; var contacts;

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('addressbookAddController', function($scope, $state, $stateParams, $timeout, $ionicHistory, addressbookService, popupService) { angular.module('copayApp.controllers').controller('addressbookAddController', function($scope, $state, $stateParams, $timeout, $ionicHistory, gettextCatalog, addressbookService, popupService) {
$scope.fromSendTab = $stateParams.fromSendTab; $scope.fromSendTab = $stateParams.fromSendTab;

View file

@ -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();
}, 10);
};
$scope.readMore = function() {
$timeout(function() {
$scope.showMore = !$scope.showMore;
$ionicScrollDelegate.resize();
}, 10);
};
$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();
});
});

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('advancedSettingsController', function($scope, $rootScope, $log, $window, lodash, configService, uxLanguage, platformInfo, pushNotificationsService, profileService, feeService) { angular.module('copayApp.controllers').controller('advancedSettingsController', function($scope, $rootScope, $log, $window, lodash, configService, uxLanguage, platformInfo, pushNotificationsService, profileService, feeService, storageService, $ionicHistory, $timeout, $ionicScrollDelegate) {
var updateConfig = function() { var updateConfig = function() {
@ -29,6 +29,50 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
}; };
}; };
$scope.global = $rootScope;
if (!$scope.global.developmentUtilitiesEnabled) {
$scope.global.developmentUtilitiesEnabled = {
value: false
};
}
$scope.toggledDevelopmentUtils = function() {
if ($scope.global.developmentUtilitiesEnabled.value) {
$log.debug('User enabled development utilities.');
$timeout(function() {
$ionicScrollDelegate.resize();
}, 10);
} else {
$log.debug('User disabled development utilities.');
}
}
$scope.activateFeedbackCard = function() {
$scope.feedbackCardActivating = true;
storageService.getFeedbackInfo(function(error, info) {
var feedbackInfo = JSON.parse(info);
// hardcoding so we can distinguish from normal operation
feedbackInfo.time = 1231006505; // genesis block time
feedbackInfo.version = window.version;
feedbackInfo.sent = false;
storageService.setFeedbackInfo(JSON.stringify(feedbackInfo), function() {
$log.debug('Activated feedback card with: ' + JSON.stringify(feedbackInfo));
$ionicHistory.clearCache();
$timeout(function() {
$scope.feedbackCardActivating = false;
$scope.feedbackCardActivated = true;
$timeout(function() {
$scope.feedbackCardActivated = false;
}, 10000);
}, 500);
});
});
}
$scope.resetActivateFeedbackCard = function() {
$scope.feedbackCardActivated = false;
}
$scope.spendUnconfirmedChange = function() { $scope.spendUnconfirmedChange = function() {
var opts = { var opts = {
wallet: { wallet: {

View file

@ -1,12 +1,12 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('amazonController', angular.module('copayApp.controllers').controller('amazonController',
function($scope, $timeout, $ionicModal, $log, lodash, bwcError, amazonService, platformInfo, externalLinkService, popupService) { function($scope, $timeout, $ionicModal, $log, lodash, amazonService, platformInfo, externalLinkService, popupService, gettextCatalog) {
$scope.network = amazonService.getEnvironment(); $scope.network = amazonService.getEnvironment();
$scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) { $scope.openExternalLink = function(url) {
externalLinkService.open(url, optIn, title, message, okText, cancelText); externalLinkService.open(url);
}; };
var initAmazon = function() { var initAmazon = function() {
@ -19,6 +19,16 @@ angular.module('copayApp.controllers').controller('amazonController',
$timeout(function() { $timeout(function() {
$scope.$digest(); $scope.$digest();
}); });
if ($scope.cardClaimCode) {
var card = lodash.find($scope.giftCards, {
claimCode: $scope.cardClaimCode
});
if (lodash.isEmpty(card)) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Card not found'));
return;
}
$scope.openCardModal(card);
}
}); });
$scope.updatePendingGiftCards(); $scope.updatePendingGiftCards();
}; };
@ -26,12 +36,16 @@ angular.module('copayApp.controllers').controller('amazonController',
$scope.updatePendingGiftCards = lodash.debounce(function() { $scope.updatePendingGiftCards = lodash.debounce(function() {
amazonService.getPendingGiftCards(function(err, gcds) { amazonService.getPendingGiftCards(function(err, gcds) {
$timeout(function() {
$scope.giftCards = gcds;
$scope.$digest();
});
lodash.forEach(gcds, function(dataFromStorage) { lodash.forEach(gcds, function(dataFromStorage) {
if (dataFromStorage.status == 'PENDING') { if (dataFromStorage.status == 'PENDING') {
$log.debug("creating gift card"); $log.debug("creating gift card");
amazonService.createGiftCard(dataFromStorage, function(err, giftCard) { amazonService.createGiftCard(dataFromStorage, function(err, giftCard) {
if (err) { if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err)); popupService.showAlert(gettextCatalog.getString('Error'), err);
return; return;
} }
if (giftCard.status != 'PENDING') { if (giftCard.status != 'PENDING') {
@ -84,6 +98,7 @@ angular.module('copayApp.controllers').controller('amazonController',
}; };
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.cardClaimCode = data.stateParams.cardClaimCode;
initAmazon(); initAmazon();
}); });
}); });

View file

@ -1,34 +1,51 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('amountController', function($rootScope, $scope, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, txFormatService, ongoingProcess, bitpayCardService, popupService, bwcError, payproService) { angular.module('copayApp.controllers').controller('amountController', function($scope, $filter, $timeout, $ionicScrollDelegate, $ionicHistory, $ionicPopover, gettextCatalog, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, txFormatService, ongoingProcess, bitpayCardService, popupService, bwcError, payproService, profileService, bitcore, amazonService, glideraService) {
var unitToSatoshi; var unitToSatoshi;
var satToUnit; var satToUnit;
var unitDecimals; var unitDecimals;
var satToBtc; var satToBtc;
var self = $scope.self;
var SMALL_FONT_SIZE_LIMIT = 10; var SMALL_FONT_SIZE_LIMIT = 10;
var LENGTH_EXPRESSION_LIMIT = 19; var LENGTH_EXPRESSION_LIMIT = 19;
var MENU_ITEM_HEIGHT = 55;
$scope.$on('$ionicView.leave', function() { $scope.$on('$ionicView.leave', function() {
angular.element($window).off('keydown'); angular.element($window).off('keydown');
}); });
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isGiftCard = data.stateParams.isGiftCard;
// Glidera parameters
$scope.isGlidera = data.stateParams.isGlidera;
$scope.glideraAccessToken = data.stateParams.glideraAccessToken;
$scope.isWallet = data.stateParams.isWallet;
$scope.cardId = data.stateParams.cardId; $scope.cardId = data.stateParams.cardId;
$scope.showMenu = $ionicHistory.backView().stateName == 'tabs.send';
var isWallet = data.stateParams.isWallet || 'false';
$scope.isWallet = (isWallet.toString().trim().toLowerCase() == 'true' ? true : false);
$scope.toAddress = data.stateParams.toAddress; $scope.toAddress = data.stateParams.toAddress;
$scope.toName = data.stateParams.toName; $scope.toName = data.stateParams.toName;
$scope.toEmail = data.stateParams.toEmail; $scope.toEmail = data.stateParams.toEmail;
$scope.showAlternativeAmount = !!$scope.cardId; $scope.showAlternativeAmount = !!$scope.cardId || !!$scope.isGiftCard || !!$scope.isGlidera;
$scope.toColor = data.stateParams.toColor; $scope.toColor = data.stateParams.toColor;
if (!$scope.cardId && !$stateParams.toAddress) { $scope.customAmount = data.stateParams.customAmount;
if (!$scope.cardId && !$scope.isGiftCard && !$scope.isGlidera && !data.stateParams.toAddress) {
$log.error('Bad params at amount') $log.error('Bad params at amount')
throw ('bad params'); throw ('bad params');
} }
if ($scope.isGlidera) {
glideraService.getLimits($scope.glideraAccessToken, function(err, limits) {
$scope.limits = limits;
$timeout(function() {
$scope.$apply();
});
});
}
var reNr = /^[1234567890\.]$/; var reNr = /^[1234567890\.]$/;
var reOp = /^[\*\+\-\/]$/; var reOp = /^[\*\+\-\/]$/;
@ -49,8 +66,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}, 10); });
}); });
var config = configService.getSync().wallet.settings; var config = configService.getSync().wallet.settings;
@ -77,6 +93,35 @@ angular.module('copayApp.controllers').controller('amountController', function($
}, 10); }, 10);
}); });
$scope.showSendMaxMenu = function($event) {
var sendMaxObj = {
text: gettextCatalog.getString('Send max amount'),
action: setSendMax,
};
$scope.items = [sendMaxObj];
$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);
});
};
function setSendMax() {
$scope.menu.hide();
$state.transitionTo('tabs.send.confirm', {
isWallet: $scope.isWallet,
toAmount: null,
toAddress: $scope.toAddress,
toName: $scope.toName,
toEmail: $scope.toEmail,
useSendMax: true,
});
};
$scope.toggleAlternative = function() { $scope.toggleAlternative = function() {
$scope.showAlternativeAmount = !$scope.showAlternativeAmount; $scope.showAlternativeAmount = !$scope.showAlternativeAmount;
@ -121,7 +166,6 @@ angular.module('copayApp.controllers').controller('amountController', function($
function isExpression(val) { function isExpression(val) {
var regex = /^\.?\d+(\.?\d+)?([\/\-\+\*x]\d?\.?\d+)+$/; var regex = /^\.?\d+(\.?\d+)?([\/\-\+\*x]\d?\.?\d+)+$/;
return regex.test(val); return regex.test(val);
}; };
@ -134,7 +178,6 @@ angular.module('copayApp.controllers').controller('amountController', function($
$scope.resetAmount = function() { $scope.resetAmount = function() {
$scope.amount = $scope.alternativeResult = $scope.amountResult = $scope.globalResult = ''; $scope.amount = $scope.alternativeResult = $scope.amountResult = $scope.globalResult = '';
$scope.allowSend = false; $scope.allowSend = false;
checkFontSize(); checkFontSize();
}; };
@ -189,6 +232,20 @@ angular.module('copayApp.controllers').controller('amountController', function($
return result.replace('x', '*'); return result.replace('x', '*');
}; };
$scope.getRates = function() {
bitpayCardService.getRates($scope.alternativeIsoCode, function(err, res) {
if (err) {
$log.warn(err);
return;
}
if ($scope.unitName == 'bits') {
$scope.exchangeRate = '1,000,000 bits ~ ' + res.rate + ' ' + $scope.alternativeIsoCode;
} else {
$scope.exchangeRate = '1 BTC ~ ' + res.rate + ' ' + $scope.alternativeIsoCode;
}
});
};
$scope.finish = function() { $scope.finish = function() {
var _amount = evaluate(format($scope.amount)); var _amount = evaluate(format($scope.amount));
@ -199,6 +256,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
amount: amountUSD, amount: amountUSD,
currency: 'USD' currency: 'USD'
}; };
ongoingProcess.set('Preparing transaction...', true); ongoingProcess.set('Preparing transaction...', true);
$timeout(function() { $timeout(function() {
@ -238,15 +296,87 @@ angular.module('copayApp.controllers').controller('amountController', function($
}); });
}); });
} else if ($scope.isGiftCard) {
ongoingProcess.set('Preparing transaction...', true);
// Get first wallet as UUID
var uuid;
try {
uuid = profileService.getWallets({
onlyComplete: true,
network: 'livenet',
})[0].id;
} catch (err) {
ongoingProcess.set('Preparing transaction...', false);
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('No wallet found!'));
return;
};
var amountUSD = $scope.showAlternativeAmount ? _amount : $filter('formatFiatAmount')(toFiat(_amount));
var dataSrc = {
currency: 'USD',
amount: amountUSD,
uuid: uuid
};
amazonService.createBitPayInvoice(dataSrc, function(err, dataInvoice) {
if (err) {
ongoingProcess.set('Preparing transaction...', false);
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err));
return;
}
amazonService.getBitPayInvoice(dataInvoice.invoiceId, function(err, invoice) {
if (err) {
ongoingProcess.set('Preparing transaction...', false);
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err));
return;
}
var payProUrl = invoice.paymentUrls.BIP73;
payproService.getPayProDetails(payProUrl, function(err, payProDetails) {
ongoingProcess.set('Preparing transaction...', false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err));
return;
}
var stateParams = {
giftCardAmountUSD: amountUSD,
giftCardAccessKey: dataInvoice.accessKey,
giftCardInvoiceTime: invoice.invoiceTime,
giftCardUUID: dataSrc.uuid,
toAmount: payProDetails.amount,
toAddress: payProDetails.toAddress,
description: payProDetails.memo,
paypro: payProDetails
};
$state.transitionTo('tabs.giftcards.amazon.confirm', stateParams);
}, true);
});
});
} else if ($scope.isGlidera) {
var amount = $scope.showAlternativeAmount ? fromFiat(_amount) : _amount;
$state.transitionTo('tabs.buyandsell.glidera.confirm', {
toAmount: (amount * unitToSatoshi).toFixed(0),
isGlidera: $scope.isGlidera,
glideraAccessToken: $scope.glideraAccessToken
});
} else { } else {
var amount = $scope.showAlternativeAmount ? fromFiat(_amount) : _amount; var amount = $scope.showAlternativeAmount ? fromFiat(_amount) : _amount;
$state.transitionTo('tabs.send.confirm', { if ($scope.customAmount) {
isWallet: $scope.isWallet, $state.transitionTo('tabs.receive.customAmount', {
toAmount: (amount * unitToSatoshi).toFixed(0), toAmount: (amount * unitToSatoshi).toFixed(0),
toAddress: $scope.toAddress, toAddress: $scope.toAddress
toName: $scope.toName, });
toEmail: $scope.toEmail } else {
}); $state.transitionTo('tabs.send.confirm', {
isWallet: $scope.isWallet,
toAmount: (amount * unitToSatoshi).toFixed(0),
toAddress: $scope.toAddress,
toName: $scope.toName,
toEmail: $scope.toEmail
});
}
} }
}; };
}); });

View file

@ -72,7 +72,7 @@ angular.module('copayApp.controllers').controller('backupController',
var showBackupResult = function() { var showBackupResult = function() {
if ($scope.backupError) { if ($scope.backupError) {
var title = 'Uh oh...'; var title = gettextCatalog.getString('Uh oh...');
var message = gettextCatalog.getString("It's important that you write your backup phrase down correctly. If something happens to your wallet, you'll need this backup to recover your money. Please review your backup and try again."); var message = gettextCatalog.getString("It's important that you write your backup phrase down correctly. If something happens to your wallet, you'll need this backup to recover your money. Please review your backup and try again.");
popupService.showAlert(title, message, function() { popupService.showAlert(title, message, function() {
$scope.setFlow(2); $scope.setFlow(2);

View file

@ -63,7 +63,9 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi
if (err) { if (err) {
$log.error(err); $log.error(err);
$scope.error = gettextCatalog.getString('Could not get transactions'); self.bitpayCardTransactionHistory = null;
self.bitpayCardCurrentBalance = null;
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not get transactions'));
return; return;
} }

View file

@ -36,7 +36,7 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f
popupService.showConfirm(title, msg, ok, cancel, function(res) { popupService.showConfirm(title, msg, ok, cancel, function(res) {
if (res) { if (res) {
// Set flag for nextStep // Set flag for nextStep
storageService.setNextStep('BitpayCard', true, function(err) {}); storageService.setNextStep('BitpayCard', 'true', function(err) {});
// Save data // Save data
bitpayCardService.setBitpayDebitCards(data, function(err) { bitpayCardService.setBitpayDebitCards(data, function(err) {
if (err) return; if (err) return;

View file

@ -1,224 +0,0 @@
'use strict';
angular.module('copayApp.controllers').controller('buyAmazonController',
function($scope, $log, $timeout, $state, lodash, profileService, bwcError, gettextCatalog, configService, walletService, amazonService, ongoingProcess, platformInfo, externalLinkService, popupService) {
var self = this;
var network = amazonService.getEnvironment();
var wallet;
$scope.$on('Wallet/Changed', function(event, w) {
if (lodash.isEmpty(w)) {
$log.debug('No wallet provided');
return;
}
wallet = w;
$log.debug('Wallet changed: ' + w.name);
});
$scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) {
externalLinkService.open(url, optIn, title, message, okText, cancelText);
};
this.confirm = function() {
var message = gettextCatalog.getString('Amazon.com Gift Card purchase for ${{amount}} USD', {
amount: $scope.formData.fiat
});
var ok = gettextCatalog.getString('Buy');
popupService.showConfirm(null, message, ok, null, function(res) {
if (res) self.createTx();
});
};
this.createTx = function() {
self.errorInfo = null;
if (lodash.isEmpty(wallet)) return;
if (!wallet.canSign() && !wallet.isPrivKeyExternal()) {
$log.info('No signing proposal: No private key');
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg('MISSING_PRIVATE_KEY'));
return;
}
var dataSrc = {
currency: 'USD',
amount: $scope.formData.fiat,
uuid: wallet.id
};
var outputs = [];
var config = configService.getSync();
var configWallet = config.wallet;
var walletSettings = configWallet.settings;
ongoingProcess.set('Processing Transaction...', true);
$timeout(function() {
amazonService.createBitPayInvoice(dataSrc, function(err, dataInvoice) {
if (err) {
ongoingProcess.set('Processing Transaction...', false);
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err));
return;
}
amazonService.getBitPayInvoice(dataInvoice.invoiceId, function(err, invoice) {
if (err) {
ongoingProcess.set('Processing Transaction...', false);
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err));
return;
}
$log.debug('Fetch PayPro Request...', invoice.paymentUrls.BIP73);
wallet.fetchPayPro({
payProUrl: invoice.paymentUrls.BIP73,
}, function(err, paypro) {
if (err) {
ongoingProcess.set('Processing Transaction...', false);
$log.warn('Could not fetch payment request:', err);
var msg = err.toString();
if (msg.match('HTTP')) {
msg = gettextCatalog.getString('Could not fetch payment information');
}
popupService.showAlert(gettextCatalog.getString('Error'), msg);
return;
}
if (!paypro.verified) {
ongoingProcess.set('Processing Transaction...', false);
$log.warn('Failed to verify payment protocol signatures');
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Payment Protocol Invalid'));
$timeout(function() {
$scope.$digest();
});
return;
}
var address, comment, amount, url;
address = paypro.toAddress;
amount = paypro.amount;
url = paypro.url;
comment = 'Amazon.com Gift Card';
outputs.push({
'toAddress': address,
'amount': amount,
'message': comment
});
var txp = {
toAddress: address,
amount: amount,
outputs: outputs,
message: comment,
payProUrl: url,
excludeUnconfirmedUtxos: configWallet.spendUnconfirmed ? false : true,
feeLevel: walletSettings.feeLevel || 'normal'
};
walletService.createTx(wallet, txp, function(err, createdTxp) {
ongoingProcess.set('Processing Transaction...', false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err));
return;
}
walletService.publishAndSign(wallet, createdTxp, function(err, tx) {
if (err) {
ongoingProcess.set('Processing Transaction...', false);
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err));
walletService.removeTx(wallet, tx, function(err) {
if (err) $log.debug(err);
});
$timeout(function() {
$scope.$digest();
});
return;
}
var count = 0;
ongoingProcess.set('Processing Transaction...', true);
dataSrc.accessKey = dataInvoice.accessKey;
dataSrc.invoiceId = invoice.id;
dataSrc.invoiceUrl = invoice.url;
dataSrc.invoiceTime = invoice.invoiceTime;
self.debounceCreate(count, dataSrc);
});
});
});
});
});
}, 100);
};
self.debounceCreate = lodash.throttle(function(count, dataSrc) {
self.debounceCreateGiftCard(count, dataSrc);
}, 8000, {
'leading': true
});
self.debounceCreateGiftCard = function(count, dataSrc) {
amazonService.createGiftCard(dataSrc, function(err, giftCard) {
$log.debug("creating gift card " + count);
if (err) {
giftCard = {};
giftCard.status = 'FAILURE';
ongoingProcess.set('Processing Transaction...', false);
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err));
self.errorInfo = dataSrc;
$timeout(function() {
$scope.$digest();
});
}
if (giftCard.status == 'PENDING' && count < 3) {
$log.debug("pending gift card not available yet");
self.debounceCreate(count + 1, dataSrc, dataSrc);
return;
}
var now = moment().unix() * 1000;
var newData = giftCard;
newData['invoiceId'] = dataSrc.invoiceId;
newData['accessKey'] = dataSrc.accessKey;
newData['invoiceUrl'] = dataSrc.invoiceUrl;
newData['amount'] = dataSrc.amount;
newData['date'] = dataSrc.invoiceTime || now;
newData['uuid'] = dataSrc.uuid;
if (newData.status == 'expired') {
amazonService.savePendingGiftCard(newData, {
remove: true
}, function(err) {
return;
});
}
amazonService.savePendingGiftCard(newData, null, function(err) {
ongoingProcess.set('Processing Transaction...', false);
$log.debug("Saving new gift card with status: " + newData.status);
self.giftCard = newData;
if (newData.status == 'PENDING') $state.transitionTo('tabs.giftcards.amazon');
$timeout(function() {
$scope.$digest();
});
});
});
};
$scope.$on("$ionicView.enter", function(event, data) {
$scope.formData = {
fiat: null
};
$scope.wallets = profileService.getWallets({
network: network,
onlyComplete: true
});
});
});

View file

@ -1,146 +0,0 @@
'use strict';
angular.module('copayApp.controllers').controller('buyGlideraController',
function($scope, $timeout, $log, profileService, walletService, glideraService, bwcError, lodash, ongoingProcess, popupService, gettextCatalog) {
var wallet;
var self = this;
this.show2faCodeInput = null;
this.success = null;
$scope.network = glideraService.getEnvironment();
$scope.$on('Wallet/Changed', function(event, w) {
if (lodash.isEmpty(w)) {
$log.debug('No wallet provided');
return;
}
wallet = w;
$log.debug('Wallet changed: ' + w.name);
});
$scope.update = function(opts) {
if (!$scope.token || !$scope.permissions) return;
$log.debug('Updating Glidera Account...');
var accessToken = $scope.token;
var permissions = $scope.permissions;
opts = opts || {};
glideraService.getStatus(accessToken, function(err, data) {
$scope.status = data;
});
glideraService.getLimits(accessToken, function(err, limits) {
$scope.limits = limits;
});
if (permissions.transaction_history) {
glideraService.getTransactions(accessToken, function(err, data) {
$scope.txs = data;
});
}
if (permissions.view_email_address && opts.fullUpdate) {
glideraService.getEmail(accessToken, function(err, data) {
$scope.email = data.email;
});
}
if (permissions.personal_info && opts.fullUpdate) {
glideraService.getPersonalInfo(accessToken, function(err, data) {
$scope.personalInfo = data;
});
}
};
this.getBuyPrice = function(token, price) {
var self = this;
if (!price || (price && !price.qty && !price.fiat)) {
this.buyPrice = null;
return;
}
this.gettingBuyPrice = true;
glideraService.buyPrice(token, price, function(err, buyPrice) {
self.gettingBuyPrice = false;
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not get exchange information. Please, try again'));
return;
}
self.buyPrice = buyPrice;
});
};
this.get2faCode = function(token) {
var self = this;
ongoingProcess.set('Sending 2FA code...', true);
$timeout(function() {
glideraService.get2faCode(token, function(err, sent) {
ongoingProcess.set('Sending 2FA code...', false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not send confirmation code to your phone'));
return;
}
self.show2faCodeInput = sent;
});
}, 100);
};
this.sendRequest = function(token, permissions, twoFaCode) {
var self = this;
ongoingProcess.set('Buying Bitcoin...', true);
$timeout(function() {
walletService.getAddress(wallet, false, function(err, walletAddr) {
if (err) {
ongoingProcess.set('Buying Bitcoin...', false);
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.cb(err, 'Could not create address'));
return;
}
var data = {
destinationAddress: walletAddr,
qty: self.buyPrice.qty,
priceUuid: self.buyPrice.priceUuid,
useCurrentPrice: false,
ip: null
};
glideraService.buy(token, twoFaCode, data, function(err, data) {
ongoingProcess.set('Buying Bitcoin...', false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
self.success = data;
$timeout(function() {
$scope.$digest();
});
});
});
}, 100);
};
$scope.$on("$ionicView.enter", function(event, data){
$scope.token = null;
$scope.permissions = null;
$scope.email = null;
$scope.personalInfo = null;
$scope.txs = null;
$scope.status = null;
$scope.limits = null;
ongoingProcess.set('connectingGlidera', true);
glideraService.init($scope.token, function(err, glidera) {
ongoingProcess.set('connectingGlidera');
if (err || !glidera) {
if (err) popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
$scope.token = glidera.token;
$scope.permissions = glidera.permissions;
$scope.update({fullUpdate: true});
});
$scope.wallets = profileService.getWallets({
network: $scope.network,
onlyComplete: true
});
});
});

View file

@ -1,103 +1,240 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, walletService, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, profileService, bitcore, gettext, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService) { angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, walletService, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, profileService, bitcore, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService, feeService, amazonService, glideraService, bwcError, bitpayCardService) {
var cachedTxp = {}; var cachedTxp = {};
var toAmount;
var isChromeApp = platformInfo.isChromeApp; var isChromeApp = platformInfo.isChromeApp;
var countDown = null; var countDown = null;
var giftCardAmountUSD;
var giftCardAccessKey;
var giftCardInvoiceTime;
var giftCardUUID;
var cachedSendMax = {};
$scope.isCordova = platformInfo.isCordova; $scope.isCordova = platformInfo.isCordova;
$ionicConfig.views.swipeBackEnabled(false); $ionicConfig.views.swipeBackEnabled(false);
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isWallet = data.stateParams.isWallet; // Amazon.com Gift Card parameters
$scope.isGiftCard = data.stateParams.isGiftCard;
giftCardAmountUSD = data.stateParams.giftCardAmountUSD;
giftCardAccessKey = data.stateParams.giftCardAccessKey;
giftCardInvoiceTime = data.stateParams.giftCardInvoiceTime;
giftCardUUID = data.stateParams.giftCardUUID;
// Glidera parameters
$scope.isGlidera = data.stateParams.isGlidera;
$scope.glideraAccessToken = data.stateParams.glideraAccessToken;
toAmount = data.stateParams.toAmount;
cachedSendMax = {};
$scope.useSendMax = data.stateParams.useSendMax == 'true' ? true : false;
var isWallet = data.stateParams.isWallet || 'false';
$scope.isWallet = (isWallet.toString().trim().toLowerCase() == 'true' ? true : false);
$scope.cardId = data.stateParams.cardId; $scope.cardId = data.stateParams.cardId;
$scope.toAmount = data.stateParams.toAmount;
$scope.toAddress = data.stateParams.toAddress; $scope.toAddress = data.stateParams.toAddress;
$scope.toName = data.stateParams.toName; $scope.toName = data.stateParams.toName;
$scope.toEmail = data.stateParams.toEmail; $scope.toEmail = data.stateParams.toEmail;
$scope.description = data.stateParams.description; $scope.description = data.stateParams.description;
$scope.paypro = data.stateParams.paypro; $scope.paypro = data.stateParams.paypro;
$scope.insufficientFunds = false;
$scope.noMatchingWallet = false;
$scope.paymentExpired = { $scope.paymentExpired = {
value: false value: false
}; };
$scope.remainingTimeStr = { $scope.remainingTimeStr = {
value: null value: null
}; };
initConfirm();
});
var initConfirm = function() {
// TODO (URL , etc)
if (!$scope.toAddress || !$scope.toAmount) {
$log.error('Bad params at amount');
throw ('bad params');
}
var config = configService.getSync().wallet; var config = configService.getSync().wallet;
$scope.feeLevel = config.settings && config.settings.feeLevel ? config.settings.feeLevel : 'normal'; var feeLevel = config.settings && config.settings.feeLevel ? config.settings.feeLevel : 'normal';
$scope.feeLevel = feeService.feeOpts[feeLevel];
if ($scope.isGlidera) $scope.network = glideraService.getEnvironment();
else $scope.network = (new bitcore.Address($scope.toAddress)).network.name;
resetValues();
setwallets();
});
$scope.toAmount = parseInt($scope.toAmount); function setwallets() {
$scope.amountStr = txFormatService.formatAmountStr($scope.toAmount); $scope.wallets = profileService.getWallets({
$scope.displayAmount = getDisplayAmount($scope.amountStr);
$scope.displayUnit = getDisplayUnit($scope.amountStr);
var networkName = (new bitcore.Address($scope.toAddress)).network.name;
$scope.network = networkName;
$scope.insuffientFunds = false;
$scope.noMatchingWallet = false;
var wallets = profileService.getWallets({
onlyComplete: true, onlyComplete: true,
network: networkName, network: $scope.network
}); });
if (!wallets || !wallets.length) { if (!$scope.wallets || !$scope.wallets.length) {
$scope.noMatchingWallet = true; $scope.noMatchingWallet = true;
if ($scope.paypro) {
displayValues();
}
$timeout(function() {
$scope.$apply();
});
return;
}
if ($scope.isGlidera == 'buy') {
initConfirm();
return;
} }
var filteredWallets = []; var filteredWallets = [];
var index = 0; var index = 0;
var enoughFunds = false; var enoughFunds = false;
lodash.each(wallets, function(w) { lodash.each($scope.wallets, function(w) {
walletService.getStatus(w, {}, function(err, status) { walletService.getStatus(w, {}, function(err, status) {
if (err || !status) { if (err || !status) {
$log.error(err); $log.error(err);
} else { } else {
w.status = status; w.status = status;
if (!status.availableBalanceSat) $log.debug('No balance available in: ' + w.name); if (!status.availableBalanceSat) $log.debug('No balance available in: ' + w.name);
if (status.availableBalanceSat > $scope.toAmount) { if (status.availableBalanceSat > toAmount) {
filteredWallets.push(w); filteredWallets.push(w);
enoughFunds = true; enoughFunds = true;
} }
} }
if (++index == wallets.length) { if (++index == $scope.wallets.length) {
if (!lodash.isEmpty(filteredWallets)) { if (!lodash.isEmpty(filteredWallets)) {
$scope.wallets = lodash.clone(filteredWallets); $scope.wallets = lodash.clone(filteredWallets);
setWallet($scope.wallets[0]); if ($scope.useSendMax) {
if ($scope.wallets.length > 1)
$scope.showWalletSelector();
else {
$scope.wallet = $scope.wallets[0];
$scope.getSendMaxInfo();
}
} else initConfirm();
} else { } else {
if (!enoughFunds) $scope.insufficientFunds = true;
if (!enoughFunds)
$scope.insuffientFunds = true;
$log.warn('No wallet available to make the payment'); $log.warn('No wallet available to make the payment');
$timeout(function() {
$scope.$apply();
});
} }
$timeout(function() {
$scope.$apply();
});
} }
}); });
}); });
};
txFormatService.formatAlternativeStr($scope.toAmount, function(v) { var initConfirm = function() {
if ($scope.paypro) _paymentTimeControl($scope.paypro.expires);
displayValues();
if ($scope.wallets.length > 1) $scope.showWalletSelector();
else setWallet($scope.wallets[0]);
$timeout(function() {
$scope.$apply();
});
};
function displayValues() {
toAmount = parseInt(toAmount);
$scope.amountStr = txFormatService.formatAmountStr(toAmount);
$scope.displayAmount = getDisplayAmount($scope.amountStr);
$scope.displayUnit = getDisplayUnit($scope.amountStr);
txFormatService.formatAlternativeStr(toAmount, function(v) {
$scope.alternativeAmountStr = v; $scope.alternativeAmountStr = v;
}); });
if ($scope.isGlidera == 'buy') $scope.getBuyPrice();
if ($scope.isGlidera == 'sell') $scope.getSellPrice();
};
if($scope.paypro) { function resetValues() {
_paymentTimeControl($scope.paypro.expires); $scope.displayAmount = $scope.displayUnit = $scope.fee = $scope.alternativeAmountStr = $scope.insufficientFunds = $scope.noMatchingWallet = null;
} };
$scope.getSendMaxInfo = function() {
resetValues();
ongoingProcess.set('gettingFeeLevels', true);
feeService.getCurrentFeeValue($scope.network, function(err, feePerKb) {
ongoingProcess.set('gettingFeeLevels', false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err.message);
return;
}
var config = configService.getSync().wallet;
ongoingProcess.set('retrievingInputs', true);
walletService.getSendMaxInfo($scope.wallet, {
feePerKb: feePerKb,
excludeUnconfirmedUtxos: !config.spendUnconfirmed,
returnInputs: true,
}, function(err, resp) {
ongoingProcess.set('retrievingInputs', false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
if (resp.amount == 0) {
$scope.insufficientFunds = true;
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'));
return;
}
$scope.sendMaxInfo = {
sendMax: true,
amount: resp.amount,
inputs: resp.inputs,
fee: resp.fee,
feePerKb: feePerKb,
};
cachedSendMax[$scope.wallet.id] = $scope.sendMaxInfo;
var msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees.", {
fee: txFormatService.formatAmountStr(resp.fee)
});
var warningMsg = verifyExcludedUtxos();
if (!lodash.isEmpty(warningMsg))
msg += '\n' + warningMsg;
popupService.showAlert(null, msg, function() {
setSendMaxValues(resp);
createTx($scope.wallet, true, function(err, txp) {
if (err) return;
cachedTxp[$scope.wallet.id] = txp;
apply(txp);
});
});
function verifyExcludedUtxos() {
var warningMsg = [];
if (resp.utxosBelowFee > 0) {
warningMsg.push(gettextCatalog.getString("A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided.", {
amountBelowFeeStr: txFormatService.formatAmountStr(resp.amountBelowFee)
}));
}
if (resp.utxosAboveMaxSize > 0) {
warningMsg.push(gettextCatalog.getString("A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size allowed for a transaction was exceeded.", {
amountAboveMaxSizeStr: txFormatService.formatAmountStr(resp.amountAboveMaxSize)
}));
}
return warningMsg.join('\n');
};
});
});
};
function setSendMaxValues(data) {
resetValues();
var config = configService.getSync().wallet;
var unitToSatoshi = config.settings.unitToSatoshi;
var satToUnit = 1 / unitToSatoshi;
var unitDecimals = config.settings.unitDecimals;
$scope.amountStr = txFormatService.formatAmountStr(data.amount, true);
$scope.displayAmount = getDisplayAmount($scope.amountStr);
$scope.displayUnit = getDisplayUnit($scope.amountStr);
$scope.fee = txFormatService.formatAmountStr(data.fee);
toAmount = parseFloat((data.amount * satToUnit).toFixed(unitDecimals));
txFormatService.formatAlternativeStr(data.amount, function(v) {
$scope.alternativeAmountStr = v;
});
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
@ -107,24 +244,25 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.approve(); $scope.approve();
}); });
$scope.$on('Wallet/Changed', function(event, wallet) {
if (lodash.isEmpty(wallet)) {
$log.debug('No wallet provided');
return;
}
$log.debug('Wallet changed: ' + wallet.name);
setWallet(wallet, true);
});
$scope.showWalletSelector = function() { $scope.showWalletSelector = function() {
$scope.walletSelectorTitle = $scope.isGlidera == 'buy' ? 'Receive in' : $scope.isGlidera == 'sell' ? 'Sell From' : gettextCatalog.getString('Send from');
if (!$scope.useSendMax && ($scope.insufficientFunds || $scope.noMatchingWallet)) return;
$scope.showWallets = true; $scope.showWallets = true;
}; };
$scope.onWalletSelect = function(wallet) { $scope.onWalletSelect = function(wallet) {
setWallet(wallet); if ($scope.useSendMax) {
$scope.wallet = wallet;
if (cachedSendMax[wallet.id]) {
$log.debug('Send max cached for wallet:', wallet.id);
setSendMaxValues(cachedSendMax[wallet.id]);
return;
}
$scope.getSendMaxInfo();
} else
setWallet(wallet);
}; };
$scope.showDescriptionPopup = function() { $scope.showDescriptionPopup = function() {
var message = gettextCatalog.getString('Add description'); var message = gettextCatalog.getString('Add description');
var opts = { var opts = {
@ -135,17 +273,17 @@ angular.module('copayApp.controllers').controller('confirmController', function(
if (typeof res != 'undefined') $scope.description = res; if (typeof res != 'undefined') $scope.description = res;
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}, 100); });
}); });
}; };
function getDisplayAmount(amountStr) { function getDisplayAmount(amountStr) {
return amountStr.split(' ')[0]; return $scope.amountStr.split(' ')[0];
} };
function getDisplayUnit(amountStr) { function getDisplayUnit(amountStr) {
return amountStr.split(' ')[1]; return $scope.amountStr.split(' ')[1];
} };
function _paymentTimeControl(expirationTime) { function _paymentTimeControl(expirationTime) {
$scope.paymentExpired.value = false; $scope.paymentExpired.value = false;
@ -167,7 +305,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
var m = Math.floor(totalSecs / 60); var m = Math.floor(totalSecs / 60);
var s = totalSecs % 60; var s = totalSecs % 60;
$scope.remainingTimeStr.value = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2); $scope.remainingTimeStr.value = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
} };
function setExpiredValues() { function setExpiredValues() {
$scope.paymentExpired.value = true; $scope.paymentExpired.value = true;
@ -176,19 +314,15 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
} };
} };
function setWallet(wallet, delayed) { function setWallet(wallet, delayed) {
var stop; var stop;
$scope.wallet = wallet; $scope.wallet = wallet;
$scope.fee = $scope.txp = null; $scope.fee = $scope.txp = null;
$timeout(function() { if ($scope.isGlidera) return;
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
if (stop) { if (stop) {
$timeout.cancel(stop); $timeout.cancel(stop);
stop = null; stop = null;
@ -205,67 +339,75 @@ angular.module('copayApp.controllers').controller('confirmController', function(
}); });
}, delayed ? 2000 : 1); }, delayed ? 2000 : 1);
} }
}
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
};
var setSendError = function(msg) { var setSendError = function(msg) {
$scope.sendStatus = ''; $scope.sendStatus = '';
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
popupService.showAlert(gettextCatalog.getString('Error at confirm:'), msg); popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg));
}; };
function apply(txp) { function apply(txp) {
$scope.fee = txFormatService.formatAmountStr(txp.fee); $scope.fee = txFormatService.formatAmountStr(txp.fee);
$scope.txp = txp; $scope.txp = txp;
$scope.$apply(); $timeout(function() {
} $scope.$apply();
});
};
var createTx = function(wallet, dryRun, cb) { var createTx = function(wallet, dryRun, cb) {
var config = configService.getSync().wallet; var config = configService.getSync().wallet;
var currentSpendUnconfirmed = config.spendUnconfirmed; var currentSpendUnconfirmed = config.spendUnconfirmed;
var outputs = [];
var paypro = $scope.paypro; var paypro = $scope.paypro;
var toAddress = $scope.toAddress; var toAddress = $scope.toAddress;
var toAmount = $scope.toAmount;
var description = $scope.description; var description = $scope.description;
var unitToSatoshi = config.settings.unitToSatoshi;
var unitDecimals = config.settings.unitDecimals;
// ToDo: use a credential's (or fc's) function for this // ToDo: use a credential's (or fc's) function for this
if (description && !wallet.credentials.sharedEncryptingKey) { if (description && !wallet.credentials.sharedEncryptingKey) {
var msg = 'Could not add message to imported wallet without shared encrypting key'; var msg = gettextCatalog.getString('Could not add message to imported wallet without shared encrypting key');
$log.warn(msg); $log.warn(msg);
return setSendError(msg); return setSendError(msg);
} }
if (toAmount > Number.MAX_SAFE_INTEGER) { if (toAmount > Number.MAX_SAFE_INTEGER) {
var msg = 'Amount too big'; var msg = gettextCatalog.getString('Amount too big');
$log.warn(msg); $log.warn(msg);
return setSendError(msg); return setSendError(msg);
} }
outputs.push({
'toAddress': toAddress,
'amount': toAmount,
'message': description
});
var txp = {}; var txp = {};
var amount;
// TODO if ($scope.useSendMax) amount = parseFloat((toAmount * unitToSatoshi).toFixed(0));
if (!lodash.isEmpty($scope.sendMaxInfo)) { else amount = toAmount;
txp.sendMax = true;
txp.outputs = [{
'toAddress': toAddress,
'amount': amount,
'message': description
}];
if ($scope.sendMaxInfo) {
txp.inputs = $scope.sendMaxInfo.inputs; txp.inputs = $scope.sendMaxInfo.inputs;
txp.fee = $scope.sendMaxInfo.fee; txp.fee = $scope.sendMaxInfo.fee;
} } else
txp.feeLevel = config.settings && config.settings.feeLevel ? config.settings.feeLevel : 'normal';
txp.outputs = outputs;
txp.message = description; txp.message = description;
if(paypro) {
if (paypro) {
txp.payProUrl = paypro.url; txp.payProUrl = paypro.url;
} }
txp.excludeUnconfirmedUtxos = config.spendUnconfirmed ? false : true; txp.excludeUnconfirmedUtxos = !currentSpendUnconfirmed;
txp.feeLevel = config.settings && config.settings.feeLevel ? config.settings.feeLevel : 'normal';
txp.dryRun = dryRun; txp.dryRun = dryRun;
walletService.createTx(wallet, txp, function(err, ctxp) { walletService.createTx(wallet, txp, function(err, ctxp) {
@ -288,6 +430,11 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.approve = function(onSendStatusChange) { $scope.approve = function(onSendStatusChange) {
var wallet = $scope.wallet;
if (!wallet) {
return;
}
if ($scope.paypro && $scope.paymentExpired.value) { if ($scope.paypro && $scope.paymentExpired.value) {
popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.')); popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.'));
$scope.sendStatus = ''; $scope.sendStatus = '';
@ -297,17 +444,45 @@ angular.module('copayApp.controllers').controller('confirmController', function(
return; return;
} }
var wallet = $scope.wallet; if ($scope.isGlidera) {
if (!wallet) { $scope.get2faCode(function(err, sent) {
return setSendError(gettextCatalog.getString('No wallet selected')); if (err) {
} popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not send confirmation code to your phone'));
return;
if (!wallet.canSign() && !wallet.isPrivKeyExternal()) { }
$log.info('No signing proposal: No private key'); if (sent) {
var title = gettextCatalog.getString("Please, enter the code below");
return walletService.onlyPublish(wallet, txp, function(err, txp) { var message = gettextCatalog.getString("A SMS containing a confirmation code was sent to your phone.");
if (err) return setSendError(err); popupService.showPrompt(title, message, null, function(twoFaCode) {
if (typeof twoFaCode == 'undefined') return;
if ($scope.isGlidera == 'buy') {
$scope.buyRequest(wallet, twoFaCode, function(err, data) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
$scope.sendStatus = 'success';
$timeout(function() {
$scope.$digest();
});
})
}
if ($scope.isGlidera == 'sell') {
$scope.sellRequest(wallet, twoFaCode, function(err, data) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
$scope.sendStatus = 'success';
$timeout(function() {
$scope.$digest();
});
})
}
});
}
}); });
return;
} }
ongoingProcess.set('creatingTx', true, onSendStatusChange); ongoingProcess.set('creatingTx', true, onSendStatusChange);
@ -356,13 +531,18 @@ angular.module('copayApp.controllers').controller('confirmController', function(
function statusChangeHandler(processName, showName, isOn) { function statusChangeHandler(processName, showName, isOn) {
$log.debug('statusChangeHandler: ', processName, showName, isOn); $log.debug('statusChangeHandler: ', processName, showName, isOn);
if ((processName === 'broadcastingTx' || ((processName === 'signingTx') && $scope.wallet.m > 1)) && !isOn) { if (
(
processName === 'broadcastingTx' ||
((processName === 'signingTx') && $scope.wallet.m > 1) ||
(processName == 'sendingTx' && !$scope.wallet.canSign() && !$scope.wallet.isPrivKeyExternal())
) && !isOn) {
$scope.sendStatus = 'success'; $scope.sendStatus = 'success';
$scope.$digest(); $scope.$digest();
} else if (showName) { } else if (showName) {
$scope.sendStatus = showName; $scope.sendStatus = showName;
} }
} };
$scope.statusChangeHandler = statusChangeHandler; $scope.statusChangeHandler = statusChangeHandler;
@ -373,6 +553,8 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.onSuccessConfirm = function() { $scope.onSuccessConfirm = function() {
var previousView = $ionicHistory.viewHistory().backView && $ionicHistory.viewHistory().backView.stateName; var previousView = $ionicHistory.viewHistory().backView && $ionicHistory.viewHistory().backView.stateName;
var fromBitPayCard = previousView.match(/tabs.bitpayCard/) ? true : false; var fromBitPayCard = previousView.match(/tabs.bitpayCard/) ? true : false;
var fromAmazon = previousView.match(/tabs.giftcards.amazon/) ? true : false;
var fromGlidera = previousView.match(/tabs.buyandsell.glidera/) ? true : false;
$ionicHistory.nextViewOptions({ $ionicHistory.nextViewOptions({
disableAnimate: true disableAnimate: true
@ -386,14 +568,288 @@ angular.module('copayApp.controllers').controller('confirmController', function(
id: $stateParams.cardId id: $stateParams.cardId
}); });
}, 100); }, 100);
} else if (fromAmazon) {
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$ionicHistory.clearHistory();
$state.go('tabs.home').then(function() {
$state.transitionTo('tabs.giftcards.amazon', {
cardClaimCode: $scope.amazonGiftCard ? $scope.amazonGiftCard.claimCode : null
});
});
} else if (fromGlidera) {
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$ionicHistory.clearHistory();
$state.go('tabs.home').then(function() {
$state.transitionTo('tabs.buyandsell.glidera');
});
} else { } else {
$state.go('tabs.send'); $ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$ionicHistory.clearHistory();
$state.go('tabs.send').then(function() {
$state.transitionTo('tabs.home');
});
} }
}; };
$scope.get2faCode = function(cb) {
ongoingProcess.set('sending2faCode', true);
$timeout(function() {
glideraService.get2faCode($scope.glideraAccessToken, function(err, sent) {
ongoingProcess.set('sending2faCode', false);
return cb(err, sent);
});
}, 100);
};
$scope.buyRequest = function(wallet, twoFaCode, cb) {
ongoingProcess.set('buyingBitcoin', true);
$timeout(function() {
walletService.getAddress(wallet, false, function(err, walletAddr) {
if (err) {
ongoingProcess.set('buyingBitcoin', false);
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.cb(err, 'Could not create address'));
return;
}
var data = {
destinationAddress: walletAddr,
qty: $scope.buyPrice.qty,
priceUuid: $scope.buyPrice.priceUuid,
useCurrentPrice: false,
ip: null
};
glideraService.buy($scope.glideraAccessToken, twoFaCode, data, function(err, data) {
ongoingProcess.set('buyingBitcoin', false);
return cb(err, data);
});
});
}, 100);
};
$scope.sellRequest = function(wallet, twoFaCode, cb) {
var outputs = [];
var config = configService.getSync();
var configWallet = config.wallet;
var walletSettings = configWallet.settings;
ongoingProcess.set('creatingTx', true);
walletService.getAddress(wallet, null, function(err, refundAddress) {
if (!refundAddress) {
ongoingProcess.clear();
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err, 'Could not create address'));
return;
}
glideraService.getSellAddress($scope.glideraAccessToken, function(err, sellAddress) {
if (!sellAddress || err) {
ongoingProcess.clear();
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not get the destination bitcoin address'));
return;
}
var amount = parseInt(($scope.sellPrice.qty * 100000000).toFixed(0));
var comment = 'Glidera transaction';
outputs.push({
'toAddress': sellAddress,
'amount': amount,
'message': comment
});
var txp = {
toAddress: sellAddress,
amount: amount,
outputs: outputs,
message: comment,
payProUrl: null,
excludeUnconfirmedUtxos: configWallet.spendUnconfirmed ? false : true,
feeLevel: walletSettings.feeLevel || 'normal',
customData: {
'glideraToken': $scope.glideraAccessToken
}
};
walletService.createTx(wallet, txp, function(err, createdTxp) {
ongoingProcess.clear();
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err.message || bwcError.msg(err));
return;
}
walletService.prepare(wallet, function(err, password) {
if (err) {
ongoingProcess.clear();
popupService.showAlert(gettextCatalog.getString('Error'), err.message || bwcError.msg(err));
return;
}
ongoingProcess.set('signingTx', true);
walletService.publishTx(wallet, createdTxp, function(err, publishedTxp) {
if (err) {
ongoingProcess.clear();
popupService.showAlert(gettextCatalog.getString('Error'), err.message ||  bwcError.msg(err));
return;
}
walletService.signTx(wallet, publishedTxp, password, function(err, signedTxp) {
if (err) {
ongoingProcess.clear();
popupService.showAlert(gettextCatalog.getString('Error'), err.message ||  bwcError.msg(err));
walletService.removeTx(wallet, signedTxp, function(err) {
if (err) $log.debug(err);
});
return;
}
var rawTx = signedTxp.raw;
var data = {
refundAddress: refundAddress,
signedTransaction: rawTx,
priceUuid: $scope.sellPrice.priceUuid,
useCurrentPrice: $scope.sellPrice.priceUuid ? false : true,
ip: null
};
ongoingProcess.set('sellingBitcoin', true);
glideraService.sell($scope.glideraAccessToken, twoFaCode, data, function(err, data) {
ongoingProcess.clear();
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err.message ||  bwcError.msg(err));
return;
}
return cb(err, data)
});
});
});
});
});
});
});
}
$scope.getBuyPrice = function() {
var satToBtc = 1 / 100000000;
var price = {};
price.qty = (toAmount * satToBtc).toFixed(8);
glideraService.buyPrice($scope.glideraAccessToken, price, function(err, buyPrice) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), 'Could not get exchange information. Please, try again');
return;
}
$scope.buyPrice = buyPrice;
});
};
$scope.getSellPrice = function() {
var satToBtc = 1 / 100000000;
var price = {};
price.qty = (toAmount * satToBtc).toFixed(8);
glideraService.sellPrice($scope.glideraAccessToken, price, function(err, sellPrice) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), 'Could not get exchange information. Please, try again');
return;
}
$scope.sellPrice = sellPrice;
});
};
function publishAndSign(wallet, txp, onSendStatusChange) { function publishAndSign(wallet, txp, onSendStatusChange) {
if (!wallet.canSign() && !wallet.isPrivKeyExternal()) {
$log.info('No signing proposal: No private key');
return walletService.onlyPublish(wallet, txp, function(err) {
if (err) setSendError(err);
}, onSendStatusChange);
}
walletService.publishAndSign(wallet, txp, function(err, txp) { walletService.publishAndSign(wallet, txp, function(err, txp) {
if (err) return setSendError(err); if (err) return setSendError(err);
var previousView = $ionicHistory.viewHistory().backView && $ionicHistory.viewHistory().backView.stateName;
var fromAmazon = previousView.match(/tabs.giftcards.amazon/) ? true : false;
if (fromAmazon) {
var count = 0;
var invoiceId = JSON.parse($scope.paypro.merchant_data).invoiceId;
var dataSrc = {
currency: 'USD',
amount: giftCardAmountUSD,
uuid: giftCardUUID,
accessKey: giftCardAccessKey,
invoiceId: invoiceId,
invoiceUrl: $scope.paypro.url,
invoiceTime: giftCardInvoiceTime
};
debounceCreate(count, dataSrc, onSendStatusChange);
}
}, onSendStatusChange); }, onSendStatusChange);
} }
var debounceCreate = lodash.throttle(function(count, dataSrc) {
debounceCreateGiftCard(count, dataSrc);
}, 8000, {
'leading': true
});
var debounceCreateGiftCard = function(count, dataSrc, onSendStatusChange) {
amazonService.createGiftCard(dataSrc, function(err, giftCard) {
$log.debug("creating gift card " + count);
if (err) {
giftCard = {};
giftCard.status = 'FAILURE';
popupService.showAlert(gettextCatalog.getString('Error'), err);
}
if (giftCard.status == 'PENDING' && count < 3) {
$log.debug("pending gift card not available yet");
debounceCreate(count + 1, dataSrc);
return;
}
var now = moment().unix() * 1000;
var newData = giftCard;
newData['invoiceId'] = dataSrc.invoiceId;
newData['accessKey'] = dataSrc.accessKey;
newData['invoiceUrl'] = dataSrc.invoiceUrl;
newData['amount'] = dataSrc.amount;
newData['date'] = dataSrc.invoiceTime || now;
newData['uuid'] = dataSrc.uuid;
if (newData.status == 'expired') {
amazonService.savePendingGiftCard(newData, {
remove: true
}, function(err) {
$log.error(err);
return;
});
}
amazonService.savePendingGiftCard(newData, null, function(err) {
$log.debug("Saving new gift card with status: " + newData.status);
$scope.amazonGiftCard = newData;
});
});
};
$scope.getRates = function() {
var config = configService.getSync().wallet.settings;
var unitName = config.unitName;
var alternativeIsoCode = config.alternativeIsoCode;
bitpayCardService.getRates(alternativeIsoCode, function(err, res) {
if (err) {
$log.warn(err);
return;
}
if (unitName == 'bits') {
$scope.exchangeRate = '1,000,000 bits ~ ' + res.rate + ' ' + alternativeIsoCode;
} else {
$scope.exchangeRate = '1 BTC ~ ' + res.rate + ' ' + alternativeIsoCode;
}
});
};
}); });

View file

@ -79,8 +79,8 @@ angular.module('copayApp.controllers').controller('copayersController',
}; };
$scope.goHome = function() { $scope.goHome = function() {
$ionicHistory.removeBackView();
$state.go('tabs.home'); $state.go('tabs.home');
$ionicHistory.clearHistory();
}; };
}); });

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('createController', angular.module('copayApp.controllers').controller('createController',
function($scope, $rootScope, $timeout, $log, lodash, $state, $ionicScrollDelegate, $ionicHistory, profileService, configService, gettext, gettextCatalog, ledger, trezor, platformInfo, derivationPathHelper, ongoingProcess, walletService, storageService, popupService) { function($scope, $rootScope, $timeout, $log, lodash, $state, $ionicScrollDelegate, $ionicHistory, profileService, configService, gettextCatalog, ledger, trezor, platformInfo, derivationPathHelper, ongoingProcess, walletService, storageService, popupService, $window) {
var isChromeApp = platformInfo.isChromeApp; var isChromeApp = platformInfo.isChromeApp;
var isCordova = platformInfo.isCordova; var isCordova = platformInfo.isCordova;
@ -33,7 +33,6 @@ angular.module('copayApp.controllers').controller('createController',
$scope.formData.derivationPath = derivationPathHelper.default; $scope.formData.derivationPath = derivationPathHelper.default;
$scope.setTotalCopayers(tc); $scope.setTotalCopayers(tc);
updateRCSelect(tc); updateRCSelect(tc);
updateSeedSourceSelect(tc);
}; };
$scope.showAdvChange = function() { $scope.showAdvChange = function() {
@ -44,7 +43,7 @@ angular.module('copayApp.controllers').controller('createController',
$scope.resizeView = function() { $scope.resizeView = function() {
$timeout(function() { $timeout(function() {
$ionicScrollDelegate.resize(); $ionicScrollDelegate.resize();
}); }, 10);
checkPasswordFields(); checkPasswordFields();
}; };
@ -67,26 +66,35 @@ angular.module('copayApp.controllers').controller('createController',
function updateSeedSourceSelect(n) { function updateSeedSourceSelect(n) {
var seedOptions = [{ var seedOptions = [{
id: 'new', id: 'new',
label: gettext('Random'), label: gettextCatalog.getString('Random'),
}, { }, {
id: 'set', id: 'set',
label: gettext('Specify Recovery Phrase...'), label: gettextCatalog.getString('Specify Recovery Phrase...'),
}]; }];
$scope.seedSource = seedOptions[0]; $scope.seedSource = seedOptions[0];
if (n > 1 && isChromeApp) /*
seedOptions.push({
id: 'ledger',
label: 'Ledger Hardware Wallet',
});
if (isChromeApp || isDevel) { Disable Hardware Wallets for BitPay distribution
seedOptions.push({
id: 'trezor', */
label: 'Trezor Hardware Wallet',
}); if ($window.appConfig.name == 'copay') {
if (n > 1 && isChromeApp) {
seedOptions.push({
id: 'ledger',
label: 'Ledger Hardware Wallet',
});
}
if (isChromeApp || isDevel) {
seedOptions.push({
id: 'trezor',
label: 'Trezor Hardware Wallet',
});
}
} }
$scope.seedOptions = seedOptions; $scope.seedOptions = seedOptions;
}; };

View file

@ -0,0 +1,27 @@
'use strict';
angular.module('copayApp.controllers').controller('customAmountController', function($rootScope, $scope, $stateParams, $ionicHistory, txFormatService, platformInfo) {
$scope.$on("$ionicView.beforeEnter", function(event, data) {
var satToBtc = 1 / 100000000;
$scope.isCordova = platformInfo.isCordova;
$scope.address = data.stateParams.toAddress;
$scope.amount = parseInt(data.stateParams.toAmount);
$scope.amountBtc = ($scope.amount * satToBtc).toFixed(8);
$scope.amountStr = txFormatService.formatAmountStr($scope.amount);
$scope.altAmountStr = txFormatService.formatAlternativeStr($scope.amount);
});
$scope.shareAddress = function(uri) {
window.plugins.socialsharing.share(uri, null, null, null);
};
$scope.finish = function() {
$ionicHistory.nextViewOptions({
disableAnimate: false,
historyRoot: true
});
$ionicHistory.goBack(-2);
};
});

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('exportController', angular.module('copayApp.controllers').controller('exportController',
function($scope, $timeout, $log, $ionicHistory, $ionicScrollDelegate, backupService, walletService, storageService, profileService, platformInfo, gettextCatalog, $state, $stateParams, popupService) { function($scope, $timeout, $log, $ionicHistory, $ionicScrollDelegate, backupService, walletService, storageService, profileService, platformInfo, gettextCatalog, $state, $stateParams, popupService, $window) {
var wallet = profileService.getWallet($stateParams.walletId); var wallet = profileService.getWallet($stateParams.walletId);
$scope.showAdvChange = function() { $scope.showAdvChange = function() {
@ -12,7 +12,7 @@ angular.module('copayApp.controllers').controller('exportController',
$scope.resizeView = function() { $scope.resizeView = function() {
$timeout(function() { $timeout(function() {
$ionicScrollDelegate.resize(); $ionicScrollDelegate.resize();
}); }, 10);
}; };
function getPassword(cb) { function getPassword(cb) {
@ -191,7 +191,7 @@ angular.module('copayApp.controllers').controller('exportController',
if ($scope.formData.noSignEnabled) if ($scope.formData.noSignEnabled)
name = name + '(No Private Key)'; name = name + '(No Private Key)';
var subject = 'Copay Wallet Backup: ' + name; var subject = $window.appConfig.nameCase + ' Wallet Backup: ' + name;
var body = 'Here is the encrypted backup of the wallet ' + name + ': \n\n' + ew + '\n\n To import this backup, copy all text between {...}, including the symbols {}'; var body = 'Here is the encrypted backup of the wallet ' + name + ': \n\n' + ew + '\n\n To import this backup, copy all text between {...}, including the symbols {}';
window.plugins.socialsharing.shareViaEmail( window.plugins.socialsharing.shareViaEmail(
body, body,

View file

@ -0,0 +1,130 @@
'use strict';
angular.module('copayApp.controllers').controller('completeController', function($scope, $stateParams, $timeout, $log, $ionicHistory, $state, $ionicNavBarDelegate, $ionicConfig, platformInfo, configService, storageService, lodash) {
$scope.isCordova = platformInfo.isCordova;
var defaults = configService.getDefaults();
function quickFeedback(cb) {
window.plugins.spinnerDialog.show();
$timeout(window.plugins.spinnerDialog.hide, 300);
$timeout(cb, 20);
}
$scope.shareFacebook = function() {
quickFeedback(function() {
window.plugins.socialsharing.shareVia($scope.shareFacebookVia, null, null, null, defaults.download.url);
});
};
$scope.shareTwitter = function() {
quickFeedback(function() {
window.plugins.socialsharing.shareVia($scope.shareTwitterVia, null, null, null, defaults.download.url);
});
};
$scope.shareGooglePlus = function() {
quickFeedback(function() {
window.plugins.socialsharing.shareVia($scope.shareGooglePlusVia, defaults.download.url);
});
};
$scope.shareEmail = function() {
quickFeedback(function() {
window.plugins.socialsharing.shareViaEmail(defaults.download.url);
});
};
$scope.shareWhatsapp = function() {
quickFeedback(function() {
window.plugins.socialsharing.shareViaWhatsApp(defaults.download.url);
});
};
$scope.shareMessage = function() {
quickFeedback(function() {
window.plugins.socialsharing.shareViaSMS(defaults.download.url);
});
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.score = (data.stateParams && data.stateParams.score) ? parseInt(data.stateParams.score) : null;
$scope.skipped = (data.stateParams && data.stateParams.skipped) ? true : false;
$scope.rated = (data.stateParams && data.stateParams.rated) ? true : false;
$scope.fromSettings = (data.stateParams && data.stateParams.fromSettings) ? true : false;
if (!$scope.fromSettings) {
$ionicConfig.views.swipeBackEnabled(false);
} else {
$ionicNavBarDelegate.showBackButton(true);
$ionicConfig.views.swipeBackEnabled(true);
}
storageService.getFeedbackInfo(function(error, info) {
var feedbackInfo = lodash.isString(info) ? JSON.parse(info) : null;
feedbackInfo.sent = true;
storageService.setFeedbackInfo(JSON.stringify(feedbackInfo), function() {});
});
if (!$scope.isCordova) return;
$scope.animate = true;
window.plugins.socialsharing.available(function(isAvailable) {
// the boolean is only false on iOS < 6
$scope.socialsharing = isAvailable;
if (isAvailable) {
window.plugins.socialsharing.canShareVia('com.apple.social.facebook', 'msg', null, null, null, function(e) {
$scope.shareFacebookVia = 'com.apple.social.facebook';
$scope.facebook = true;
}, function(e) {
window.plugins.socialsharing.canShareVia('com.facebook.katana', 'msg', null, null, null, function(e) {
$scope.shareFacebookVia = 'com.facebook.katana';
$scope.facebook = true;
}, function(e) {
$log.debug('facebook error: ' + e);
$scope.facebook = false;
});
});
window.plugins.socialsharing.canShareVia('com.apple.social.twitter', 'msg', null, null, null, function(e) {
$scope.shareTwitterVia = 'com.apple.social.twitter';
$scope.twitter = true;
}, function(e) {
window.plugins.socialsharing.canShareVia('com.twitter.android', 'msg', null, null, null, function(e) {
$scope.shareTwitterVia = 'com.twitter.android';
$scope.twitter = true;
}, function(e) {
$log.debug('twitter error: ' + e);
$scope.twitter = false;
});
});
window.plugins.socialsharing.canShareVia('com.google.android.apps.plus', 'msg', null, null, null, function(e) {
$scope.shareGooglePlusVia = 'com.google.android.apps.plus';
$scope.googleplus = true;
}, function(e) {
$log.debug('googlePlus error: ' + e);
$scope.googleplus = false;
});
window.plugins.socialsharing.canShareViaEmail(function(e) {
$scope.email = true;
}, function(e) {
$log.debug('email error: ' + e);
$scope.email = false;
});
window.plugins.socialsharing.canShareVia('whatsapp', 'msg', null, null, null, function(e) {
$scope.whatsapp = true;
}, function(e) {
$log.debug('whatsapp error: ' + e);
$scope.whatsapp = false;
});
}
}, 100);
});
$scope.close = function() {
$ionicHistory.nextViewOptions({
disableAnimate: false,
historyRoot: true
});
if ($scope.score == 5) $ionicHistory.goBack(-3);
else $ionicHistory.goBack(-2);
};
});

View file

@ -0,0 +1,50 @@
'use strict';
angular.module('copayApp.controllers').controller('rateAppController', function($scope, $state, $stateParams, $window, lodash, externalLinkService, configService, platformInfo, feedbackService, ongoingProcess, popupService) {
$scope.score = parseInt($stateParams.score);
var isAndroid = platformInfo.isAndroid;
var isIOS = platformInfo.isIOS;
var isWP = platformInfo.isWP;
var config = configService.getSync();
$scope.skip = function() {
var dataSrc = {
"Email": lodash.values(config.emailFor)[0] || ' ',
"Feedback": ' ',
"Score": $stateParams.score,
"AppVersion": $window.version,
"Platform": ionic.Platform.platform(),
"DeviceVersion": ionic.Platform.version()
};
feedbackService.send(dataSrc, function(err) {
if (err) {
// try to send, but not essential, since the user didn't add a message
$log.warn('Could not send feedback.');
}
});
$state.go('tabs.rate.complete', {
score: $stateParams.score,
skipped: true
});
};
$scope.sendFeedback = function() {
$state.go('tabs.rate.send', {
score: $scope.score
});
};
$scope.goAppStore = function() {
var defaults = configService.getDefaults();
var url;
if (isAndroid) url = defaults.rateApp.android;
if (isIOS) url = defaults.rateApp.ios;
// if (isWP) url = defaults.rateApp.windows; // TODO
externalLinkService.open(url);
$state.go('tabs.rate.complete', {
score: $stateParams.score,
skipped: true,
rated: true
});
};
});

View file

@ -0,0 +1,59 @@
'use strict';
angular.module('copayApp.controllers').controller('rateCardController', function($scope, $state, $timeout, $log, gettextCatalog, platformInfo, storageService) {
$scope.isCordova = platformInfo.isCordova;
$scope.score = 0;
$scope.goFeedbackFlow = function() {
$scope.hideCard();
if ($scope.isCordova && $scope.score == 5) {
$state.go('tabs.rate.rateApp', {
score: $scope.score
});
} else {
$state.go('tabs.rate.send', {
score: $scope.score
});
}
};
$scope.setScore = function(score) {
$scope.score = score;
switch ($scope.score) {
case 1:
$scope.button_title = gettextCatalog.getString("I think this app is terrible.");
break;
case 2:
$scope.button_title = gettextCatalog.getString("I don't like it");
break;
case 3:
$scope.button_title = gettextCatalog.getString("Meh - it's alright");
break;
case 4:
$scope.button_title = gettextCatalog.getString("I like the app");
break;
case 5:
$scope.button_title = gettextCatalog.getString("This app is fantastic!");
break;
}
$timeout(function() {
$scope.$apply();
});
};
$scope.hideCard = function() {
$log.debug('Feedback card dismissed.')
storageService.getFeedbackInfo(function(error, info) {
var feedbackInfo = JSON.parse(info);
feedbackInfo.sent = true;
storageService.setFeedbackInfo(JSON.stringify(feedbackInfo), function() {
$scope.showRateCard.value = false;
$timeout(function() {
$scope.$apply();
}, 100);
});
});
}
});

View file

@ -0,0 +1,92 @@
'use strict';
angular.module('copayApp.controllers').controller('sendController', function($scope, $state, $log, $timeout, $stateParams, $ionicNavBarDelegate, $ionicHistory, $ionicConfig, $window, gettextCatalog, popupService, configService, lodash, feedbackService, ongoingProcess) {
$scope.sendFeedback = function(feedback, goHome) {
var config = configService.getSync();
var dataSrc = {
"Email": lodash.values(config.emailFor)[0] || ' ',
"Feedback": goHome ? ' ' : feedback,
"Score": $stateParams.score || ' ',
"AppVersion": $window.version,
"Platform": ionic.Platform.platform(),
"DeviceVersion": ionic.Platform.version()
};
if (!goHome) ongoingProcess.set('sendingFeedback', true);
feedbackService.send(dataSrc, function(err) {
if (goHome) return;
ongoingProcess.set('sendingFeedback', false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Feedback could not be submitted. Please try again later.'));
return;
}
if (!$stateParams.score) {
popupService.showAlert(gettextCatalog.getString('Thank you!'), gettextCatalog.getString('A member of the team will review your feedback as soon as possible.'), function() {
$scope.feedback.value = '';
$ionicHistory.nextViewOptions({
disableAnimate: false,
historyRoot: true
});
$ionicHistory.goBack();
}, gettextCatalog.getString('Finish'));
return;
}
$state.go('tabs.rate.complete', {
score: $stateParams.score
});
});
if (goHome) $state.go('tabs.home');
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.score = (data.stateParams && data.stateParams.score) ? parseInt(data.stateParams.score) : null;
$scope.feedback = {};
if ($scope.score) {
$ionicConfig.views.swipeBackEnabled(false);
}
switch ($scope.score) {
case 1:
$scope.reaction = "Ouch!";
$scope.comment = gettextCatalog.getString("There's obviously something we're doing wrong.") + ' ' + gettextCatalog.getString("How could we improve your experience?");
break;
case 2:
$scope.reaction = gettextCatalog.getString("Oh no!");
$scope.comment = gettextCatalog.getString("There's obviously something we're doing wrong.") + ' ' + gettextCatalog.getString("How could we improve your experience?");
break;
case 3:
$scope.reaction = "Hmm...";
$scope.comment = gettextCatalog.getString("We'd love to do better.") + ' ' + gettextCatalog.getString("How could we improve your experience?");
break;
case 4:
$scope.reaction = gettextCatalog.getString("Thanks!");
$scope.comment = gettextCatalog.getString("That's exciting to hear. We'd love to earn that fifth star from you how could we improve your experience?");
break;
case 5:
$scope.reaction = gettextCatalog.getString("Thank you!");
$scope.comment = gettextCatalog.getString("We're always looking for ways to improve BitPay.") + ' ' + gettextCatalog.getString("Is there anything we could do better?");
break;
default:
$scope.justFeedback = true;
$scope.comment = gettextCatalog.getString("We're always looking for ways to improve BitPay. How could we improve your experience?");
break;
}
});
$scope.$on("$ionicView.afterEnter", function() {
$scope.showForm = true;
});
$scope.goBack = function() {
$ionicHistory.nextViewOptions({
disableAnimate: false,
historyRoot: true
});
$ionicHistory.goBack();
};
});

View file

@ -18,9 +18,11 @@ angular.module('copayApp.controllers').controller('glideraController',
$scope.status = null; $scope.status = null;
$scope.limits = null; $scope.limits = null;
$scope.connectingGlidera = true;
ongoingProcess.set('connectingGlidera', true); ongoingProcess.set('connectingGlidera', true);
glideraService.init($scope.token, function(err, glidera) { glideraService.init($scope.token, function(err, glidera) {
ongoingProcess.set('connectingGlidera'); ongoingProcess.set('connectingGlidera');
$scope.connectingGlidera = false;
if (err || !glidera) { if (err || !glidera) {
if (err) popupService.showAlert(gettextCatalog.getString('Error'), err); if (err) popupService.showAlert(gettextCatalog.getString('Error'), err);
return; return;
@ -67,11 +69,11 @@ angular.module('copayApp.controllers').controller('glideraController',
} }
}; };
this.getAuthenticateUrl = function() { $scope.getAuthenticateUrl = function() {
return glideraService.getOauthCodeUrl(); return glideraService.getOauthCodeUrl();
}; };
this.submitOauthCode = function(code) { $scope.submitOauthCode = function(code) {
ongoingProcess.set('connectingGlidera', true); ongoingProcess.set('connectingGlidera', true);
$timeout(function() { $timeout(function() {
glideraService.getToken(code, function(err, data) { glideraService.getToken(code, function(err, data) {
@ -90,10 +92,7 @@ angular.module('copayApp.controllers').controller('glideraController',
}, 100); }, 100);
}; };
this.openTxModal = function(token, tx) { $scope.openTxModal = function(token, tx) {
var self = this;
$scope.self = self;
$scope.tx = tx; $scope.tx = tx;
glideraService.getTransaction(token, tx.transactionUuid, function(err, tx) { glideraService.getTransaction(token, tx.transactionUuid, function(err, tx) {

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('headController', angular.module('copayApp.controllers').controller('headController',
function($scope, $window, $log, glideraService) { function($scope, $window, $log) {
$scope.appConfig = $window.appConfig; $scope.appConfig = $window.appConfig;
$log.info('Running head controller:' + $window.appConfig.nameCase) $log.info('Running head controller:' + $window.appConfig.nameCase)
}); });

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('importController', angular.module('copayApp.controllers').controller('importController',
function($scope, $timeout, $log, $state, $stateParams, $ionicHistory, $ionicScrollDelegate, profileService, configService, sjcl, ledger, trezor, derivationPathHelper, platformInfo, bwcService, ongoingProcess, walletService, popupService, gettextCatalog) { function($scope, $timeout, $log, $state, $stateParams, $ionicHistory, $ionicScrollDelegate, profileService, configService, sjcl, ledger, trezor, derivationPathHelper, platformInfo, bwcService, ongoingProcess, walletService, popupService, gettextCatalog, $window) {
var isChromeApp = platformInfo.isChromeApp; var isChromeApp = platformInfo.isChromeApp;
var isDevel = platformInfo.isDevel; var isDevel = platformInfo.isDevel;
@ -17,6 +17,7 @@ angular.module('copayApp.controllers').controller('importController',
$scope.formData.derivationPath = derivationPathHelper.default; $scope.formData.derivationPath = derivationPathHelper.default;
$scope.formData.account = 1; $scope.formData.account = 1;
$scope.importErr = false; $scope.importErr = false;
$scope.showHardwareWallet = $window.appConfig.name == 'copay';
if ($stateParams.code) if ($stateParams.code)
$scope.processWalletInfo($stateParams.code); $scope.processWalletInfo($stateParams.code);
@ -359,7 +360,11 @@ angular.module('copayApp.controllers').controller('importController',
$scope.resizeView = function() { $scope.resizeView = function() {
$timeout(function() { $timeout(function() {
$ionicScrollDelegate.resize(); $ionicScrollDelegate.resize();
}); }, 10);
}; };
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.init();
});
}); });

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('joinController', angular.module('copayApp.controllers').controller('joinController',
function($scope, $rootScope, $timeout, $state, $ionicHistory, $ionicScrollDelegate, profileService, configService, storageService, applicationService, gettext, gettextCatalog, lodash, ledger, trezor, platformInfo, derivationPathHelper, ongoingProcess, walletService, $log, $stateParams, popupService) { function($scope, $rootScope, $timeout, $state, $ionicHistory, $ionicScrollDelegate, profileService, configService, storageService, applicationService, gettextCatalog, lodash, ledger, trezor, platformInfo, derivationPathHelper, ongoingProcess, walletService, $log, $stateParams, popupService, $window) {
var isChromeApp = platformInfo.isChromeApp; var isChromeApp = platformInfo.isChromeApp;
var isDevel = platformInfo.isDevel; var isDevel = platformInfo.isDevel;
@ -20,7 +20,7 @@ angular.module('copayApp.controllers').controller('joinController',
$scope.resizeView = function() { $scope.resizeView = function() {
$timeout(function() { $timeout(function() {
$ionicScrollDelegate.resize(); $ionicScrollDelegate.resize();
}); }, 10);
checkPasswordFields(); checkPasswordFields();
}; };
@ -50,26 +50,33 @@ angular.module('copayApp.controllers').controller('joinController',
var updateSeedSourceSelect = function() { var updateSeedSourceSelect = function() {
self.seedOptions = [{ self.seedOptions = [{
id: 'new', id: 'new',
label: gettext('Random'), label: gettextCatalog.getString('Random'),
}, { }, {
id: 'set', id: 'set',
label: gettext('Specify Recovery Phrase...'), label: gettextCatalog.getString('Specify Recovery Phrase...'),
}]; }];
$scope.seedSource = self.seedOptions[0]; $scope.seedSource = self.seedOptions[0];
/*
if (isChromeApp) { Disable Hardware Wallets
self.seedOptions.push({
id: 'ledger',
label: 'Ledger Hardware Wallet',
});
}
if (isChromeApp || isDevel) { */
self.seedOptions.push({
id: 'trezor', if ($window.appConfig.name == 'copay') {
label: 'Trezor Hardware Wallet', if (isChromeApp) {
}); self.seedOptions.push({
id: 'ledger',
label: 'Ledger Hardware Wallet',
});
}
if (isChromeApp || isDevel) {
self.seedOptions.push({
id: 'trezor',
label: 'Trezor Hardware Wallet',
});
}
} }
}; };

View file

@ -62,8 +62,8 @@ angular.module('copayApp.controllers').controller('amazonCardDetailsController',
$scope.amazonCardDetailsModal.hide(); $scope.amazonCardDetailsModal.hide();
}; };
$scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) { $scope.openExternalLink = function(url) {
externalLinkService.open(url, optIn, title, message, okText, cancelText); externalLinkService.open(url);
}; };
}); });

View file

@ -1,79 +0,0 @@
'use strict';
angular.module('copayApp.controllers').controller('customAmountController', function($scope, $timeout, $filter, platformInfo, rateService) {
var self = $scope.self;
$scope.unitName = self.unitName;
$scope.alternativeAmount = self.alternativeAmount;
$scope.alternativeName = self.alternativeName;
$scope.alternativeIsoCode = self.alternativeIsoCode;
$scope.isRateAvailable = self.isRateAvailable;
$scope.unitToSatoshi = self.unitToSatoshi;
$scope.unitDecimals = self.unitDecimals;
var satToUnit = 1 / self.unitToSatoshi;
$scope.showAlternative = false;
$scope.isCordova = platformInfo.isCordova;
Object.defineProperty($scope,
"_customAlternative", {
get: function() {
return $scope.customAlternative;
},
set: function(newValue) {
$scope.customAlternative = newValue;
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
$scope.customAmount = parseFloat((rateService.fromFiat(newValue, $scope.alternativeIsoCode) * satToUnit).toFixed($scope.unitDecimals), 10);
} else {
$scope.customAmount = null;
}
},
enumerable: true,
configurable: true
});
Object.defineProperty($scope,
"_customAmount", {
get: function() {
return $scope.customAmount;
},
set: function(newValue) {
$scope.customAmount = newValue;
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
$scope.customAlternative = parseFloat((rateService.toFiat(newValue * $scope.unitToSatoshi, $scope.alternativeIsoCode)).toFixed(2), 10);
} else {
$scope.customAlternative = null;
}
$scope.alternativeAmount = $scope.customAlternative;
},
enumerable: true,
configurable: true
});
$scope.submitForm = function(form) {
var satToBtc = 1 / 100000000;
var amount = form.amount.$modelValue;
var amountSat = parseInt((amount * $scope.unitToSatoshi).toFixed(0));
$timeout(function() {
$scope.customizedAmountUnit = amount + ' ' + $scope.unitName;
$scope.customizedAlternativeUnit = $filter('formatFiatAmount')(form.alternative.$modelValue) + ' ' + $scope.alternativeIsoCode;
if ($scope.unitName == 'bits') {
amount = (amountSat * satToBtc).toFixed(8);
}
$scope.customizedAmountBtc = amount;
}, 1);
};
$scope.toggleAlternative = function() {
$scope.showAlternative = !$scope.showAlternative;
};
$scope.shareAddress = function(uri) {
if (platformInfo.isCordova) {
window.plugins.socialsharing.share(uri, null, null, null);
}
};
$scope.cancel = function() {
$scope.customAmountModal.hide();
};
});

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('searchController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $ionicNavBarDelegate, $state, $stateParams, $ionicScrollDelegate, bwcError, profileService, lodash, configService, gettext, gettextCatalog, platformInfo, walletService) { angular.module('copayApp.controllers').controller('searchController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicScrollDelegate, bwcError, profileService, lodash, configService, gettext, gettextCatalog, platformInfo, walletService) {
var HISTORY_SHOW_LIMIT = 10; var HISTORY_SHOW_LIMIT = 10;
var currentTxHistoryPage = 0; var currentTxHistoryPage = 0;

View file

@ -1,9 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('txpDetailsController', function($scope, $rootScope, $timeout, $interval, $ionicModal, $log, ongoingProcess, platformInfo, $ionicScrollDelegate, txFormatService, fingerprintService, bwcError, gettextCatalog, lodash, walletService, popupService, $state, $ionicHistory) { angular.module('copayApp.controllers').controller('txpDetailsController', function($scope, $rootScope, $timeout, $interval, $log, ongoingProcess, platformInfo, $ionicScrollDelegate, txFormatService, bwcError, gettextCatalog, lodash, walletService, popupService, $ionicHistory) {
var self = $scope.self;
var tx = $scope.tx;
var copayers = $scope.copayers;
var isGlidera = $scope.isGlidera; var isGlidera = $scope.isGlidera;
var GLIDERA_LOCK_TIME = 6 * 60 * 60; var GLIDERA_LOCK_TIME = 6 * 60 * 60;
var now = Math.floor(Date.now() / 1000); var now = Math.floor(Date.now() / 1000);
@ -18,8 +15,8 @@ angular.module('copayApp.controllers').controller('txpDetailsController', functi
$scope.color = $scope.wallet.color; $scope.color = $scope.wallet.color;
$scope.data = {}; $scope.data = {};
$scope.hasClick = platformInfo.hasClick; $scope.hasClick = platformInfo.hasClick;
$scope.displayAmount = getDisplayAmount(tx.amountStr); $scope.displayAmount = getDisplayAmount($scope.tx.amountStr);
$scope.displayUnit = getDisplayUnit(tx.amountStr); $scope.displayUnit = getDisplayUnit($scope.tx.amountStr);
initActionList(); initActionList();
checkPaypro(); checkPaypro();
} }
@ -46,12 +43,12 @@ angular.module('copayApp.controllers').controller('txpDetailsController', functi
$scope.actionList.push({ $scope.actionList.push({
type: 'created', type: 'created',
time: tx.createdOn, time: $scope.tx.createdOn,
description: actionDescriptions['created'], description: actionDescriptions['created'],
by: tx.creatorName by: $scope.tx.creatorName
}); });
lodash.each(tx.actions, function(action) { lodash.each($scope.tx.actions, function(action) {
$scope.actionList.push({ $scope.actionList.push({
type: action.type, type: action.type,
time: action.createdOn, time: action.createdOn,
@ -61,103 +58,14 @@ angular.module('copayApp.controllers').controller('txpDetailsController', functi
}); });
}; };
$scope.$on('accepted', function(event) {
$scope.sign();
});
// ToDo: use tx.customData instead of tx.message
if (tx.message === 'Glidera transaction' && isGlidera) {
tx.isGlidera = true;
if (tx.canBeRemoved) {
tx.canBeRemoved = (Date.now() / 1000 - (tx.ts || tx.createdOn)) > GLIDERA_LOCK_TIME;
}
}
var setSendError = function(msg) {
$scope.sendStatus = '';
var error = msg || gettextCatalog.getString('Could not send payment');
popupService.showAlert(gettextCatalog.getString('Error'), error);
}
$scope.sign = function(onSendStatusChange) {
$scope.loading = true;
walletService.publishAndSign($scope.wallet, $scope.tx, function(err, txp) {
$scope.$emit('UpdateTx');
if (err) return setSendError(err);
success();
}, onSendStatusChange);
};
function setError(err, prefix) {
$scope.loading = false;
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err, prefix));
};
$scope.reject = function(txp) {
var title = gettextCatalog.getString('Warning!');
var msg = gettextCatalog.getString('Are you sure you want to reject this transaction?');
popupService.showConfirm(title, msg, null, null, function(res) {
if (res) {
$scope.loading = true;
walletService.reject($scope.wallet, $scope.tx, function(err, txpr) {
if (err)
return setError(err, gettextCatalog.getString('Could not reject payment'));
$scope.close();
});
}
});
};
$scope.remove = function() {
$scope.loading = true;
$timeout(function() {
ongoingProcess.set('removeTx', true);
walletService.removeTx($scope.wallet, $scope.tx, function(err) {
ongoingProcess.set('removeTx', false);
// Hacky: request tries to parse an empty response
if (err && !(err.message && err.message.match(/Unexpected/))) {
$scope.$emit('UpdateTx');
return setError(err, gettextCatalog.getString('Could not delete payment proposal'));
}
$scope.close();
});
}, 10);
};
$scope.broadcast = function(txp) {
$scope.loading = true;
$timeout(function() {
ongoingProcess.set('broadcastTx', true);
walletService.broadcastTx($scope.wallet, $scope.tx, function(err, txpb) {
ongoingProcess.set('broadcastTx', false);
if (err) {
return setError(err, gettextCatalog.getString('Could not broadcast payment'));
}
$scope.close();
});
}, 10);
};
$scope.getShortNetworkName = function() {
return $scope.wallet.credentials.networkName.substring(0, 4);
};
function checkPaypro() { function checkPaypro() {
if (tx.payProUrl && !platformInfo.isChromeApp) { if ($scope.tx.payProUrl && !platformInfo.isChromeApp) {
$scope.wallet.fetchPayPro({ $scope.wallet.fetchPayPro({
payProUrl: tx.payProUrl, payProUrl: $scope.tx.payProUrl,
}, function(err, paypro) { }, function(err, paypro) {
if (err) return; if (err) return;
tx.paypro = paypro; $scope.tx.paypro = paypro;
paymentTimeControl(tx.paypro.expires); paymentTimeControl($scope.tx.paypro.expires);
$timeout(function() { $timeout(function() {
$ionicScrollDelegate.resize(); $ionicScrollDelegate.resize();
}, 10); }, 10);
@ -187,32 +95,132 @@ angular.module('copayApp.controllers').controller('txpDetailsController', functi
}; };
}; };
lodash.each(['TxProposalRejectedBy', 'TxProposalAcceptedBy', 'transactionProposalRemoved', 'TxProposalRemoved', 'NewOutgoingTx', 'UpdateTx'], function(eventName) { $scope.$on('accepted', function(event) {
$rootScope.$on(eventName, function() { $scope.sign();
$scope.wallet.getTx($scope.tx.id, function(err, tx) { });
if (err) {
if (err.message && err.message == 'TX_NOT_FOUND' && // ToDo: use tx.customData instead of tx.message
(eventName == 'transactionProposalRemoved' || eventName == 'TxProposalRemoved')) { if ($scope.tx.message === 'Glidera transaction' && isGlidera) {
$scope.tx.removed = true; $scope.tx.isGlidera = true;
$scope.tx.canBeRemoved = false; if ($scope.tx.canBeRemoved) {
$scope.tx.pendingForUs = false; $scope.tx.canBeRemoved = (Date.now() / 1000 - ($scope.tx.ts || $scope.tx.createdOn)) > GLIDERA_LOCK_TIME;
$scope.$apply(); }
}
var setError = function(err, prefix) {
$scope.sendStatus = '';
$scope.loading = false;
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err, prefix));
};
$scope.sign = function(onSendStatusChange) {
$scope.loading = true;
walletService.publishAndSign($scope.wallet, $scope.tx, function(err, txp) {
$scope.$emit('UpdateTx');
if (err) return setError(err, gettextCatalog.getString('Could not send payment'));
success();
}, onSendStatusChange);
};
$scope.reject = function(txp) {
var title = gettextCatalog.getString('Warning!');
var msg = gettextCatalog.getString('Are you sure you want to reject this transaction?');
popupService.showConfirm(title, msg, null, null, function(res) {
if (res) {
$scope.loading = true;
walletService.reject($scope.wallet, $scope.tx, function(err, txpr) {
if (err)
return setError(err, gettextCatalog.getString('Could not reject payment'));
$scope.close();
});
}
});
};
$scope.remove = function() {
var title = gettextCatalog.getString('Warning!');
var msg = gettextCatalog.getString('Are you sure you want to remove this transaction?');
popupService.showConfirm(title, msg, null, null, function(res) {
if (res) {
ongoingProcess.set('removeTx', true);
walletService.removeTx($scope.wallet, $scope.tx, function(err) {
ongoingProcess.set('removeTx', false);
// Hacky: request tries to parse an empty response
if (err && !(err.message && err.message.match(/Unexpected/))) {
$scope.$emit('UpdateTx');
return setError(err, gettextCatalog.getString('Could not delete payment proposal'));
} }
return;
$scope.close();
});
}
});
};
$scope.broadcast = function(txp) {
$scope.loading = true;
$timeout(function() {
ongoingProcess.set('broadcastingTx', true);
walletService.broadcastTx($scope.wallet, $scope.tx, function(err, txpb) {
ongoingProcess.set('broadcastingTx', false);
if (err) {
return setError(err, gettextCatalog.getString('Could not broadcast payment'));
} }
var action = lodash.find(tx.actions, { $scope.close();
copayerId: $scope.wallet.credentials.copayerId
});
$scope.tx = txFormatService.processTx(tx);
if (!action && tx.status == 'pending')
$scope.tx.pendingForUs = true;
$scope.updateCopayerList();
$scope.$apply();
}); });
}, 10);
};
$scope.getShortNetworkName = function() {
return $scope.wallet.credentials.networkName.substring(0, 4);
};
var updateTxInfo = function(eventName) {
$scope.wallet.getTx($scope.tx.id, function(err, tx) {
if (err) {
if (err.message && err.message == 'Transaction proposal not found' &&
(eventName == 'transactionProposalRemoved' || eventName == 'TxProposalRemoved')) {
$scope.tx.removed = true;
$scope.tx.canBeRemoved = false;
$scope.tx.pendingForUs = false;
$scope.$apply();
}
return;
}
var action = lodash.find(tx.actions, {
copayerId: $scope.wallet.credentials.copayerId
});
$scope.tx = txFormatService.processTx(tx);
if (!action && tx.status == 'pending')
$scope.tx.pendingForUs = true;
$scope.updateCopayerList();
initActionList();
$scope.$apply();
});
};
var bwsEvent = $rootScope.$on('bwsEvent', function(e, walletId, type, n) {
lodash.each([
'TxProposalRejectedBy',
'TxProposalAcceptedBy',
'transactionProposalRemoved',
'TxProposalRemoved',
'NewOutgoingTx',
'UpdateTx'
], function(eventName) {
if (walletId == $scope.wallet.id && type == eventName) {
updateTxInfo(eventName);
}
}); });
}); });
@ -252,6 +260,7 @@ angular.module('copayApp.controllers').controller('txpDetailsController', functi
}; };
$scope.close = function() { $scope.close = function() {
bwsEvent();
$scope.loading = null; $scope.loading = null;
$scope.txpDetailsModal.hide(); $scope.txpDetailsModal.hide();
}; };

View file

@ -9,8 +9,8 @@ angular.module('copayApp.controllers').controller('backupWarningController', fun
$scope.openPopup = function() { $scope.openPopup = function() {
$ionicModal.fromTemplateUrl('views/includes/screenshotWarningModal.html', { $ionicModal.fromTemplateUrl('views/includes/screenshotWarningModal.html', {
scope: $scope, scope: $scope,
backdropClickToClose: false, backdropClickToClose: true,
hardwareBackButtonClose: false hardwareBackButtonClose: true
}).then(function(modal) { }).then(function(modal) {
$scope.warningModal = modal; $scope.warningModal = modal;
$scope.warningModal.show(); $scope.warningModal.show();

View file

@ -1,17 +1,18 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('disclaimerController', function($scope, $timeout, $state, $log, $ionicModal, profileService, uxLanguage, externalLinkService, storageService, $stateParams, startupService) { angular.module('copayApp.controllers').controller('disclaimerController', function($scope, $timeout, $state, $log, $ionicModal, profileService, uxLanguage, externalLinkService, storageService, $stateParams, startupService, $rootScope) {
$scope.$on("$ionicView.afterEnter", function() { $scope.$on("$ionicView.afterEnter", function() {
startupService.ready(); startupService.ready();
}); });
$scope.init = function() { $scope.init = function() {
$scope.lang = uxLanguage.currentLanguage; $scope.lang = uxLanguage.currentLanguage;
$scope.terms = {}; $scope.terms = {};
$scope.accept1 = $scope.accept2 = $scope.accept3 = false; $scope.accepted = {};
$scope.accepted.first = $scope.accepted.second = $scope.accepted.third = false;
$scope.backedUp = $stateParams.backedUp; $scope.backedUp = $stateParams.backedUp;
$scope.resume = $stateParams.resume; $scope.resume = $stateParams.resume;
$scope.shrinkView = false;
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}, 1); }, 1);
@ -32,14 +33,9 @@ angular.module('copayApp.controllers').controller('disclaimerController', functi
externalLinkService.open(url, target); externalLinkService.open(url, target);
}; };
$scope.openTermsModal = function() { $scope.openTerms = function() {
$ionicModal.fromTemplateUrl('views/modals/terms.html', { $scope.shrinkView = !$scope.shrinkView;
scope: $scope }
}).then(function(modal) {
$scope.termsModal = modal;
$scope.termsModal.show();
});
};
$scope.goBack = function() { $scope.goBack = function() {
$state.go('onboarding.backupRequest', { $state.go('onboarding.backupRequest', {

View file

@ -1,8 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('termsController', function($scope, $log, $state, $window, uxLanguage, profileService, externalLinkService) { angular.module('copayApp.controllers').controller('termsController', function($scope, $log, $state, $window, uxLanguage, profileService, externalLinkService, gettextCatalog) {
$scope.lang = uxLanguage.currentLanguage; $scope.lang = uxLanguage.currentLanguage;
$scope.disclaimerUrl = $window.appConfig.disclaimerUrl;
$scope.confirm = function() { $scope.confirm = function() {
profileService.setDisclaimerAccepted(function(err) { profileService.setDisclaimerAccepted(function(err) {
@ -15,7 +14,13 @@ angular.module('copayApp.controllers').controller('termsController', function($s
}); });
}; };
$scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) { $scope.openExternalLink = function() {
var url = $window.appConfig.disclaimerUrl;
var optIn = true;
var title = gettextCatalog.getString('View Terms of Service');
var message = gettextCatalog.getString('The official English Terms of Service are available on the BitPay website. Would you like to view them?');
var okText = gettextCatalog.getString('Open Website');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText); externalLinkService.open(url, optIn, title, message, okText, cancelText);
}; };

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('welcomeController', function($scope, $state, $timeout, $ionicConfig, $log, profileService, startupService) { angular.module('copayApp.controllers').controller('welcomeController', function($scope, $state, $timeout, $ionicConfig, $log, profileService, startupService, storageService) {
$ionicConfig.views.swipeBackEnabled(false); $ionicConfig.views.swipeBackEnabled(false);

View file

@ -1,24 +1,14 @@
angular.module('copayApp.controllers').controller('paperWalletController', angular.module('copayApp.controllers').controller('paperWalletController',
function($scope, $timeout, $log, $ionicModal, $ionicHistory, popupService, gettextCatalog, platformInfo, configService, profileService, $state, bitcore, ongoingProcess, txFormatService, $stateParams, walletService) { function($scope, $timeout, $log, $ionicModal, $ionicHistory, feeService, popupService, gettextCatalog, platformInfo, configService, profileService, $state, bitcore, ongoingProcess, txFormatService, $stateParams, walletService) {
$scope.onQrCodeScanned = function(data) {
$scope.formData.inputData = data;
$scope.onData(data);
};
$scope.onData = function(data) {
$scope.scannedKey = data;
$scope.isPkEncrypted = (data.substring(0, 2) == '6P');
};
function _scanFunds(cb) { function _scanFunds(cb) {
function getPrivateKey(scannedKey, isPkEncrypted, passphrase, cb) { function getPrivateKey(scannedKey, isPkEncrypted, passphrase, cb) {
if (!isPkEncrypted) return cb(null, scannedKey); if (!isPkEncrypted) return cb(null, scannedKey);
wallet.decryptBIP38PrivateKey(scannedKey, passphrase, null, cb); $scope.wallet.decryptBIP38PrivateKey(scannedKey, passphrase, null, cb);
}; };
function getBalance(privateKey, cb) { function getBalance(privateKey, cb) {
wallet.getBalanceFromPrivateKey(privateKey, cb); $scope.wallet.getBalanceFromPrivateKey(privateKey, cb);
}; };
function checkPrivateKey(privateKey) { function checkPrivateKey(privateKey) {
@ -42,9 +32,6 @@ angular.module('copayApp.controllers').controller('paperWalletController',
}; };
$scope.scanFunds = function() { $scope.scanFunds = function() {
$scope.privateKey = '';
$scope.balanceSat = 0;
ongoingProcess.set('scanning', true); ongoingProcess.set('scanning', true);
$timeout(function() { $timeout(function() {
_scanFunds(function(err, privateKey, balance) { _scanFunds(function(err, privateKey, balance) {
@ -52,32 +39,40 @@ angular.module('copayApp.controllers').controller('paperWalletController',
if (err) { if (err) {
$log.error(err); $log.error(err);
popupService.showAlert(gettextCatalog.getString('Error scanning funds:'), err || err.toString()); popupService.showAlert(gettextCatalog.getString('Error scanning funds:'), err || err.toString());
$state.go('tabs.home');
} else { } else {
$scope.privateKey = privateKey; $scope.privateKey = privateKey;
$scope.balanceSat = balance; $scope.balanceSat = balance;
if ($scope.balanceSat <= 0)
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not funds found'));
var config = configService.getSync().wallet.settings; var config = configService.getSync().wallet.settings;
$scope.balance = txFormatService.formatAmount(balance) + ' ' + config.unitName; $scope.balance = txFormatService.formatAmount(balance) + ' ' + config.unitName;
$scope.scanned = true;
} }
$scope.$apply(); $scope.$apply();
}); });
}, 100); }, 100);
}; };
function _sweepWallet(cb) { function _sweepWallet(cb) {
walletService.getAddress(wallet, true, function(err, destinationAddress) { walletService.getAddress($scope.wallet, true, function(err, destinationAddress) {
if (err) return cb(err); if (err) return cb(err);
wallet.buildTxFromPrivateKey($scope.privateKey, destinationAddress, null, function(err, tx) { $scope.wallet.buildTxFromPrivateKey($scope.privateKey, destinationAddress, null, function(err, testTx) {
if (err) return cb(err); if (err) return cb(err);
var rawTxLength = testTx.serialize().length;
wallet.broadcastRawTx({ feeService.getCurrentFeeValue('livenet', function(err, feePerKB) {
rawTx: tx.serialize(), var opts = {};
network: 'livenet' opts.fee = Math.round((feePerKB * rawTxLength) / 2000);
}, function(err, txid) { $scope.wallet.buildTxFromPrivateKey($scope.privateKey, destinationAddress, opts, function(err, tx) {
if (err) return cb(err); if (err) return cb(err);
return cb(null, destinationAddress, txid); $scope.wallet.broadcastRawTx({
rawTx: tx.serialize(),
network: 'livenet'
}, function(err, txid) {
if (err) return cb(err);
return cb(null, destinationAddress, txid);
});
});
}); });
}); });
}); });
@ -95,45 +90,61 @@ angular.module('copayApp.controllers').controller('paperWalletController',
$log.error(err); $log.error(err);
popupService.showAlert(gettextCatalog.getString('Error sweeping wallet:'), err || err.toString()); popupService.showAlert(gettextCatalog.getString('Error sweeping wallet:'), err || err.toString());
} else { } else {
$scope.openStatusModal('broadcasted', function() { $scope.sendStatus = 'success';
$ionicHistory.removeBackView();
$state.go('tabs.home');
});
} }
$scope.$apply(); $scope.$apply();
}); });
}, 100); }, 100);
}; };
$scope.openStatusModal = function(type, cb) { $scope.onSuccessConfirm = function() {
$scope.tx = {}; $state.go('tabs.home');
$scope.tx.amountStr = $scope.balance;
$scope.type = type;
$scope.color = wallet.backgroundColor;
$scope.cb = cb;
$ionicModal.fromTemplateUrl('views/modals/tx-status.html', {
scope: $scope
}).then(function(modal) {
$scope.txStatusModal = modal;
$scope.txStatusModal.show();
});
}; };
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on('Wallet/Changed', function(event, wallet) {
var wallet = profileService.getWallet($stateParams.walletId); if (!wallet) {
$log.debug('No wallet provided');
return;
}
if (wallet == $scope.wallet) {
$log.debug('No change in wallet');
return;
}
$scope.wallet = wallet; $scope.wallet = wallet;
$scope.needsBackup = wallet.needsBackup; $log.debug('Wallet changed: ' + wallet.name);
$scope.walletAlias = wallet.name;
$scope.walletName = wallet.credentials.walletName;
$scope.formData = {};
$scope.formData.inputData = null;
$scope.scannedKey = null;
$scope.balance = null;
$scope.balanceSat = null;
$scope.scanned = false;
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}, 10); });
}); });
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.scannedKey = (data.stateParams && data.stateParams.privateKey) ? data.stateParams.privateKey : null;
$scope.isPkEncrypted = $scope.scannedKey ? ($scope.scannedKey.substring(0, 2) == '6P') : null;
$scope.sendStatus = null;
$scope.error = false;
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: 'livenet',
});
if (!$scope.wallets || !$scope.wallets.length) {
$scope.noMatchingWallet = true;
return;
}
});
$scope.$on("$ionicView.enter", function(event, data) {
$scope.wallet = $scope.wallets[0];
if (!$scope.wallet) return;
if (!$scope.isPkEncrypted) $scope.scanFunds();
else {
var message = gettextCatalog.getString('Private key encrypted. Enter password');
popupService.showPrompt(null, message, null, function(res) {
$scope.passphrase = res;
$scope.scanFunds();
});
}
});
}); });

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesController', angular.module('copayApp.controllers').controller('preferencesController',
function($scope, $rootScope, $timeout, $log, $stateParams, $ionicHistory, gettextCatalog, configService, profileService, fingerprintService, walletService) { function($scope, $rootScope, $timeout, $log, $stateParams, $ionicHistory, configService, profileService, fingerprintService, walletService) {
var wallet = profileService.getWallet($stateParams.walletId); var wallet = profileService.getWallet($stateParams.walletId);
var walletId = wallet.credentials.walletId; var walletId = wallet.credentials.walletId;
$scope.wallet = wallet; $scope.wallet = wallet;
@ -10,6 +10,16 @@ angular.module('copayApp.controllers').controller('preferencesController',
value: walletService.isEncrypted(wallet) value: walletService.isEncrypted(wallet)
}; };
$scope.hiddenBalanceChange = function() {
var opts = {
balance: {
enabled: $scope.hiddenBalance.value
}
};
profileService.toggleHideBalanceFlag(walletId, function(err) {
if (err) $log.error(err);
});
};
$scope.encryptChange = function() { $scope.encryptChange = function() {
if (!wallet) return; if (!wallet) return;
@ -75,7 +85,9 @@ angular.module('copayApp.controllers').controller('preferencesController',
var config = configService.getSync(); var config = configService.getSync();
$scope.hiddenBalance = {
value: $scope.wallet.balanceHidden
};
if (wallet.isPrivKeyExternal) if (wallet.isPrivKeyExternal)
$scope.externalSource = wallet.getPrivKeyExternalSourceName() == 'ledger' ? 'Ledger' : 'Trezor'; $scope.externalSource = wallet.getPrivKeyExternalSourceName() == 'ledger' ? 'Ledger' : 'Trezor';

View file

@ -6,9 +6,14 @@ angular.module('copayApp.controllers').controller('preferencesAbout',
$scope.title = gettextCatalog.getString('About') + ' ' + $window.appConfig.nameCase; $scope.title = gettextCatalog.getString('About') + ' ' + $window.appConfig.nameCase;
$scope.version = $window.version; $scope.version = $window.version;
$scope.commitHash = $window.commitHash; $scope.commitHash = $window.commitHash;
$scope.name = $window.appConfig.gitHubRepoName;
$scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) { $scope.openExternalLink = function() {
var url = 'https://github.com/bitpay/' + $window.appConfig.gitHubRepoName + '/tree/' + $window.commitHash + '';
var optIn = true;
var title = gettextCatalog.getString('Open GitHub Project');
var message = gettextCatalog.getString('You can see the latest developments and contribute to this open source app by visiting our project on GitHub.');
var okText = gettextCatalog.getString('Open GitHub');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText); externalLinkService.open(url, optIn, title, message, okText, cancelText);
}; };
}); });

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesAliasController', angular.module('copayApp.controllers').controller('preferencesAliasController',
function($scope, $timeout, $stateParams, $ionicHistory, gettextCatalog, configService, profileService, walletService) { function($scope, $timeout, $stateParams, $ionicHistory, configService, profileService, walletService) {
var wallet = profileService.getWallet($stateParams.walletId); var wallet = profileService.getWallet($stateParams.walletId);
var walletId = wallet.credentials.walletId; var walletId = wallet.credentials.walletId;
var config = configService.getSync(); var config = configService.getSync();

View file

@ -1,22 +1,35 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesAltCurrencyController', angular.module('copayApp.controllers').controller('preferencesAltCurrencyController',
function($scope, $log, $timeout, $ionicHistory, gettextCatalog, configService, rateService, lodash, profileService, walletService) { function($scope, $log, $timeout, $ionicHistory, configService, rateService, lodash, profileService, walletService, storageService) {
var next = 10; var next = 10;
var completeAlternativeList; var completeAlternativeList;
var config = configService.getSync(); function init() {
$scope.currentCurrency = config.wallet.settings.alternativeIsoCode; var unusedCurrencyList = [{
$scope.listComplete = false; isoCode: 'LTL'
}, {
isoCode: 'BTC'
}];
rateService.whenAvailable(function() {
rateService.whenAvailable(function() { $scope.listComplete = false;
completeAlternativeList = rateService.listAlternatives();
lodash.remove(completeAlternativeList, function(c) { var idx = lodash.indexBy(unusedCurrencyList, 'isoCode');
return c.isoCode == 'BTC'; var idx2 = lodash.indexBy($scope.lastUsedAltCurrencyList, 'isoCode');
completeAlternativeList = lodash.reject(rateService.listAlternatives(true), function(c) {
return idx[c.isoCode] || idx2[c.isoCode];
});
$scope.altCurrencyList = completeAlternativeList.slice(0, 10);
$timeout(function() {
$scope.$apply();
});
}); });
$scope.altCurrencyList = completeAlternativeList.slice(0, next); }
});
$scope.loadMore = function() { $scope.loadMore = function() {
$timeout(function() { $timeout(function() {
@ -27,6 +40,17 @@ angular.module('copayApp.controllers').controller('preferencesAltCurrencyControl
}, 100); }, 100);
}; };
$scope.findCurrency = function(search) {
if (!search) init();
$scope.altCurrencyList = lodash.filter(completeAlternativeList, function(item) {
var val = item.name;
return lodash.includes(val.toLowerCase(), search.toLowerCase());
});
$timeout(function() {
$scope.$apply();
});
};
$scope.save = function(newAltCurrency) { $scope.save = function(newAltCurrency) {
var opts = { var opts = {
wallet: { wallet: {
@ -41,9 +65,27 @@ angular.module('copayApp.controllers').controller('preferencesAltCurrencyControl
if (err) $log.warn(err); if (err) $log.warn(err);
$ionicHistory.goBack(); $ionicHistory.goBack();
saveLastUsed(newAltCurrency);
walletService.updateRemotePreferences(profileService.getWallets(), {}, function() { walletService.updateRemotePreferences(profileService.getWallets(), {}, function() {
$log.debug('Remote preferences saved'); $log.debug('Remote preferences saved');
}); });
}); });
}; };
function saveLastUsed(newAltCurrency) {
$scope.lastUsedAltCurrencyList.unshift(newAltCurrency);
$scope.lastUsedAltCurrencyList = lodash.uniq($scope.lastUsedAltCurrencyList, 'isoCode');
$scope.lastUsedAltCurrencyList = $scope.lastUsedAltCurrencyList.slice(0, 3);
storageService.setLastCurrencyUsed(JSON.stringify($scope.lastUsedAltCurrencyList), function() {});
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
var config = configService.getSync();
$scope.currentCurrency = config.wallet.settings.alternativeIsoCode;
storageService.getLastCurrencyUsed(function(err, lastUsedAltCurrency) {
$scope.lastUsedAltCurrencyList = lastUsedAltCurrency ? JSON.parse(lastUsedAltCurrency) : [];
init();
});
});
}); });

View file

@ -13,8 +13,11 @@ angular.module('copayApp.controllers').controller('preferencesBitpayCardControll
}; };
var remove = function(card) { var remove = function(card) {
bitpayCardService.remove(card, function() { bitpayCardService.remove(card, function(err) {
$ionicHistory.removeBackView(); if (err) {
return popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not remove card'));
}
$ionicHistory.clearHistory();
$timeout(function() { $timeout(function() {
$state.go('tabs.home'); $state.go('tabs.home');
}, 100); }, 100);

View file

@ -1,14 +1,14 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesBwsUrlController', angular.module('copayApp.controllers').controller('preferencesBwsUrlController',
function($scope, $log, $stateParams, configService, applicationService, profileService, storageService) { function($scope, $log, $stateParams, configService, applicationService, profileService, storageService, $window) {
$scope.success = null; $scope.success = null;
var wallet = profileService.getWallet($stateParams.walletId); var wallet = profileService.getWallet($stateParams.walletId);
var walletId = wallet.credentials.walletId; var walletId = wallet.credentials.walletId;
var defaults = configService.getDefaults(); var defaults = configService.getDefaults();
var config = configService.getSync(); var config = configService.getSync();
$scope.appName = $window.appConfig.nameCase;
$scope.bwsurl = { $scope.bwsurl = {
value: (config.bwsFor && config.bwsFor[walletId]) || defaults.bws.url value: (config.bwsFor && config.bwsFor[walletId]) || defaults.bws.url
}; };

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesColorController', function($scope, $timeout, $log, $stateParams, $ionicHistory, gettextCatalog, configService, profileService) { angular.module('copayApp.controllers').controller('preferencesColorController', function($scope, $timeout, $log, $stateParams, $ionicHistory, configService, profileService) {
var wallet = profileService.getWallet($stateParams.walletId); var wallet = profileService.getWallet($stateParams.walletId);
$scope.wallet = wallet; $scope.wallet = wallet;
var walletId = wallet.credentials.walletId; var walletId = wallet.credentials.walletId;

View file

@ -1,10 +1,14 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesFeeController', function($scope, $timeout, $ionicHistory, gettextCatalog, configService, feeService, ongoingProcess) { angular.module('copayApp.controllers').controller('preferencesFeeController', function($scope, $timeout, $ionicHistory, gettextCatalog, configService, feeService, ongoingProcess, popupService) {
ongoingProcess.set('gettingFeeLevels', true); ongoingProcess.set('gettingFeeLevels', true);
feeService.getFeeLevels(function(levels) { feeService.getFeeLevels(function(err, levels) {
ongoingProcess.set('gettingFeeLevels', false); ongoingProcess.set('gettingFeeLevels', false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
$scope.feeLevels = levels; $scope.feeLevels = levels;
$scope.$apply(); $scope.$apply();
}); });

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesGlideraController', angular.module('copayApp.controllers').controller('preferencesGlideraController',
function($scope, $log, $timeout, $state, ongoingProcess, glideraService, popupService, gettextCatalog) { function($scope, $log, $timeout, $state, $ionicHistory, ongoingProcess, glideraService, popupService, gettextCatalog) {
$scope.update = function(opts) { $scope.update = function(opts) {
if (!$scope.token || !$scope.permissions) return; if (!$scope.token || !$scope.permissions) return;
@ -41,8 +41,9 @@ angular.module('copayApp.controllers').controller('preferencesGlideraController'
popupService.showConfirm('Glidera', 'Are you sure you would like to log out of your Glidera account?', null, null, function(res) { popupService.showConfirm('Glidera', 'Are you sure you would like to log out of your Glidera account?', null, null, function(res) {
if (res) { if (res) {
glideraService.removeToken(function() { glideraService.removeToken(function() {
$ionicHistory.clearHistory();
$timeout(function() { $timeout(function() {
$state.go('tabs.buyandsell.glidera'); $state.go('tabs.home');
}, 100); }, 100);
}); });
} }
@ -52,14 +53,6 @@ angular.module('copayApp.controllers').controller('preferencesGlideraController'
$scope.$on("$ionicView.enter", function(event, data){ $scope.$on("$ionicView.enter", function(event, data){
$scope.network = glideraService.getEnvironment(); $scope.network = glideraService.getEnvironment();
$scope.token = null;
$scope.permissions = null;
$scope.email = null;
$scope.personalInfo = null;
$scope.txs = null;
$scope.status = null;
$scope.limits = null;
ongoingProcess.set('connectingGlidera', true); ongoingProcess.set('connectingGlidera', true);
glideraService.init($scope.token, function(err, glidera) { glideraService.init($scope.token, function(err, glidera) {
ongoingProcess.set('connectingGlidera'); ongoingProcess.set('connectingGlidera');

View file

@ -1,10 +1,11 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesHistory', angular.module('copayApp.controllers').controller('preferencesHistory',
function($scope, $log, $stateParams, $timeout, $state, $ionicHistory, gettextCatalog, storageService, platformInfo, profileService, lodash) { function($scope, $log, $stateParams, $timeout, $state, $ionicHistory, storageService, platformInfo, profileService, lodash, $window) {
$scope.wallet = profileService.getWallet($stateParams.walletId); $scope.wallet = profileService.getWallet($stateParams.walletId);
$scope.csvReady = false; $scope.csvReady = false;
$scope.isCordova = platformInfo.isCordova; $scope.isCordova = platformInfo.isCordova;
$scope.appName = $window.appConfig.nameCase;
$scope.csvHistory = function(cb) { $scope.csvHistory = function(cb) {
var allTxs = []; var allTxs = [];
@ -31,8 +32,7 @@ angular.module('copayApp.controllers').controller('preferencesHistory',
if (err) { if (err) {
$log.warn('Failed to generate CSV:', err); $log.warn('Failed to generate CSV:', err);
$scope.err = err; $scope.err = err;
} } else {
else {
$log.warn('Failed to generate CSV: no transactions'); $log.warn('Failed to generate CSV: no transactions');
$scope.err = 'no transactions'; $scope.err = 'no transactions';
} }
@ -45,7 +45,7 @@ angular.module('copayApp.controllers').controller('preferencesHistory',
var data = txs; var data = txs;
var satToBtc = 1 / 100000000; var satToBtc = 1 / 100000000;
$scope.csvContent = []; $scope.csvContent = [];
$scope.csvFilename = 'Copay-' + $scope.wallet.name + '.csv'; $scope.csvFilename = $scope.appName + '-' + $scope.wallet.name + '.csv';
$scope.csvHeader = ['Date', 'Destination', 'Description', 'Amount', 'Currency', 'Txid', 'Creator', 'Copayers', 'Comment']; $scope.csvHeader = ['Date', 'Destination', 'Description', 'Amount', 'Currency', 'Txid', 'Creator', 'Copayers', 'Comment'];
var _amount, _note, _copayers, _creator, _comment; var _amount, _note, _copayers, _creator, _comment;

View file

@ -1,62 +1,15 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesInformation', angular.module('copayApp.controllers').controller('preferencesInformation',
function($scope, $log, $timeout, $ionicHistory, $ionicScrollDelegate, platformInfo, gettextCatalog, lodash, profileService, configService, $stateParams, walletService, $state) { function($scope, $log, $ionicHistory, platformInfo, lodash, profileService, configService, $stateParams, $state) {
var base = 'xpub';
var wallet = profileService.getWallet($stateParams.walletId); var wallet = profileService.getWallet($stateParams.walletId);
var walletId = wallet.id; var walletId = wallet.id;
var config = configService.getSync(); var config = configService.getSync();
var b = 1; var colorCounter = 1;
var BLACK_WALLET_COLOR = '#202020';
$scope.isCordova = platformInfo.isCordova; $scope.isCordova = platformInfo.isCordova;
config.colorFor = config.colorFor || {}; 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() { $scope.saveBlack = function() {
function save(color) { function save(color) {
var opts = { var opts = {
@ -71,14 +24,8 @@ angular.module('copayApp.controllers').controller('preferencesInformation',
}); });
}; };
if (b != 5) return b++; if (colorCounter != 5) return colorCounter++;
save('#202020'); save(BLACK_WALLET_COLOR);
};
$scope.scan = function() {
walletService.startScan(wallet);
$ionicHistory.removeBackView();
$state.go('tabs.home');
}; };
$scope.$on("$ionicView.enter", function(event, data) { $scope.$on("$ionicView.enter", function(event, data) {
@ -95,29 +42,5 @@ angular.module('copayApp.controllers').controller('preferencesInformation',
$scope.M = c.m; $scope.M = c.m;
$scope.N = c.n; $scope.N = c.n;
$scope.pubKeys = lodash.pluck(c.publicKeyRing, 'xPubKey'); $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);
});
}); });
}); });

View file

@ -1,12 +1,18 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesLanguageController', angular.module('copayApp.controllers').controller('preferencesLanguageController',
function($scope, $log, $ionicHistory, gettextCatalog, configService, profileService, uxLanguage, walletService, externalLinkService) { function($scope, $log, $ionicHistory, configService, profileService, uxLanguage, walletService, externalLinkService, gettextCatalog) {
$scope.availableLanguages = uxLanguage.getLanguages(); $scope.availableLanguages = uxLanguage.getLanguages();
$scope.openExternalLink = function(url, target) { $scope.openExternalLink = function() {
externalLinkService.open(url, target); var url = 'https://crowdin.com/project/copay';
var optIn = true;
var title = gettextCatalog.getString('Open Translation Community');
var message = gettextCatalog.getString('You can make contributions by signing up on our Crowdin community translation website. Were looking forward to hearing from you!');
var okText = gettextCatalog.getString('Open Crowdin');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText);
}; };
$scope.save = function(newLang) { $scope.save = function(newLang) {
@ -18,19 +24,18 @@ angular.module('copayApp.controllers').controller('preferencesLanguageController
} }
}; };
uxLanguage._set(newLang);
configService.set(opts, function(err) { configService.set(opts, function(err) {
if (err) $log.warn(err); if (err) $log.warn(err);
walletService.updateRemotePreferences(profileService.getWallets(), {}, function() {
$ionicHistory.goBack(); $log.debug('Remote preferences saved');
uxLanguage.init(function() {
walletService.updateRemotePreferences(profileService.getWallets(), {}, function() {
$log.debug('Remote preferences saved');
});
}); });
}); });
$ionicHistory.goBack();
}; };
$scope.$on("$ionicView.enter", function(event, data){ $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.currentLanguage = uxLanguage.getCurrentLanguage(); $scope.currentLanguage = uxLanguage.getCurrentLanguage();
}); });
}); });

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesLogs', angular.module('copayApp.controllers').controller('preferencesLogs',
function($scope, historicLog, gettextCatalog) { function($scope, historicLog) {
$scope.$on("$ionicView.enter", function(event, data) { $scope.$on("$ionicView.enter", function(event, data) {
$scope.logs = historicLog.get(); $scope.logs = historicLog.get();

View file

@ -1,24 +1,34 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('proposalsController', angular.module('copayApp.controllers').controller('proposalsController',
function($timeout, $scope, profileService, $log, txpModalService) { function($timeout, $scope, profileService, $log, txpModalService, addressbookService) {
$scope.fetchingProposals = true; $scope.fetchingProposals = true;
$scope.$on("$ionicView.enter", function(event, data){ $scope.$on("$ionicView.enter", function(event, data) {
profileService.getTxps(50, function(err, txps) { addressbookService.list(function(err, ab) {
$scope.fetchingProposals = false; if (err) $log.error(err);
if (err) { $scope.addressbook = ab || {};
$log.error(err);
return; profileService.getTxps(50, function(err, txps) {
} $scope.fetchingProposals = false;
$scope.txps = txps; if (err) {
$timeout(function() { $log.error(err);
$scope.$apply(); return;
}, 1); }
$scope.txps = txps;
$timeout(function() {
$scope.$apply();
});
});
}); });
}); });
$scope.openTxpModal = txpModalService.open; $scope.openTxpModal = txpModalService.open;
$scope.createdWithinPastDay = function(time) {
var now = new Date();
var date = new Date(time * 1000);
return (now.getTime() - date.getTime()) < (1000 * 60 * 60 * 24);
};
}); });

View file

@ -1,219 +0,0 @@
'use strict';
angular.module('copayApp.controllers').controller('sellGlideraController',
function($scope, $timeout, $log, profileService, glideraService, bwcError, lodash, walletService, configService, ongoingProcess, popupService, gettextCatalog) {
var self = this;
this.data = {};
this.show2faCodeInput = null;
this.success = null;
var wallet;
$scope.network = glideraService.getEnvironment();
$scope.$on('Wallet/Changed', function(event, w) {
if (lodash.isEmpty(w)) {
$log.debug('No wallet provided');
return;
}
wallet = w;
$log.debug('Wallet changed: ' + w.name);
});
$scope.update = function(opts) {
if (!$scope.token || !$scope.permissions) return;
$log.debug('Updating Glidera Account...');
var accessToken = $scope.token;
var permissions = $scope.permissions;
opts = opts || {};
glideraService.getStatus(accessToken, function(err, data) {
$scope.status = data;
});
glideraService.getLimits(accessToken, function(err, limits) {
$scope.limits = limits;
});
if (permissions.transaction_history) {
glideraService.getTransactions(accessToken, function(err, data) {
$scope.txs = data;
});
}
if (permissions.view_email_address && opts.fullUpdate) {
glideraService.getEmail(accessToken, function(err, data) {
$scope.email = data.email;
});
}
if (permissions.personal_info && opts.fullUpdate) {
glideraService.getPersonalInfo(accessToken, function(err, data) {
$scope.personalInfo = data;
});
}
};
this.getSellPrice = function(token, price) {
var self = this;
if (!price || (price && !price.qty && !price.fiat)) {
self.sellPrice = null;
return;
}
self.gettingSellPrice = true;
glideraService.sellPrice(token, price, function(err, sellPrice) {
self.gettingSellPrice = false;
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not get exchange information. Please, try again'));
return;
}
self.sellPrice = sellPrice;
});
};
this.get2faCode = function(token) {
var self = this;
ongoingProcess.set('Sending 2FA code...', true);
$timeout(function() {
glideraService.get2faCode(token, function(err, sent) {
ongoingProcess.set('Sending 2FA code...', false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not send confirmation code to your phone'));
} else {
self.show2faCodeInput = sent;
}
});
}, 100);
};
this.createTx = function(token, permissions, twoFaCode) {
var self = this;
var outputs = [];
var config = configService.getSync();
var configWallet = config.wallet;
var walletSettings = configWallet.settings;
if (!wallet) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('No wallet selected'));
return;
}
ongoingProcess.set('creatingTx', true);
walletService.getAddress(wallet, null, function(err, refundAddress) {
if (!refundAddress) {
ongoingProcess.clear();
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err, 'Could not create address'));
return;
}
glideraService.getSellAddress(token, function(err, sellAddress) {
if (!sellAddress || err) {
ongoingProcess.clear();
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not get the destination bitcoin address'));
return;
}
var amount = parseInt((self.sellPrice.qty * 100000000).toFixed(0));
var comment = 'Glidera transaction';
outputs.push({
'toAddress': sellAddress,
'amount': amount,
'message': comment
});
var txp = {
toAddress: sellAddress,
amount: amount,
outputs: outputs,
message: comment,
payProUrl: null,
excludeUnconfirmedUtxos: configWallet.spendUnconfirmed ? false : true,
feeLevel: walletSettings.feeLevel || 'normal',
customData: {
'glideraToken': token
}
};
walletService.createTx(wallet, txp, function(err, createdTxp) {
ongoingProcess.clear();
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err.message || bwcError.msg(err));
return;
}
walletService.prepare(wallet, function(err, password) {
if (err) {
ongoingProcess.clear();
popupService.showAlert(gettextCatalog.getString('Error'), err.message || bwcError.msg(err));
return;
}
ongoingProcess.set('signingTx', true);
walletService.publishTx(wallet, createdTxp, function(err, publishedTxp) {
if (err) {
ongoingProcess.clear();
popupService.showAlert(gettextCatalog.getString('Error'), err.message || bwcError.msg(err));
return;
}
walletService.signTx(wallet, publishedTxp, password, function(err, signedTxp) {
if (err) {
ongoingProcess.clear();
popupService.showAlert(gettextCatalog.getString('Error'), err.message || bwcError.msg(err));
walletService.removeTx(wallet, signedTxp, function(err) {
if (err) $log.debug(err);
});
return;
}
var rawTx = signedTxp.raw;
var data = {
refundAddress: refundAddress,
signedTransaction: rawTx,
priceUuid: self.sellPrice.priceUuid,
useCurrentPrice: self.sellPrice.priceUuid ? false : true,
ip: null
};
ongoingProcess.set('Selling Bitcoin', true);
glideraService.sell(token, twoFaCode, data, function(err, data) {
ongoingProcess.clear();
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err.message || bwcError.msg(err));
return;
}
self.success = data;
$timeout(function() {
$scope.$digest();
});
});
});
});
});
});
});
});
};
$scope.$on("$ionicView.enter", function(event, data){
$scope.token = null;
$scope.permissions = null;
$scope.email = null;
$scope.personalInfo = null;
$scope.txs = null;
$scope.status = null;
$scope.limits = null;
ongoingProcess.set('connectingGlidera', true);
glideraService.init($scope.token, function(err, glidera) {
ongoingProcess.set('connectingGlidera');
if (err || !glidera) {
if (err) popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
$scope.token = glidera.token;
$scope.permissions = glidera.permissions;
$scope.update({fullUpdate: true});
});
$scope.wallets = profileService.getWallets({
network: $scope.network,
n: 1,
onlyComplete: true
});
});
});

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('tabHomeController', angular.module('copayApp.controllers').controller('tabHomeController',
function($rootScope, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, gettextCatalog, lodash, popupService, ongoingProcess, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, storageService, txpModalService, $window, bitpayCardService, startupService, addressbookService) { function($rootScope, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, gettextCatalog, lodash, popupService, ongoingProcess, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, storageService, txpModalService, $window, bitpayCardService, startupService, addressbookService, feedbackService) {
var wallet; var wallet;
var listeners = []; var listeners = [];
var notifications = []; var notifications = [];
@ -13,29 +13,132 @@ angular.module('copayApp.controllers').controller('tabHomeController',
$scope.isCordova = platformInfo.isCordova; $scope.isCordova = platformInfo.isCordova;
$scope.isAndroid = platformInfo.isAndroid; $scope.isAndroid = platformInfo.isAndroid;
$scope.isNW = platformInfo.isNW; $scope.isNW = platformInfo.isNW;
$scope.showRateCard = {};
$scope.$on("$ionicView.afterEnter", function() { $scope.$on("$ionicView.afterEnter", function() {
startupService.ready(); startupService.ready();
}); });
if (!$scope.homeTip) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
storageService.getHomeTipAccepted(function(error, value) { if (!$scope.homeTip) {
$scope.homeTip = (value == 'accepted') ? false : true; storageService.getHomeTipAccepted(function(error, value) {
}); $scope.homeTip = (value == 'accepted') ? false : true;
} });
}
if ($scope.isNW) { if ($scope.isNW) {
latestReleaseService.checkLatestRelease(function(err, newRelease) { latestReleaseService.checkLatestRelease(function(err, newRelease) {
if (err) { if (err) {
$log.warn(err); $log.warn(err);
return; return;
}
if (newRelease) $scope.newRelease = true;
});
}
storageService.getFeedbackInfo(function(error, info) {
if (!info) {
initFeedBackInfo();
} else {
var feedbackInfo = JSON.parse(info);
//Check if current version is greater than saved version
var currentVersion = window.version;
var savedVersion = feedbackInfo.version;
var isVersionUpdated = feedbackService.isVersionUpdated(currentVersion, savedVersion);
if (!isVersionUpdated) {
initFeedBackInfo();
return;
}
var now = moment().unix();
var timeExceeded = (now - feedbackInfo.time) >= 24 * 7 * 60 * 60;
$scope.showRateCard.value = timeExceeded && !feedbackInfo.sent;
$timeout(function() {
$scope.$apply();
});
} }
if (newRelease) $scope.newRelease = true;
}); });
}
$scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) { function initFeedBackInfo() {
var feedbackInfo = {};
feedbackInfo.time = moment().unix();
feedbackInfo.version = window.version;
feedbackInfo.sent = false;
storageService.setFeedbackInfo(JSON.stringify(feedbackInfo), function() {
$scope.showRateCard.value = false;
});
};
});
$scope.$on("$ionicView.enter", function(event, data) {
updateAllWallets();
addressbookService.list(function(err, ab) {
if (err) $log.error(err);
$scope.addressbook = ab || {};
});
listeners = [
$rootScope.$on('bwsEvent', function(e, walletId, type, n) {
var wallet = profileService.getWallet(walletId);
updateWallet(wallet);
if ($scope.recentTransactionsEnabled) getNotifications();
}),
$rootScope.$on('Local/TxAction', function(e, walletId) {
$log.debug('Got action for wallet ' + walletId);
var wallet = profileService.getWallet(walletId);
updateWallet(wallet);
if ($scope.recentTransactionsEnabled) getNotifications();
})
];
configService.whenAvailable(function() {
nextStep(function() {
var config = configService.getSync();
var isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova;
$scope.glideraEnabled = config.glidera.enabled && !isWindowsPhoneApp;
$scope.coinbaseEnabled = config.coinbase.enabled && !isWindowsPhoneApp;
$scope.amazonEnabled = config.amazon.enabled;
$scope.bitpayCardEnabled = config.bitpayCard.enabled;
var buyAndSellEnabled = !$scope.externalServices.BuyAndSell && ($scope.glideraEnabled || $scope.coinbaseEnabled);
var amazonEnabled = !$scope.externalServices.AmazonGiftCards && $scope.amazonEnabled;
var bitpayCardEnabled = !$scope.externalServices.BitpayCard && $scope.bitpayCardEnabled;
$scope.nextStepEnabled = buyAndSellEnabled || amazonEnabled || bitpayCardEnabled;
$scope.recentTransactionsEnabled = config.recentTransactions.enabled;
if ($scope.recentTransactionsEnabled) getNotifications();
if ($scope.bitpayCardEnabled) bitpayCardCache();
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
});
});
});
$scope.$on("$ionicView.leave", function(event, data) {
lodash.each(listeners, function(x) {
x();
});
});
$scope.createdWithinPastDay = function(time) {
var now = new Date();
var date = new Date(time * 1000);
return (now.getTime() - date.getTime()) < (1000 * 60 * 60 * 24);
};
$scope.openExternalLink = function() {
var url = 'https://github.com/bitpay/copay/releases/latest';
var optIn = true;
var title = gettextCatalog.getString('Update Available');
var message = gettextCatalog.getString('An update to this app is available. For your security, please update to the latest version.');
var okText = gettextCatalog.getString('View Update');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText); externalLinkService.open(url, optIn, title, message, okText, cancelText);
}; };
@ -43,7 +146,10 @@ angular.module('copayApp.controllers').controller('tabHomeController',
wallet = profileService.getWallet(n.walletId); wallet = profileService.getWallet(n.walletId);
if (n.txid) { if (n.txid) {
openTxModal(n); $state.transitionTo('tabs.wallet.tx-details', {
txid: n.txid,
walletId: n.walletId
});
} else { } else {
var txp = lodash.find($scope.txps, { var txp = lodash.find($scope.txps, {
id: n.txpId id: n.txpId
@ -65,37 +171,6 @@ angular.module('copayApp.controllers').controller('tabHomeController',
} }
}; };
var openTxModal = function(n) {
wallet = profileService.getWallet(n.walletId);
ongoingProcess.set('loadingTxInfo', true);
walletService.getTx(wallet, n.txid, function(err, tx) {
ongoingProcess.set('loadingTxInfo', false);
if (err) {
$log.error(err);
return popupService.showAlert(gettextCatalog.getString('Error'), err);
}
if (!tx) {
$log.warn('No tx found');
return popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Transaction not found'));
}
$scope.wallet = wallet;
$scope.btx = lodash.cloneDeep(tx);
$state.transitionTo('tabs.wallet.tx-details', {
txid: $scope.btx.txid,
walletId: $scope.walletId
});
walletService.getTxNote(wallet, n.txid, function(err, note) {
if (err) $log.warn('Could not fetch transaction note: ' + err);
$scope.btx.note = note;
});
});
};
$scope.openWallet = function(wallet) { $scope.openWallet = function(wallet) {
if (!wallet.isComplete()) { if (!wallet.isComplete()) {
return $state.go('tabs.copayers', { return $state.go('tabs.copayers', {
@ -117,7 +192,8 @@ angular.module('copayApp.controllers').controller('tabHomeController',
$scope.txpsN = n; $scope.txpsN = n;
$timeout(function() { $timeout(function() {
$ionicScrollDelegate.resize(); $ionicScrollDelegate.resize();
}, 100); $scope.$apply();
}, 10);
}) })
}; };
@ -132,8 +208,11 @@ angular.module('copayApp.controllers').controller('tabHomeController',
lodash.each($scope.wallets, function(wallet) { lodash.each($scope.wallets, function(wallet) {
walletService.getStatus(wallet, {}, function(err, status) { walletService.getStatus(wallet, {}, function(err, status) {
if (err) { if (err) {
if (err === 'WALLET_NOT_REGISTERED') wallet.error = gettextCatalog.getString('Wallet not registered');
else wallet.error = gettextCatalog.getString('Could not update');;
$log.error(err); $log.error(err);
} else { } else {
wallet.error = null;
wallet.status = status; wallet.status = status;
} }
if (++j == i) { if (++j == i) {
@ -141,25 +220,6 @@ angular.module('copayApp.controllers').controller('tabHomeController',
} }
}); });
}); });
if (!$scope.recentTransactionsEnabled) return;
$scope.fetchingNotifications = true;
profileService.getNotifications({
limit: 3
}, function(err, n) {
if (err) {
$log.error(err);
return;
}
$scope.fetchingNotifications = false;
$scope.notifications = n;
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 100);
})
}; };
var updateWallet = function(wallet) { var updateWallet = function(wallet) {
@ -171,20 +231,22 @@ angular.module('copayApp.controllers').controller('tabHomeController',
} }
wallet.status = status; wallet.status = status;
updateTxps(); updateTxps();
});
};
if (!$scope.recentTransactionsEnabled) return; var getNotifications = function() {
profileService.getNotifications({
$scope.fetchingNotifications = true; limit: 3
profileService.getNotifications({ }, function(err, n) {
limit: 3 if (err) {
}, function(err, notifications) { $log.error(err);
$scope.fetchingNotifications = false; return;
if (err) { }
$log.error(err); $scope.notifications = n;
return; $timeout(function() {
} $ionicScrollDelegate.resize();
$scope.notifications = notifications; $scope.$apply();
}); }, 10);
}); });
}; };
@ -202,7 +264,7 @@ angular.module('copayApp.controllers').controller('tabHomeController',
var services = ['AmazonGiftCards', 'BitpayCard', 'BuyAndSell']; var services = ['AmazonGiftCards', 'BitpayCard', 'BuyAndSell'];
lodash.each(services, function(service) { lodash.each(services, function(service) {
storageService.getNextStep(service, function(err, value) { storageService.getNextStep(service, function(err, value) {
$scope.externalServices[service] = value ? true : false; $scope.externalServices[service] = value == 'true' ? true : false;
if (++i == services.length) return cb(); if (++i == services.length) return cb();
}); });
}); });
@ -213,7 +275,7 @@ angular.module('copayApp.controllers').controller('tabHomeController',
$timeout(function() { $timeout(function() {
$ionicScrollDelegate.resize(); $ionicScrollDelegate.resize();
$scope.$apply(); $scope.$apply();
}, 100); }, 10);
}; };
var bitpayCardCache = function() { var bitpayCardCache = function() {
@ -241,57 +303,4 @@ angular.module('copayApp.controllers').controller('tabHomeController',
}, 300); }, 300);
updateAllWallets(); updateAllWallets();
}; };
$scope.$on("$ionicView.enter", function(event, data) {
updateAllWallets();
addressbookService.list(function(err, ab) {
if (err) $log.error(err);
$scope.addressbook = ab || {};
});
listeners = [
$rootScope.$on('bwsEvent', function(e, walletId, type, n) {
var wallet = profileService.getWallet(walletId);
updateWallet(wallet);
}),
$rootScope.$on('Local/TxAction', function(e, walletId) {
$log.debug('Got action for wallet ' + walletId);
var wallet = profileService.getWallet(walletId);
updateWallet(wallet);
})
];
configService.whenAvailable(function() {
nextStep(function() {
var config = configService.getSync();
var isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova;
$scope.glideraEnabled = config.glidera.enabled && !isWindowsPhoneApp;
$scope.coinbaseEnabled = config.coinbase.enabled && !isWindowsPhoneApp;
$scope.amazonEnabled = config.amazon.enabled;
$scope.bitpayCardEnabled = config.bitpayCard.enabled;
var buyAndSellEnabled = !$scope.externalServices.BuyAndSell && ($scope.glideraEnabled || $scope.coinbaseEnabled);
var amazonEnabled = !$scope.externalServices.AmazonGiftCards && $scope.amazonEnabled;
var bitpayCardEnabled = !$scope.externalServices.BitpayCard && $scope.bitpayCardEnabled;
$scope.nextStepEnabled = buyAndSellEnabled || amazonEnabled || bitpayCardEnabled;
$scope.recentTransactionsEnabled = config.recentTransactions.enabled;
if ($scope.bitpayCardEnabled) bitpayCardCache();
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
});
});
});
$scope.$on("$ionicView.leave", function(event, data) {
lodash.each(listeners, function(x) {
x();
});
});
}); });

View file

@ -1,9 +1,12 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('tabReceiveController', function($scope, $timeout, $log, $ionicModal, $state, $ionicHistory, storageService, platformInfo, walletService, profileService, configService, lodash, gettextCatalog, popupService) { angular.module('copayApp.controllers').controller('tabReceiveController', function($rootScope, $scope, $timeout, $log, $ionicModal, $state, $ionicHistory, $ionicPopover, storageService, platformInfo, walletService, profileService, configService, lodash, gettextCatalog, popupService, bwcError) {
var listeners = [];
var MENU_ITEM_HEIGHT = 55;
$scope.isCordova = platformInfo.isCordova; $scope.isCordova = platformInfo.isCordova;
$scope.isNW = platformInfo.isNW; $scope.isNW = platformInfo.isNW;
$scope.walletAddrs = {};
$scope.shareAddress = function(addr) { $scope.shareAddress = function(addr) {
if ($scope.generatingAddress) return; if ($scope.generatingAddress) return;
@ -18,14 +21,21 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
$scope.generatingAddress = true; $scope.generatingAddress = true;
walletService.getAddress($scope.wallet, forceNew, function(err, addr) { walletService.getAddress($scope.wallet, forceNew, function(err, addr) {
$scope.generatingAddress = false; $scope.generatingAddress = false;
if (err) popupService.showAlert(gettextCatalog.getString('Error'), err); if (err) popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err));
$scope.addr = addr; $scope.addr = addr;
if ($scope.walletAddrs[$scope.wallet.id]) $scope.walletAddrs[$scope.wallet.id] = addr;
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}, 10); }, 10);
}); });
}; };
$scope.loadAddresses = function(wallet, index) {
walletService.getAddress(wallet, false, function(err, addr) {
$scope.walletAddrs[wallet.id] = addr;
});
}
$scope.goCopayers = function() { $scope.goCopayers = function() {
$ionicHistory.removeBackView(); $ionicHistory.removeBackView();
$ionicHistory.nextViewOptions({ $ionicHistory.nextViewOptions({
@ -39,6 +49,12 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
}, 100); }, 100);
}; };
$scope.showAddresses = function() {
$state.transitionTo('tabs.receive.addresses', {
walletId: $scope.wallet.credentials.walletId
});
};
$scope.openBackupNeededModal = function() { $scope.openBackupNeededModal = function() {
$ionicModal.fromTemplateUrl('views/includes/backupNeededPopup.html', { $ionicModal.fromTemplateUrl('views/includes/backupNeededPopup.html', {
scope: $scope, scope: $scope,
@ -67,6 +83,24 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
}); });
}; };
$scope.setWallet = function(index) {
$scope.wallet = $scope.wallets[index];
$scope.walletIndex = index;
if ($scope.walletAddrs[$scope.wallet.id].addr) $scope.addr = $scope.walletAddrs[$scope.walletIndex].addr;
else $scope.setAddress(false);
}
$scope.isActive = function(index) {
return $scope.wallets[index] == $scope.wallet;
}
$scope.walletPosition = function(index) {
if (index == $scope.walletIndex) return 'current';
if (index < $scope.walletIndex) return 'prev';
if (index > $scope.walletIndex) return 'next';
}
$scope.$on('Wallet/Changed', function(event, wallet) { $scope.$on('Wallet/Changed', function(event, wallet) {
if (!wallet) { if (!wallet) {
$log.debug('No wallet provided'); $log.debug('No wallet provided');
@ -77,14 +111,82 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
return; return;
} }
$scope.wallet = wallet; $scope.wallet = wallet;
$scope.generatingAddress = false;
$log.debug('Wallet changed: ' + wallet.name); $log.debug('Wallet changed: ' + wallet.name);
$scope.walletIndex = lodash.findIndex($scope.wallets, function(wallet) {
return wallet.id == $scope.wallet.id;
});
if (!$scope.walletAddrs[wallet.id]) $scope.setAddress(false);
else $scope.addr = $scope.walletAddrs[wallet.id];
$timeout(function() { $timeout(function() {
$scope.setAddress(false); $scope.$apply();
}, 100); }, 100);
}); });
$scope.updateCurrentWallet = function() {
walletService.getStatus($scope.wallet, {}, function(err, status) {
if (err) {
$log.error(err);
}
$timeout(function() {
$scope.wallet = profileService.getWallet($scope.wallet.id);
$scope.wallet.status = status;
$scope.setAddress();
$scope.$apply();
}, 200);
});
};
var goRequestAmount = function() {
$scope.menu.hide();
$state.go('tabs.receive.amount', {
customAmount: true,
toAddress: $scope.addr
});
}
$scope.showMenu = function(allAddresses, $event) {
var requestAmountObj = {
text: gettextCatalog.getString('Request Specific amount'),
action: goRequestAmount,
};
$scope.items = [requestAmountObj];
$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);
});
};
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.wallets = profileService.getWallets(); $scope.wallets = profileService.getWallets();
lodash.each($scope.wallets, function(wallet, index) {
$scope.loadAddresses(wallet);
});
listeners = [
$rootScope.$on('bwsEvent', function(e, walletId, type, n) {
// Update current address
if ($scope.wallet && walletId == $scope.wallet.id) $scope.updateCurrentWallet();
})
];
// Update current wallet
if ($scope.wallet) $scope.updateCurrentWallet();
});
$scope.$on("$ionicView.leave", function(event, data) {
lodash.each(listeners, function(x) {
x();
});
}); });
}); });

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('tabSendController', function($scope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, $rootScope) { angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService) {
var originalList; var originalList;
var CONTACTS_SHOW_LIMIT; var CONTACTS_SHOW_LIMIT;
@ -55,7 +55,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
$timeout(function() { $timeout(function() {
$ionicScrollDelegate.resize(); $ionicScrollDelegate.resize();
$scope.$apply(); $scope.$apply();
}); }, 10);
}); });
}; };
@ -128,10 +128,12 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
}); });
} }
$scope.checkingBalance = true;
var index = 0; var index = 0;
lodash.each(wallets, function(w) { lodash.each(wallets, function(w) {
walletService.getStatus(w, {}, function(err, status) { walletService.getStatus(w, {}, function(err, status) {
++index; ++index;
if (index == wallets.length) $scope.checkingBalance = false;
if (err || !status) { if (err || !status) {
$log.error(err); $log.error(err);
return; return;
@ -143,15 +145,15 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
} }
if (index == wallets.length) { if (index == wallets.length) {
if ($scope.hasFunds != true) {
$ionicScrollDelegate.freezeScroll(true);
}
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
} }
}); });
}); });
if ($scope.hasFunds != true) {
$ionicScrollDelegate.freezeScroll(true);
}
}; };
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {

View file

@ -1,30 +1,58 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('tabSettingsController', function($scope, $window, uxLanguage, platformInfo, profileService, feeService, configService, externalLinkService) { angular.module('copayApp.controllers').controller('tabSettingsController', function($scope, $window, $ionicModal, $log, lodash, uxLanguage, platformInfo, profileService, feeService, configService, externalLinkService, bitpayCardService, storageService, glideraService, gettextCatalog) {
var updateConfig = function() { var updateConfig = function() {
var config = configService.getSync();
var isCordova = platformInfo.isCordova; var isCordova = platformInfo.isCordova;
var isWP = platformInfo.isWP; var isWP = platformInfo.isWP;
var isWindowsPhoneApp = platformInfo.isWP && isCordova;
$scope.usePushNotifications = isCordova && !isWP; $scope.usePushNotifications = isCordova && !isWP;
$scope.isCordova = isCordova;
$scope.appName = $window.appConfig.nameCase; $scope.appName = $window.appConfig.nameCase;
$scope.unitName = config.wallet.settings.unitName;
$scope.currentLanguageName = uxLanguage.getCurrentLanguageName(); $scope.currentLanguageName = uxLanguage.getCurrentLanguageName();
$scope.selectedAlternative = {
name: config.wallet.settings.alternativeName,
isoCode: config.wallet.settings.alternativeIsoCode
};
$scope.feeOpts = feeService.feeOpts; $scope.feeOpts = feeService.feeOpts;
$scope.currentFeeLevel = feeService.getCurrentFeeLevel(); $scope.currentFeeLevel = feeService.getCurrentFeeLevel();
$scope.wallets = profileService.getWallets(); $scope.wallets = profileService.getWallets();
configService.whenAvailable(function(config) {
$scope.unitName = config.wallet.settings.unitName;
$scope.selectedAlternative = {
name: config.wallet.settings.alternativeName,
isoCode: config.wallet.settings.alternativeIsoCode
};
$scope.bitpayCardEnabled = config.bitpayCard.enabled;
$scope.glideraEnabled = config.glidera.enabled && !isWindowsPhoneApp;
if ($scope.bitpayCardEnabled) {
bitpayCardService.getBitpayDebitCards(function(err, data) {
if (err) $log.error(err);
if (!lodash.isEmpty(data)) {
$scope.bitpayCards = true;
}
});
}
if ($scope.glideraEnabled) {
storageService.getGlideraToken(glideraService.getEnvironment(), function(err, token) {
if (err) $log.error(err);
$scope.glideraToken = token;
});
}
});
}; };
$scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) { $scope.openExternalLink = function() {
var url = 'https://help.bitpay.com/bitpay-app';
var optIn = true;
var title = gettextCatalog.getString('BitPay Help Center');
var message = gettextCatalog.getString('Help and support information is available at the BitPay Help Center website. Would you like to go there now?');
var okText = gettextCatalog.getString('Open Help Center');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText); externalLinkService.open(url, optIn, title, message, okText, cancelText);
}; };

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, incomingData, lodash, popupService) { angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, incomingData, lodash, popupService, gettextCatalog) {
$scope.onScan = function(data) { $scope.onScan = function(data) {
if (!incomingData.redir(data)) { if (!incomingData.redir(data)) {

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('termOfUseController', angular.module('copayApp.controllers').controller('termOfUseController',
function($scope, $window, uxLanguage, gettextCatalog, externalLinkService) { function($scope, $window, uxLanguage, externalLinkService) {
$scope.lang = uxLanguage.currentLanguage; $scope.lang = uxLanguage.currentLanguage;
$scope.disclaimerUrl = $window.appConfig.disclaimerUrl; $scope.disclaimerUrl = $window.appConfig.disclaimerUrl;

View file

@ -1,8 +1,14 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('translatorsController', angular.module('copayApp.controllers').controller('translatorsController',
function($scope, externalLinkService) { function($scope, externalLinkService, gettextCatalog) {
$scope.openExternalLink = function(url, target) { $scope.openExternalLink = function() {
externalLinkService.open(url, target); var url = 'https://crowdin.com/project/copay';
var optIn = true;
var title = gettextCatalog.getString('Open Translation Community');
var message = gettextCatalog.getString('You can make contributions by signing up on our Crowdin community translation website. Were looking forward to hearing from you!');
var okText = gettextCatalog.getString('Open Crowdin');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText);
}; };
}); });

View file

@ -1,27 +1,23 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('txDetailsController', function($log, $timeout, $ionicHistory, $scope, $filter, $stateParams, ongoingProcess, walletService, lodash, gettextCatalog, profileService, configService, txFormatService, externalLinkService, popupService) { angular.module('copayApp.controllers').controller('txDetailsController', function($log, $ionicHistory, $scope, $timeout, walletService, lodash, gettextCatalog, profileService, configService, externalLinkService, popupService, ongoingProcess) {
var config = configService.getSync();
var configWallet = config.wallet;
var walletSettings = configWallet.settings;
var wallet = profileService.getWallet($stateParams.walletId);
$scope.wallet = wallet; $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.title = gettextCatalog.getString('Transaction'); $scope.title = gettextCatalog.getString('Transaction');
$scope.wallet = profileService.getWallet(data.stateParams.walletId);
$scope.color = $scope.wallet.color;
$scope.copayerId = $scope.wallet.credentials.copayerId;
$scope.isShared = $scope.wallet.credentials.n > 1;
$scope.init = function() { ongoingProcess.set('loadingTxInfo', true);
$scope.alternativeIsoCode = walletSettings.alternativeIsoCode; walletService.getTx($scope.wallet, data.stateParams.txid, function(err, tx) {
$scope.color = wallet.color; ongoingProcess.set('loadingTxInfo', false);
$scope.copayerId = wallet.credentials.copayerId;
$scope.isShared = wallet.credentials.n > 1;
walletService.getTx(wallet, $stateParams.txid, function(err, tx) {
if (err) { if (err) {
$log.warn('Could not get tx'); $log.warn('Could not get tx');
$ionicHistory.goBack(); $ionicHistory.goBack();
return; return popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Transaction not found'));
} }
$scope.btx = tx; $scope.btx = tx;
$scope.btx.feeLevel = walletSettings.feeLevel;
if ($scope.btx.action != 'invalid') { if ($scope.btx.action != 'invalid') {
if ($scope.btx.action == 'sent') $scope.title = gettextCatalog.getString('Sent Funds'); if ($scope.btx.action == 'sent') $scope.title = gettextCatalog.getString('Sent Funds');
if ($scope.btx.action == 'received') $scope.title = gettextCatalog.getString('Received Funds'); if ($scope.btx.action == 'received') $scope.title = gettextCatalog.getString('Received Funds');
@ -33,8 +29,11 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
updateMemo(); updateMemo();
initActionList(); initActionList();
$timeout(function() {
$scope.$apply();
});
}); });
}; });
function getDisplayAmount(amountStr) { function getDisplayAmount(amountStr) {
return amountStr.split(' ')[0]; return amountStr.split(' ')[0];
@ -45,7 +44,7 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
} }
function updateMemo() { function updateMemo() {
walletService.getTxNote(wallet, $scope.btx.txid, function(err, note) { walletService.getTxNote($scope.wallet, $scope.btx.txid, function(err, note) {
if (err) { if (err) {
$log.warn('Could not fetch transaction note: ' + err); $log.warn('Could not fetch transaction note: ' + err);
return; return;
@ -53,18 +52,7 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
if (!note) return; if (!note) return;
$scope.btx.note = note; $scope.btx.note = note;
$scope.$apply();
walletService.getTx(wallet, $scope.btx.txid, function(err, tx) {
if (err) {
$log.error(err);
return;
}
tx.note = note;
$timeout(function() {
$scope.$apply();
});
});
}); });
} }
@ -109,9 +97,12 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
} }
if ($scope.btx.note && $scope.btx.note.body) opts.defaultText = $scope.btx.note.body; if ($scope.btx.note && $scope.btx.note.body) opts.defaultText = $scope.btx.note.body;
popupService.showPrompt(wallet.name, gettextCatalog.getString('Memo'), opts, function(text) { popupService.showPrompt($scope.wallet.name, gettextCatalog.getString('Memo'), opts, function(text) {
if (typeof text == "undefined") return; if (typeof text == "undefined") return;
$scope.btx.note = {
body: text
};
$log.debug('Saving memo'); $log.debug('Saving memo');
var args = { var args = {
@ -119,17 +110,10 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
body: text body: text
}; };
walletService.editTxNote(wallet, args, function(err, res) { walletService.editTxNote($scope.wallet, args, function(err, res) {
if (err) { if (err) {
$log.debug('Could not save tx comment ' + err); $log.debug('Could not save tx comment ' + err);
return;
} }
// This is only to refresh the current screen data
updateMemo();
$scope.btx.searcheableString = null;
$timeout(function() {
$scope.$apply();
});
}); });
}); });
}; };
@ -137,23 +121,42 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
$scope.viewOnBlockchain = function() { $scope.viewOnBlockchain = function() {
var btx = $scope.btx; var btx = $scope.btx;
var url = 'https://' + ($scope.getShortNetworkName() == 'test' ? 'test-' : '') + 'insight.bitpay.com/tx/' + btx.txid; var url = 'https://' + ($scope.getShortNetworkName() == 'test' ? 'test-' : '') + 'insight.bitpay.com/tx/' + btx.txid;
var title = 'View Transaction on Insight'; var optIn = true;
var message = 'Would you like to view this transaction on the Insight blockchain explorer?'; var title = gettextCatalog.getString('View Transaction on Insight');
$scope.openExternalLink(url, true, title, message, 'Open Insight', 'Go back'); var message = gettextCatalog.getString('Would you like to view this transaction on the Insight blockchain explorer?');
}; var okText = gettextCatalog.getString('Open Insight');
var cancelText = gettextCatalog.getString('Go Back');
$scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) {
externalLinkService.open(url, optIn, title, message, okText, cancelText); externalLinkService.open(url, optIn, title, message, okText, cancelText);
}; };
$scope.getShortNetworkName = function() { $scope.getShortNetworkName = function() {
var n = wallet.credentials.network; var n = $scope.wallet.credentials.network;
return n.substring(0, 4); return n.substring(0, 4);
}; };
$scope.getFiatRate = function() {
if ($scope.rateDate) return;
var alternativeIsoCode = $scope.wallet.status.alternativeIsoCode;
$scope.loadingRate = true;
$scope.wallet.getFiatRate({
code: alternativeIsoCode,
ts: $scope.btx.time * 1000
}, function(err, res) {
$scope.loadingRate = false;
if (err) {
$log.debug('Could not get historic rate');
return;
}
if (res && res.rate) {
$scope.rateDate = res.fetchedOn;
$scope.rateStr = res.rate + ' ' + alternativeIsoCode;
$scope.$apply();
}
});
};
$scope.cancel = function() { $scope.cancel = function() {
$scope.txDetailsModal.hide(); $scope.txDetailsModal.hide();
}; };
$scope.init();
}); });

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, profileService, lodash, configService, gettextCatalog, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService) { angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicHistory, profileService, lodash, configService, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog) {
var HISTORY_SHOW_LIMIT = 10; var HISTORY_SHOW_LIMIT = 10;
var currentTxHistoryPage = 0; var currentTxHistoryPage = 0;
@ -10,6 +10,9 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
$scope.openTxpModal = txpModalService.open; $scope.openTxpModal = txpModalService.open;
$scope.isCordova = platformInfo.isCordova; $scope.isCordova = platformInfo.isCordova;
$scope.isAndroid = platformInfo.isAndroid; $scope.isAndroid = platformInfo.isAndroid;
$scope.isIOS = platformInfo.isIOS;
$scope.amountIsCollapsible = !$scope.isAndroid;
$scope.openExternalLink = function(url, target) { $scope.openExternalLink = function(url, target) {
externalLinkService.open(url, target); externalLinkService.open(url, target);
@ -46,7 +49,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
var updateStatus = function(force) { var updateStatus = function(force) {
$scope.updatingStatus = true; $scope.updatingStatus = true;
$scope.updateStatusError = false; $scope.updateStatusError = null;
$scope.walletNotRegistered = false; $scope.walletNotRegistered = false;
walletService.getStatus($scope.wallet, { walletService.getStatus($scope.wallet, {
@ -57,18 +60,17 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
if (err === 'WALLET_NOT_REGISTERED') { if (err === 'WALLET_NOT_REGISTERED') {
$scope.walletNotRegistered = true; $scope.walletNotRegistered = true;
} else { } else {
$scope.updateStatusError = true; $scope.updateStatusError = bwcError.msg(err, gettextCatalog.getString('BWS Error'));
} }
$scope.status = null; $scope.status = null;
return; } else {
setPendingTxps(status.pendingTxps);
$scope.status = status;
} }
refreshAmountSection();
setPendingTxps(status.pendingTxps);
$scope.status = status;
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}, 1); });
}); });
}; };
@ -87,6 +89,14 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
$scope.close = function() { $scope.close = function() {
$scope.searchModal.hide(); $scope.searchModal.hide();
}; };
$scope.openTx = function(tx) {
$ionicHistory.nextViewOptions({
disableAnimate: true
});
$scope.searchModal.hide();
$scope.openTxModal(tx);
};
}; };
$scope.openTxModal = function(btx) { $scope.openTxModal = function(btx) {
@ -98,11 +108,25 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
}); });
}; };
$scope.openBalanceModal = function() {
$ionicModal.fromTemplateUrl('views/modals/wallet-balance.html', {
scope: $scope
}).then(function(modal) {
$scope.walletBalanceModal = modal;
$scope.walletBalanceModal.show();
});
$scope.close = function() {
$scope.walletBalanceModal.hide();
};
};
$scope.recreate = function() { $scope.recreate = function() {
walletService.recreate($scope.wallet, function(err) { walletService.recreate($scope.wallet, function(err) {
if (err) return; if (err) return;
$timeout(function() { $timeout(function() {
walletService.startScan($scope.wallet, function() { walletService.startScan($scope.wallet, function() {
$scope.updateAll();
$scope.$apply(); $scope.$apply();
}); });
}); });
@ -151,6 +175,52 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
} }
}; };
$scope.getDate = function(txCreated) {
var date = new Date(txCreated * 1000);
return date;
};
$scope.isFirstInGroup = function(index) {
if (index === 0) {
return true;
}
var curTx = $scope.txHistory[index];
var prevTx = $scope.txHistory[index - 1];
return !createdDuringSameMonth(curTx, prevTx);
};
$scope.isLastInGroup = function(index) {
if (index === $scope.txHistory.length - 1) {
return true;
}
return $scope.isFirstInGroup(index + 1);
};
function createdDuringSameMonth(tx1, tx2) {
var date1 = new Date(tx1.time * 1000);
var date2 = new Date(tx2.time * 1000);
return getMonthYear(date1) === getMonthYear(date2);
}
$scope.createdWithinPastDay = function(time) {
var now = new Date();
var date = new Date(time * 1000);
return (now.getTime() - date.getTime()) < (1000 * 60 * 60 * 24);
};
$scope.isDateInCurrentMonth = function(date) {
var now = new Date();
return getMonthYear(now) === getMonthYear(date);
};
function getMonthYear(date) {
return date.getMonth() + date.getFullYear();
}
$scope.isUnconfirmed = function(tx) {
return !tx.confirmations || tx.confirmations === 0;
};
$scope.showMore = function() { $scope.showMore = function() {
$timeout(function() { $timeout(function() {
currentTxHistoryPage++; currentTxHistoryPage++;
@ -177,9 +247,99 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
}); });
}; };
$scope.$on("$ionicView.beforeEnter", function(event, data) { var prevPos;
$scope.wallet = profileService.getWallet(data.stateParams.walletId); function getScrollPosition() {
var scrollPosition = $ionicScrollDelegate.getScrollPosition();
if (!scrollPosition) {
$window.requestAnimationFrame(function() {
getScrollPosition();
});
return;
}
var pos = scrollPosition.top;
if (pos === prevPos) {
$window.requestAnimationFrame(function() {
getScrollPosition();
});
return;
}
prevPos = pos;
refreshAmountSection(pos);
};
function refreshAmountSection(scrollPos) {
$scope.showBalanceButton = false;
if ($scope.wallet.status) {
$scope.showBalanceButton = ($scope.wallet.status.totalBalanceSat != $scope.wallet.status.spendableAmount);
}
if (!$scope.amountIsCollapsible) {
var t = ($scope.showBalanceButton ? 15 : 45);
$scope.amountScale = 'translateY(' + t + 'px)';
return;
}
scrollPos = scrollPos || 0;
var amountHeight = 210 - scrollPos;
if (amountHeight < 80) {
amountHeight = 80;
}
var contentMargin = amountHeight;
if (contentMargin > 210) {
contentMargin = 210;
}
var amountScale = (amountHeight / 210);
if (amountScale < 0.5) {
amountScale = 0.5;
}
if (amountScale > 1.1) {
amountScale = 1.1;
}
var s = amountScale;
// Make space for the balance button when it needs to display.
var TOP_NO_BALANCE_BUTTON = 115;
var TOP_BALANCE_BUTTON = 30;
var top = TOP_NO_BALANCE_BUTTON;
if ($scope.showBalanceButton) {
top = TOP_BALANCE_BUTTON;
}
var amountTop = ((amountScale - 0.7) / 0.7) * top;
if (amountTop < -10) {
amountTop = -10;
}
if (amountTop > top) {
amountTop = top;
}
var t = amountTop;
$scope.altAmountOpacity = (amountHeight - 100) / 80;
$window.requestAnimationFrame(function() {
$scope.amountHeight = amountHeight + 'px';
$scope.contentMargin = contentMargin + 'px';
$scope.amountScale = 'scale3d(' + s + ',' + s + ',' + s + ') translateY(' + t + 'px)';
$scope.$digest();
getScrollPosition();
});
}
var scrollWatcherInitialized;
$scope.$on("$ionicView.enter", function(event, data) {
if ($scope.isCordova && $scope.isAndroid) setAndroidStatusBarColor();
if (scrollWatcherInitialized || !$scope.amountIsCollapsible) {
return;
}
scrollWatcherInitialized = true;
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.walletId = data.stateParams.walletId;
$scope.wallet = profileService.getWallet($scope.walletId);
$scope.requiresMultipleSignatures = $scope.wallet.credentials.m > 1; $scope.requiresMultipleSignatures = $scope.wallet.credentials.m > 1;
addressbookService.list(function(err, ab) { addressbookService.list(function(err, ab) {
@ -188,6 +348,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
}); });
$scope.updateAll(); $scope.updateAll();
refreshAmountSection();
listeners = [ listeners = [
$rootScope.$on('bwsEvent', function(e, walletId) { $rootScope.$on('bwsEvent', function(e, walletId) {
@ -201,9 +362,55 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
]; ];
}); });
$scope.$on("$ionicView.beforeLeave", function(event, data) {
if ($window.StatusBar) {
$window.StatusBar.backgroundColorByHexString('#1e3186');
}
});
$scope.$on("$ionicView.leave", function(event, data) { $scope.$on("$ionicView.leave", function(event, data) {
lodash.each(listeners, function(x) { lodash.each(listeners, function(x) {
x(); x();
}); });
}); });
function setAndroidStatusBarColor() {
var SUBTRACT_AMOUNT = 15;
var rgb = hexToRgb($scope.wallet.color);
var keys = Object.keys(rgb);
keys.forEach(function(k) {
if (rgb[k] - SUBTRACT_AMOUNT < 0) {
rgb[k] = 0;
} else {
rgb[k] -= SUBTRACT_AMOUNT;
}
});
var statusBarColorHexString = rgbToHex(rgb.r, rgb.g, rgb.b);
if ($window.StatusBar)
$window.StatusBar.backgroundColorByHexString(statusBarColorHexString);
}
function hexToRgb(hex) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(r, g, b) {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
}); });

View file

@ -8,10 +8,11 @@ angular.module('copayApp.directives')
transclude: true, transclude: true,
scope: { scope: {
sendStatus: '=clickSendStatus', sendStatus: '=clickSendStatus',
wallet: '=hasWalletChosen'
}, },
link: function(scope, element, attrs) { link: function(scope, element, attrs) {
scope.$watch('sendStatus', function() { scope.$watch('sendStatus', function() {
if(scope.sendStatus !== 'success') { if (scope.sendStatus !== 'success') {
scope.displaySendStatus = scope.sendStatus; scope.displaySendStatus = scope.sendStatus;
} }
}); });

View file

@ -16,6 +16,6 @@ angular.module('copayApp.directives')
scope.emailHash = md5.createHash(scope.email.toLowerCase() || ''); scope.emailHash = md5.createHash(scope.email.toLowerCase() || '');
} }
}, },
template: '<img class="gravatar" alt="{{ name }}" height="{{ height }}" width="{{ width }}" src="https://secure.gravatar.com/avatar/{{ emailHash }}.jpg?s={{ width }}&d=mm">' template: '<img class="gravatar" alt="{{ name }}" height="{{ height }}" width="{{ width }}" src="https://secure.gravatar.com/avatar/{{ emailHash }}.jpg?s={{ width }}&d=identicon">'
} };
}); });

View file

@ -13,8 +13,8 @@ angular.module('copayApp.directives')
scope.showMenu = true; scope.showMenu = true;
scope.https = false; scope.https = false;
if(scope.type === 'url') { if (scope.type === 'url') {
if(scope.data.indexOf('https://') === 0) { if (scope.data.indexOf('https://') === 0) {
scope.https = true; scope.https = true;
} }
} }
@ -24,14 +24,16 @@ angular.module('copayApp.directives')
scope.showMenu = false; scope.showMenu = false;
$rootScope.$broadcast('incomingDataMenu.menuHidden'); $rootScope.$broadcast('incomingDataMenu.menuHidden');
}; };
scope.goToUrl = function(url){ scope.goToUrl = function(url) {
externalLinkService.open(url); externalLinkService.open(url);
}; };
scope.sendPaymentToAddress = function(bitcoinAddress) { scope.sendPaymentToAddress = function(bitcoinAddress) {
scope.showMenu = false; scope.showMenu = false;
$state.go('tabs.send').then(function() { $state.go('tabs.send').then(function() {
$timeout(function() { $timeout(function() {
$state.transitionTo('tabs.send.amount', {toAddress: bitcoinAddress}); $state.transitionTo('tabs.send.amount', {
toAddress: bitcoinAddress
});
}, 50); }, 50);
}); });
}; };
@ -40,11 +42,23 @@ angular.module('copayApp.directives')
$timeout(function() { $timeout(function() {
$state.go('tabs.send').then(function() { $state.go('tabs.send').then(function() {
$timeout(function() { $timeout(function() {
$state.transitionTo('tabs.send.addressbook', {addressbookEntry: bitcoinAddress}); $state.transitionTo('tabs.send.addressbook', {
addressbookEntry: bitcoinAddress
});
}); });
}); });
}, 100); }, 100);
}; };
scope.scanPaperWallet = function(privateKey) {
scope.showMenu = false;
$state.go('tabs.home').then(function() {
$timeout(function() {
$state.transitionTo('tabs.home.paperWallet', {
privateKey: privateKey
});
}, 50);
});
};
} }
}; };
}); });

View file

@ -8,7 +8,8 @@ angular.module('copayApp.directives')
transclude: true, transclude: true,
scope: { scope: {
sendStatus: '=slideSendStatus', sendStatus: '=slideSendStatus',
onConfirm: '&slideOnConfirm' onConfirm: '&slideOnConfirm',
wallet: '=hasWalletChosen'
}, },
link: function(scope, element, attrs) { link: function(scope, element, attrs) {
@ -33,9 +34,9 @@ angular.module('copayApp.directives')
scope.displaySendStatus = ''; scope.displaySendStatus = '';
scope.$watch('sendStatus', function() { scope.$watch('sendStatus', function() {
if(!scope.sendStatus) { if (!scope.sendStatus) {
reset(); reset();
} else if(scope.sendStatus === 'success') { } else if (scope.sendStatus === 'success') {
scope.displaySendStatus = ''; scope.displaySendStatus = '';
$timeout(function() { $timeout(function() {
reset(); reset();
@ -51,19 +52,20 @@ angular.module('copayApp.directives')
var startTime = currentEaseStartTime; var startTime = currentEaseStartTime;
var initialPct = fromPct; var initialPct = fromPct;
var distance = pct - fromPct; var distance = pct - fromPct;
function ease() { function ease() {
if(startTime !== currentEaseStartTime) { if (startTime !== currentEaseStartTime) {
return; return;
} }
$window.requestAnimationFrame(function() { $window.requestAnimationFrame(function() {
var now = Date.now(); var now = Date.now();
var elapsed = now - startTime; var elapsed = now - startTime;
var normalizedElapsedTime = elapsed/duration; var normalizedElapsedTime = elapsed / duration;
var newVal = easeFx(normalizedElapsedTime); var newVal = easeFx(normalizedElapsedTime);
var newPct = newVal*distance + initialPct; var newPct = newVal * distance + initialPct;
animateFx(newPct); animateFx(newPct);
scope.$digest(); scope.$digest();
if(elapsed < duration) { if (elapsed < duration) {
ease(); ease();
} else { } else {
deferred.resolve(); deferred.resolve();
@ -93,31 +95,33 @@ angular.module('copayApp.directives')
function setNewSliderStyle(pct) { function setNewSliderStyle(pct) {
var knobWidthPct = getKnobWidthPercentage(); var knobWidthPct = getKnobWidthPercentage();
var translatePct = pct - knobWidthPct; var translatePct = pct - knobWidthPct;
if(isSliding) { if (isSliding) {
translatePct += 0.35*pct; translatePct += 0.35 * pct;
} }
scope.sliderStyle = getTransformStyle(translatePct); scope.sliderStyle = getTransformStyle(translatePct);
curSliderPct = pct; curSliderPct = pct;
} }
function setNewBitcoinStyle(pct) { function setNewBitcoinStyle(pct) {
var translatePct = -2.25*pct; var translatePct = -2.25 * pct;
scope.bitcoinStyle = getTransformStyle(translatePct); scope.bitcoinStyle = getTransformStyle(translatePct);
curBitcoinPct = pct; curBitcoinPct = pct;
} }
function setNewTextStyle(pct) { function setNewTextStyle(pct) {
var translatePct = -0.1*pct; var translatePct = -0.1 * pct;
scope.textStyle = getTransformStyle(translatePct); scope.textStyle = getTransformStyle(translatePct);
curTextPct = pct; curTextPct = pct;
} }
function getTransformStyle(translatePct) { function getTransformStyle(translatePct) {
return {'transform': 'translateX(' + translatePct + '%)'}; return {
'transform': 'translateX(' + translatePct + '%)'
};
} }
function getKnobWidthPercentage() { function getKnobWidthPercentage() {
var knobWidthPct = (KNOB_WIDTH/elm.clientWidth)*100; var knobWidthPct = (KNOB_WIDTH / elm.clientWidth) * 100;
return knobWidthPct; return knobWidthPct;
} }
@ -175,8 +179,8 @@ angular.module('copayApp.directives')
function getTouchXPosition($event) { function getTouchXPosition($event) {
var x; var x;
if($event.touches || $event.changedTouches) { if ($event.touches || $event.changedTouches) {
if($event.touches.length) { if ($event.touches.length) {
x = $event.touches[0].clientX; x = $event.touches[0].clientX;
} else { } else {
x = $event.changedTouches[0].clientX; x = $event.changedTouches[0].clientX;
@ -190,18 +194,18 @@ angular.module('copayApp.directives')
function getSlidPercentage($event) { function getSlidPercentage($event) {
var x = getTouchXPosition($event); var x = getTouchXPosition($event);
var width = elm.clientWidth; var width = elm.clientWidth;
var pct = (x/width)*100; var pct = (x / width) * 100;
if(x >= width) { if (x >= width) {
pct = 100; pct = 100;
} }
return pct; return pct;
} }
scope.onTouchstart = function($event) { scope.onTouchstart = function($event) {
if(scope.isSlidFully) { if (scope.isSlidFully) {
return; return;
} }
if(!isSliding) { if (!isSliding) {
var pct = getSlidPercentage($event); var pct = getSlidPercentage($event);
if (pct > MAX_SLIDE_START_PERCENTAGE) { if (pct > MAX_SLIDE_START_PERCENTAGE) {
jiggleSlider(); jiggleSlider();
@ -209,7 +213,7 @@ angular.module('copayApp.directives')
} else { } else {
isSliding = true; isSliding = true;
var knobWidthPct = getKnobWidthPercentage(); var knobWidthPct = getKnobWidthPercentage();
if(pct < knobWidthPct) { if (pct < knobWidthPct) {
pct = knobWidthPct; pct = knobWidthPct;
} }
pct += PERCENTAGE_BUMP; pct += PERCENTAGE_BUMP;
@ -219,12 +223,12 @@ angular.module('copayApp.directives')
}; };
scope.onTouchmove = function($event) { scope.onTouchmove = function($event) {
if(!isSliding || scope.isSlidFully) { if (!isSliding || scope.isSlidFully) {
return; return;
} }
var pct = getSlidPercentage($event); var pct = getSlidPercentage($event);
var knobWidthPct = getKnobWidthPercentage(); var knobWidthPct = getKnobWidthPercentage();
if(pct < knobWidthPct) { if (pct < knobWidthPct) {
pct = knobWidthPct; pct = knobWidthPct;
} }
pct += PERCENTAGE_BUMP; pct += PERCENTAGE_BUMP;
@ -233,11 +237,11 @@ angular.module('copayApp.directives')
}; };
scope.onTouchend = function($event) { scope.onTouchend = function($event) {
if(scope.isSlidFully) { if (scope.isSlidFully) {
return; return;
} }
var pct = getSlidPercentage($event); var pct = getSlidPercentage($event);
if(isSliding && pct > FULLY_SLID_PERCENTAGE) { if (isSliding && pct > FULLY_SLID_PERCENTAGE) {
pct = 100; pct = 100;
setSliderPosition(pct); setSliderPosition(pct);
alertSlidFully(); alertSlidFully();

View file

@ -7,6 +7,7 @@ angular.module('copayApp.directives')
templateUrl: 'views/includes/walletSelector.html', templateUrl: 'views/includes/walletSelector.html',
transclude: true, transclude: true,
scope: { scope: {
title: '=walletSelectorTitle',
show: '=walletSelectorShow', show: '=walletSelectorShow',
wallets: '=walletSelectorWallets', wallets: '=walletSelectorWallets',
selectedWallet: '=walletSelectorSelectedWallet', selectedWallet: '=walletSelectorSelectedWallet',

View file

@ -151,7 +151,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
*/ */
.state('tabs.wallet', { .state('tabs.wallet', {
url: '/wallet/{walletId}/{fromOnboarding}', url: '/wallet/:walletId/:fromOnboarding',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'walletDetailsController', controller: 'walletDetailsController',
@ -182,7 +182,24 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'txDetailsController', controller: 'txDetailsController',
templateUrl: 'views/modals/tx-details.html' templateUrl: 'views/tx-details.html'
}
}
})
.state('tabs.wallet.backupWarning', {
url: '/backupWarning/:from/:walletId',
views: {
'tab-home@tabs': {
templateUrl: 'views/backupWarning.html'
}
}
})
.state('tabs.wallet.backup', {
url: '/backup/:walletId',
views: {
'tab-home@tabs': {
templateUrl: 'views/backup.html',
controller: 'backupController'
} }
} }
}) })
@ -269,7 +286,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}) })
.state('tabs.send.confirm', { .state('tabs.send.confirm', {
url: '/confirm/:isWallet/:toAddress/:toName/:toAmount/:toEmail/:description', url: '/confirm/:isWallet/:toAddress/:toName/:toAmount/:toEmail/:description/:useSendMax',
views: { views: {
'tab-send@tabs': { 'tab-send@tabs': {
controller: 'confirmController', controller: 'confirmController',
@ -538,15 +555,6 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
} }
}) })
.state('tabs.preferences.paperWallet', {
url: '/paperWallet',
views: {
'tab-settings@tabs': {
controller: 'paperWalletController',
templateUrl: 'views/paperWallet.html'
}
}
})
/* /*
* *
@ -601,7 +609,57 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
/* /*
* *
* Back flow from receive * 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'
}
}
})
/*
*
* Request Specific amount
*
*/
.state('tabs.receive.amount', {
url: '/amount/:customAmount/:toAddress',
views: {
'tab-receive@tabs': {
controller: 'amountController',
templateUrl: 'views/amount.html'
}
}
})
.state('tabs.receive.customAmount', {
url: '/customAmount/:toAmount/:toAddress',
views: {
'tab-receive@tabs': {
controller: 'customAmountController',
templateUrl: 'views/customAmount.html'
}
}
})
/*
*
* Init backup flow
* *
*/ */
@ -625,10 +683,25 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
/* /*
* *
* Onboarding * Paper Wallet
* *
*/ */
.state('tabs.home.paperWallet', {
url: '/paperWallet/:privateKey',
views: {
'tab-home@tabs': {
controller: 'paperWalletController',
templateUrl: 'views/paperWallet.html'
}
}
})
/*
*
* Onboarding
*
*/
.state('onboarding', { .state('onboarding', {
url: '/onboarding', url: '/onboarding',
abstract: true, abstract: true,
@ -695,7 +768,8 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
url: '/disclaimer/:walletId/:backedUp/:resume', url: '/disclaimer/:walletId/:backedUp/:resume',
views: { views: {
'onboarding': { 'onboarding': {
templateUrl: 'views/onboarding/disclaimer.html' templateUrl: 'views/onboarding/disclaimer.html',
controller: 'disclaimerController'
} }
} }
}) })
@ -720,6 +794,67 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}, },
}) })
/*
*
* Feedback
*
*/
.state('tabs.feedback', {
url: '/feedback',
views: {
'tab-settings@tabs': {
templateUrl: 'views/feedback/send.html',
controller: 'sendController'
}
}
})
.state('tabs.shareApp', {
url: '/shareApp/:score/:skipped/:fromSettings',
views: {
'tab-settings@tabs': {
controller: 'completeController',
templateUrl: 'views/feedback/complete.html'
}
}
})
.state('tabs.rate', {
url: '/rate',
abstract: true
})
.state('tabs.rate.send', {
url: '/send/:score',
views: {
'tab-home@tabs': {
templateUrl: 'views/feedback/send.html',
controller: 'sendController'
}
}
})
.state('tabs.rate.complete', {
url: '/complete/:score/:skipped',
views: {
'tab-home@tabs': {
controller: 'completeController',
templateUrl: 'views/feedback/complete.html'
}
},
customConfig: {
hideStatusBar: true
}
})
.state('tabs.rate.rateApp', {
url: '/rateApp/:score',
views: {
'tab-home@tabs': {
controller: 'rateAppController',
templateUrl: 'views/feedback/rateApp.html'
}
},
customConfig: {
hideStatusBar: true
}
})
/* /*
* *
@ -753,30 +888,28 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
} }
}) })
.state('tabs.buyandsell.glidera.buy', { .state('tabs.buyandsell.glidera.amount', {
url: '/buy', url: '/amount/:isGlidera/:glideraAccessToken',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'buyGlideraController', controller: 'amountController',
controllerAs: 'buy', templateUrl: 'views/amount.html'
templateUrl: 'views/buyGlidera.html'
} }
} }
}) })
.state('tabs.buyandsell.glidera.sell', { .state('tabs.buyandsell.glidera.confirm', {
url: '/sell', url: '/confirm/:toAmount/:isGlidera/:glideraAccessToken',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'sellGlideraController', controller: 'confirmController',
controllerAs: 'sell', templateUrl: 'views/confirm.html'
templateUrl: 'views/sellGlidera.html'
} }
} }
}) })
.state('tabs.buyandsell.glidera.preferences', { .state('tabs.preferences.glidera', {
url: '/preferences', url: '/glidera',
views: { views: {
'tab-home@tabs': { 'tab-settings@tabs': {
controller: 'preferencesGlideraController', controller: 'preferencesGlideraController',
templateUrl: 'views/preferencesGlidera.html' templateUrl: 'views/preferencesGlidera.html'
} }
@ -830,16 +963,36 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
controller: 'amazonController', controller: 'amazonController',
templateUrl: 'views/amazon.html' templateUrl: 'views/amazon.html'
} }
},
params: {
cardClaimCode: null
} }
}) })
.state('tabs.giftcards.amazon.buy', { .state('tabs.giftcards.amazon.amount', {
url: '/buy', url: '/amount',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'buyAmazonController', controller: 'amountController',
controllerAs: 'buy', templateUrl: 'views/amount.html'
templateUrl: 'views/buyAmazon.html'
} }
},
params: {
isGiftCard: true,
toName: 'Amazon.com Gift Card'
}
})
.state('tabs.giftcards.amazon.confirm', {
url: '/confirm/:toAmount/:toAddress/:description/:giftCardAmountUSD/:giftCardAccessKey/:giftCardInvoiceTime/:giftCardUUID',
views: {
'tab-home@tabs': {
controller: 'confirmController',
templateUrl: 'views/confirm.html'
}
},
params: {
isGiftCard: true,
toName: 'Amazon.com Gift Card',
paypro: null
} }
}) })
@ -889,10 +1042,10 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
paypro: null paypro: null
} }
}) })
.state('tabs.bitpayCard.preferences', { .state('tabs.preferences.bitpayCard', {
url: '/preferences', url: '/bitpay-card',
views: { views: {
'tab-home@tabs': { 'tab-settings@tabs': {
controller: 'preferencesBitpayCardController', controller: 'preferencesBitpayCardController',
templateUrl: 'views/preferencesBitpayCard.html' templateUrl: 'views/preferencesBitpayCard.html'
} }
@ -1030,4 +1183,14 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
$log.debug(' toParams:' + JSON.stringify(toParams || {})); $log.debug(' toParams:' + JSON.stringify(toParams || {}));
$log.debug(' fromParams:' + JSON.stringify(fromParams || {})); $log.debug(' fromParams:' + JSON.stringify(fromParams || {}));
}); });
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
if ($window.StatusBar) {
if (toState.customConfig && toState.customConfig.hideStatusBar) {
$window.StatusBar.hide();
} else {
$window.StatusBar.show();
}
}
});
}); });

View file

@ -70,7 +70,7 @@ angular.module('copayApp.services').factory('amazonService', function($http, $lo
}); });
// Show pending task from the UI // Show pending task from the UI
storageService.setNextStep('AmazonGiftCards', true, function(err) {}); storageService.setNextStep('AmazonGiftCards', 'true', function(err) {});
}; };
root.getPendingGiftCards = function(cb) { root.getPendingGiftCards = function(cb) {

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.services') angular.module('copayApp.services')
.factory('backupService', function backupServiceFactory($log, $timeout, $stateParams, profileService, sjcl) { .factory('backupService', function backupServiceFactory($log, $timeout, $stateParams, profileService, sjcl, $window) {
var root = {}; var root = {};
@ -80,7 +80,7 @@ angular.module('copayApp.services')
var walletName = (wallet.alias || '') + (wallet.alias ? '-' : '') + wallet.credentials.walletName; var walletName = (wallet.alias || '') + (wallet.alias ? '-' : '') + wallet.credentials.walletName;
if (opts.noSign) walletName = walletName + '-noSign' if (opts.noSign) walletName = walletName + '-noSign'
var filename = walletName + '-Copaybackup.aes.json'; var filename = walletName + '-' + $window.appConfig.nameCase + 'backup.aes.json';
_download(ew, filename, cb) _download(ew, filename, cb)
}; };
return root; return root;

View file

@ -7,7 +7,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
var _setError = function(msg, e) { var _setError = function(msg, e) {
$log.error(msg); $log.error(msg);
var error = e.data ? e.data.error : msg; var error = (e && e.data && e.data.error) ? e.data.error : msg;
return error; return error;
}; };
@ -104,7 +104,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
}; };
root.bitAuthPair = function(obj, cb) { root.bitAuthPair = function(obj, cb) {
var deviceName = 'Unknow device'; var deviceName = 'Unknown device';
if (platformInfo.isNW) { if (platformInfo.isNW) {
deviceName = require('os').platform(); deviceName = require('os').platform();
} else if (platformInfo.isCordova) { } else if (platformInfo.isCordova) {
@ -143,7 +143,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
root.getBitpayDebitCards(function(err, data) { root.getBitpayDebitCards(function(err, data) {
if (err) return cb(err); if (err) return cb(err);
var card = lodash.find(data, {id : cardId}); var card = lodash.find(data, {id : cardId});
if (!card) return cb(_setError('Not card found')); if (!card) return cb(_setError('Card not found'));
// Get invoices // Get invoices
$http(_post('/api/v2/' + card.token, json, appIdentity)).then(function(data) { $http(_post('/api/v2/' + card.token, json, appIdentity)).then(function(data) {
$log.info('BitPay Get Invoices: SUCCESS'); $log.info('BitPay Get Invoices: SUCCESS');
@ -180,7 +180,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
root.getBitpayDebitCards(function(err, data) { root.getBitpayDebitCards(function(err, data) {
if (err) return cb(err); if (err) return cb(err);
var card = lodash.find(data, {id : cardId}); var card = lodash.find(data, {id : cardId});
if (!card) return cb(_setError('Not card found')); if (!card) return cb(_setError('Card not found'));
$http(_post('/api/v2/' + card.token, json, appIdentity)).then(function(data) { $http(_post('/api/v2/' + card.token, json, appIdentity)).then(function(data) {
$log.info('BitPay TopUp: SUCCESS'); $log.info('BitPay TopUp: SUCCESS');
if(data.data.error) { if(data.data.error) {
@ -258,13 +258,30 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
root.remove = function(card, cb) { root.remove = function(card, cb) {
storageService.removeBitpayDebitCard(BITPAY_CARD_NETWORK, card, function(err) { storageService.removeBitpayDebitCard(BITPAY_CARD_NETWORK, card, function(err) {
if (err) {
$log.error('Error removing BitPay debit card: ' + err);
// Continue, try to remove/cleanup card history
}
storageService.removeBitpayDebitCardHistory(BITPAY_CARD_NETWORK, card, function(err) { storageService.removeBitpayDebitCardHistory(BITPAY_CARD_NETWORK, card, function(err) {
$log.info('BitPay Debit Card(s) Removed: SUCCESS'); if (err) {
$log.error('Error removing BitPay debit card transaction history: ' + err);
return cb(err);
}
$log.info('Successfully removed BitPay debit card');
return cb(); return cb();
}); });
}); });
}; };
root.getRates = function(currency, cb) {
$http(_get('/rates/' + currency)).then(function(data) {
$log.info('BitPay Get Rates: SUCCESS');
return cb(data.data.error, data.data.data);
}, function(data) {
return cb(_setError('BitPay Error: Get Rates', data));
});
};
/* /*
* CONSTANTS * CONSTANTS
*/ */

View file

@ -15,6 +15,16 @@ angular.module('copayApp.services').factory('configService', function(storageSer
url: 'https://bws.bitpay.com/bws/api', url: 'https://bws.bitpay.com/bws/api',
}, },
download: {
url: 'https://bitpay.com/wallet',
},
rateApp: {
ios: 'http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=1149581638&pageNumber=0&sortOrdering=2&type=Purple+Software&mt=8',
android: 'https://play.google.com/store/apps/details?id=com.bitpay.wallet',
wp: ''
},
// wallet default config // wallet default config
wallet: { wallet: {
requiredCopayers: 2, requiredCopayers: 2,

View file

@ -21,14 +21,10 @@ angular.module('copayApp.services').service('externalLinkService', function(plat
_restoreHandleOpenURL(old); _restoreHandleOpenURL(old);
} else { } else {
if (optIn) { if (optIn) {
var message = gettextCatalog.getString(message), var openBrowser = function(res) {
title = gettextCatalog.getString(title), if (res) window.open(url, '_system');
okText = gettextCatalog.getString(okText), _restoreHandleOpenURL(old);
cancelText = gettextCatalog.getString(cancelText), };
openBrowser = function(res) {
if (res) window.open(url, '_system');
_restoreHandleOpenURL(old);
};
popupService.showConfirm(title, message, okText, cancelText, openBrowser); popupService.showConfirm(title, message, okText, cancelText, openBrowser);
} else { } else {
window.open(url, '_system'); window.open(url, '_system');

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.services').factory('feeService', function($log, $stateParams, bwcService, walletService, configService, gettext, lodash, txFormatService) { angular.module('copayApp.services').factory('feeService', function($log, $stateParams, bwcService, walletService, configService, gettext, lodash, txFormatService, gettextCatalog) {
var root = {}; var root = {};
// Constant fee options to translate // Constant fee options to translate
@ -15,48 +15,48 @@ angular.module('copayApp.services').factory('feeService', function($log, $stateP
return configService.getSync().wallet.settings.feeLevel || 'normal'; return configService.getSync().wallet.settings.feeLevel || 'normal';
}; };
root.getCurrentFeeValue = function(cb) { root.getCurrentFeeValue = function(network, cb) {
console.log('[feeService.js.18:getCurrentFeeValue:] TODO TODO TODO'); //TODO network = network || 'livenet';
// TODO TODO TODO
var wallet = profileService.getWallet($stateParams.walletId);
var feeLevel = root.getCurrentFeeLevel(); var feeLevel = root.getCurrentFeeLevel();
wallet.getFeeLevels(wallet.credentials.network, function(err, levels) { root.getFeeLevels(function(err, levels) {
if (err) if (err) return cb(err);
return cb({
message: 'Could not get dynamic fee'
});
var feeLevelValue = lodash.find(levels, { var feeLevelValue = lodash.find(levels[network], {
level: feeLevel level: feeLevel
}); });
if (!feeLevelValue || !feeLevelValue.feePerKB)
if (!feeLevelValue || !feeLevelValue.feePerKB) {
return cb({ return cb({
message: 'Could not get dynamic fee for level: ' + feeLevel message: gettextCatalog.getString("Could not get dynamic fee for level: {{feeLevel}}", {
feeLevel: feeLevel
})
}); });
}
var fee = feeLevelValue.feePerKB; var fee = feeLevelValue.feePerKB;
$log.debug('Dynamic fee: ' + feeLevel + ' ' + fee + ' SAT'); $log.debug('Dynamic fee: ' + feeLevel + ' ' + fee + ' SAT');
return cb(null, fee); return cb(null, fee);
}); });
}; };
root.getFeeLevels = function(cb) { root.getFeeLevels = function(cb) {
var walletClient = bwcService.getClient(); var walletClient = bwcService.getClient();
var unitName = configService.getSync().wallet.settings.unitName; var unitName = configService.getSync().wallet.settings.unitName;
walletClient.getFeeLevels('livenet', function(errLivenet, levelsLivenet) { walletClient.getFeeLevels('livenet', function(errLivenet, levelsLivenet) {
walletClient.getFeeLevels('testnet', function(errTestnet, levelsTestnet) { walletClient.getFeeLevels('testnet', function(errTestnet, levelsTestnet) {
if (errLivenet || errTestnet) $log.debug('Could not get dynamic fee'); if (errLivenet || errTestnet) {
else { return cb(gettextCatalog.getString('Could not get dynamic fee'));
} else {
for (var i = 0; i < 4; i++) { for (var i = 0; i < 4; i++) {
levelsLivenet[i]['feePerKBUnit'] = txFormatService.formatAmount(levelsLivenet[i].feePerKB) + ' ' + unitName; levelsLivenet[i]['feePerKBUnit'] = txFormatService.formatAmount(levelsLivenet[i].feePerKB) + ' ' + unitName;
levelsTestnet[i]['feePerKBUnit'] = txFormatService.formatAmount(levelsTestnet[i].feePerKB) + ' ' + unitName; levelsTestnet[i]['feePerKBUnit'] = txFormatService.formatAmount(levelsTestnet[i].feePerKB) + ' ' + unitName;
} }
} }
return cb({ return cb(null, {
'livenet': levelsLivenet, 'livenet': levelsLivenet,
'testnet': levelsTestnet 'testnet': levelsTestnet
}); });

View file

@ -0,0 +1,58 @@
'use strict';
angular.module('copayApp.services').factory('feedbackService', function($http, $log, $httpParamSerializer, configService) {
var root = {};
var URL = "https://script.google.com/macros/s/AKfycbybtvNSQKUfgzgXcj3jYLlvCKrcBoktjiJ1V8_cwd2yVkpUBGe3/exec";
root.send = function(dataSrc, cb) {
$http(_post(dataSrc)).then(function() {
$log.info("SUCCESS: Feedback sent");
return cb();
}, function(err) {
$log.info("ERROR: Feedback sent anyway.");
return cb(err);
});
};
var _post = function(dataSrc) {
return {
method: 'POST',
url: URL,
headers: {
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: $httpParamSerializer(dataSrc)
};
};
root.isVersionUpdated = function(currentVersion, savedVersion) {
if (!verifyTagFormat(currentVersion))
return 'Cannot verify the format of version tag: ' + currentVersion;
if (!verifyTagFormat(savedVersion))
return 'Cannot verify the format of the saved version tag: ' + savedVersion;
var current = formatTagNumber(currentVersion);
var saved = formatTagNumber(savedVersion);
if (saved.major > current.major || (saved.major == current.major && saved.minor > current.minor))
return false;
return true;
function verifyTagFormat(tag) {
var regex = /^v?\d+\.\d+\.\d+$/i;
return regex.exec(tag);
};
function formatTagNumber(tag) {
var formattedNumber = tag.replace(/^v/i, '').split('.');
return {
major: +formattedNumber[0],
minor: +formattedNumber[1],
patch: +formattedNumber[2]
};
};
};
return root;
});

View file

@ -81,7 +81,7 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
$http(req).then(function(data) { $http(req).then(function(data) {
$log.info('Glidera Authorization Access Token: SUCCESS'); $log.info('Glidera Authorization Access Token: SUCCESS');
// Show pending task from the UI // Show pending task from the UI
storageService.setNextStep('BuyAndSell', true, function(err) {}); storageService.setNextStep('BuyAndSell', 'true', function(err) {});
return cb(null, data.data); return cb(null, data.data);
}, function(data) { }, function(data) {
$log.error('Glidera Authorization Access Token: ERROR ' + data.statusText); $log.error('Glidera Authorization Access Token: ERROR ' + data.statusText);
@ -192,8 +192,13 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
}; };
root.get2faCode = function(token, cb) { root.get2faCode = function(token, cb) {
if (!token) return cb('Invalid Token'); if (!token) {
$log.error('Glidera Sent 2FA code by SMS: ERROR Invalid Token');
return cb('Invalid Token');
}
$http(_get('/authentication/get2faCode', token)).then(function(data) { $http(_get('/authentication/get2faCode', token)).then(function(data) {
$log.info('Glidera Sent 2FA code by SMS: SUCCESS'); $log.info('Glidera Sent 2FA code by SMS: SUCCESS');
return cb(null, data.status == 200 ? true : false); return cb(null, data.status == 200 ? true : false);
}, function(data) { }, function(data) {

View file

@ -31,17 +31,31 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
if (!url) return; if (!url) return;
name = name.replace(/[\[\]]/g, "\\$&"); name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url); results = regex.exec(url);
if (!results) return null; if (!results) return null;
if (!results[2]) return ''; if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " ")); return decodeURIComponent(results[2].replace(/\+/g, " "));
} }
function checkPrivateKey(privateKey) {
try {
new bitcore.PrivateKey(privateKey, 'livenet');
} catch (err) {
return false;
}
return true;
}
// data extensions for Payment Protocol with non-backwards-compatible request // data extensions for Payment Protocol with non-backwards-compatible request
if ((/^bitcoin:\?r=[\w+]/).exec(data)) { if ((/^bitcoin:\?r=[\w+]/).exec(data)) {
data = decodeURIComponent(data.replace('bitcoin:?r=', '')); data = decodeURIComponent(data.replace('bitcoin:?r=', ''));
$state.go('tabs.send', {}, {'reload': true, 'notify': $state.current.name == 'tabs.send' ? false : true}).then(function() { $state.go('tabs.send', {}, {
$state.transitionTo('tabs.send.confirm', {paypro: data}); 'reload': true,
'notify': $state.current.name == 'tabs.send' ? false : true
}).then(function() {
$state.transitionTo('tabs.send.confirm', {
paypro: data
});
}); });
return true; return true;
} }
@ -55,31 +69,43 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
var addr = parsed.address ? parsed.address.toString() : ''; var addr = parsed.address ? parsed.address.toString() : '';
var message = parsed.message; var message = parsed.message;
var amount = parsed.amount ? parsed.amount : ''; var amount = parsed.amount ? parsed.amount : '';
if (parsed.r) { if (parsed.r) {
payproService.getPayProDetails(parsed.r, function(err, details) { payproService.getPayProDetails(parsed.r, function(err, details) {
handlePayPro(details); handlePayPro(details);
}); });
} else { } else {
$state.go('tabs.send', {}, {'reload': true, 'notify': $state.current.name == 'tabs.send' ? false : true}); $state.go('tabs.send', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.send' ? false : true
});
// Timeout is required to enable the "Back" button // Timeout is required to enable the "Back" button
$timeout(function() { $timeout(function() {
if (amount) { if (amount) {
$state.transitionTo('tabs.send.confirm', {toAmount: amount, toAddress: addr, description:message}); $state.transitionTo('tabs.send.confirm', {
toAmount: amount,
toAddress: addr,
description: message
});
} else { } else {
$state.transitionTo('tabs.send.amount', {toAddress: addr}); $state.transitionTo('tabs.send.amount', {
toAddress: addr
});
} }
}, 100); }, 100);
} }
return true; return true;
// Plain URL // Plain URL
} else if (/^https?:\/\//.test(data)) { } else if (/^https?:\/\//.test(data)) {
payproService.getPayProDetails(data, function(err, details) { payproService.getPayProDetails(data, function(err, details) {
if(err) { if (err) {
root.showMenu({data: data, type: 'url'}); root.showMenu({
data: data,
type: 'url'
});
return; return;
} }
handlePayPro(details); handlePayPro(details);
@ -87,47 +113,75 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
}); });
// Plain Address // Plain Address
} else if (bitcore.Address.isValid(data, 'livenet') || bitcore.Address.isValid(data, 'testnet')) { } else if (bitcore.Address.isValid(data, 'livenet') || bitcore.Address.isValid(data, 'testnet')) {
if($state.includes('tabs.scan')) { if ($state.includes('tabs.scan')) {
root.showMenu({data: data, type: 'bitcoinAddress'}); root.showMenu({
data: data,
type: 'bitcoinAddress'
});
} else { } else {
goToAmountPage(data); goToAmountPage(data);
} }
} else if (data && data.indexOf($window.appConfig.name + '://glidera') === 0) { } else if (data && data.indexOf($window.appConfig.name + '://glidera') === 0) {
return $state.go('uriglidera', {url: data}); return $state.go('uriglidera', {
url: data
});
} else if (data && data.indexOf($window.appConfig.name + '://coinbase') === 0) { } else if (data && data.indexOf($window.appConfig.name + '://coinbase') === 0) {
return $state.go('uricoinbase', {url: data}); return $state.go('uricoinbase', {
url: data
});
// BitPayCard Authentication // BitPayCard Authentication
} else if (data && data.indexOf($window.appConfig.name + '://') === 0) { } else if (data && data.indexOf($window.appConfig.name + '://') === 0) {
var secret = getParameterByName('secret', data); var secret = getParameterByName('secret', data);
var email = getParameterByName('email', data); var email = getParameterByName('email', data);
var otp = getParameterByName('otp', data); var otp = getParameterByName('otp', data);
$state.go('tabs.home', {}, {'reload': true, 'notify': $state.current.name == 'tabs.home' ? false : true}).then(function() { $state.go('tabs.home', {}, {
$state.transitionTo('tabs.bitpayCardIntro', { 'reload': true,
secret: secret, 'notify': $state.current.name == 'tabs.home' ? false : true
email: email, }).then(function() {
otp: otp $state.transitionTo('tabs.bitpayCardIntro', {
}); secret: secret,
email: email,
otp: otp
}); });
return true; });
return true;
// Join // Join
} else if (data && data.match(/^copay:[0-9A-HJ-NP-Za-km-z]{70,80}$/)) { } else if (data && data.match(/^copay:[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
$state.go('tabs.home', {}, {'reload': true, 'notify': $state.current.name == 'tabs.home' ? false : true}).then(function() { $state.go('tabs.home', {}, {
$state.transitionTo('tabs.add.join', {url: data}); 'reload': true,
'notify': $state.current.name == 'tabs.home' ? false : true
}).then(function() {
$state.transitionTo('tabs.add.join', {
url: data
});
}); });
return true; return true;
// Old join // Old join
} else if (data && data.match(/^[0-9A-HJ-NP-Za-km-z]{70,80}$/)) { } else if (data && data.match(/^[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
$state.go('tabs.home', {}, {'reload': true, 'notify': $state.current.name == 'tabs.home' ? false : true}).then(function() { $state.go('tabs.home', {}, {
$state.transitionTo('tabs.add.join', {url: data}); 'reload': true,
'notify': $state.current.name == 'tabs.home' ? false : true
}).then(function() {
$state.transitionTo('tabs.add.join', {
url: data
});
}); });
return true; return true;
} else if (data && (data.substring(0, 2) == '6P' || checkPrivateKey(data))) {
root.showMenu({
data: data,
type: 'privateKey'
});
} else { } else {
if($state.includes('tabs.scan')) { if ($state.includes('tabs.scan')) {
root.showMenu({data: data, type: 'text'}); root.showMenu({
data: data,
type: 'text'
});
} }
} }
@ -136,13 +190,18 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
}; };
function goToAmountPage(toAddress) { function goToAmountPage(toAddress) {
$state.go('tabs.send', {}, {'reload': true, 'notify': $state.current.name == 'tabs.send' ? false : true}); $state.go('tabs.send', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.send' ? false : true
});
$timeout(function() { $timeout(function() {
$state.transitionTo('tabs.send.amount', {toAddress: toAddress}); $state.transitionTo('tabs.send.amount', {
toAddress: toAddress
});
}, 100); }, 100);
} }
function handlePayPro(payProDetails){ function handlePayPro(payProDetails) {
var stateParams = { var stateParams = {
toAmount: payProDetails.amount, toAmount: payProDetails.amount,
toAddress: payProDetails.toAddress, toAddress: payProDetails.toAddress,
@ -150,7 +209,10 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
paypro: payProDetails paypro: payProDetails
}; };
scannerService.pausePreview(); scannerService.pausePreview();
$state.go('tabs.send', {}, {'reload': true, 'notify': $state.current.name == 'tabs.send' ? false : true}).then(function() { $state.go('tabs.send', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.send' ? false : true
}).then(function() {
$timeout(function() { $timeout(function() {
$state.transitionTo('tabs.send.confirm', stateParams); $state.transitionTo('tabs.send.confirm', stateParams);
}); });

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.services') angular.module('copayApp.services')
.factory('localStorageService', function(platformInfo, $timeout, $log) { .factory('localStorageService', function(platformInfo, $timeout, $log, lodash) {
var isNW = platformInfo.isNW; var isNW = platformInfo.isNW;
var isChromeApp = platformInfo.isChromeApp; var isChromeApp = platformInfo.isChromeApp;
var root = {}; var root = {};
@ -45,6 +45,14 @@ angular.module('copayApp.services')
root.set = function(k, v, cb) { root.set = function(k, v, cb) {
if (isChromeApp || isNW) { if (isChromeApp || isNW) {
var obj = {}; var obj = {};
if (lodash.isObject(v)) {
v = JSON.stringify(v);
}
if (!lodash.isString(v)) {
v = v.toString();
}
obj[k] = v; obj[k] = v;
chrome.storage.local.set(obj, cb); chrome.storage.local.set(obj, cb);

View file

@ -25,7 +25,7 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
'recreating': gettext('Recreating Wallet...'), 'recreating': gettext('Recreating Wallet...'),
'rejectTx': gettext('Rejecting payment proposal'), 'rejectTx': gettext('Rejecting payment proposal'),
'removeTx': gettext('Deleting payment proposal'), 'removeTx': gettext('Deleting payment proposal'),
'retrivingInputs': gettext('Retrieving inputs information'), 'retrievingInputs': gettext('Retrieving inputs information'),
'scanning': gettext('Scanning Wallet funds...'), 'scanning': gettext('Scanning Wallet funds...'),
'sendingTx': gettext('Sending transaction'), 'sendingTx': gettext('Sending transaction'),
'signingTx': gettext('Signing transaction'), 'signingTx': gettext('Signing transaction'),
@ -33,6 +33,13 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
'validatingWallet': gettext('Validating wallet integrity...'), 'validatingWallet': gettext('Validating wallet integrity...'),
'validatingWords': gettext('Validating recovery phrase...'), 'validatingWords': gettext('Validating recovery phrase...'),
'loadingTxInfo': gettext('Loading transaction info...'), 'loadingTxInfo': gettext('Loading transaction info...'),
'sendingFeedback': gettext('Sending feedback...'),
'generatingNewAddress': gettext('Generating new address...'),
'gettingAddresses': gettext('Getting addresses...'),
'sendingByEmail': gettext('Preparing addresses...'),
'sending2faCode': gettext('Sending 2FA code...'),
'buyingBitcoin': gettext('Buying Bitcoin...'),
'sellingBitcoin': gettext('Selling Bitcoin...')
}; };
root.clear = function() { root.clear = function() {
@ -64,7 +71,7 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
var showName = $filter('translate')(processNames[name] || name); var showName = $filter('translate')(processNames[name] || name);
if(customHandler) { if (customHandler) {
customHandler(processName, showName, isOn); customHandler(processName, showName, isOn);
} else if (root.onGoingProcessName) { } else if (root.onGoingProcessName) {
if (isCordova) { if (isCordova) {

View file

@ -34,7 +34,8 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
$ionicPopup.prompt({ $ionicPopup.prompt({
title: title, title: title,
subTitle: message, subTitle: message,
inputType: opts.inputType, cssClass: opts.class,
template: '<input ng-model="data.response" type="' + opts.inputType + '" autofocus>',
inputPlaceholder: opts.inputPlaceholder, inputPlaceholder: opts.inputPlaceholder,
defaultText: opts.defaultText defaultText: opts.defaultText
}).then(function(res) { }).then(function(res) {
@ -51,12 +52,12 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
var _cordovaConfirm = function(title, message, okText, cancelText, cb) { var _cordovaConfirm = function(title, message, okText, cancelText, cb) {
var onConfirm = function(buttonIndex) { var onConfirm = function(buttonIndex) {
if (buttonIndex == 1) return cb(true); if (buttonIndex == 2) return cb(true);
else return cb(false); else return cb(false);
} }
okText = okText || gettextCatalog.getString('OK'); okText = okText || gettextCatalog.getString('OK');
cancelText = cancelText || gettextCatalog.getString('Cancel'); cancelText = cancelText || gettextCatalog.getString('Cancel');
navigator.notification.confirm(message, onConfirm, title, [okText, cancelText]); navigator.notification.confirm(message, onConfirm, title, [cancelText, okText]);
}; };
var _cordovaPrompt = function(title, message, opts, cb) { var _cordovaPrompt = function(title, message, opts, cb) {
@ -118,7 +119,7 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
this.showPrompt = function(title, message, opts, cb) { this.showPrompt = function(title, message, opts, cb) {
$log.warn(title ? (title + ': ' + message) : message); $log.warn(title ? (title + ': ' + message) : message);
opts = opts || {}; opts = opts ||  {};
if (isCordova && !opts.forceHTMLPrompt) if (isCordova && !opts.forceHTMLPrompt)
_cordovaPrompt(title, message, opts, cb); _cordovaPrompt(title, message, opts, cb);

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.services') angular.module('copayApp.services')
.factory('profileService', function profileServiceFactory($rootScope, $timeout, $filter, $log, sjcl, lodash, storageService, bwcService, configService, pushNotificationsService, gettext, gettextCatalog, bwcError, uxLanguage, platformInfo, txFormatService, $state) { .factory('profileService', function profileServiceFactory($rootScope, $timeout, $filter, $log, sjcl, lodash, storageService, bwcService, configService, pushNotificationsService, gettextCatalog, bwcError, uxLanguage, platformInfo, txFormatService, $state) {
var isChromeApp = platformInfo.isChromeApp; var isChromeApp = platformInfo.isChromeApp;
@ -361,14 +361,14 @@ angular.module('copayApp.services')
} catch (ex) { } catch (ex) {
$log.info(ex); $log.info(ex);
return cb(gettext('Could not create: Invalid wallet recovery phrase')); return cb(gettextCatalog.getString('Could not create: Invalid wallet recovery phrase'));
} }
} else if (opts.extendedPrivateKey) { } else if (opts.extendedPrivateKey) {
try { try {
walletClient.seedFromExtendedPrivateKey(opts.extendedPrivateKey); walletClient.seedFromExtendedPrivateKey(opts.extendedPrivateKey);
} catch (ex) { } catch (ex) {
$log.warn(ex); $log.warn(ex);
return cb(gettext('Could not create using the specified extended private key')); return cb(gettextCatalog.getString('Could not create using the specified extended private key'));
} }
} else if (opts.extendedPublicKey) { } else if (opts.extendedPublicKey) {
try { try {
@ -378,7 +378,7 @@ angular.module('copayApp.services')
}); });
} catch (ex) { } catch (ex) {
$log.warn("Creating wallet from Extended Public Key Arg:", ex, opts); $log.warn("Creating wallet from Extended Public Key Arg:", ex, opts);
return cb(gettext('Could not create using the specified extended public key')); return cb(gettextCatalog.getString('Could not create using the specified extended public key'));
} }
} else { } else {
var lang = uxLanguage.getCurrentLanguage(); var lang = uxLanguage.getCurrentLanguage();
@ -421,7 +421,7 @@ angular.module('copayApp.services')
singleAddress: opts.singleAddress, singleAddress: opts.singleAddress,
walletPrivKey: opts.walletPrivKey, walletPrivKey: opts.walletPrivKey,
}, function(err, secret) { }, function(err, secret) {
if (err) return bwcError.cb(err, gettext('Error creating wallet'), cb); if (err) return bwcError.cb(err, gettextCatalog.getString('Error creating wallet'), cb);
return cb(null, walletClient, secret); return cb(null, walletClient, secret);
}); });
}); });
@ -451,11 +451,11 @@ angular.module('copayApp.services')
if (lodash.find(root.profile.credentials, { if (lodash.find(root.profile.credentials, {
'walletId': walletData.walletId 'walletId': walletData.walletId
})) { })) {
return cb(gettext('Cannot join the same wallet more that once')); return cb(gettextCatalog.getString('Cannot join the same wallet more that once'));
} }
} catch (ex) { } catch (ex) {
$log.debug(ex); $log.debug(ex);
return cb(gettext('Bad wallet invitation')); return cb(gettextCatalog.getString('Bad wallet invitation'));
} }
opts.networkName = walletData.network; opts.networkName = walletData.network;
$log.debug('Joining Wallet:', opts); $log.debug('Joining Wallet:', opts);
@ -464,7 +464,7 @@ angular.module('copayApp.services')
if (err) return cb(err); if (err) return cb(err);
walletClient.joinWallet(opts.secret, opts.myName || 'me', {}, function(err) { walletClient.joinWallet(opts.secret, opts.myName || 'me', {}, function(err) {
if (err) return bwcError.cb(err, gettext('Could not join wallet'), cb); if (err) return bwcError.cb(err, gettextCatalog.getString('Could not join wallet'), cb);
addAndBindWalletClient(walletClient, { addAndBindWalletClient(walletClient, {
bwsurl: opts.bwsurl bwsurl: opts.bwsurl
}, cb); }, cb);
@ -521,12 +521,12 @@ angular.module('copayApp.services')
// Adds and bind a new client to the profile // Adds and bind a new client to the profile
var addAndBindWalletClient = function(client, opts, cb) { var addAndBindWalletClient = function(client, opts, cb) {
if (!client || !client.credentials) if (!client || !client.credentials)
return cb(gettext('Could not access wallet')); return cb(gettextCatalog.getString('Could not access wallet'));
var walletId = client.credentials.walletId var walletId = client.credentials.walletId
if (!root.profile.addWallet(JSON.parse(client.export()))) if (!root.profile.addWallet(JSON.parse(client.export())))
return cb(gettext('Wallet already in Copay')); return cb(gettextCatalog.getString('Wallet already in Copay'));
var skipKeyValidation = root.profile.isChecked(platformInfo.ua, walletId); var skipKeyValidation = root.profile.isChecked(platformInfo.ua, walletId);
@ -595,15 +595,7 @@ angular.module('copayApp.services')
password: opts.password password: opts.password
}); });
} catch (err) { } catch (err) {
return cb(gettext('Could not import. Check input file and spending password')); return cb(gettextCatalog.getString('Could not import. Check input file and spending password'));
}
if (walletClient.hasPrivKeyEncrypted()) {
try {
walletClient.disablePrivateKeyEncryption();
} catch (e) {
$log.warn(e);
}
} }
str = JSON.parse(str); str = JSON.parse(str);
@ -634,7 +626,7 @@ angular.module('copayApp.services')
if (err instanceof errors.NOT_AUTHORIZED) if (err instanceof errors.NOT_AUTHORIZED)
return cb(err); return cb(err);
return bwcError.cb(err, gettext('Could not import'), cb); return bwcError.cb(err, gettextCatalog.getString('Could not import'), cb);
} }
addAndBindWalletClient(walletClient, { addAndBindWalletClient(walletClient, {
@ -665,7 +657,7 @@ angular.module('copayApp.services')
if (err instanceof errors.NOT_AUTHORIZED) if (err instanceof errors.NOT_AUTHORIZED)
return cb(err); return cb(err);
return bwcError.cb(err, gettext('Could not import'), cb); return bwcError.cb(err, gettextCatalog.getString('Could not import'), cb);
} }
addAndBindWalletClient(walletClient, { addAndBindWalletClient(walletClient, {
@ -688,7 +680,7 @@ angular.module('copayApp.services')
if (err instanceof errors.NOT_AUTHORIZED) if (err instanceof errors.NOT_AUTHORIZED)
err.name = 'WALLET_DOES_NOT_EXIST'; err.name = 'WALLET_DOES_NOT_EXIST';
return bwcError.cb(err, gettext('Could not import'), cb); return bwcError.cb(err, gettextCatalog.getString('Could not import'), cb);
} }
addAndBindWalletClient(walletClient, { addAndBindWalletClient(walletClient, {
@ -773,6 +765,12 @@ angular.module('copayApp.services')
}); });
} }
if (opts.m) {
ret = lodash.filter(ret, function(w) {
return (w.credentials.m == opts.m);
});
}
if (opts.onlyComplete) { if (opts.onlyComplete) {
ret = lodash.filter(ret, function(w) { ret = lodash.filter(ret, function(w) {
return w.isComplete(); return w.isComplete();
@ -795,7 +793,7 @@ angular.module('copayApp.services')
root.getNotifications = function(opts, cb) { root.getNotifications = function(opts, cb) {
opts = opts || {}; opts = opts || {};
var TIME_STAMP = 60 * 60 * 24 * 7; var TIME_STAMP = 60 * 60 * 6;
var MAX = 100; var MAX = 100;
var typeFilter = { var typeFilter = {
@ -861,26 +859,25 @@ angular.module('copayApp.services')
var finale = shown; // GROUPING DISABLED! var finale = shown; // GROUPING DISABLED!
// var finale = [], var finale = [],
// prev; prev;
//
//
// // Item grouping... DISABLED. // Item grouping... DISABLED.
//
// // REMOVE (if we want 1-to-1 notification) ???? // REMOVE (if we want 1-to-1 notification) ????
// lodash.each(shown, function(x) { lodash.each(shown, function(x) {
// if (prev && prev.walletId === x.walletId && prev.txpId && prev.txpId === x.txpId && prev.creatorId && prev.creatorId === x.creatorId) { if (prev && prev.walletId === x.walletId && prev.txpId && prev.txpId === x.txpId && prev.creatorId && prev.creatorId === x.creatorId) {
// prev.types.push(x.type); prev.types.push(x.type);
// prev.data = lodash.assign(prev.data, x.data); prev.data = lodash.assign(prev.data, x.data);
// prev.txid = prev.txid || x.txid; prev.txid = prev.txid || x.txid;
// prev.amountStr = prev.amountStr || x.amountStr; prev.amountStr = prev.amountStr || x.amountStr;
// prev.creatorName = prev.creatorName || x.creatorName; prev.creatorName = prev.creatorName || x.creatorName;
// } else { } else {
// finale.push(x); finale.push(x);
// prev = x; prev = x;
// } }
// }); });
//
var u = bwcService.getUtils(); var u = bwcService.getUtils();
lodash.each(finale, function(x) { lodash.each(finale, function(x) {

View file

@ -111,18 +111,24 @@ RateService.prototype.fromFiat = function(amount, code) {
return amount / this.getRate(code) * this.BTC_TO_SAT; return amount / this.getRate(code) * this.BTC_TO_SAT;
}; };
RateService.prototype.listAlternatives = function() { RateService.prototype.listAlternatives = function(sort) {
var self = this; var self = this;
if (!this.isAvailable()) { if (!this.isAvailable()) {
return []; return [];
} }
return self.lodash.map(this.getAlternatives(), function(item) { var alternatives = self.lodash.map(this.getAlternatives(), function(item) {
return { return {
name: item.name, name: item.name,
isoCode: item.isoCode isoCode: item.isoCode
} }
}); });
if (sort) {
alternatives.sort(function(a, b) {
return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1;
});
}
return self.lodash.uniq(alternatives, 'isoCode');
}; };
angular.module('copayApp.services').factory('rateService', function($http, lodash) { angular.module('copayApp.services').factory('rateService', function($http, lodash) {

View file

@ -244,6 +244,14 @@ angular.module('copayApp.services')
storage.remove('profile', cb); storage.remove('profile', cb);
}; };
root.setFeedbackInfo = function(feedbackValues, cb) {
storage.set('feedback', feedbackValues, cb);
};
root.getFeedbackInfo = function(cb) {
storage.get('feedback', cb);
};
root.storeFocusedWalletId = function(id, cb) { root.storeFocusedWalletId = function(id, cb) {
storage.set('focusedWalletId', id || '', cb); storage.set('focusedWalletId', id || '', cb);
}; };
@ -390,6 +398,14 @@ angular.module('copayApp.services')
storage.remove('nextStep-' + service, cb); storage.remove('nextStep-' + service, cb);
}; };
root.setLastCurrencyUsed = function(lastCurrencyUsed, cb) {
storage.set('lastCurrencyUsed', lastCurrencyUsed, cb)
};
root.getLastCurrencyUsed = function(cb) {
storage.get('lastCurrencyUsed', cb)
};
root.checkQuota = function() { root.checkQuota = function() {
var block = ''; var block = '';
// 50MB // 50MB
@ -459,7 +475,10 @@ angular.module('copayApp.services')
if (lodash.isEmpty(data) || !data.email) return cb('No card(s) to set'); if (lodash.isEmpty(data) || !data.email) return cb('No card(s) to set');
storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) {
if (err) return cb(err); if (err) return cb(err);
bitpayAccounts = JSON.parse(bitpayAccounts) || {}; if (lodash.isString(bitpayAccounts)) {
bitpayAccounts = JSON.parse(bitpayAccounts);
}
bitpayAccounts = bitpayAccounts || {};
bitpayAccounts[data.email] = bitpayAccounts[data.email] || {}; bitpayAccounts[data.email] = bitpayAccounts[data.email] || {};
bitpayAccounts[data.email]['bitpayDebitCards-' + network] = data; bitpayAccounts[data.email]['bitpayDebitCards-' + network] = data;
storage.set('bitpayAccounts-' + network, JSON.stringify(bitpayAccounts), cb); storage.set('bitpayAccounts-' + network, JSON.stringify(bitpayAccounts), cb);
@ -468,7 +487,10 @@ angular.module('copayApp.services')
root.getBitpayDebitCards = function(network, cb) { root.getBitpayDebitCards = function(network, cb) {
storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) {
bitpayAccounts = JSON.parse(bitpayAccounts) || {}; if (lodash.isString(bitpayAccounts)) {
bitpayAccounts = JSON.parse(bitpayAccounts);
}
bitpayAccounts = bitpayAccounts || {};
var cards = []; var cards = [];
Object.keys(bitpayAccounts).forEach(function(email) { Object.keys(bitpayAccounts).forEach(function(email) {
// For the UI, add the account email to the card object. // For the UI, add the account email to the card object.
@ -490,15 +512,20 @@ angular.module('copayApp.services')
if (lodash.isEmpty(card) || !card.eid) return cb('No card to remove'); if (lodash.isEmpty(card) || !card.eid) return cb('No card to remove');
storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) { storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) {
if (err) cb(err); if (err) cb(err);
bitpayAccounts = JSON.parse(bitpayAccounts) || {}; if (lodash.isString(bitpayAccounts)) {
bitpayAccounts = JSON.parse(bitpayAccounts);
}
bitpayAccounts = bitpayAccounts || {};
Object.keys(bitpayAccounts).forEach(function(userId) { Object.keys(bitpayAccounts).forEach(function(userId) {
var data = bitpayAccounts[userId]['bitpayDebitCards-' + network]; var data = bitpayAccounts[userId]['bitpayDebitCards-' + network];
var newCards = lodash.reject(data.cards, {'eid': card.eid}); var newCards = lodash.reject(data.cards, {
'eid': card.eid
});
data.cards = newCards; data.cards = newCards;
root.setBitpayDebitCards(network, data, function(err) { root.setBitpayDebitCards(network, data, function(err) {
if (err) cb(err); if (err) cb(err);
// If there are no more cards in storage then re-enable the next step entry. // If there are no more cards in storage then re-enable the next step entry.
root.getBitpayDebitCards(network, function(err, cards){ root.getBitpayDebitCards(network, function(err, cards) {
if (err) cb(err); if (err) cb(err);
if (cards.length == 0) { if (cards.length == 0) {
root.removeNextStep('BitpayCard', cb); root.removeNextStep('BitpayCard', cb);

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.services') angular.module('copayApp.services')
.factory('trezor', function($log, $timeout, gettext, lodash, bitcore, hwWallet) { .factory('trezor', function($log, $timeout, lodash, bitcore, hwWallet) {
var root = {}; var root = {};
var SETTLE_TIME = 3000; var SETTLE_TIME = 3000;
@ -82,7 +82,7 @@ angular.module('copayApp.services')
if (txp.outputs.length > 1) if (txp.outputs.length > 1)
return callback('Only single output TXPs are supported in TREZOR'); return callback('Only single output TXPs are supported in TREZOR');
} else { } else {
return callback('Unknown TXP at TREZOR'); return callback('Unknown TXP at TREZOR');
} }
if (txp.outputs) { if (txp.outputs) {

View file

@ -8,42 +8,39 @@ angular.module('copayApp.services')
root.availableLanguages = [{ root.availableLanguages = [{
name: 'English', name: 'English',
isoCode: 'en', isoCode: 'en',
}, {
name: 'Český',
isoCode: 'cs',
}, {
name: 'Français',
isoCode: 'fr',
}, {
name: 'Italiano',
isoCode: 'it',
}, {
name: 'Deutsch',
isoCode: 'de',
}, { }, {
name: 'Español', name: 'Español',
isoCode: 'es', isoCode: 'es',
}, { }, {
name: '日本語', name: 'Français',
isoCode: 'ja', isoCode: 'fr',
useIdeograms: true, // }, {
}, { // name: 'Český',
name: '中文(简体)', // isoCode: 'cs',
isoCode: 'zh', // }, {
useIdeograms: true, // name: 'Italiano',
}, { // isoCode: 'it',
name: 'Polski', // }, {
isoCode: 'pl', // name: 'Deutsch',
}, { // isoCode: 'de',
name: 'Pусский', // }, {
isoCode: 'ru', // name: '日本語',
// isoCode: 'ja',
// useIdeograms: true,
// }, {
// name: '中文(简体)',
// isoCode: 'zh',
// useIdeograms: true,
// }, {
// name: 'Polski',
// isoCode: 'pl',
// }, {
// name: 'Pусский',
// isoCode: 'ru',
}]; }];
root._detect = function(cb) { root._detect = function(cb) {
return cb('en'); //disable auto detection for release;
var userLang, androidLang; var userLang, androidLang;
if (navigator && navigator.globalization) { if (navigator && navigator.globalization) {

View file

@ -29,16 +29,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
}); });
}; };
// // RECEIVE
// // Check address
// root.isUsed(wallet.walletId, balance.byAddress, function(err, used) {
// if (used) {
// $log.debug('Address used. Creating new');
// $rootScope.$emit('Local/AddressIsUsed');
// }
// });
//
var _signWithLedger = function(wallet, txp, cb) { var _signWithLedger = function(wallet, txp, cb) {
$log.info('Requesting Ledger Chrome app to sign the transaction'); $log.info('Requesting Ledger Chrome app to sign the transaction');
@ -67,32 +57,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
}); });
}; };
// TODO
// This handles errors from BWS/index which normally
// trigger from async events (like updates).
// Debounce function avoids multiple popups
var _handleError = function(err) {
$log.warn('wallet ERROR: ', err);
$log.warn('TODO');
return; // TODO!!!
if (err instanceof errors.NOT_AUTHORIZED) {
console.log('[walletService.js.93] TODO NOT AUTH'); //TODO
// TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
wallet.notAuthorized = true;
$state.go('tabs.home');
} else if (err instanceof errors.NOT_FOUND) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not access Wallet Service: Not found'));
} else {
var msg = ""
$rootScope.$emit('Local/ClientError', (err.error ? err.error : err));
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err, gettextCatalog.getString('Error at Wallet Service')));
}
};
root.handleError = lodash.debounce(_handleError, 1000);
root.invalidateCache = function(wallet) { root.invalidateCache = function(wallet) {
if (wallet.cachedStatus) if (wallet.cachedStatus)
wallet.cachedStatus.isValid = false; wallet.cachedStatus.isValid = false;
@ -180,8 +144,9 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
if (err instanceof errors.NOT_AUTHORIZED) { if (err instanceof errors.NOT_AUTHORIZED) {
return cb('WALLET_NOT_REGISTERED'); return cb('WALLET_NOT_REGISTERED');
} }
return cb(bwcError.msg(err, gettext('Could not update Wallet'))); return cb(err);
} }
return cb(null, ret); return cb(null, ret);
}); });
}; };
@ -196,19 +161,22 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
// Address with Balance // Address with Balance
cache.balanceByAddress = balance.byAddress; cache.balanceByAddress = balance.byAddress;
// Total wallet balance is same regardless of 'spend unconfirmed funds' setting.
cache.totalBalanceSat = balance.totalAmount;
// Spend unconfirmed funds // Spend unconfirmed funds
if (config.spendUnconfirmed) { if (config.spendUnconfirmed) {
cache.totalBalanceSat = balance.totalAmount;
cache.lockedBalanceSat = balance.lockedAmount; cache.lockedBalanceSat = balance.lockedAmount;
cache.availableBalanceSat = balance.availableAmount; cache.availableBalanceSat = balance.availableAmount;
cache.totalBytesToSendMax = balance.totalBytesToSendMax; cache.totalBytesToSendMax = balance.totalBytesToSendMax;
cache.pendingAmount = null; cache.pendingAmount = 0;
cache.spendableAmount = balance.totalAmount - balance.lockedAmount;
} else { } else {
cache.totalBalanceSat = balance.totalConfirmedAmount;
cache.lockedBalanceSat = balance.lockedConfirmedAmount; cache.lockedBalanceSat = balance.lockedConfirmedAmount;
cache.availableBalanceSat = balance.availableConfirmedAmount; cache.availableBalanceSat = balance.availableConfirmedAmount;
cache.totalBytesToSendMax = balance.totalBytesToSendConfirmedMax; cache.totalBytesToSendMax = balance.totalBytesToSendConfirmedMax;
cache.pendingAmount = balance.totalAmount - balance.totalConfirmedAmount; cache.pendingAmount = balance.totalAmount - balance.totalConfirmedAmount;
cache.spendableAmount = balance.totalConfirmedAmount - balance.lockedAmount;
} }
// Selected unit // Selected unit
@ -220,25 +188,35 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
cache.totalBalanceStr = txFormatService.formatAmount(cache.totalBalanceSat) + ' ' + cache.unitName; cache.totalBalanceStr = txFormatService.formatAmount(cache.totalBalanceSat) + ' ' + cache.unitName;
cache.lockedBalanceStr = txFormatService.formatAmount(cache.lockedBalanceSat) + ' ' + cache.unitName; cache.lockedBalanceStr = txFormatService.formatAmount(cache.lockedBalanceSat) + ' ' + cache.unitName;
cache.availableBalanceStr = txFormatService.formatAmount(cache.availableBalanceSat) + ' ' + cache.unitName; cache.availableBalanceStr = txFormatService.formatAmount(cache.availableBalanceSat) + ' ' + cache.unitName;
cache.pendingBalanceStr = txFormatService.formatAmount(cache.totalBalanceSat + (cache.pendingAmount === null? 0 : cache.pendingAmount)) + ' ' + cache.unitName; cache.spendableBalanceStr = txFormatService.formatAmount(cache.spendableAmount) + ' ' + cache.unitName;
cache.pendingBalanceStr = txFormatService.formatAmount(cache.pendingAmount) + ' ' + cache.unitName;
if (cache.pendingAmount !== null && cache.pendingAmount !== 0) {
cache.pendingAmountStr = txFormatService.formatAmount(cache.pendingAmount) + ' ' + cache.unitName;
} else {
cache.pendingAmountStr = null;
}
cache.alternativeName = config.settings.alternativeName; cache.alternativeName = config.settings.alternativeName;
cache.alternativeIsoCode = config.settings.alternativeIsoCode; cache.alternativeIsoCode = config.settings.alternativeIsoCode;
// Check address
root.isAddressUsed(wallet, balance.byAddress, function(err, used) {
if (used) {
$log.debug('Address used. Creating new');
// Force new address
root.getAddress(wallet, true, function(err, addr) {
$log.debug('New address: ', addr);
});
}
});
rateService.whenAvailable(function() { rateService.whenAvailable(function() {
var totalBalanceAlternative = rateService.toFiat(cache.totalBalanceSat, cache.alternativeIsoCode); var totalBalanceAlternative = rateService.toFiat(cache.totalBalanceSat, cache.alternativeIsoCode);
var pendingBalanceAlternative = rateService.toFiat(cache.pendingAmount, cache.alternativeIsoCode);
var lockedBalanceAlternative = rateService.toFiat(cache.lockedBalanceSat, cache.alternativeIsoCode); var lockedBalanceAlternative = rateService.toFiat(cache.lockedBalanceSat, cache.alternativeIsoCode);
var spendableBalanceAlternative = rateService.toFiat(cache.spendableAmount, cache.alternativeIsoCode);
var alternativeConversionRate = rateService.toFiat(100000000, cache.alternativeIsoCode); var alternativeConversionRate = rateService.toFiat(100000000, cache.alternativeIsoCode);
cache.totalBalanceAlternative = $filter('formatFiatAmount')(totalBalanceAlternative); cache.totalBalanceAlternative = $filter('formatFiatAmount')(totalBalanceAlternative);
cache.pendingBalanceAlternative = $filter('formatFiatAmount')(pendingBalanceAlternative);
cache.lockedBalanceAlternative = $filter('formatFiatAmount')(lockedBalanceAlternative); cache.lockedBalanceAlternative = $filter('formatFiatAmount')(lockedBalanceAlternative);
cache.spendableBalanceAlternative = $filter('formatFiatAmount')(spendableBalanceAlternative);
cache.alternativeConversionRate = $filter('formatFiatAmount')(alternativeConversionRate); cache.alternativeConversionRate = $filter('formatFiatAmount')(alternativeConversionRate);
cache.alternativeBalanceAvailable = true; cache.alternativeBalanceAvailable = true;
@ -337,7 +315,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
return tx.txid != endingTxid; return tx.txid != endingTxid;
}); });
return cb(null, res, res.length == limit); return cb(null, res, res.length >= limit);
}); });
}; };
@ -428,7 +406,16 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
function getNewTxs(newTxs, skip, cb) { function getNewTxs(newTxs, skip, cb) {
getTxsFromServer(wallet, skip, endingTxid, requestLimit, function(err, res, shouldContinue) { getTxsFromServer(wallet, skip, endingTxid, requestLimit, function(err, res, shouldContinue) {
if (err) return cb(err); if (err) {
$log.warn(bwcError.msg(err, 'BWS Error')); //TODO
if (err instanceof errors.CONNECTION_ERROR || (err.message && err.message.match(/5../))) {
log.info('Retrying history download in 5 secs...');
return $timeout(function() {
return getNewTxs(newTxs, skip, cb);
}, 5000);
};
return cb(err);
}
newTxs = newTxs.concat(processNewTxs(wallet, lodash.compact(res))); newTxs = newTxs.concat(processNewTxs(wallet, lodash.compact(res)));
@ -587,20 +574,13 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
if (lodash.isEmpty(txp) || lodash.isEmpty(wallet)) if (lodash.isEmpty(txp) || lodash.isEmpty(wallet))
return cb('MISSING_PARAMETER'); return cb('MISSING_PARAMETER');
if (txp.sendMax) { wallet.createTxProposal(txp, function(err, createdTxp) {
wallet.createTxProposal(txp, function(err, createdTxp) { if (err) return cb(err);
if (err) return cb(err); else {
else return cb(null, createdTxp); $log.debug('Transaction created');
}); return cb(null, createdTxp);
} else { }
wallet.createTxProposal(txp, function(err, createdTxp) { });
if (err) return cb(err);
else {
$log.debug('Transaction created');
return cb(null, createdTxp);
}
});
}
}; };
root.publishTx = function(wallet, txp, cb) { root.publishTx = function(wallet, txp, cb) {
@ -760,12 +740,13 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
}); });
}; };
root.isUsed = function(wallet, byAddress, cb) { // Check address
root.isAddressUsed = function(wallet, byAddress, cb) {
storageService.getLastAddress(wallet.id, function(err, addr) { storageService.getLastAddress(wallet.id, function(err, addr) {
var used = lodash.find(byAddress, { var used = lodash.find(byAddress, {
address: addr address: addr
}); });
return cb(null, used); return cb(err, used);
}); });
}; };
@ -797,13 +778,30 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
}); });
}; };
root.getAddress = function(wallet, forceNew, cb) { 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) { storageService.getLastAddress(wallet.id, function(err, addr) {
if (err) return cb(err); if (err) return cb(err);
if (!forceNew && addr) return cb(null, addr); if (!forceNew && addr) return cb(null, addr);
if (!wallet.isComplete())
return cb('WALLET_NOT_COMPLETE');
createAddress(wallet, function(err, _addr) { createAddress(wallet, function(err, _addr) {
if (err) return cb(err, addr); if (err) return cb(err, addr);
storageService.storeLastAddress(wallet.id, _addr, function() { storageService.storeLastAddress(wallet.id, _addr, function() {
@ -829,7 +827,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
var askPassword = function(name, title, cb) { var askPassword = function(name, title, cb) {
var opts = { var opts = {
inputType: 'password', inputType: 'password',
forceHTMLPrompt: true forceHTMLPrompt: true,
class: 'text-warn'
}; };
popupService.showPrompt(title, name, opts, function(res) { popupService.showPrompt(title, name, opts, function(res) {
if (!res) return cb(); if (!res) return cb();
@ -839,9 +838,12 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
root.encrypt = function(wallet, cb) { root.encrypt = function(wallet, cb) {
askPassword(wallet.name, gettext('Enter new spending password'), function(password) { var title = gettextCatalog.getString('Enter new spending password');
var warnMsg = gettextCatalog.getString('Your wallet key will be encrypted. The Spending Password cannot be recovered. Be sure to write it down.');
askPassword(warnMsg, title, function(password) {
if (!password) return cb('no password'); if (!password) return cb('no password');
askPassword(wallet.name, gettext('Confirm you new spending password'), function(password2) { title = gettextCatalog.getString('Confirm you new spending password');
askPassword(warnMsg, gettextCatalog.getString('Confirm you new spending password'), function(password2) {
if (!password2 || password != password2) if (!password2 || password != password2)
return cb('password mismatch'); return cb('password mismatch');
@ -854,7 +856,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
root.decrypt = function(wallet, cb) { root.decrypt = function(wallet, cb) {
$log.debug('Disabling private key encryption for' + wallet.name); $log.debug('Disabling private key encryption for' + wallet.name);
askPassword(wallet.name, gettext('Enter Spending Password'), function(password) { askPassword(null, gettextCatalog.getString('Enter Spending Password'), function(password) {
if (!password) return cb('no password'); if (!password) return cb('no password');
try { try {
@ -869,7 +871,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
root.handleEncryptedWallet = function(wallet, cb) { root.handleEncryptedWallet = function(wallet, cb) {
if (!root.isEncrypted(wallet)) return cb(); if (!root.isEncrypted(wallet)) return cb();
askPassword(wallet.name, gettext('Enter Spending Password'), function(password) { askPassword(wallet.name, gettextCatalog.getString('Enter Spending Password'), function(password) {
if (!password) return cb('No password'); if (!password) return cb('No password');
if (!wallet.checkPassword(password)) return cb('Wrong password'); if (!wallet.checkPassword(password)) return cb('Wrong password');
@ -892,20 +894,14 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
}; };
root.onlyPublish = function(wallet, txp, cb) { root.onlyPublish = function(wallet, txp, cb, customStatusHandler) {
ongoingProcess.set('sendingTx', true); ongoingProcess.set('sendingTx', true, customStatusHandler);
root.publishTx(wallet, txp, function(err, publishedTxp) { root.publishTx(wallet, txp, function(err, publishedTxp) {
root.invalidateCache(wallet); root.invalidateCache(wallet);
ongoingProcess.set('sendingTx', false, customStatusHandler);
ongoingProcess.set('sendingTx', false); if (err) return cb(bwcError.msg(err));
if (err) return cb(err); $rootScope.$emit('Local/TxAction', wallet.id);
return cb();
var type = root.getViewStatus(wallet, createdTxp);
root.openStatusModal(type, createdTxp, function() {
$rootScope.$emit('Local/TxAction', wallet.id);
return;
});
return cb(null, publishedTxp);
}); });
}; };
@ -934,13 +930,13 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
} }
root.prepare(wallet, function(err, password) { root.prepare(wallet, function(err, password) {
if (err) return cb('Prepare error: ' + err); if (err) return cb(bwcError.msg(err));
ongoingProcess.set('sendingTx', true, customStatusHandler); ongoingProcess.set('sendingTx', true, customStatusHandler);
publishFn(wallet, txp, function(err, publishedTxp) { publishFn(wallet, txp, function(err, publishedTxp) {
ongoingProcess.set('sendingTx', false, customStatusHandler); ongoingProcess.set('sendingTx', false, customStatusHandler);
if (err) return cb('Send Error: ' + err); if (err) return cb(bwcError.msg(err));
ongoingProcess.set('signingTx', true, customStatusHandler); ongoingProcess.set('signingTx', true, customStatusHandler);
root.signTx(wallet, publishedTxp, password, function(err, signedTxp) { root.signTx(wallet, publishedTxp, password, function(err, signedTxp) {
@ -950,10 +946,9 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
if (err) { if (err) {
$log.warn('sign error:' + err); $log.warn('sign error:' + err);
// TODO? var msg = err && err.message ?
var msg = err.message ?
err.message : err.message :
gettext('The payment was created but could not be completed. Please try again from home screen'); gettextCatalog.getString('The payment was created but could not be completed. Please try again from home screen');
$rootScope.$emit('Local/TxAction', wallet.id); $rootScope.$emit('Local/TxAction', wallet.id);
return cb(msg); return cb(msg);
@ -963,7 +958,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
ongoingProcess.set('broadcastingTx', true, customStatusHandler); ongoingProcess.set('broadcastingTx', true, customStatusHandler);
root.broadcastTx(wallet, signedTxp, function(err, broadcastedTxp) { root.broadcastTx(wallet, signedTxp, function(err, broadcastedTxp) {
ongoingProcess.set('broadcastingTx', false, customStatusHandler); ongoingProcess.set('broadcastingTx', false, customStatusHandler);
if (err) return cb('sign error' + err); if (err) return cb(bwcError.msg(err));
$rootScope.$emit('Local/TxAction', wallet.id); $rootScope.$emit('Local/TxAction', wallet.id);
var type = root.getViewStatus(wallet, broadcastedTxp); var type = root.getViewStatus(wallet, broadcastedTxp);
@ -1090,6 +1085,12 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
return type; return type;
}; };
root.getSendMaxInfo = function(wallet, opts, cb) {
opts = opts || {};
wallet.getSendMaxInfo(opts, function(err, res) {
return cb(err, res);
});
};
return root; return root;
}); });

View file

@ -19,8 +19,7 @@ $button-secondary-border: transparent;
$button-secondary-active-bg: darken($subtle-gray, 5%); $button-secondary-active-bg: darken($subtle-gray, 5%);
$button-secondary-active-border: transparent; $button-secondary-active-border: transparent;
%button-standard, %button-standard {
click-to-accept {
width: 85%; width: 85%;
max-width: 300px; max-width: 300px;
margin-left: auto; margin-left: auto;

View file

@ -6,9 +6,6 @@
/* Ionic Overrides and Workarounds */ /* Ionic Overrides and Workarounds */
// Please include a description of the problem solved by the workaround. // Please include a description of the problem solved by the workaround.
// class to dynamically hide the ion-nav-bar for v1 Amazon flow
ion-nav-bar.hide { display: block !important; }
// the ion tabs element never needs it's own background (backgrounds are // the ion tabs element never needs it's own background (backgrounds are
// rendered by the tabs), and the default background would cover the scanner // rendered by the tabs), and the default background would cover the scanner
ion-tabs.ion-tabs-transparent { ion-tabs.ion-tabs-transparent {
@ -40,3 +37,20 @@ $placeholder-icon-padding: 10px;
.card { .card {
margin: ($content-padding * 2) 14px; margin: ($content-padding * 2) 14px;
} }
// A somewhat dirty solution to the nav-bar "flashing" during page transitions.
// Since the old nav-bar is hidden before the new one is shown, this pseudo
// element fills the space with the proper background color.
ion-view.deflash-blue:before {
content: " ";
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
height: $bar-height;
background-color: $royal;
}
.platform-ios.platform-cordova:not(.fullscreen) ion-view.deflash-blue:before {
height: $bar-height + $ios-statusbar-height;
}

View file

@ -30,6 +30,17 @@
} }
} }
.text-warn {
.popup-sub-title {
margin-top: 15px;
color: #e42112 !important;
}
}
.item-toggle, .item {
white-space: normal;
}
.input-label { .input-label {
max-width: none; max-width: none;
width: inherit; width: inherit;
@ -437,3 +448,7 @@ input[type=file] {
.keyboard-open .has-tabs { .keyboard-open .has-tabs {
bottom: 0; bottom: 0;
} }
.white-space-initial {
white-space: initial;
}

View file

@ -20,6 +20,7 @@
} }
} }
#view-address-book { #view-address-book {
@extend .deflash-blue;
.scroll { .scroll {
height:100%; height:100%;
} }

View file

@ -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;
}
}
}

View file

@ -1,4 +1,5 @@
#view-amount { #view-amount {
@extend .deflash-blue;
.recipient-label { .recipient-label {
font-size: 14px; font-size: 14px;
padding-bottom: 0; padding-bottom: 0;
@ -10,6 +11,9 @@
.icon-bitpay-card { .icon-bitpay-card {
background-image: url("../img/icon-bitpay.svg"); background-image: url("../img/icon-bitpay.svg");
} }
.icon-amazon {
background-image: url("../img/icon-amazon.svg");
}
@media(max-width: 480px) { @media(max-width: 480px) {
.bitcoin-address { .bitcoin-address {
.icon { .icon {
@ -33,7 +37,7 @@
position: absolute; position: absolute;
top: 10px; top: 10px;
} }
.amount-pane { .amount-pane-recipient {
position: absolute; position: absolute;
top: 95px; top: 95px;
bottom: 0; bottom: 0;
@ -64,6 +68,42 @@
} }
} }
} }
.amount-pane-no-recipient {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
background-color: #fff;
padding: 0 16px;
.amount-bar {
padding: 24px 0;
font-size: 18px;
.title {
float: left;
padding-top: 10px;
color: $dark-gray;
font-weight: bold;
.limits {
margin-top: 10px;
color: $light-gray;
font-size: 12px;
}
}
}
.amount {
display: flex;
flex-direction: column;
justify-content: center;
flex-grow: 1;
position: absolute;
bottom: 254px;
top: 66px;
.light {
color: $light-gray;
}
}
}
.amount { .amount {
&__editable { &__editable {
margin-bottom: 1rem; margin-bottom: 1rem;

View file

@ -1,4 +1,5 @@
#bitpayCard { #bitpayCard {
background: white;
.bar-header { .bar-header {
border: 0; border: 0;
background: #1e3186; background: #1e3186;
@ -9,15 +10,35 @@
background-color: transparent; background-color: transparent;
} }
} }
.amount-wrapper {
position: relative;
overflow: visible;
.amount-bg {
content: '';
top: -1000px;
left: 0;
position: absolute;
height: 1000px;
width: 100%;
background-color: #1e3186;
}
}
.amount { .amount {
width: 100%; width: 100%;
text-align: center; text-align: center;
padding: 2rem 1rem 1.5rem 1rem; padding: 2rem 1rem 1.5rem 1rem;
height: 140px; height: 160px;
border-color: #172565; border-color: #172565;
background-color: #1e3186; background-color: #1e3186;
background-image: linear-gradient(0deg, #172565, #172565 0%, transparent 0%); background-image: linear-gradient(0deg, #172565, #172565 0%, transparent 0%);
color: #fff; color: #fff;
&__balance {
margin-bottom: 25px;
font-weight: 600;
font-size: 34px;
}
} }
.wallet-details-wallet-info { .wallet-details-wallet-info {
bottom: 5px; bottom: 5px;
@ -37,4 +58,26 @@
.item-select select { .item-select select {
color: #667; color: #667;
} }
.get-started {
margin-top: 20px;
&__arrow {
font-size: 56px;
opacity: .2;
}
h1 {
font-size: 28px;
color: #4A4A4A;
}
&__text {
font-weight: 300;
color: #8e8e8e;
max-width: 300px;
margin: 0 auto;
}
}
} }

View file

@ -11,5 +11,12 @@
color: $light-gray; color: $light-gray;
font-size: 14px; font-size: 14px;
} }
&.item-icon-right {
.icon-hotspot {
right: 0px;
padding-left: 11px;
padding-right: 11px;
}
}
} }
} }

Some files were not shown because too many files have changed in this diff Show more