This commit is contained in:
magmahindenburg 2017-07-06 11:27:29 +09:00
commit 98c93e3c6f
133 changed files with 5157 additions and 3511 deletions

View file

@ -93,7 +93,14 @@ module.exports = function(grunt) {
'src/js/controllers/**/*.js'
],
tasks: ['concat:js']
}
},
gettext: {
files: [
'i18n/po/*.po',
'i18n/po/*.pot'
],
tasks: ['nggettext_compile','concat']
},
},
sass: {
dist: {

View file

@ -43,11 +43,11 @@ cd copay
Ensure you have [Node](https://nodejs.org/) installed, then install and start Copay:
```sh
npm install
npm run apply:copay
npm start
```
Visit [`localhost:3000`](http://localhost:3000/) to view the app.
Visit [`localhost:8100`](http://localhost:8100/) to view the app.
A watch task is also available to rebuild components of the app as changes are made. This task can be run in a separate process while the server started by `npm start` is running to quickly test changes.

View file

@ -17,13 +17,13 @@
"url": "https://bitpay.com",
"appDescription": "Secure Bitcoin Wallet",
"winAppName": "BitPayWallet",
"wpPublisherId": "{}",
"wpProductId": "{}",
"WindowsStoreIdentityName": "18C7659D.BitPaySecureBitcoinWallet",
"WindowsStoreDisplayName": "BitPay - Secure Bitcoin Wallet",
"windowsAppId": "2d1002d7-ee34-4f60-bd29-0c871ba0c195",
"pushSenderId": "1036948132229",
"description": "Secure Bitcoin Wallet",
"version": "3.5.0",
"androidVersion": "350000",
"version": "3.6.3",
"androidVersion": "363000",
"_extraCSS": null,
"_enabledExtensions": {
"coinbase": true,

View file

@ -17,6 +17,9 @@
<preference name="DisallowOverscroll" value="true"/>
<preference name="HideKeyboardFormAccessoryBar" value="true"/>
<!-- #355 -->
<preference name="WindowsStoreIdentityName" value="*WINDOWSSTOREIDENTITYNAME*"/>
<preference name="WindowsStoreDisplayName" value="*WINDOWSSTOREDISPLAYNAME*"/>
<!-- <preference name="KeyboardDisplayRequiresUserAction" value="false" /> -->
<preference name="StatusBarBackgroundColor" value="#494949" />
<preference name="BackupWebStorage" value="none"/>
@ -43,7 +46,7 @@
<plugin name="cordova-plugin-statusbar" spec="~2.2.0" />
<plugin name="cordova-plugin-inappbrowser" spec="~1.5.0" />
<plugin name="cordova-plugin-x-toast" spec="~2.5.2" />
<plugin name="com.verso.cordova.clipboard" spec="https://github.com/VersoSolutions/CordovaClipboard" />
<plugin name="com.verso.cordova.clipboard" spec="https://github.com/Visigo/CordovaClipboard" />
<plugin name="cordova-plugin-x-socialsharing" spec="https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin.git" />
<plugin name="cordova-plugin-spinner-dialog" spec="~1.3.1" />
<plugin name="cordova-plugin-dialogs" spec="~1.3.0" />
@ -68,10 +71,13 @@
<plugin name="cordova-plugin-queries-schemes" spec="~0.1.5" />
<plugin name="cordova-plugin-fcm" spec="https://github.com/cmgustavo/cordova-plugin-fcm.git" />
<!-- Delete cordova-plugin-qrscanner plugin and enable phonegap-plugin-barcodescanner to build cordova windows-->
<!-- <plugin name="phonegap-plugin-barcodescanner" spec="https://github.com/phonegap/phonegap-plugin-barcodescanner.git" /> -->
<!-- Supported Platforms -->
<engine name="ios" spec="~4.2.1" />
<engine name="android" spec="~6.2.3" />
<engine name="windows" spec="~4.4.3" />
<engine name="windows" spec="~5.0.0" />
<!-- Platform Specific Settings -->
<platform name="ios">

View file

@ -17,13 +17,13 @@
"url": "https://copay.io",
"appDescription": "Copay Bitcoin Wallet",
"winAppName": "CopayWallet",
"wpPublisherId": "{31cdd08b-457c-413d-b440-f6665eec847d}",
"wpProductId": "{5381aa50-9069-11e4-84cc-293caf9cbdc8}",
"WindowsStoreIdentityName": "18C7659D.Copay-SecureBitcoinWallet",
"WindowsStoreDisplayName": "Copay - Secure Bitcoin Wallet",
"windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c",
"pushSenderId": "1036948132229",
"description": "A Secure Bitcoin Wallet",
"version": "3.5.0",
"androidVersion": "350000",
"version": "3.6.3",
"androidVersion": "363000",
"_extraCSS": null,
"_enabledExtensions": {
"coinbase": true,

View file

@ -81,7 +81,7 @@
},
"scripts": {
"postinstall": "bower install",
"start": "npm run build:www && ionic serve --nolivereload --nogulp -s",
"start": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0",
"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:windows": "npm run build:www && npm run build:windows",
@ -91,10 +91,10 @@
"build:www-release": "grunt prod",
"build:ios": "cordova prepare ios && cordova build ios --debug",
"build:android": "cordova prepare android && cordova build android --debug",
"build:windows": "cordova prepare windows && cordova build windows -- --arch=\"x86\"",
"build:windows": "cordova prepare windows && cordova build windows -- --arch=\"ARM\"",
"build:ios-release": "cordova prepare ios && cordova build ios --release",
"build:android-release": "cordova prepare android && cordova build android --release",
"build:windows-release": "cordova prepare windows && cordova build windows --release --arch=\"x86\"",
"build:windows-release": "cordova prepare windows && cordova build windows --release --arch=\"ARM\"",
"build:desktop": "grunt desktop",
"build:osx": "grunt osx",
"open:ios": "open platforms/ios/*.xcodeproj",

11
build.json Normal file
View file

@ -0,0 +1,11 @@
{
"windows": {
"debug": {
"packageCertificateKeyFile": "platforms\\windows\\CordovaApp_TemporaryKey.pfx"
},
"release": {
"packageThumbprint": "ABCABCABCABC123123123123",
"publisherId": "CN=F89609D1-EB3E-45FD-A58A-C2E3895FCE7B"
}
}
}

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

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

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

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('addressesController', function($scope, $log, $stateParams, $state, $timeout, $ionicHistory, $ionicScrollDelegate, configService, popupService, gettextCatalog, ongoingProcess, lodash, profileService, walletService, bwcError, platformInfo, appConfigService) {
angular.module('copayApp.controllers').controller('addressesController', function($scope, $log, $stateParams, $state, $timeout, $ionicHistory, $ionicScrollDelegate, configService, popupService, gettextCatalog, ongoingProcess, lodash, profileService, walletService, bwcError, platformInfo, appConfigService, txFormatService, feeService) {
var UNUSED_ADDRESS_LIMIT = 5;
var BALANCE_ADDRESS_LIMIT = 5;
var config = configService.getSync().wallet.settings;
@ -55,7 +55,7 @@ angular.module('copayApp.controllers').controller('addressesController', functio
$scope.latestWithBalance = lodash.slice(withBalance, 0, BALANCE_ADDRESS_LIMIT);
lodash.each(withBalance, function(a) {
a.balanceStr = (a.amount * satToUnit).toFixed(unitDecimals) + ' ' + unitName;
a.balanceStr = txFormatService.formatAmount(a.amount);
});
$scope.viewAll = {
@ -72,6 +72,31 @@ angular.module('copayApp.controllers').controller('addressesController', functio
});
});
});
feeService.getFeeLevels(function(err, levels){
walletService.getLowUtxos($scope.wallet, levels, function(err, resp) {
if (err) return;
if (resp.allUtxos && resp.allUtxos.length) {
var allSum = lodash.sum(resp.allUtxos || 0, 'satoshis');
var per = (resp.minFee / allSum) * 100;
$scope.lowWarning = resp.warning;
$scope.lowUtxosNb = resp.lowUtxos.length;
$scope.allUtxosNb = resp.allUtxos.length;
$scope.lowUtxosSum = txFormatService.formatAmountStr(lodash.sum(resp.lowUtxos || 0, 'satoshis'));
$scope.allUtxosSum = txFormatService.formatAmountStr(allSum);
$scope.minFee = txFormatService.formatAmountStr(resp.minFee || 0);
$scope.minFeePer = per.toFixed(2) + '%';
}
});
});
};
function processPaths(list) {
@ -116,7 +141,14 @@ angular.module('copayApp.controllers').controller('addressesController', functio
};
$scope.viewAllAddresses = function() {
$state.go('tabs.settings.allAddresses', {
var fromView = $ionicHistory.currentStateName();
var path;
if (fromView.indexOf('settings') !== -1) {
path = 'tabs.settings.allAddresses';
} else {
path = 'tabs.wallet.allAddresses';
}
$state.go(path, {
walletId: $scope.wallet.id
});
};

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('advancedSettingsController', function($scope, $log, configService) {
angular.module('copayApp.controllers').controller('advancedSettingsController', function($scope, $log, configService, platformInfo) {
var updateConfig = function() {
var config = configService.getSync();
@ -50,6 +50,7 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
updateConfig();
});

View file

@ -44,66 +44,77 @@ angular.module('copayApp.controllers').controller('buyCoinbaseController', funct
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isFiat = data.stateParams.currency != 'bits' && data.stateParams.currency != 'BTC' ? true : false;
var parsedAmount = txFormatService.parseAmount(
data.stateParams.amount,
data.stateParams.amount,
data.stateParams.currency);
amount = parsedAmount.amount;
currency = parsedAmount.currency;
// Buy always in BTC
amount = (parsedAmount.amountSat / 100000000).toFixed(8);
currency = 'BTC';
$scope.amountUnitStr = parsedAmount.amountUnitStr;
$scope.network = coinbaseService.getNetwork();
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: $scope.network
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('No wallets available');
return;
}
$scope.wallet = $scope.wallets[0]; // Default first wallet
ongoingProcess.set('connectingCoinbase', true);
coinbaseService.init(function(err, res) {
ongoingProcess.set('calculatingFee', true);
coinbaseService.checkEnoughFundsForFee(amount, function(err) {
ongoingProcess.set('calculatingFee', false);
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
var accessToken = res.accessToken;
coinbaseService.buyPrice(accessToken, coinbaseService.getAvailableCurrency(), function(err, b) {
$scope.buyPrice = b.data || null;
$scope.network = coinbaseService.getNetwork();
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: $scope.network
});
$scope.paymentMethods = [];
$scope.selectedPaymentMethodId = { value : null };
coinbaseService.getPaymentMethods(accessToken, function(err, p) {
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('No wallets available');
return;
}
$scope.wallet = $scope.wallets[0]; // Default first wallet
ongoingProcess.set('connectingCoinbase', true);
coinbaseService.init(function(err, res) {
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
var accessToken = res.accessToken;
var hasPrimary;
var pm;
for(var i = 0; i < p.data.length; i++) {
pm = p.data[i];
if (pm.allow_buy) {
$scope.paymentMethods.push(pm);
coinbaseService.buyPrice(accessToken, coinbaseService.getAvailableCurrency(), function(err, b) {
$scope.buyPrice = b.data || null;
});
$scope.paymentMethods = [];
$scope.selectedPaymentMethodId = { value : null };
coinbaseService.getPaymentMethods(accessToken, function(err, p) {
if (err) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack(err);
return;
}
if (pm.allow_buy && pm.primary_buy) {
hasPrimary = true;
$scope.selectedPaymentMethodId.value = pm.id;
var hasPrimary;
var pm;
for(var i = 0; i < p.data.length; i++) {
pm = p.data[i];
if (pm.allow_buy) {
$scope.paymentMethods.push(pm);
}
if (pm.allow_buy && pm.primary_buy) {
hasPrimary = true;
$scope.selectedPaymentMethodId.value = pm.id;
}
}
}
if (lodash.isEmpty($scope.paymentMethods)) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack('No payment method available to buy');
return;
}
if (!hasPrimary) $scope.selectedPaymentMethodId.value = $scope.paymentMethods[0].id;
$scope.buyRequest();
if (lodash.isEmpty($scope.paymentMethods)) {
ongoingProcess.set('connectingCoinbase', false);
showErrorAndBack('No payment method available to buy');
return;
}
if (!hasPrimary) $scope.selectedPaymentMethodId.value = $scope.paymentMethods[0].id;
$scope.buyRequest();
});
});
});
});
@ -139,12 +150,12 @@ angular.module('copayApp.controllers').controller('buyCoinbaseController', funct
};
$scope.buyConfirm = function() {
var message = 'Buy bitcoin for ' + amount + ' ' + currency;
var message = 'Buy bitcoin for ' + $scope.amountUnitStr;
var okText = 'Confirm';
var cancelText = 'Cancel';
popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) return;
ongoingProcess.set('buyingBitcoin', true, statusChangeHandler);
coinbaseService.init(function(err, res) {
if (err) {

View file

@ -1,14 +1,38 @@
'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, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService, feeService, bwcError) {
var cachedTxp = {};
var feeLevel;
var feePerKb;
var toAmount;
var isChromeApp = platformInfo.isChromeApp;
var countDown = null;
var cachedSendMax = {};
$scope.isCordova = platformInfo.isCordova;
var CONFIRM_LIMIT_USD = 20;
var FEE_TOO_HIGH_LIMIT_PER = 15;
var tx = {};
// Config Related values
var config = configService.getSync();
var walletConfig = config.wallet;
var unitToSatoshi = walletConfig.settings.unitToSatoshi;
var unitDecimals = walletConfig.settings.unitDecimals;
var satToUnit = 1 / unitToSatoshi;
var configFeeLevel = walletConfig.settings.feeLevel ? walletConfig.settings.feeLevel : 'normal';
// Platform info
var isChromeApp = platformInfo.isChromeApp;
var isCordova = platformInfo.isCordova;
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
function refresh() {
$timeout(function() {
$scope.$apply();
}, 1);
}
$scope.showWalletSelector = function() {
$scope.walletSelector = true;
refresh();
};
$scope.$on("$ionicView.beforeLeave", function(event, data) {
$ionicConfig.views.swipeBackEnabled(true);
@ -18,305 +42,346 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$ionicConfig.views.swipeBackEnabled(false);
});
function exitWithError(err) {
$log.info('Error setting wallet selector:' + err);
popupService.showAlert(gettextCatalog.getString(), bwcError.msg(err), function() {
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$ionicHistory.clearHistory();
$state.go('tabs.send');
});
};
function setNoWallet(msg) {
$scope.wallet = null;
$scope.noWalletMessage = gettextCatalog.getString(msg);
$log.warn('Not ready to make the payment:' + msg);
$timeout(function() {
$scope.$apply();
});
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
toAmount = data.stateParams.toAmount;
cachedSendMax = {};
function setWalletSelector(network, minAmount, cb) {
// no min amount? (sendMax) => look for no empty wallets
minAmount = minAmount || 1;
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: network
});
if (!$scope.wallets || !$scope.wallets.length) {
setNoWallet('No wallets available');
return cb();
}
var filteredWallets = [];
var index = 0;
var walletsUpdated = 0;
lodash.each($scope.wallets, function(w) {
walletService.getStatus(w, {}, function(err, status) {
if (err || !status) {
$log.error(err);
} else {
walletsUpdated++;
w.status = status;
if (!status.availableBalanceSat)
$log.debug('No balance available in: ' + w.name);
if (status.availableBalanceSat > minAmount) {
filteredWallets.push(w);
}
}
if (++index == $scope.wallets.length) {
if (!walletsUpdated)
return cb('Could not update any wallet');
if (lodash.isEmpty(filteredWallets)) {
setNoWallet('Insufficent funds');
}
$scope.wallets = lodash.clone(filteredWallets);
return cb();
}
});
});
};
// Setup $scope
// Grab stateParams
tx = {
toAmount: parseInt(data.stateParams.toAmount),
sendMax: data.stateParams.useSendMax == 'true' ? true : false,
toAddress: data.stateParams.toAddress,
description: data.stateParams.description,
paypro: data.stateParams.paypro,
feeLevel: configFeeLevel,
spendUnconfirmed: walletConfig.spendUnconfirmed,
// Vanity tx info (not in the real tx)
recipientType: data.stateParams.recipientType || null,
toName: data.stateParams.toName,
toEmail: data.stateParams.toEmail,
toColor: data.stateParams.toColor,
network: (new bitcore.Address(data.stateParams.toAddress)).network.name,
txp: {},
};
// Other Scope vars
$scope.isCordova = isCordova;
$scope.isWindowsPhoneApp = isWindowsPhoneApp;
$scope.showAddress = false;
$scope.useSendMax = data.stateParams.useSendMax == 'true' ? true : false;
$scope.recipientType = data.stateParams.recipientType || null;
$scope.toAddress = data.stateParams.toAddress;
$scope.toName = data.stateParams.toName;
$scope.toEmail = data.stateParams.toEmail;
$scope.toColor = data.stateParams.toColor;
$scope.description = data.stateParams.description;
$scope.paypro = data.stateParams.paypro;
$scope.insufficientFunds = false;
$scope.noMatchingWallet = false;
$scope.paymentExpired = {
value: false
};
$scope.remainingTimeStr = {
value: null
};
$scope.network = (new bitcore.Address($scope.toAddress)).network.name;
setFee();
resetValues();
setwallets();
applyButtonText();
updateTx(tx, null, {}, function() {
$scope.walletSelectorTitle = gettextCatalog.getString('Send from');
setWalletSelector(tx.network, tx.toAmount, function(err) {
if (err) {
return exitWithError('Could not update wallets');
}
if ($scope.wallets.length > 1) {
$scope.showWalletSelector();
} else if ($scope.wallets.length) {
setWallet($scope.wallets[0], tx);
}
});
});
});
function setFee(customFeeLevel, cb) {
feeService.getCurrentFeeValue($scope.network, customFeeLevel, function(err, currentFeePerKb) {
var config = configService.getSync().wallet;
var configFeeLevel = (config.settings && config.settings.feeLevel) ? config.settings.feeLevel : 'normal';
feePerKb = currentFeePerKb;
feeLevel = customFeeLevel ? customFeeLevel : configFeeLevel;
$scope.feeLevel = feeService.feeOpts[feeLevel];
if (cb) return cb();
function getSendMaxInfo(tx, wallet, cb) {
if (!tx.sendMax) return cb();
//ongoingProcess.set('retrievingInputs', true);
walletService.getSendMaxInfo(wallet, {
feePerKb: tx.feeRate,
excludeUnconfirmedUtxos: !tx.spendUnconfirmed,
returnInputs: true,
}, cb);
};
function getTxp(tx, wallet, dryRun, cb) {
// ToDo: use a credential's (or fc's) function for this
if (tx.description && !wallet.credentials.sharedEncryptingKey) {
var msg = gettextCatalog.getString('Could not add message to imported wallet without shared encrypting key');
$log.warn(msg);
return setSendError(msg);
}
if (tx.toAmount > Number.MAX_SAFE_INTEGER) {
var msg = gettextCatalog.getString('Amount too big');
$log.warn(msg);
return setSendError(msg);
}
var txp = {};
txp.outputs = [{
'toAddress': tx.toAddress,
'amount': tx.toAmount,
'message': tx.description
}];
if (tx.sendMaxInfo) {
txp.inputs = tx.sendMaxInfo.inputs;
txp.fee = tx.sendMaxInfo.fee;
} else {
txp.feeLevel = tx.feeLevel;
}
txp.message = tx.description;
if (tx.paypro) {
txp.payProUrl = tx.paypro.url;
}
txp.excludeUnconfirmedUtxos = !tx.spendUnconfirmed;
txp.dryRun = dryRun;
walletService.createTx(wallet, txp, function(err, ctxp) {
if (err) {
setSendError(err);
return cb(err);
}
return cb(null, ctxp);
});
};
function updateTx(tx, wallet, opts, cb) {
if (opts.clearCache) {
tx.txp = {};
}
$scope.tx = tx;
function updateAmount() {
if (!tx.toAmount) return;
// Amount
tx.amountStr = txFormatService.formatAmountStr(tx.toAmount);
tx.amountValueStr = tx.amountStr.split(' ')[0];
tx.amountUnitStr = tx.amountStr.split(' ')[1];
txFormatService.formatAlternativeStr(tx.toAmount, function(v) {
tx.alternativeAmountStr = v;
});
}
updateAmount();
refresh();
// End of quick refresh, before wallet is selected.
if (!wallet)return cb();
feeService.getFeeRate(tx.network, tx.feeLevel, function(err, feeRate) {
if (err) return cb(err);
tx.feeRate = feeRate;
tx.feeLevelName = feeService.feeOpts[tx.feeLevel];
if (!wallet)
return cb();
getSendMaxInfo(lodash.clone(tx), wallet, function(err, sendMaxInfo) {
if (err) {
var msg = gettextCatalog.getString('Error getting SendMax information');
return setSendError(msg);
}
if (sendMaxInfo) {
$log.debug('Send max info', sendMaxInfo);
if (tx.sendMax && sendMaxInfo.amount == 0) {
setNoWallet('Insufficent funds');
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'));
return cb('no_funds');
}
tx.sendMaxInfo = sendMaxInfo;
tx.toAmount = tx.sendMaxInfo.amount;
updateAmount();
showSendMaxWarning(sendMaxInfo);
}
// txp already generated for this wallet?
if (tx.txp[wallet.id]) {
refresh();
return cb();
}
getTxp(lodash.clone(tx), wallet, opts.dryRun, function(err, txp) {
if (err) return cb(err);
txp.feeStr = txFormatService.formatAmountStr(txp.fee);
txFormatService.formatAlternativeStr(txp.fee, function(v) {
txp.alternativeFeeStr = v;
});
var per = (txp.fee / (txp.amount + txp.fee) * 100);
txp.feeRatePerStr = per.toFixed(2) + '%';
txp.feeToHigh = per > FEE_TOO_HIGH_LIMIT_PER;
tx.txp[wallet.id] = txp;
$log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx);
refresh();
return cb();
});
});
});
}
function useSelectedWallet() {
if (!$scope.useSendMax) displayValues();
if (!$scope.useSendMax) {
showAmount(tx.toAmount);
}
$scope.onWalletSelect($scope.wallet);
}
function applyButtonText(multisig) {
$scope.buttonText = $scope.isCordova ? gettextCatalog.getString('Slide') + ' ' : gettextCatalog.getString('Click') + ' ';
function setButtonText(isMultisig, isPayPro) {
$scope.buttonText = gettextCatalog.getString(isCordova && !isWindowsPhoneApp ? 'Slide' : 'Click') + ' ';
if ($scope.paypro) {
if (isPayPro) {
$scope.buttonText += gettextCatalog.getString('to pay');
} else if (multisig) {
} else if (isMultisig) {
$scope.buttonText += gettextCatalog.getString('to accept');
} else
$scope.buttonText += gettextCatalog.getString('to send');
};
function setwallets() {
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: $scope.network
});
if (!$scope.wallets || !$scope.wallets.length) {
$scope.noMatchingWallet = true;
displayValues();
$log.warn('No ' + $scope.network + ' wallets to make the payment');
$timeout(function() {
$scope.$apply();
});
return;
}
var filteredWallets = [];
var index = 0;
var enoughFunds = false;
var walletsUpdated = 0;
lodash.each($scope.wallets, function(w) {
walletService.getStatus(w, {}, function(err, status) {
if (err || !status) {
$log.error(err);
} else {
walletsUpdated++;
w.status = status;
if (!status.availableBalanceSat) $log.debug('No balance available in: ' + w.name);
if (status.availableBalanceSat > toAmount) {
filteredWallets.push(w);
enoughFunds = true;
}
}
if (++index == $scope.wallets.length) {
if (!lodash.isEmpty(filteredWallets)) {
$scope.wallets = lodash.clone(filteredWallets);
if ($scope.useSendMax) {
if ($scope.wallets.length > 1)
$scope.showWalletSelector();
else {
$scope.wallet = $scope.wallets[0];
$scope.getSendMaxInfo();
}
} else initConfirm();
} else {
// Were we able to update any wallet?
if (walletsUpdated) {
if (!enoughFunds) $scope.insufficientFunds = true;
displayValues();
$log.warn('No wallet available to make the payment');
} else {
popupService.showAlert(gettextCatalog.getString('Could not update wallets'), bwcError.msg(err), function() {
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$ionicHistory.clearHistory();
$state.go('tabs.send');
});
}
}
$timeout(function() {
$scope.$apply();
});
}
});
});
};
$scope.toggleAddress = function() {
$scope.showAddress = !$scope.showAddress;
};
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 showSendMaxWarning(sendMaxInfo) {
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;
});
};
function resetValues() {
$scope.displayAmount = $scope.displayUnit = $scope.fee = $scope.feeFiat = $scope.feeRateStr = $scope.alternativeAmountStr = $scope.insufficientFunds = $scope.noMatchingWallet = null;
$scope.showAddress = false;
};
$scope.getSendMaxInfo = function() {
resetValues();
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;
function verifyExcludedUtxos() {
var warningMsg = [];
if (sendMaxInfo.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(sendMaxInfo.amountBelowFee)
}));
}
if (resp.amount == 0) {
$scope.insufficientFunds = true;
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'));
return;
if (sendMaxInfo.utxosAboveMaxSize > 0) {
warningMsg.push(gettextCatalog.getString("A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size allowed for a transaction was exceeded.", {
amountAboveMaxSizeStr: txFormatService.formatAmountStr(sendMaxInfo.amountAboveMaxSize)
}));
}
return warningMsg.join('\n');
};
$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');
};
var msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees.", {
fee: txFormatService.formatAmountStr(sendMaxInfo.fee)
});
};
var warningMsg = verifyExcludedUtxos();
function setSendMaxValues(data) {
resetValues();
var config = configService.getSync().wallet;
var unitToSatoshi = config.settings.unitToSatoshi;
var satToUnit = 1 / unitToSatoshi;
var unitDecimals = config.settings.unitDecimals;
if (!lodash.isEmpty(warningMsg))
msg += '\n' + warningMsg;
$scope.amountStr = txFormatService.formatAmountStr(data.amount, true);
$scope.displayAmount = getDisplayAmount($scope.amountStr);
$scope.displayUnit = getDisplayUnit($scope.amountStr);
$scope.fee = txFormatService.formatAmountStr(data.fee);
txFormatService.formatAlternativeStr(data.fee, function(v) {
$scope.feeFiat = v;
});
toAmount = parseFloat((data.amount * satToUnit).toFixed(unitDecimals));
txFormatService.formatAlternativeStr(data.amount, function(v) {
$scope.alternativeAmountStr = v;
});
$scope.feeRateStr = (data.fee / (data.amount + data.fee) * 100).toFixed(2) + '%';
$timeout(function() {
$scope.$apply();
});
};
$scope.$on('accepted', function(event) {
$scope.approve();
});
$scope.showWalletSelector = function() {
$scope.walletSelectorTitle = gettextCatalog.getString('Send from');
if (!$scope.useSendMax && ($scope.insufficientFunds || $scope.noMatchingWallet)) return;
$scope.showWallets = true;
popupService.showAlert(null, msg, function() {});
};
$scope.onWalletSelect = function(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);
applyButtonText(wallet.credentials.m > 1);
setWallet(wallet, tx);
};
$scope.showDescriptionPopup = function() {
$scope.showDescriptionPopup = function(tx) {
var message = gettextCatalog.getString('Add description');
var opts = {
defaultText: $scope.description
defaultText: tx.description
};
popupService.showPrompt(null, message, opts, function(res) {
if (typeof res != 'undefined') $scope.description = res;
if (typeof res != 'undefined') tx.description = res;
$timeout(function() {
$scope.$apply();
});
});
};
function getDisplayAmount(amountStr) {
return $scope.amountStr.split(' ')[0];
};
function getDisplayUnit(amountStr) {
return $scope.amountStr.split(' ')[1];
};
function _paymentTimeControl(expirationTime) {
$scope.paymentExpired.value = false;
$scope.paymentExpired = false;
setExpirationTime();
countDown = $interval(function() {
@ -334,12 +399,12 @@ angular.module('copayApp.controllers').controller('confirmController', function(
var totalSecs = expirationTime - now;
var m = Math.floor(totalSecs / 60);
var s = totalSecs % 60;
$scope.remainingTimeStr.value = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
$scope.remainingTimeStr = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
};
function setExpiredValues() {
$scope.paymentExpired.value = true;
$scope.remainingTimeStr.value = gettextCatalog.getString('Expired');
$scope.paymentExpired = true;
$scope.remainingTimeStr = gettextCatalog.getString('Expired');
if (countDown) $interval.cancel(countDown);
$timeout(function() {
$scope.$apply();
@ -347,31 +412,27 @@ angular.module('copayApp.controllers').controller('confirmController', function(
};
};
function setWallet(wallet, delayed) {
var stop;
/* sets a wallet on the UI, creates a TXPs for that wallet */
function setWallet(wallet, tx) {
$scope.wallet = wallet;
$scope.fee = $scope.txp = null;
if (stop) {
$timeout.cancel(stop);
stop = null;
}
if (cachedTxp[wallet.id]) {
apply(cachedTxp[wallet.id]);
} else {
stop = $timeout(function() {
createTx(wallet, true, function(err, txp) {
if (err) return;
cachedTxp[wallet.id] = txp;
apply(txp);
});
}, delayed ? 2000 : 1);
}
setButtonText(wallet.credentials.m > 1, !!tx.paypro);
if (tx.paypro)
_paymentTimeControl(tx.paypro.expires);
updateTx(tx, wallet, {
dryRun: true
}, function(err) {
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
});
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
};
var setSendError = function(msg) {
@ -382,75 +443,6 @@ angular.module('copayApp.controllers').controller('confirmController', function(
popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg));
};
function apply(txp) {
$scope.fee = txFormatService.formatAmountStr(txp.fee);
txFormatService.formatAlternativeStr(txp.fee, function(v) {
$scope.feeFiat = v;
});
$scope.txp = txp;
$scope.feeRateStr = (txp.fee / (txp.amount + txp.fee) * 100).toFixed(2) + '%';
$timeout(function() {
$scope.$apply();
});
};
var createTx = function(wallet, dryRun, cb) {
var config = configService.getSync().wallet;
var currentSpendUnconfirmed = config.spendUnconfirmed;
var paypro = $scope.paypro;
var toAddress = $scope.toAddress;
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
if (description && !wallet.credentials.sharedEncryptingKey) {
var msg = gettextCatalog.getString('Could not add message to imported wallet without shared encrypting key');
$log.warn(msg);
return setSendError(msg);
}
if (toAmount > Number.MAX_SAFE_INTEGER) {
var msg = gettextCatalog.getString('Amount too big');
$log.warn(msg);
return setSendError(msg);
}
var txp = {};
var amount;
if ($scope.useSendMax) amount = parseFloat((toAmount * unitToSatoshi).toFixed(0));
else amount = toAmount;
txp.outputs = [{
'toAddress': toAddress,
'amount': amount,
'message': description
}];
if ($scope.sendMaxInfo) {
txp.inputs = $scope.sendMaxInfo.inputs;
txp.fee = $scope.sendMaxInfo.fee;
} else
txp.feeLevel = feeLevel;
txp.message = description;
if (paypro) {
txp.payProUrl = paypro.url;
}
txp.excludeUnconfirmedUtxos = !currentSpendUnconfirmed;
txp.dryRun = dryRun;
walletService.createTx(wallet, txp, function(err, ctxp) {
if (err) {
setSendError(err);
return cb(err);
}
return cb(null, ctxp);
});
};
$scope.openPPModal = function() {
$ionicModal.fromTemplateUrl('views/modals/paypro.html', {
scope: $scope
@ -464,14 +456,11 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.payproModal.hide();
};
$scope.approve = function(onSendStatusChange) {
$scope.approve = function(tx, wallet, onSendStatusChange) {
var wallet = $scope.wallet;
if (!wallet) {
return;
}
if (!tx || !wallet) return;
if ($scope.paypro && $scope.paymentExpired.value) {
if ($scope.paymentExpired) {
popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.'));
$scope.sendStatus = '';
$timeout(function() {
@ -481,46 +470,54 @@ angular.module('copayApp.controllers').controller('confirmController', function(
}
ongoingProcess.set('creatingTx', true, onSendStatusChange);
createTx(wallet, false, function(err, txp) {
getTxp(lodash.clone(tx), wallet, false, function(err, txp) {
ongoingProcess.set('creatingTx', false, onSendStatusChange);
if (err) return;
var config = configService.getSync();
var spendingPassEnabled = walletService.isEncrypted(wallet);
var touchIdEnabled = config.touchIdFor && config.touchIdFor[wallet.id];
var isCordova = $scope.isCordova;
var bigAmount = parseFloat(txFormatService.formatToUSD(txp.amount)) > 20;
var message = gettextCatalog.getString('Sending {{amountStr}} from your {{name}} wallet', {
amountStr: $scope.amountStr,
name: wallet.name
});
var okText = gettextCatalog.getString('Confirm');
var cancelText = gettextCatalog.getString('Cancel');
// confirm txs for more that 20usd, if not spending/touchid is enabled
function confirmTx(cb) {
if (walletService.isEncrypted(wallet))
return cb();
if (!spendingPassEnabled && !touchIdEnabled) {
if (isCordova) {
if (bigAmount) {
popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) {
$scope.sendStatus = '';
$timeout(function() {
$scope.$apply();
});
return;
}
publishAndSign(wallet, txp, onSendStatusChange);
});
} else publishAndSign(wallet, txp, onSendStatusChange);
} else {
popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) {
$scope.sendStatus = '';
return;
}
publishAndSign(wallet, txp, onSendStatusChange);
});
var amountUsd = parseFloat(txFormatService.formatToUSD(txp.amount));
if (amountUsd <= CONFIRM_LIMIT_USD)
return cb();
var message = gettextCatalog.getString('Sending {{amountStr}} from your {{name}} wallet', {
amountStr: tx.amountStr,
name: wallet.name
});
var okText = gettextCatalog.getString('Confirm');
var cancelText = gettextCatalog.getString('Cancel');
popupService.showConfirm(null, message, okText, cancelText, function(ok) {
return cb(!ok);
});
};
function publishAndSign() {
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);
}
} else publishAndSign(wallet, txp, onSendStatusChange);
walletService.publishAndSign(wallet, txp, function(err, txp) {
if (err) return setSendError(err);
}, onSendStatusChange);
};
confirmTx(function(nok) {
if (nok) {
$scope.sendStatus = '';
$timeout(function() {
$scope.$apply();
});
return;
}
publishAndSign();
});
});
};
@ -543,68 +540,47 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.statusChangeHandler = statusChangeHandler;
$scope.onConfirm = function() {
$scope.approve(statusChangeHandler);
};
$scope.onSuccessConfirm = function() {
var previousView = $ionicHistory.viewHistory().backView && $ionicHistory.viewHistory().backView.stateName;
$ionicHistory.nextViewOptions({
disableAnimate: true
});
$ionicHistory.removeBackView();
$scope.sendStatus = '';
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$ionicHistory.clearHistory();
$state.go('tabs.send').then(function() {
$ionicHistory.clearHistory();
$state.transitionTo('tabs.home');
});
};
function publishAndSign(wallet, txp, onSendStatusChange) {
$scope.chooseFeeLevel = function(tx, wallet) {
if (!wallet.canSign() && !wallet.isPrivKeyExternal()) {
$log.info('No signing proposal: No private key');
var scope = $rootScope.$new(true);
scope.network = tx.network;
scope.feeLevel = tx.feeLevel;
scope.noSave = true;
return walletService.onlyPublish(wallet, txp, function(err) {
if (err) setSendError(err);
}, onSendStatusChange);
}
walletService.publishAndSign(wallet, txp, function(err, txp) {
if (err) return setSendError(err);
}, onSendStatusChange);
};
$scope.chooseFeeLevel = function() {
$scope.customFeeLevel = feeLevel;
$ionicModal.fromTemplateUrl('views/modals/chooseFeeLevel.html', {
scope: $scope,
scope: scope,
}).then(function(modal) {
$scope.chooseFeeLevelModal = modal;
$scope.openModal();
scope.chooseFeeLevelModal = modal;
scope.openModal();
});
$scope.openModal = function() {
$scope.chooseFeeLevelModal.show();
scope.openModal = function() {
scope.chooseFeeLevelModal.show();
};
$scope.hideModal = function(customFeeLevel) {
if (customFeeLevel) {
cachedTxp = {};
cachedSendMax = {};
ongoingProcess.set('gettingFeeLevels', true);
setFee(customFeeLevel, function() {
ongoingProcess.set('gettingFeeLevels', false);
resetValues();
if ($scope.wallet) useSelectedWallet();
})
}
$scope.chooseFeeLevelModal.hide();
scope.hideModal = function(customFeeLevel) {
scope.chooseFeeLevelModal.hide();
$log.debug('Custom fee level choosen:' + customFeeLevel + ' was:' + tx.feeLevel);
if (tx.feeLevel == customFeeLevel)
return;
tx.feeLevel = customFeeLevel;
updateTx(tx, wallet, {
clearCache: true,
dryRun: true,
}, function() {
});
};
};

View file

@ -5,7 +5,7 @@ angular.module('copayApp.controllers').controller('rateAppController', function(
$scope.appName = appConfigService.nameCase;
var isAndroid = platformInfo.isAndroid;
var isIOS = platformInfo.isIOS;
var isWP = platformInfo.isWP;
var config = configService.getSync();
$scope.skip = function() {
@ -42,8 +42,6 @@ angular.module('copayApp.controllers').controller('rateAppController', function(
url = $scope.appName == 'Copay' ? defaults.rateApp.copay.android : defaults.rateApp.bitpay.android;
if (isIOS)
url = $scope.appName == 'Copay' ? defaults.rateApp.copay.ios : defaults.rateApp.bitpay.ios;
// if (isWP)
// url = $scope.appName == 'Copay' ? defaults.rateApp.copay.windows : defaults.rateApp.bitpay.windows;
externalLinkService.open(url);
$state.go('tabs.rate.complete', {

View file

@ -50,6 +50,7 @@ angular.module('copayApp.controllers').controller('joinController',
$scope.onQrCodeScannedJoin = function(data) {
$scope.formData.secret = data;
$scope.$apply();
};
if ($stateParams.url) {
@ -136,7 +137,7 @@ angular.module('copayApp.controllers').controller('joinController',
}
if ($scope.formData.seedSource.id == walletService.externalSource.ledger.id || $scope.formData.seedSource.id == walletService.externalSource.trezor.id || $scope.formData.seedSource.id == walletService.externalSource.intelTEE.id) {
var account = $scope.account;
var account = $scope.formData.account;
if (!account || account < 1) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid account number'));
return;

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('txpDetailsController', function($scope, $rootScope, $timeout, $interval, $log, ongoingProcess, platformInfo, $ionicScrollDelegate, txFormatService, bwcError, gettextCatalog, lodash, walletService, popupService, $ionicHistory) {
angular.module('copayApp.controllers').controller('txpDetailsController', function($scope, $rootScope, $timeout, $interval, $log, ongoingProcess, platformInfo, $ionicScrollDelegate, txFormatService, bwcError, gettextCatalog, lodash, walletService, popupService, $ionicHistory, feeService) {
var isGlidera = $scope.isGlidera;
var GLIDERA_LOCK_TIME = 6 * 60 * 60;
var now = Math.floor(Date.now() / 1000);
@ -9,37 +9,41 @@ angular.module('copayApp.controllers').controller('txpDetailsController', functi
$scope.init = function() {
$scope.loading = null;
$scope.isCordova = platformInfo.isCordova;
$scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
$scope.copayers = $scope.wallet.status.wallet.copayers;
$scope.copayerId = $scope.wallet.credentials.copayerId;
$scope.isShared = $scope.wallet.credentials.n > 1;
$scope.canSign = $scope.wallet.canSign() || $scope.wallet.isPrivKeyExternal();
$scope.color = $scope.wallet.color;
$scope.data = {};
$scope.displayAmount = getDisplayAmount($scope.tx.amountStr);
$scope.displayUnit = getDisplayUnit($scope.tx.amountStr);
displayFeeValues();
initActionList();
checkPaypro();
applyButtonText();
};
function displayFeeValues() {
txFormatService.formatAlternativeStr($scope.tx.fee, function(v) {
$scope.tx.feeFiatStr = v;
});
$scope.tx.feeRateStr = ($scope.tx.fee / ($scope.tx.amount + $scope.tx.fee) * 100).toFixed(2) + '%';
$scope.tx.feeLevelStr = feeService.feeOpts[$scope.tx.feeLevel];
};
function applyButtonText() {
$scope.buttonText = $scope.isCordova ? gettextCatalog.getString('Slide') + ' ' : gettextCatalog.getString('Click') + ' ';
$scope.buttonText = $scope.isCordova && !$scope.isWindowsPhoneApp ? gettextCatalog.getString('Slide') + ' ' : gettextCatalog.getString('Click') + ' ';
var lastSigner = lodash.filter($scope.tx.actions, {
type: 'accept'
}).length == $scope.tx.requiredSignatures - 1;
if (lastSigner)
if (lastSigner) {
$scope.buttonText += gettextCatalog.getString('to send');
else
$scope.successText = gettextCatalog.getString('Payment Sent');
} else {
$scope.buttonText += gettextCatalog.getString('to accept');
};
function getDisplayAmount(amountStr) {
return amountStr.split(' ')[0];
};
function getDisplayUnit(amountStr) {
return amountStr.split(' ')[1];
$scope.successText = gettextCatalog.getString('Payment Accepted');
}
};
function initActionList() {

View file

@ -60,7 +60,7 @@ angular.module('copayApp.controllers').controller('paperWalletController',
$scope.wallet.buildTxFromPrivateKey($scope.privateKey, destinationAddress, null, function(err, testTx) {
if (err) return cb(err);
var rawTxLength = testTx.serialize().length;
feeService.getCurrentFeeValue('livenet', null, function(err, feePerKB) {
feeService.getCurrentFeeRate('livenet', function(err, feePerKB) {
var opts = {};
opts.fee = Math.round((feePerKB * rawTxLength) / 2000);
$scope.wallet.buildTxFromPrivateKey($scope.privateKey, destinationAddress, opts, function(err, tx) {

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('preferencesController',
function($scope, $rootScope, $timeout, $log, $ionicHistory, configService, profileService, fingerprintService, walletService) {
function($scope, $rootScope, $timeout, $log, $ionicHistory, configService, profileService, fingerprintService, walletService, platformInfo) {
var wallet;
var walletId;
@ -76,7 +76,7 @@ angular.module('copayApp.controllers').controller('preferencesController',
wallet = profileService.getWallet(data.stateParams.walletId);
walletId = wallet.credentials.walletId;
$scope.wallet = wallet;
$scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
$scope.externalSource = null;
if (!wallet)

View file

@ -2,13 +2,14 @@
angular.module('copayApp.controllers').controller('preferencesFeeController', function($scope, $timeout, $ionicHistory, lodash, gettextCatalog, configService, feeService, ongoingProcess, popupService) {
$scope.save = function(newFee) {
var network;
if ($scope.customFeeLevel) {
$scope.currentFeeLevel = newFee;
updateCurrentValues();
$scope.save = function(newFee) {
$scope.currentFeeLevel = newFee;
updateCurrentValues();
if ($scope.noSave)
return;
}
var opts = {
wallet: {
@ -20,8 +21,6 @@ angular.module('copayApp.controllers').controller('preferencesFeeController', fu
configService.set(opts, function(err) {
if (err) $log.debug(err);
$scope.currentFeeLevel = newFee;
updateCurrentValues();
$timeout(function() {
$scope.$apply();
});
@ -33,8 +32,10 @@ angular.module('copayApp.controllers').controller('preferencesFeeController', fu
});
$scope.init = function() {
$scope.network = $scope.network || 'livenet';
$scope.feeOpts = feeService.feeOpts;
$scope.currentFeeLevel = $scope.customFeeLevel ? $scope.customFeeLevel : feeService.getCurrentFeeLevel();
$scope.currentFeeLevel = $scope.feeLevel || feeService.getCurrentFeeLevel();
$scope.loadingFee = true;
feeService.getFeeLevels(function(err, levels) {
$scope.loadingFee = false;
@ -45,22 +46,27 @@ angular.module('copayApp.controllers').controller('preferencesFeeController', fu
}
$scope.feeLevels = levels;
updateCurrentValues();
$scope.$apply();
$timeout(function() {
$scope.$apply();
});
});
};
var updateCurrentValues = function() {
if (lodash.isEmpty($scope.feeLevels) || lodash.isEmpty($scope.currentFeeLevel)) return;
var feeLevelValue = lodash.find($scope.feeLevels['livenet'], {
var value = lodash.find($scope.feeLevels[$scope.network], {
level: $scope.currentFeeLevel
});
if (lodash.isEmpty(feeLevelValue)) {
if (lodash.isEmpty(value)) {
$scope.feePerSatByte = null;
$scope.avgConfirmationTime = null;
return;
}
$scope.feePerSatByte = (feeLevelValue.feePerKB / 1000).toFixed();
$scope.avgConfirmationTime = feeLevelValue.nbBlocks * 10;
$scope.feePerSatByte = (value.feePerKB / 1000).toFixed();
$scope.avgConfirmationTime = value.nbBlocks * 10;
};
$scope.chooseNewFee = function() {

View file

@ -12,6 +12,7 @@ angular.module('copayApp.controllers').controller('tabHomeController',
$scope.homeTip = $stateParams.fromOnboarding;
$scope.isCordova = platformInfo.isCordova;
$scope.isAndroid = platformInfo.isAndroid;
$scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
$scope.isNW = platformInfo.isNW;
$scope.showRateCard = {};
@ -42,6 +43,11 @@ angular.module('copayApp.controllers').controller('tabHomeController',
}
storageService.getFeedbackInfo(function(error, info) {
if ($scope.isWindowsPhoneApp) {
$scope.showRateCard.value = false;
return;
}
if (!info) {
initFeedBackInfo();
} else {

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, bwcError, gettextCatalog) {
angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, bwcError, gettextCatalog, scannerService) {
var originalList;
var CONTACTS_SHOW_LIMIT;
@ -120,7 +120,20 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
};
$scope.openScanner = function() {
$state.go('tabs.scan');
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
if (!isWindowsPhoneApp) {
$state.go('tabs.scan');
return;
}
scannerService.useOldScanner(function(err, contents) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
incomingData.redir(contents);
});
};
$scope.showMore = function() {

View file

@ -51,6 +51,7 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isCordova = platformInfo.isCordova;
$scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
$scope.isDevel = platformInfo.isDevel;
$scope.appName = appConfigService.nameCase;
configService.whenAvailable(function(config) {

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, incomingData, lodash, popupService, gettextCatalog) {
angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, platformInfo, incomingData, lodash, popupService, gettextCatalog, scannerService) {
$scope.onScan = function(data) {
if (!incomingData.redir(data)) {
@ -22,8 +22,23 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
}, 1);
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$rootScope.hideTabs = '';
});
$scope.chooseScanner = function() {
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
if (!isWindowsPhoneApp) {
$state.go('tabs.scan');
return;
}
scannerService.useOldScanner(function(err, contents) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
incomingData.redir(contents);
});
};
});

View file

@ -1,24 +1,18 @@
'use strict';
angular.module('copayApp.controllers').controller('topUpController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicConfig, lodash, popupService, profileService, ongoingProcess, walletService, configService, platformInfo, bitpayService, bitpayCardService, payproService, bwcError, txFormatService, sendMaxService) {
var amount;
var currency;
var cardId;
var sendMax;
angular.module('copayApp.controllers').controller('topUpController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicConfig, lodash, popupService, profileService, ongoingProcess, walletService, configService, platformInfo, bitpayService, bitpayCardService, payproService, bwcError, txFormatService, sendMaxService, gettextCatalog) {
$scope.isCordova = platformInfo.isCordova;
$scope.$on("$ionicView.beforeLeave", function(event, data) {
$ionicConfig.views.swipeBackEnabled(true);
});
$scope.$on("$ionicView.enter", function(event, data) {
$ionicConfig.views.swipeBackEnabled(false);
});
var cardId;
var useSendMax;
var amount;
var currency;
var createdTx;
var message;
var configWallet = configService.getSync().wallet;
var showErrorAndBack = function(title, msg) {
title = title || 'Error';
title = title || gettextCatalog.getString('Error');
$scope.sendStatus = '';
$log.error(msg);
msg = msg.errors ? msg.errors[0].message : msg;
@ -27,17 +21,24 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
});
};
var showError = function(title, msg) {
title = title || 'Error';
var showError = function(title, msg, cb) {
cb = cb || function() {};
title = title || gettextCatalog.getString('Error');
$scope.sendStatus = '';
$log.error(msg);
msg = msg.errors ? msg.errors[0].message : msg;
popupService.showAlert(title, msg);
popupService.showAlert(title, msg, cb);
};
var satToFiat = function(sat, cb) {
txFormatService.toFiat(sat, $scope.currencyIsoCode, function(value) {
return cb(value);
});
};
var publishAndSign = function (wallet, txp, onSendStatusChange, cb) {
if (!wallet.canSign() && !wallet.isPrivKeyExternal()) {
var err = 'No signing proposal: No private key';
var err = gettextCatalog.getString('No signing proposal: No private key');
$log.info(err);
return cb(err);
}
@ -50,7 +51,7 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
var statusChangeHandler = function (processName, showName, isOn) {
$log.debug('statusChangeHandler: ', processName, showName, isOn);
if ( processName == 'topup' && !isOn) {
if (processName == 'topup' && !isOn) {
$scope.sendStatus = 'success';
$timeout(function() {
$scope.$digest();
@ -60,137 +61,237 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
}
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
cardId = data.stateParams.id;
sendMax = data.stateParams.useSendMax;
var setTotalAmount = function(amountSat, invoiceFeeSat, networkFeeSat) {
satToFiat(amountSat, function(a) {
$scope.amount = Number(a);
if (!cardId) {
showErrorAndBack(null, 'No card selected');
return;
satToFiat(invoiceFeeSat, function(i) {
$scope.invoiceFee = Number(i);
satToFiat(networkFeeSat, function(n) {
$scope.networkFee = Number(n);
$scope.totalAmount = $scope.amount + $scope.invoiceFee + $scope.networkFee;
$timeout(function() {
$scope.$digest();
});
});
});
});
};
var createInvoice = function(data, cb) {
bitpayCardService.topUp(cardId, data, function(err, invoiceId) {
if (err) {
return cb({
title: gettextCatalog.getString('Could not create the invoice'),
message: err
});
}
bitpayCardService.getInvoice(invoiceId, function(err, inv) {
if (err) {
return cb({
title: gettextCatalog.getString('Could not get the invoice'),
message: err
});
}
return cb(null, inv);
});
});
};
var createTx = function(wallet, invoice, message, cb) {
var payProUrl = (invoice && invoice.paymentUrls) ? invoice.paymentUrls.BIP73 : null;
if (!payProUrl) {
return cb({
title: gettextCatalog.getString('Error in Payment Protocol'),
message: gettextCatalog.getString('Invalid URL')
});
}
var parsedAmount = txFormatService.parseAmount(
data.stateParams.amount,
data.stateParams.currency);
amount = parsedAmount.amount;
currency = parsedAmount.currency;
$scope.amountUnitStr = parsedAmount.amountUnitStr;
var outputs = [];
var toAddress = invoice.bitcoinAddress;
var amountSat = parseInt(invoice.btcDue * 100000000); // BTC to Satoshi
$scope.network = bitpayService.getEnvironment().network;
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: $scope.network,
hasFunds: true,
minAmount: parsedAmount.amountSat
outputs.push({
'toAddress': toAddress,
'amount': amountSat,
'message': message
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack(null, 'Insufficient funds');
return;
var txp = {
toAddress: toAddress,
amount: amountSat,
outputs: outputs,
message: message,
payProUrl: payProUrl,
excludeUnconfirmedUtxos: configWallet.spendUnconfirmed ? false : true,
feeLevel: configWallet.settings.feeLevel || 'normal'
};
walletService.createTx(wallet, txp, function(err, ctxp) {
if (err) {
return cb({
title: gettextCatalog.getString('Could not create transaction'),
message: bwcError.msg(err)
});
}
return cb(null, ctxp);
});
};
var calculateAmount = function(wallet, cb) {
// Global variables defined beforeEnter
var a = amount;
var c = currency;
if (useSendMax) {
sendMaxService.getInfo(wallet, function(err, maxValues) {
if (err) {
return cb({
title: null,
message: err
})
}
if (maxValues.amount == 0) {
return cb({message: gettextCatalog.getString('Insufficient funds for fee')});
}
var maxAmountBtc = Number((maxValues.amount / 100000000).toFixed(8));
createInvoice({amount: maxAmountBtc, currency: 'BTC'}, function(err, inv) {
if (err) return cb(err);
var invoiceFeeSat = parseInt((inv.buyerPaidBtcMinerFee * 100000000).toFixed());
var newAmountSat = maxValues.amount - invoiceFeeSat;
if (newAmountSat <= 0) {
return cb({message: gettextCatalog.getString('Insufficient funds for fee')});
}
return cb(null, newAmountSat, 'sat');
});
});
} else {
return cb(null, a, c);
}
$scope.onWalletSelect($scope.wallets[0]); // Default first wallet
};
var initializeTopUp = function(wallet, parsedAmount) {
$scope.amountUnitStr = parsedAmount.amountUnitStr;
var dataSrc = {
amount: parsedAmount.amount,
currency: parsedAmount.currency
};
ongoingProcess.set('loadingTxInfo', true);
createInvoice(dataSrc, function(err, invoice) {
if (err) {
ongoingProcess.set('loadingTxInfo', false);
showErrorAndBack(err.title, err.message);
return;
}
var invoiceFeeSat = (invoice.buyerPaidBtcMinerFee * 100000000).toFixed();
message = gettextCatalog.getString("Top up {{amountStr}} to debit card ({{cardLastNumber}})", {
amountStr: $scope.amountUnitStr,
cardLastNumber: $scope.lastFourDigits
});
createTx(wallet, invoice, message, function(err, ctxp) {
ongoingProcess.set('loadingTxInfo', false);
if (err) {
showErrorAndBack(err.title, err.message);
return;
}
// Save TX in memory
createdTx = ctxp;
$scope.totalAmountStr = txFormatService.formatAmountStr(ctxp.amount);
setTotalAmount(parsedAmount.amountSat, invoiceFeeSat, ctxp.fee);
});
});
};
$scope.$on("$ionicView.beforeLeave", function(event, data) {
$ionicConfig.views.swipeBackEnabled(true);
});
$scope.$on("$ionicView.enter", function(event, data) {
$ionicConfig.views.swipeBackEnabled(false);
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
cardId = data.stateParams.id;
useSendMax = data.stateParams.useSendMax;
amount = data.stateParams.amount;
currency = data.stateParams.currency;
bitpayCardService.get({ cardId: cardId, noRefresh: true }, function(err, card) {
if (err) {
showErrorAndBack(null, err);
return;
}
$scope.cardInfo = card[0];
bitpayCardService.setCurrencySymbol($scope.cardInfo);
bitpayCardService.getRates($scope.cardInfo.currency, function(err, data) {
if (err) $log.error(err);
$scope.rate = data.rate;
});
});
bitpayCardService.setCurrencySymbol(card[0]);
$scope.lastFourDigits = card[0].lastFourDigits;
$scope.currencySymbol = card[0].currencySymbol;
$scope.currencyIsoCode = card[0].currency;
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: bitpayService.getEnvironment().network,
hasFunds: true
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack(null, gettextCatalog.getString('No wallets available'));
return;
}
bitpayCardService.getRates($scope.currencyIsoCode, function(err, r) {
if (err) $log.error(err);
$scope.rate = r.rate;
});
$scope.onWalletSelect($scope.wallets[0]); // Default first wallet
});
});
$scope.topUpConfirm = function() {
var config = configService.getSync();
var configWallet = config.wallet;
var walletSettings = configWallet.settings;
if (!createdTx) {
showError(null, gettextCatalog.getString('Transaction has not been created'));
return;
}
var message = 'Add ' + amount + ' ' + currency + ' to debit card';
var okText = 'Confirm';
var cancelText = 'Cancel';
popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) return;
var title = gettextCatalog.getString('Confirm');
var okText = gettextCatalog.getString('OK');
var cancelText = gettextCatalog.getString('Cancel');
popupService.showConfirm(title, message, okText, cancelText, function(ok) {
if (!ok) {
$scope.sendStatus = '';
return;
}
var dataSrc = {
amount: amount,
currency: currency
};
ongoingProcess.set('topup', true, statusChangeHandler);
bitpayCardService.topUp(cardId, dataSrc, function(err, invoiceId) {
publishAndSign($scope.wallet, createdTx, function() {}, function(err, txSent) {
if (err) {
ongoingProcess.set('topup', false, statusChangeHandler);
showErrorAndBack('Could not create the invoice', err);
ongoingProcess.set('topup', false);
$scope.sendStatus = '';
showError(gettextCatalog.getString('Could not send transaction'), err);
return;
}
bitpayCardService.getInvoice(invoiceId, function(err, invoice) {
if (err) {
ongoingProcess.set('topup', false, statusChangeHandler);
showError('Could not get the invoice', err);
return;
}
var payProUrl = (invoice && invoice.paymentUrls) ? invoice.paymentUrls.BIP73 : null;
if (!payProUrl) {
ongoingProcess.set('topup', false, statusChangeHandler);
showError('Error in Payment Protocol', 'Invalid URL');
return;
}
payproService.getPayProDetails(payProUrl, function(err, payProDetails) {
if (err) {
ongoingProcess.set('topup', false, statusChangeHandler);
showError('Error fetching invoice', err);
return;
}
var outputs = [];
var toAddress = payProDetails.toAddress;
var amountSat = payProDetails.amount;
var comment = 'Top up ' + amount + ' ' + currency + ' to Debit Card (' + $scope.cardInfo.lastFourDigits + ')';
outputs.push({
'toAddress': toAddress,
'amount': amountSat,
'message': comment
});
var txp = {
toAddress: toAddress,
amount: amountSat,
outputs: outputs,
message: comment,
payProUrl: payProUrl,
excludeUnconfirmedUtxos: configWallet.spendUnconfirmed ? false : true,
feeLevel: walletSettings.feeLevel || 'normal'
};
walletService.createTx($scope.wallet, txp, function(err, ctxp) {
if (err) {
ongoingProcess.set('topup', false, statusChangeHandler);
showError('Could not create transaction', bwcError.msg(err));
return;
}
publishAndSign($scope.wallet, ctxp, function() {}, function(err, txSent) {
ongoingProcess.set('topup', false, statusChangeHandler);
if (err) {
showError('Could not send transaction', err);
return;
}
});
});
}, true); // Disable loader
});
});
});
ongoingProcess.set('topup', false, statusChangeHandler);
});
});
};
$scope.showWalletSelector = function() {
@ -200,29 +301,19 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
$scope.onWalletSelect = function(wallet) {
$scope.wallet = wallet;
if (sendMax) {
ongoingProcess.set('retrievingInputs', true);
sendMaxService.getInfo($scope.wallet, function(err, values) {
ongoingProcess.set('retrievingInputs', false);
if (err) {
showErrorAndBack(null, err);
return;
}
var config = configService.getSync().wallet.settings;
var unitName = config.unitName;
var amountUnit = txFormatService.satToUnit(values.amount);
var parsedAmount = txFormatService.parseAmount(
amountUnit,
unitName);
amount = parsedAmount.amount;
currency = parsedAmount.currency;
$scope.amountUnitStr = parsedAmount.amountUnitStr;
$timeout(function() {
$scope.$digest();
}, 100);
});
}
ongoingProcess.set('retrievingInputs', true);
calculateAmount(wallet, function(err, a, c) {
ongoingProcess.set('retrievingInputs', false);
if (err) {
createdTx = message = $scope.totalAmountStr = $scope.amountUnitStr = $scope.wallet = null;
showError(err.title, err.message, function() {
$scope.showWalletSelector();
});
return;
}
var parsedAmount = txFormatService.parseAmount(a, c);
initializeTopUp(wallet, parsedAmount);
});
};
$scope.goBackHome = function() {

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('txDetailsController', function($rootScope, $log, $ionicHistory, $scope, $timeout, walletService, lodash, gettextCatalog, profileService, externalLinkService, popupService, ongoingProcess, txFormatService, txConfirmNotification) {
angular.module('copayApp.controllers').controller('txDetailsController', function($rootScope, $log, $ionicHistory, $scope, $timeout, walletService, lodash, gettextCatalog, profileService, externalLinkService, popupService, ongoingProcess, txFormatService, txConfirmNotification, feeService) {
var txId;
var listeners = [];
@ -11,20 +11,22 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
$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.isShared = $scope.wallet.credentials.n > 1;
txConfirmNotification.checkIfEnabled(txId, function(res) {
$scope.txNotification = { value: res };
$scope.txNotification = {
value: res
};
});
});
$scope.$on("$ionicView.afterEnter", function(event) {
updateTx();
listeners = [
$rootScope.$on('bwsEvent', function(e, walletId, type, n) {
if (type == 'NewBlock' && n && n.data && n.data.network == 'livenet') {
updateTxDebounced({hideLoading: true});
updateTxDebounced({
hideLoading: true
});
}
})
];
@ -36,14 +38,6 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
});
});
function getDisplayAmount(amountStr) {
return amountStr.split(' ')[0];
}
function getDisplayUnit(amountStr) {
return amountStr.split(' ')[1];
}
function updateMemo() {
walletService.getTxNote($scope.wallet, $scope.btx.txid, function(err, note) {
if (err) {
@ -108,7 +102,8 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
$scope.btx = txFormatService.processTx(tx);
txFormatService.formatAlternativeStr(tx.fees, function(v) {
$scope.feeFiatStr = v;
$scope.btx.feeFiatStr = v;
$scope.btx.feeRateStr = ($scope.btx.fees / ($scope.btx.amount + $scope.btx.fees) * 100).toFixed(2) + '%';
});
if ($scope.btx.action != 'invalid') {
@ -117,20 +112,30 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
if ($scope.btx.action == 'moved') $scope.title = gettextCatalog.getString('Moved Funds');
}
$scope.displayAmount = getDisplayAmount($scope.btx.amountStr);
$scope.displayUnit = getDisplayUnit($scope.btx.amountStr);
updateMemo();
initActionList();
getFiatRate();
$timeout(function() {
$scope.$apply();
$scope.$digest();
});
feeService.getFeeLevels(function(err, levels) {
if (err) return;
walletService.getLowAmount($scope.wallet, levels, function(err, amount) {
if (err) return;
$scope.btx.lowAmount = tx.amount < amount;
$timeout(function() {
$scope.$apply();
});
});
});
});
};
var updateTxDebounced = lodash.debounce(updateTx, 5000);
$scope.showCommentPopup = function() {
var opts = {};
if ($scope.btx.message) {
@ -197,7 +202,9 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
$scope.txConfirmNotificationChange = function() {
if ($scope.txNotification.value) {
txConfirmNotification.subscribe($scope.wallet, { txid: txId });
txConfirmNotification.subscribe($scope.wallet, {
txid: txId
});
} else {
txConfirmNotification.unsubscribe($scope.wallet, txId);
}

View file

@ -1,6 +1,6 @@
'use strict';
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, timeService) {
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, timeService, feeService) {
var HISTORY_SHOW_LIMIT = 10;
var currentTxHistoryPage = 0;
@ -47,6 +47,21 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
$scope.txps = lodash.sortBy(txps, 'createdOn').reverse();
};
var analyzeUtxosDone;
var analyzeUtxos = function() {
if (analyzeUtxosDone) return;
feeService.getFeeLevels(function(err, levels){
if (err) return;
walletService.getLowUtxos($scope.wallet, levels, function(err, resp){
if (err || !resp) return;
analyzeUtxosDone = true;
$scope.lowUtxosWarning = resp.warning;
});
});
};
var updateStatus = function(force) {
$scope.updatingStatus = true;
$scope.updateStatusError = null;
@ -72,6 +87,8 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
$scope.$apply();
});
analyzeUtxos();
});
};
@ -154,9 +171,10 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
});
};
$timeout(function() {
feeService.getFeeLevels(function(err, levels){
walletService.getTxHistory($scope.wallet, {
progressFn: progressFn,
feeLevels: levels,
}, function(err, txHistory) {
$scope.updatingTxHistory = false;
if (err) {

View file

@ -8,9 +8,7 @@ angular.module('copayApp.directives')
transclude: true,
scope: {
sendStatus: '=clickSendStatus',
hasWalletChosen: '=hasWalletChosen',
insufficientFunds: '=insufficientFunds',
noMatchingWallet: '=noMatchingWallet'
isDisabled: '=isDisabled',
},
link: function(scope, element, attrs) {
scope.$watch('sendStatus', function() {

View file

@ -0,0 +1,67 @@
'use strict';
angular.module('copayApp.directives')
.directive('timer', function() {
return {
restrict: 'EAC',
replace: false,
scope: {
countdown: "=",
interval: "=",
active: "=",
onZeroCallback: "="
},
template:"{{formatted}}",
controller: function ($scope, $attrs, $timeout, lodash) {
$scope.format = $attrs.outputFormat;
var queueTick = function () {
$scope.timer = $timeout(function () {
if ($scope.countdown > 0) {
$scope.countdown -= 1;
if ($scope.countdown > 0) {
queueTick();
} else {
$scope.countdown = 0;
$scope.active = false;
if (!lodash.isUndefined($scope.onZeroCallback)) {
$scope.onZeroCallback();
}
}
}
}, $scope.interval);
};
if ($scope.active) {
queueTick();
}
$scope.$watch('active', function (newValue, oldValue) {
if (newValue !== oldValue) {
if (newValue === true) {
if ($scope.countdown > 0) {
queueTick();
} else {
$scope.active = false;
}
} else {
$timeout.cancel($scope.timer);
}
}
});
$scope.$watch('countdown', function () {
updateFormatted();
});
var updateFormatted = function () {
$scope.formatted = moment($scope.countdown * $scope.interval).format($scope.format);
};
updateFormatted();
$scope.$on('$destroy', function () {
$timeout.cancel($scope.timer);
});
}
};
});

View file

@ -12,7 +12,7 @@ angular.module('copayApp.directives')
elem.bind('click', function() {
configService.whenAvailable(function(config) {
if (config.wallet.settings.feeLevel.match(/conomy/)) {
if (config.wallet.settings.feeLevel && config.wallet.settings.feeLevel.match(/conomy/)) {
$log.debug('Economy Fee setting... disabling link:' + elem.text());
popupService.showAlert('Low Fee Error', 'Please change your Bitcoin Network Fee Policy setting to Normal or higher to use this service', function() {
$ionicHistory.goBack();

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.directives')
.directive('qrScanner', function($state, $rootScope, $log, $ionicHistory) {
.directive('qrScanner', function($state, $rootScope, $log, $ionicHistory, platformInfo, scannerService, popupService) {
return {
restrict: 'E',
@ -9,26 +9,49 @@ angular.module('copayApp.directives')
onScan: "&"
},
replace: true,
template: '<a on-tap="openScanner()" nav-transition="none"><i class="icon ion-qr-scanner"></i></a>',
template: '<a on-tap="chooseScanner()" nav-transition="none"><i class="icon ion-qr-scanner"></i></a>',
link: function(scope, el, attrs) {
scope.chooseScanner = function() {
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
if (!isWindowsPhoneApp) {
scope.openScanner();
return;
}
scannerService.useOldScanner(function(err, contents) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
scope.onScan({
data: contents
});
});
};
scope.openScanner = function() {
$log.debug('Opening scanner by directive...');
$ionicHistory.nextViewOptions({
disableAnimate: true
});
$state.go('scanner', { passthroughMode: 1 });
$state.go('scanner', {
passthroughMode: 1
});
};
var afterEnter = $rootScope.$on('$ionicView.afterEnter', function() {
if($rootScope.scanResult) {
scope.onScan({ data: $rootScope.scanResult });
if ($rootScope.scanResult) {
scope.onScan({
data: $rootScope.scanResult
});
$rootScope.scanResult = null;
}
});
// Destroy event
scope.$on('$destroy', function(){
scope.$on('$destroy', function() {
afterEnter();
});
}

View file

@ -0,0 +1,15 @@
'use strict';
angular.module('copayApp.directives')
.directive('showTabs', function($rootScope, $timeout) {
return {
restrict: 'A',
link: function($scope, $el) {
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$timeout(function() {
$rootScope.hideTabs = '';
$rootScope.$apply();
});
});
}
};
});

View file

@ -9,7 +9,7 @@ angular.module('copayApp.directives')
scope: {
sendStatus: '=slideSendStatus',
onConfirm: '&slideOnConfirm',
wallet: '=hasWalletChosen'
isDisabled: '=isDisabled'
},
link: function(scope, element, attrs) {

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.directives')
.directive('slideToAcceptSuccess', function($timeout) {
.directive('slideToAcceptSuccess', function($timeout, platformInfo) {
return {
restrict: 'E',
templateUrl: 'views/includes/slideToAcceptSuccess.html',
@ -12,10 +12,13 @@ angular.module('copayApp.directives')
hideOnConfirm: '=slideSuccessHideOnConfirm'
},
link: function(scope, element, attrs) {
scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
var elm = element[0];
elm.style.display = 'none';
scope.$watch('isShown', function() {
if(scope.isShown) {
if (scope.isShown) {
elm.style.display = 'flex';
$timeout(function() {
scope.fillScreen = true;
@ -24,7 +27,7 @@ angular.module('copayApp.directives')
});
scope.onConfirmButtonClick = function() {
scope.onConfirm();
if(scope.hideOnConfirm) {
if (scope.hideOnConfirm) {
scope.fillScreen = false;
elm.style.display = 'none';
}

View file

@ -112,11 +112,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
.state('starting', {
url: '/starting',
template: '<ion-view id="starting"><ion-content>{{starting}}</ion-content></ion-view>',
controller: function($scope, $log, gettextCatalog) {
$log.info('Starting...');
$scope.starting = gettextCatalog.getString('Starting...');
}
template: '<ion-view id="starting"><ion-content><div class="block-spinner row"><ion-spinner class="spinner-stable" icon="crescent"></ion-spinner></div></ion-content></ion-view>'
})
/*
@ -136,10 +132,6 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
})
}
})
.state('uripayment', {
url: '/uri-payment/:url',
templateUrl: 'views/paymentUri.html'
})
/*
*
@ -202,6 +194,25 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
})
.state('tabs.wallet.addresses', {
url: '/addresses/:walletId/:toAddress',
views: {
'tab-home@tabs': {
controller: 'addressesController',
templateUrl: 'views/addresses.html'
}
}
})
.state('tabs.wallet.allAddresses', {
url: '/allAddresses/:walletId',
views: {
'tab-home@tabs': {
controller: 'addressesController',
templateUrl: 'views/allAddresses.html'
}
}
})
/*
*
* Tabs
@ -1204,10 +1215,12 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
$ionicHistory.goBack();
} else
if ($rootScope.backButtonPressedOnceToExit) {
ionic.Platform.exitApp();
navigator.app.exitApp();
} else {
$rootScope.backButtonPressedOnceToExit = true;
window.plugins.toast.showShortBottom(gettextCatalog.getString('Press again to exit'));
$rootScope.$apply(function() {
ionicToast.show(gettextCatalog.getString('Press again to exit'), 'bottom', false, 1000);
});
$timeout(function() {
$rootScope.backButtonPressedOnceToExit = false;
}, 3000);

View file

@ -14,18 +14,18 @@ angular.module('copayApp.services').factory('bitpayAccountService', function($lo
* email: email address associated with bitpay account
* otp: two-factor one-time use password
* }
*
*
* pairingReason - text string to be embedded into popup message. If `null` then the reason
* message is not shown to the UI.
* "To {{reason}} you must pair this app with your BitPay account ({{email}})."
*
*
* cb - callback after completion
* callback(err, paired, apiContext)
*
* err - something unexpected happened which prevented the pairing
*
*
* paired - boolean indicating whether the pairing was compledted by the user
*
*
* apiContext - the context needed for making future api calls
* {
* token: api token for use in future calls
@ -33,6 +33,7 @@ angular.module('copayApp.services').factory('bitpayAccountService', function($lo
* appIdentity: the identity of this app
* }
*/
root.pair = function(pairData, pairingReason, cb) {
checkOtp(pairData, function(otp) {
pairData.otp = otp;
@ -66,14 +67,19 @@ angular.module('copayApp.services').factory('bitpayAccountService', function($lo
fetchBasicInfo(apiContext, function(err, basicInfo) {
if (err) return cb(err);
var title = gettextCatalog.getString('Add BitPay Account?');
var msgDetail = 'Add this BitPay account ({{email}})?';
var msg;
if (pairingReason) {
msgDetail = 'To {{reason}} you must first add your BitPay account - {{email}}';
}
var msg = gettextCatalog.getString(msgDetail, {
reason: pairingReason,
email: pairData.email
});
msg = gettextCatalog.getString('To {{reason}} you must first add your BitPay account - {{email}}', {
reason: pairingReason,
email: pairData.email
});
} else {
msg = gettextCatalog.getString('Add this BitPay account ({{email}})?', {
email: pairData.email
});
}
var ok = gettextCatalog.getString('Add Account');
var cancel = gettextCatalog.getString('Go back');
popupService.showConfirm(title, msg, ok, cancel, function(res) {
@ -182,5 +188,5 @@ angular.module('copayApp.services').factory('bitpayAccountService', function($lo
};
return root;
});

View file

@ -110,7 +110,7 @@ angular.module('copayApp.services')
body = gettextCatalog.getString('Amount below minimum allowed');
break;
case 'INCORRECT_ADDRESS_NETWORK':
body = gettextCatalog.getString('Incorrect address network');
body = gettextCatalog.getString('Incorrect network address');
break;
case 'COPAYER_REGISTERED':
body = gettextCatalog.getString('Key already associated with an existing wallet');

View file

@ -1,11 +1,11 @@
'use strict';
angular.module('copayApp.services').factory('coinbaseService', function($http, $log, $window, $filter, platformInfo, lodash, storageService, configService, appConfigService, txFormatService, buyAndSellService, $rootScope) {
angular.module('copayApp.services').factory('coinbaseService', function($http, $log, $window, $filter, platformInfo, lodash, storageService, configService, appConfigService, txFormatService, buyAndSellService, $rootScope, feeService) {
var root = {};
var credentials = {};
var isCordova = platformInfo.isCordova;
var isNW = platformInfo.isNW;
var isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova;
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
root.priceSensitivity = [{
value: 0.5,
@ -107,6 +107,19 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
};
};
root.checkEnoughFundsForFee = function(amount, cb) {
_getNetAmount(amount, function(err, reducedAmount) {
if (err) return cb(err);
// Check if transaction has enough funds to transfer bitcoin from Coinbase to Copay
if (reducedAmount < 0) {
return cb('Not enough funds for fee');
}
return cb();
});
};
root.getSignupUrl = function() {
return credentials.HOST + '/signup';
}
@ -153,6 +166,17 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
});
};
var _getNetAmount = function(amount, cb) {
// Fee Normal for a single transaction (450 bytes)
var txNormalFeeKB = 450 / 1000;
feeService.getFeeRate(null, 'normal', function(err, feePerKB) {
if (err) return cb(err);
var feeBTC = (feePerKB * txNormalFeeKB / 100000000).toFixed(8);
return cb(null, amount - feeBTC, feeBTC);
});
};
var _refreshToken = function(refreshToken, cb) {
var req = {
method: 'POST',
@ -303,14 +327,14 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
};
root.getBuyOrder = function(token, accountId, buyId, cb) {
if (!token) return cb('Invalid Token');
$http(_get('/accounts/' + accountId + '/buys/' + buyId, token)).then(function(data) {
$log.info('Coinbase Buy Info: SUCCESS');
return cb(null, data.data);
}, function(data) {
$log.error('Coinbase Buy Info: ERROR ' + data.statusText);
return cb(data.data);
});
if (!token) return cb('Invalid Token');
$http(_get('/accounts/' + accountId + '/buys/' + buyId, token)).then(function(data) {
$log.info('Coinbase Buy Info: SUCCESS');
return cb(null, data.data);
}, function(data) {
$log.error('Coinbase Buy Info: ERROR ' + data.statusText);
return cb(data.data);
});
};
root.getTransaction = function(token, accountId, transactionId, cb) {
@ -657,13 +681,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
var _sendToWallet = function(tx, accessToken, accountId, coinbasePendingTransactions) {
if (!tx) return;
var desc = appConfigService.nameCase + ' Wallet';
var data = {
to: tx.toAddr,
amount: tx.amount.amount,
currency: tx.amount.currency,
description: desc
};
root.sendTo(accessToken, accountId, data, function(err, res) {
_getNetAmount(tx.amount.amount, function(err, amountBTC, feeBTC) {
if (err) {
_savePendingTransaction(tx, {
status: 'error',
@ -672,8 +690,18 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
if (err) $log.debug(err);
_updateTxs(coinbasePendingTransactions);
});
} else {
if (res.data && !res.data.id) {
return;
}
var data = {
to: tx.toAddr,
amount: amountBTC,
currency: tx.amount.currency,
description: desc,
fee: feeBTC
};
root.sendTo(accessToken, accountId, data, function(err, res) {
if (err) {
_savePendingTransaction(tx, {
status: 'error',
error: err
@ -681,19 +709,29 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
if (err) $log.debug(err);
_updateTxs(coinbasePendingTransactions);
});
return;
}
root.getTransaction(accessToken, accountId, res.data.id, function(err, sendTx) {
_savePendingTransaction(tx, {
remove: true
}, function(err) {
_savePendingTransaction(sendTx.data, {}, function(err) {
} else {
if (res.data && !res.data.id) {
_savePendingTransaction(tx, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err);
_updateTxs(coinbasePendingTransactions);
});
return;
}
root.getTransaction(accessToken, accountId, res.data.id, function(err, sendTx) {
_savePendingTransaction(tx, {
remove: true
}, function(err) {
_savePendingTransaction(sendTx.data, {}, function(err) {
if (err) $log.debug(err);
_updateTxs(coinbasePendingTransactions);
});
});
});
});
}
}
});
});
};
@ -723,7 +761,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
var register = function() {
root.isActive(function(err, isActive){
root.isActive(function(err, isActive) {
if (err) return;
buyAndSellService.register({
@ -742,7 +780,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
$rootScope.$on('bwsEvent', function(e, walletId, type, n) {
if (type == 'NewBlock' && n && n.data && n.data.network == 'livenet') {
root.isActive(function(err,isActive){
root.isActive(function(err, isActive) {
// Update Coinbase
if (isActive)
root.updatePendingTransactions();

View file

@ -1,8 +1,10 @@
'use strict';
angular.module('copayApp.services').factory('configService', function(storageService, lodash, $log, $timeout, $rootScope) {
angular.module('copayApp.services').factory('configService', function(storageService, lodash, $log, $timeout, $rootScope, platformInfo) {
var root = {};
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
var defaultConfig = {
// wallet limits
limits: {
@ -73,7 +75,7 @@ angular.module('copayApp.services').factory('configService', function(storageSer
},
hideNextSteps: {
enabled: false,
enabled: isWindowsPhoneApp ? true : false,
},
rates: {

View file

@ -1,8 +1,10 @@
'use strict';
angular.module('copayApp.services').factory('feeService', function($log, $stateParams, bwcService, walletService, configService, gettext, lodash, txFormatService, gettextCatalog) {
angular.module('copayApp.services').factory('feeService', function($log, $timeout, $stateParams, bwcService, walletService, configService, gettext, lodash, txFormatService, gettextCatalog) {
var root = {};
var CACHE_TIME_TS = 60; // 1 min
// Constant fee options to translate
root.feeOpts = {
urgent: gettext('Urgent'),
@ -12,22 +14,26 @@ angular.module('copayApp.services').factory('feeService', function($log, $stateP
superEconomy: gettext('Super Economy')
};
var cache = {
updateTs: 0,
};
root.getCurrentFeeLevel = function() {
return configService.getSync().wallet.settings.feeLevel || 'normal';
};
root.getCurrentFeeValue = function(network, customFeeLevel, cb) {
network = network || 'livenet';
var feeLevel = customFeeLevel || root.getCurrentFeeLevel();
root.getFeeLevels(function(err, levels) {
root.getFeeRate = function(network, feeLevel, cb) {
network = network || 'livenet';
root.getFeeLevels(function(err, levels, fromCache) {
if (err) return cb(err);
var feeLevelValue = lodash.find(levels[network], {
var feeLevelRate = lodash.find(levels[network], {
level: feeLevel
});
if (!feeLevelValue || !feeLevelValue.feePerKB) {
if (!feeLevelRate || !feeLevelRate.feePerKB) {
return cb({
message: gettextCatalog.getString("Could not get dynamic fee for level: {{feeLevel}}", {
feeLevel: feeLevel
@ -35,14 +41,24 @@ angular.module('copayApp.services').factory('feeService', function($log, $stateP
});
}
var fee = feeLevelValue.feePerKB;
$log.debug('Dynamic fee: ' + feeLevel + ' ' + fee + ' SAT');
var feeRate = feeLevelRate.feePerKB;
return cb(null, fee);
if (!fromCache) $log.debug('Dynamic fee: ' + feeLevel + '/' + network + ' ' + (feeLevelRate.feePerKB / 1000).toFixed() + ' SAT/B');
return cb(null, feeRate);
});
};
root.getCurrentFeeRate = function(network, cb) {
return root.getFeeRate(network, root.getCurrentFeeLevel(), cb);
};
root.getFeeLevels = function(cb) {
if (cache.updateTs > Date.now() - CACHE_TIME_TS * 1000) {
return cb(null, cache.data, true);
}
var walletClient = bwcService.getClient();
var unitName = configService.getSync().wallet.settings.unitName;
@ -51,13 +67,18 @@ angular.module('copayApp.services').factory('feeService', function($log, $stateP
if (errLivenet || errTestnet) {
return cb(gettextCatalog.getString('Could not get dynamic fee'));
}
return cb(null, {
cache.updateTs = Date.now();
cache.data = {
'livenet': levelsLivenet,
'testnet': levelsTestnet
});
};
return cb(null, cache.data);
});
});
};
return root;
});

View file

@ -4,7 +4,7 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
var root = {};
var credentials = {};
var isCordova = platformInfo.isCordova;
var isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova;
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
var setCredentials = function() {
if (!$window.externalServices || !$window.externalServices.glidera) {

View file

@ -3,7 +3,7 @@
angular.module('copayApp.services').factory('ongoingProcess', function($log, $timeout, $filter, lodash, $ionicLoading, gettext, platformInfo) {
var root = {};
var isCordova = platformInfo.isCordova;
var isWP = platformInfo.isWP;
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
var ongoingProcess = {};
@ -17,8 +17,8 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
'creatingTx': gettext('Creating transaction'),
'creatingWallet': gettext('Creating Wallet...'),
'deletingWallet': gettext('Deleting Wallet...'),
'extractingWalletInfo': gettext('Extracting Wallet Information...'),
'fetchingPayPro': gettext('Fetching Payment Information'),
'extractingWalletInfo': gettext('Extracting Wallet information...'),
'fetchingPayPro': gettext('Fetching payment information'),
'generatingCSV': gettext('Generating .csv file...'),
'gettingFeeLevels': gettext('Getting fee levels...'),
'importingWallet': gettext('Importing Wallet...'),
@ -45,12 +45,12 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
'cancelingGiftCard': 'Canceling Gift Card...',
'creatingGiftCard': 'Creating Gift Card...',
'buyingGiftCard': 'Buying Gift Card...',
'topup': 'Top up in progress...'
'topup': gettext('Top up in progress...')
};
root.clear = function() {
ongoingProcess = {};
if (isCordova && !isWP) {
if (isCordova && !isWindowsPhoneApp) {
window.plugins.spinnerDialog.hide();
} else {
$ionicLoading.hide();
@ -80,19 +80,19 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
if (customHandler) {
customHandler(processName, showName, isOn);
} else if (root.onGoingProcessName) {
if (isCordova && !isWP) {
if (isCordova && !isWindowsPhoneApp) {
window.plugins.spinnerDialog.show(null, showName, root.clear);
} else {
var tmpl;
if (isWP) tmpl = '<div>' + showName + '</div>';
if (isWindowsPhoneApp) tmpl = '<div>' + showName + '</div>';
else tmpl = '<div class="item-icon-left">' + showName + '<ion-spinner class="spinner-stable" icon="lines"></ion-spinner></div>';
$ionicLoading.show({
template: tmpl
});
}
} else {
if (isCordova && !isWP) {
if (isCordova && !isWindowsPhoneApp) {
window.plugins.spinnerDialog.hide();
} else {
$ionicLoading.hide();

View file

@ -3,16 +3,17 @@
angular.module('copayApp.services').service('popupService', function($log, $ionicPopup, platformInfo, gettextCatalog) {
var isCordova = platformInfo.isCordova;
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
/*************** Ionic ****************/
var _ionicAlert = function(title, message, cb, buttonName) {
var _ionicAlert = function(title, message, cb, okText) {
if (!cb) cb = function() {};
$ionicPopup.alert({
title: title,
subTitle: message,
okType: 'button-clear button-positive',
okText: buttonName || gettextCatalog.getString('OK'),
okText: okText || gettextCatalog.getString('OK'),
}).then(cb);
};
@ -45,9 +46,11 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
/*************** Cordova ****************/
var _cordovaAlert = function(title, message, cb, buttonName) {
var _cordovaAlert = function(title, message, cb, okText) {
if (!cb) cb = function() {};
navigator.notification.alert(message, cb, title, buttonName);
title = title ? title : '';
okText = okText || gettextCatalog.getString('OK');
navigator.notification.alert(message, cb, title, okText);
};
var _cordovaConfirm = function(title, message, okText, cancelText, cb) {
@ -57,6 +60,7 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
}
okText = okText || gettextCatalog.getString('OK');
cancelText = cancelText || gettextCatalog.getString('Cancel');
title = title ? title : '';
navigator.notification.confirm(message, onConfirm, title, [cancelText, okText]);
};
@ -65,7 +69,10 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
if (results.buttonIndex == 1) return cb(results.input1);
else return cb();
}
navigator.notification.prompt(message, onPrompt, title, null, opts.defaultText);
var okText = gettextCatalog.getString('OK');
var cancelText = gettextCatalog.getString('Cancel');
title = title ? title : '';
navigator.notification.prompt(message, onPrompt, title, [cancelText, okText], opts.defaultText);
};
/**
@ -76,14 +83,14 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
* @param {Callback} Function (optional)
*/
this.showAlert = function(title, msg, cb, buttonName) {
this.showAlert = function(title, msg, cb, okText) {
var message = (msg && msg.message) ? msg.message : msg;
$log.warn(title ? (title + ': ' + message) : message);
if (isCordova)
_cordovaAlert(title, message, cb, buttonName);
_cordovaAlert(title, message, cb, okText);
else
_ionicAlert(title, message, cb, buttonName);
_ionicAlert(title, message, cb, okText);
};
/**
@ -121,7 +128,7 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
opts = opts ||  {};
if (isCordova && !opts.forceHTMLPrompt)
if (isCordova && !isWindowsPhoneApp && !opts.forceHTMLPrompt)
_cordovaPrompt(title, message, opts, cb);
else
_ionicPrompt(title, message, opts, cb);

View file

@ -5,12 +5,12 @@ angular.module('copayApp.services')
var isChromeApp = platformInfo.isChromeApp;
var isCordova = platformInfo.isCordova;
var isWP = platformInfo.isWP;
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
var isIOS = platformInfo.isIOS;
var root = {};
var errors = bwcService.getErrors();
var usePushNotifications = isCordova && !isWP;
var usePushNotifications = isCordova && !isWindowsPhoneApp;
var UPDATE_PERIOD = 15;
@ -208,9 +208,9 @@ angular.module('copayApp.services')
};
var shouldSkipValidation = function(walletId) {
return root.profile.isChecked(platformInfo.ua, walletId) || isIOS || isWP;
}
// Used when reading wallets from the profile
return root.profile.isChecked(platformInfo.ua, walletId) || isIOS || isWindowsPhoneApp;
}
// Used when reading wallets from the profile
root.bindWallet = function(credentials, cb) {
if (!credentials.walletId || !credentials.m)
return cb('bindWallet should receive credentials JSON');

View file

@ -17,27 +17,27 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
var canChangeCamera = false;
var canOpenSettings = false;
function _checkCapabilities(status){
function _checkCapabilities(status) {
$log.debug('scannerService is reviewing platform capabilities...');
// Permission can be assumed on the desktop builds
hasPermission = (isDesktop || status.authorized)? true: false;
isDenied = status.denied? true : false;
isRestricted = status.restricted? true : false;
canEnableLight = status.canEnableLight? true : false;
canChangeCamera = status.canChangeCamera? true : false;
canOpenSettings = status.canOpenSettings? true : false;
hasPermission = (isDesktop || status.authorized) ? true : false;
isDenied = status.denied ? true : false;
isRestricted = status.restricted ? true : false;
canEnableLight = status.canEnableLight ? true : false;
canChangeCamera = status.canChangeCamera ? true : false;
canOpenSettings = status.canOpenSettings ? true : false;
_logCapabilities();
}
function _logCapabilities(){
function _orIsNot(bool){
return bool? '' : 'not ';
function _logCapabilities() {
function _orIsNot(bool) {
return bool ? '' : 'not ';
}
$log.debug('A camera is ' + _orIsNot(isAvailable) + 'available to this app.');
var access = 'not authorized';
if(hasPermission) access = 'authorized';
if(isDenied) access = 'denied';
if(isRestricted) access = 'restricted';
if (hasPermission) access = 'authorized';
if (isDenied) access = 'denied';
if (isRestricted) access = 'restricted';
$log.debug('Camera access is ' + access + '.');
$log.debug('Support for opening device settings is ' + _orIsNot(canOpenSettings) + 'available on this platform.');
$log.debug('A light is ' + _orIsNot(canEnableLight) + 'available on this platform.');
@ -47,7 +47,7 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
/**
* Immediately return known capabilities of the current platform.
*/
this.getCapabilities = function(){
this.getCapabilities = function() {
return {
isAvailable: isAvailable,
hasPermission: hasPermission,
@ -68,18 +68,18 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
* The `status` of QRScanner is returned to the callback.
*/
this.gentleInitialize = function(callback) {
if(initializeStarted && !isDesktop){
QRScanner.getStatus(function(status){
if (initializeStarted && !isDesktop) {
QRScanner.getStatus(function(status) {
_completeInitialization(status, callback);
});
return;
}
initializeStarted = true;
$log.debug('Trying to pre-initialize QRScanner.');
if(!isDesktop){
QRScanner.getStatus(function(status){
if (!isDesktop) {
QRScanner.getStatus(function(status) {
_checkCapabilities(status);
if(status.authorized){
if (status.authorized) {
$log.debug('Camera permission already granted.');
initialize(callback);
} else {
@ -92,14 +92,14 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
}
};
function initialize(callback){
function initialize(callback) {
$log.debug('Initializing scanner...');
QRScanner.prepare(function(err, status){
if(err){
QRScanner.prepare(function(err, status) {
if (err) {
isAvailable = false;
$log.error(err);
// does not return `status` if there is an error
QRScanner.getStatus(function(status){
QRScanner.getStatus(function(status) {
_completeInitialization(status, callback);
});
} else {
@ -112,18 +112,19 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
// This could be much cleaner with a Promise API
// (needs a polyfill for some platforms)
var initializeCompleted = false;
function _completeInitialization(status, callback){
function _completeInitialization(status, callback) {
_checkCapabilities(status);
initializeCompleted = true;
$rootScope.$emit('scannerServiceInitialized');
if(typeof callback === "function"){
if (typeof callback === "function") {
callback(status);
}
}
this.isInitialized = function(){
this.isInitialized = function() {
return initializeCompleted;
};
this.initializeStarted = function(){
this.initializeStarted = function() {
return initializeStarted;
};
@ -140,21 +141,21 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
*/
this.activate = function(callback) {
$log.debug('Activating scanner...');
QRScanner.show(function(status){
initializeCompleted = true;
_checkCapabilities(status);
if(typeof callback === "function"){
callback(status);
}
});
if(nextHide !== null){
$timeout.cancel(nextHide);
nextHide = null;
}
if(nextDestroy !== null){
$timeout.cancel(nextDestroy);
nextDestroy = null;
QRScanner.show(function(status) {
initializeCompleted = true;
_checkCapabilities(status);
if (typeof callback === "function") {
callback(status);
}
});
if (nextHide !== null) {
$timeout.cancel(nextHide);
nextHide = null;
}
if (nextDestroy !== null) {
$timeout.cancel(nextDestroy);
nextDestroy = null;
}
};
/**
@ -193,18 +194,18 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
// Natively hide the QRScanner's preview
// On mobile platforms, this can reduce GPU/power usage
// On desktop, this fully turns off the camera (and any associated privacy lights)
function _hide(){
function _hide() {
$log.debug('Scanner not in use for ' + hideAfterSeconds + ' seconds, hiding...');
QRScanner.hide();
}
// Reduce QRScanner power/processing consumption by the maximum amount
function _destroy(){
function _destroy() {
$log.debug('Scanner not in use for ' + destroyAfterSeconds + ' seconds, destroying...');
QRScanner.destroy();
}
this.reinitialize = function(callback){
this.reinitialize = function(callback) {
initializeCompleted = false;
QRScanner.destroy();
initialize(callback);
@ -217,17 +218,18 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
*/
this.toggleLight = function(callback) {
$log.debug('Toggling light...');
if(lightEnabled){
if (lightEnabled) {
QRScanner.disableLight(_handleResponse);
} else {
QRScanner.enableLight(_handleResponse);
}
function _handleResponse(err, status){
if(err){
function _handleResponse(err, status) {
if (err) {
$log.error(err);
} else {
lightEnabled = status.lightEnabled;
var state = lightEnabled? 'enabled' : 'disabled';
var state = lightEnabled ? 'enabled' : 'disabled';
$log.debug('Light ' + state + '.');
}
callback(lightEnabled);
@ -241,16 +243,17 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
* is complete.
*/
this.toggleCamera = function(callback) {
var nextCamera = backCamera? 1 : 0;
function cameraToString(index){
return index === 1? 'front' : 'back'; // front = 1, back = 0
var nextCamera = backCamera ? 1 : 0;
function cameraToString(index) {
return index === 1 ? 'front' : 'back'; // front = 1, back = 0
}
$log.debug('Toggling to the ' + cameraToString(nextCamera) + ' camera...');
QRScanner.useCamera(nextCamera, function(err, status){
if(err){
QRScanner.useCamera(nextCamera, function(err, status) {
if (err) {
$log.error(err);
}
backCamera = status.currentCamera === 1? false : true;
backCamera = status.currentCamera === 1 ? false : true;
$log.debug('Camera toggled. Now using the ' + cameraToString(backCamera) + ' camera.');
callback(status);
});
@ -260,4 +263,15 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
$log.debug('Attempting to open device settings...');
QRScanner.openSettings();
};
this.useOldScanner = function(callback) {
cordova.plugins.barcodeScanner.scan(
function(result) {
callback(null, result.text);
},
function(error) {
callback(error);
}
);
}
});

View file

@ -10,7 +10,7 @@ angular.module('copayApp.services').service('sendMaxService', function(feeServic
*
*/
this.getInfo = function(wallet, cb) {
feeService.getCurrentFeeValue(wallet.credentials.network, null, function(err, feePerKb) {
feeService.getCurrentFeeRate(wallet.credentials.network, function(err, feePerKb) {
if (err) return cb(err);
var config = configService.getSync().wallet;

View file

@ -23,6 +23,26 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
return root.formatAmount(satoshis) + ' ' + config.unitName;
};
root.toFiat = function(satoshis, code, cb) {
if (isNaN(satoshis)) return;
var val = function() {
var v1 = rateService.toFiat(satoshis, code);
if (!v1) return null;
return v1.toFixed(2);
};
// Async version
if (cb) {
rateService.whenAvailable(function() {
return cb(val());
});
} else {
if (!rateService.isAvailable()) return null;
return val();
};
};
root.formatToUSD = function(satoshis, cb) {
if (isNaN(satoshis)) return;
var val = function() {
@ -93,6 +113,11 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
tx.alternativeAmountStr = root.formatAlternativeStr(tx.amount);
tx.feeStr = root.formatAmountStr(tx.fee || tx.fees);
if (tx.amountStr) {
tx.amountValueStr = tx.amountStr.split(' ')[0];
tx.amountUnitStr = tx.amountStr.split(' ')[1];
}
return tx;
};
@ -164,9 +189,15 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
var alternativeIsoCode = config.alternativeIsoCode;
// If fiat currency
if (currency != 'bits' && currency != 'BTC') {
if (currency != 'bits' && currency != 'BTC' && currency != 'sat') {
amountUnitStr = $filter('formatFiatAmount')(amount) + ' ' + currency;
amountSat = rateService.fromFiat(amount, currency).toFixed(0);
} else if (currency == 'sat') {
amountSat = amount;
amountUnitStr = root.formatAmountStr(amountSat);
// convert sat to BTC
amount = (amountSat * satToBtc).toFixed(8);
currency = 'BTC';
} else {
amountSat = parseInt((amount * unitToSatoshi).toFixed(0));
amountUnitStr = root.formatAmountStr(amountSat);
@ -176,8 +207,8 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
}
return {
amount: amount,
currency: currency,
amount: amount,
currency: currency,
alternativeIsoCode: alternativeIsoCode,
amountSat: amountSat,
amountUnitStr: amountUnitStr

View file

@ -1,7 +1,12 @@
'use strict';
angular.module('copayApp.services').factory('walletService', function($log, $timeout, lodash, trezor, ledger, intelTEE, storageService, configService, rateService, uxLanguage, $filter, gettextCatalog, bwcError, $ionicPopup, fingerprintService, ongoingProcess, gettext, $rootScope, txFormatService, $ionicModal, $state, bwcService, bitcore, popupService) {
// `wallet` is a decorated version of client.
// Ratio low amount warning (fee/amount) in incoming TX
var LOW_AMOUNT_RATIO = 0.15;
// Ratio of "many utxos" warning in total balance (fee/amount)
var TOTAL_LOW_WARNING_RATIO = .3;
var root = {};
@ -401,6 +406,11 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
var progressFn = opts.progressFn || function() {};
var foundLimitTx = false;
if (opts.feeLevels) {
opts.lowAmount = root.getLowAmount(wallet, opts.feeLevels);
}
var fixTxsUnit = function(txs) {
if (!txs || !txs[0] || !txs[0].amountStr) return;
@ -413,7 +423,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
$log.debug('Fixing Tx Cache Unit to:' + name)
lodash.each(txs, function(tx) {
tx.amountStr = txFormatService.formatAmount(tx.amount) + name;
tx.feeStr = txFormatService.formatAmount(tx.fees) + name;
});
@ -511,6 +520,16 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
});
}
function updateLowAmount(txs) {
if (!opts.lowAmount) return;
lodash.each(txs, function(tx) {
tx.lowAmount = tx.amount < opts.lowAmount;
});
};
updateLowAmount(txs);
updateNotes(function() {
// <HACK>
@ -567,9 +586,9 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
root.getTx = function(wallet, txid, cb) {
function finish(list){
function finish(list) {
var tx = lodash.find(list, {
txid: txid
txid: txid
});
if (!tx) return cb('Could not get transaction');
@ -602,7 +621,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
});
};
root.getTxHistory = function(wallet, opts, cb) {
opts = opts || {};
@ -873,6 +892,81 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
});
};
// These 2 functions were taken from
// https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/txproposal.js#L243
function getEstimatedSizeForSingleInput(wallet) {
switch (wallet.credentials.addressType) {
case 'P2PKH':
return 147;
default:
case 'P2SH':
return wallet.m * 72 + wallet.n * 36 + 44;
}
};
root.getEstimatedTxSize = function(wallet, nbOutputs) {
// Note: found empirically based on all multisig P2SH inputs and within m & n allowed limits.
var safetyMargin = 0.02;
var overhead = 4 + 4 + 9 + 9;
var inputSize = getEstimatedSizeForSingleInput(wallet);
var outputSize = 34;
var nbInputs = 1; //Assume 1 input
var nbOutputs = nbOutputs || 2; // Assume 2 outputs
var size = overhead + inputSize * nbInputs + outputSize * nbOutputs;
return parseInt((size * (1 + safetyMargin)).toFixed(0));
};
// Approx utxo amount, from which the uxto is economically redeemable
root.getMinFee = function(wallet, feeLevels, nbOutputs) {
var lowLevelRate = (lodash.find(feeLevels[wallet.network], {
level: 'normal',
}).feePerKB / 1000).toFixed(0);
var size = root.getEstimatedTxSize(wallet, nbOutputs);
return size * lowLevelRate;
};
// Approx utxo amount, from which the uxto is economically redeemable
root.getLowAmount = function(wallet, feeLevels, nbOutputs) {
var minFee = root.getMinFee(wallet,feeLevels, nbOutputs);
return parseInt( minFee / LOW_AMOUNT_RATIO);
};
root.getLowUtxos = function(wallet, levels, cb) {
wallet.getUtxos({}, function(err, resp) {
if (err || !resp || !resp.length) return cb();
var minFee = root.getMinFee(wallet, levels, resp.length);
var balance = lodash.sum(resp, 'satoshis');
// for 2 outputs
var lowAmount = root.getLowAmount(wallet, levels);
var lowUtxos = lodash.filter(resp, function(x) {
return x.satoshis < lowAmount;
});
var totalLow = lodash.sum(lowUtxos, 'satoshis');
return cb(err, {
allUtxos: resp || [],
lowUtxos: lowUtxos || [],
warning: minFee / balance > TOTAL_LOW_WARNING_RATIO,
minFee: minFee,
});
});
};
root.getAddress = function(wallet, forceNew, cb) {
storageService.getLastAddress(wallet.id, function(err, addr) {
if (err) return cb(err);

View file

@ -11,7 +11,7 @@
font-size: 14px;
font-weight: 300;
color: #727272;
padding: 2px 1rem;
padding: 1rem;
background: #f8f8f9;
i {
position: absolute;
@ -52,8 +52,8 @@
width: 35px;
}
span {
text-transform: capitalize;
.big-icon-svg {
padding: 0 12px 0 0;
}
}
.amount-label{
@ -101,6 +101,9 @@
color: $v-bitcoin-orange;
}
}
.total {
font-weight: bold;
}
}
.tx-icon {
margin-right: 25px;

View file

@ -5,6 +5,10 @@
float: none;
.fee-rate {
display: inline-block;
.warn {
color: red;
}
}
}
.icon-amazon {

View file

@ -24,7 +24,6 @@ click-to-accept {
height: 100%;
width: 100%;
z-index: 4;
text-transform: capitalize;
-webkit-transform: translateY(2rem);
transform: translateY(2rem);
opacity: 0;

View file

@ -95,7 +95,6 @@ slide-to-accept {
width: 100%;
font-size: 17px;
letter-spacing: 0.02rem;
text-transform: capitalize;
-webkit-transform: translateY(2rem);
transform: translateY(2rem);
opacity: 0;

View file

@ -12,6 +12,12 @@ slide-to-accept-success {
.slide-success {
$duration: 400ms;
&__windows-background {
background: $v-success-bg-color;
height: 100%;
width: 100%;
position: fixed;
}
&__background {
$start-radius: 5;
$scale-factor: 20;

View file

@ -5,7 +5,12 @@
$item-vertical-padding: 10px;
$item-border-color: #EFEFEF;
$item-label-color: #6C6C6E;
.item-note {
float: none;
.fee-rate {
display: inline-block;
}
}
.list {
background: #f5f5f5;
}
@ -27,10 +32,6 @@
height: 35px;
width: 35px;
}
span {
text-transform: capitalize;
}
}
.amount-label{
line-height: 30px;
@ -93,7 +94,7 @@
&.low-fees {
display: flex;
font-size: 14px;
color: #aaa;
color: #777;
align-items: center;
i {
padding-right: 20px;
@ -107,7 +108,7 @@
margin-top: 5px;
font-size: 14px;
color: $item-label-color;
}
}
.item-divider {
padding-top: 1.2rem;
color: $item-label-color;

View file

@ -11,7 +11,7 @@
stroke: black;
fill: black;
}
.add-bottom-for-cta {
bottom: 92px;
}
@ -31,10 +31,6 @@
width: 35px;
}
span {
text-transform: capitalize;
}
.big-icon-svg {
margin-right: 0.6rem;
}
@ -80,7 +76,7 @@
color: $item-label-color;
margin-bottom: 8px;
}
.capitalized {
text-transform: capitalize;
}

View file

@ -28,10 +28,6 @@
height: 35px;
width: 35px;
}
span {
text-transform: capitalize;
}
}
.amount-label{
line-height: 30px;

View file

@ -28,10 +28,6 @@
height: 35px;
width: 35px;
}
span {
text-transform: capitalize;
}
}
.amount-label{
line-height: 30px;

View file

@ -18,4 +18,7 @@
vertical-align: middle;
text-align: center;
}
.block-spinner {
display: block;
}
}

View file

@ -85,7 +85,7 @@
font-size: 14px;
font-weight: 300;
color: #727272;
padding: 2px 1rem;
padding: 1rem;
background: #f8f8f9;
}
}
@ -248,6 +248,7 @@
text-decoration: none;
z-index: 9999;
position: relative;
padding: 2px 5px;
}
a.item {

View file

@ -1,4 +1,4 @@
<ion-view>
<ion-view show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>
{{'Recent Transactions'|translate}}

View file

@ -1,4 +1,4 @@
<ion-view id="view-add">
<ion-view id="view-add" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>{{'Add wallet' | translate}}</ion-nav-title>
<ion-nav-back-button>

View file

@ -1,4 +1,4 @@
<ion-view id="addresses" class="addr">
<ion-view id="addresses" class="addr" hide-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>{{'Wallet Addresses' | translate}}</ion-nav-title>
<ion-nav-back-button>
@ -33,9 +33,9 @@
<div class="item item-icon-right view-all" ng-if="viewAll.value" ng-click="viewAllAddresses()">
<span translate>View All Addresses</span>
<i class="icon ion-ios-arrow-thin-right"></i>
</div>
</div>
<div class="item item-divider item-icon-right" ng-click="newAddress()">
<div class="item item-divider item-icon-right" ng-click="newAddress()">
<span translate>Unused Addresses</span>
<i class="icon ion-ios-plus-empty"></i>
</div>
@ -70,6 +70,34 @@
<div class="addr-balance">{{w.balanceStr}}</div>
</div>
</div>
<div ng-if="allUtxosNb">
<div class="item item-divider" translate>
Wallet Inputs
</div>
<div class="item" >
<span translate> Total wallet inputs </span>
<div class="addr-path">
{{allUtxosNb}} [{{allUtxosSum}}]
</div>
</div>
<div class="item" >
<span translate> Low amount inputs </span>
<div class="addr-path">
{{lowUtxosNb}} [{{ lowUtxosSum }}]
</div>
</div>
<div class="item" >
<span translate> Approximate Bitcoin network fee to transfer wallet's balance (with normal priority) </span>
<div class="addr-path">
{{minFeePer}} [{{minFee}}]
</div>
</div>
</div>
</div>
</div>
</ion-content>

View file

@ -1,4 +1,4 @@
<ion-view id="advanced-settings" class="settings">
<ion-view id="advanced-settings" class="settings" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>{{'Advanced Settings' | translate}}</ion-nav-title>
<ion-nav-back-button>
@ -16,7 +16,7 @@
<div class="item item-divider"></div>
<ion-toggle class="has-comment" ng-show="!isWP" ng-model="recentTransactionsEnabled.value" toggle-class="toggle-balanced" ng-change="recentTransactionsChange()">
<ion-toggle class="has-comment" ng-model="recentTransactionsEnabled.value" toggle-class="toggle-balanced" ng-change="recentTransactionsChange()">
<span class="toggle-label" translate>Recent Transaction Card</span>
</ion-toggle>
<div class="comment" translate>
@ -25,7 +25,7 @@
<div class="item item-divider"></div>
<ion-toggle ng-model="hideNextSteps.value" toggle-class="toggle-balanced" ng-change="nextStepsChange()">
<ion-toggle ng-model="hideNextSteps.value" ng-if="!isWindowsPhoneApp" toggle-class="toggle-balanced" ng-change="nextStepsChange()">
<span class="toggle-label" translate>Hide Next Steps Card</span>
</ion-toggle>
</div>

View file

@ -1,4 +1,4 @@
<ion-view hide-tabs id="addresses" class="addr">
<ion-view id="addresses" class="addr" hide-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>{{'All Addresses' | translate}}</ion-nav-title>
<ion-nav-back-button>

View file

@ -1,4 +1,4 @@
<ion-view>
<ion-view show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>

View file

@ -1,4 +1,4 @@
<ion-view>
<ion-view show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>
@ -16,7 +16,7 @@
</ion-spinner>
<h2 ng-if="item.amount">
{{item.amount | currency : '$ ' : 2}} {{item.currency}}
</h2>
</h2>
<p>
<span class="assertive" ng-if="item.status == 'FAILURE' || item.status == 'RESEND'">Error</span>
<span class="assertive" ng-if="item.status == 'expired'">Expired</span>

View file

@ -1,4 +1,4 @@
<ion-view id="bitpayCard">
<ion-view id="bitpayCard" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>
@ -48,7 +48,7 @@
<i class="icon ion-ios-arrow-thin-up get-started__arrow"></i>
<h1 translate>Get started</h1>
<div class="get-started__text" translate>
Your BitPay Card is ready. Add funds to your card to start using your card at stores and ATMs worldwide.
Your BitPay Card is ready. Add funds to your card to start using it at stores and ATMs worldwide.
</div>
</div>
<div class="list" ng-show="!bitpayCard.getStarted">
@ -64,8 +64,8 @@
</label>
<div ng-if="bitpayCard.bitpayCardTransactionHistoryConfirming[0]">
<label class="item status-label" ng-click="bitpayCard.openExternalLink('https://help.bitpay.com/bitpay-card/why-do-you-require-one-blockchain-confirmation-for-bitpay-card-loads')">
<div translate>
Confirming
<div>
<span translate>Confirming</span>
<i class="icon">
<img src="img/icon-help-support.svg" class="bg"/>
</i>
@ -77,8 +77,8 @@
</div>
<div ng-if="bitpayCard.bitpayCardTransactionHistoryPreAuth[0]">
<label class="item status-label" ng-click="bitpayCard.openExternalLink('https://help.bitpay.com/bitpay-card/why-was-i-overcharged-on-my-bitpay-card-account-why-is-there-a-hold-on-my-account')">
<div translate>
Pre-Auth Holds
<div>
<span translate>Pre-Auth Holds</span>
<i class="icon">
<img src="img/icon-help-support.svg" class="bg"/>
</i>
@ -89,7 +89,7 @@
</div>
</div>
<div ng-if="bitpayCard.bitpayCardTransactionHistoryCompleted[0]">
<label class="item status-label">
<label class="item status-label" ng-if="bitpayCard.bitpayCardTransactionHistoryPreAuth[0] || bitpayCard.bitpayCardTransactionHistoryConfirming[0]">
<div translate>
Completed
</div>

View file

@ -53,23 +53,17 @@
</ion-content>
<click-to-accept
ng-disabled="!wallet"
ng-click="buyConfirm()"
ng-if="!isCordova"
click-send-status="sendStatus"
has-wallet-chosen="wallet"
insufficient-funds="false"
no-matching-wallet="false">
is-disabled="!wallet">
Confirm purchase
</click-to-accept>
<slide-to-accept
ng-disabled="!wallet"
ng-if="isCordova"
slide-on-confirm="buyConfirm()"
slide-send-status="sendStatus"
has-wallet-chosen="wallet"
insufficient-funds="false"
no-matching-wallet="false">
is-disabled="!wallet">
Slide to buy
</slide-to-accept>
<slide-to-accept-success

View file

@ -17,7 +17,7 @@
<div class="amount-label">
<div class="amount">{{amountUnitStr}}</div>
<div class="alternative" ng-if="buyPrice">
<span ng-show="isFiat">{{buyRequestInfo.amount.amount}} {{buyRequestInfo.amount.currency}}</span>
<span ng-show="isFiat">{{buyRequestInfo.amount.amount}} {{buyRequestInfo.amount.currency}}</span>
@ ${{buyPrice.amount}} per BTC
</div>
</div>
@ -59,7 +59,7 @@
{{fee.type}} fee
</span>
<span class="item-note">
{{fee.amount.amount}} {{fee.amount.currency}}
{{fee.amount.amount}} {{fee.amount.currency}}
</span>
</div>
<div class="item">
@ -74,23 +74,17 @@
</ion-content>
<click-to-accept
ng-disabled="!selectedPaymentMethodId.value || !buyRequestInfo || !wallet"
ng-click="buyConfirm()"
ng-if="!isCordova && buyRequestInfo"
click-send-status="sendStatus"
has-wallet-chosen="wallet"
insufficient-funds="!selectedPaymentMethodId.value"
no-matching-wallet="!buyRequestInfo">
is-disabled="!selectedPaymentMethodId.value || !buyRequestInfo || !wallet">
Confirm purchase
</click-to-accept>
<slide-to-accept
ng-disabled="!selectedPaymentMethodId.value || !buyRequestInfo || !wallet"
ng-if="isCordova && buyRequestInfo"
slide-on-confirm="buyConfirm()"
slide-send-status="sendStatus"
has-wallet-chosen="wallet"
insufficient-funds="!selectedPaymentMethodId.value"
no-matching-wallet="!buyRequestInfo">
is-disabled="!selectedPaymentMethodId.value || !buyRequestInfo || !wallet">
Slide to buy
</slide-to-accept>
<slide-to-accept-success

View file

@ -17,8 +17,8 @@
<div class="amount-label">
<div class="amount">{{amountUnitStr}}</div>
<div class="alternative">
<span ng-show="!isFiat">{{buyInfo.subtotal}} {{buyInfo.currency}}</span>
<span ng-show="isFiat">{{buyInfo.qty}} BTC</span>
<span ng-show="!isFiat">{{buyInfo.subtotal}} {{buyInfo.currency}}</span>
<span ng-show="isFiat">{{buyInfo.qty}} BTC</span>
@ ${{buyInfo.price}} per BTC
</div>
</div>
@ -64,23 +64,17 @@
</ion-content>
<click-to-accept
ng-disabled="!buyInfo || !wallet"
ng-click="buyConfirm()"
ng-if="!isCordova && buyInfo"
click-send-status="sendStatus"
has-wallet-chosen="wallet"
insufficient-funds="false"
no-matching-wallet="!buyInfo">
is-disabled="!buyInfo || !wallet">
Confirm purchase
</click-to-accept>
<slide-to-accept
ng-disabled="!buyInfo || !wallet"
ng-if="isCordova && buyInfo"
slide-on-confirm="buyConfirm()"
slide-send-status="sendStatus"
has-wallet-chosen="wallet"
insufficient-funds="false"
no-matching-wallet="!buyInfo">
is-disabled="!buyInfo || !wallet">
Slide to buy
</slide-to-accept>
<slide-to-accept-success

View file

@ -1,4 +1,4 @@
<ion-view id="buy-and-sell">
<ion-view id="buy-and-sell" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>

View file

@ -1,4 +1,4 @@
<ion-view id="coinbase">
<ion-view id="coinbase" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>

View file

@ -7,24 +7,24 @@
</ion-nav-back-button>
</ion-nav-bar>
<ion-content ng-class="{'add-bottom-for-cta': !insufficientFunds && !noMatchingWallet}">
<ion-content class="add-bottom-for-cta">
<div class="list">
<div class="item head">
<div class="sending-label">
<img src="img/icon-tx-sent-outline.svg">
<span translate ng-if="!useSendMax">Sending</span>
<span translate ng-if="useSendMax">Sending maximum amount</span>
<span translate ng-if="!tx.sendMax">Sending</span>
<span translate ng-if="tx.sendMax">Sending maximum amount</span>
</div>
<div class="amount-label">
<div class="amount">{{displayAmount || '...'}} <span class="unit">{{displayUnit}}</span></div>
<div class="alternative">{{alternativeAmountStr || '...'}}</div>
<div class="amount">{{tx.amountValueStr || '...'}} <span class="unit">{{tx.amountUnitStr}}</span></div>
<div class="alternative">{{tx.alternativeAmountStr || '...'}}</div>
</div>
</div>
<div class="info">
<div class="item single-line" ng-if="paypro">
<div class="item single-line" ng-if="tx.paypro">
<span class="label" translate>Payment Expires:</span>
<span class="item-note" ng-if="!paymentExpired.value">{{remainingTimeStr.value}}</span>
<span class="item-note" ng-if="paymentExpired.value" ng-style="{'color': 'red'}" translate>Expired</span>
<span class="item-note" ng-if="!paymentExpired">{{remainingTimeStr}}</span>
<span class="item-note" ng-if="paymentExpired" ng-style="{'color': 'red'}" translate>Expired</span>
</div>
<div class="item">
@ -32,36 +32,36 @@
<span class="payment-proposal-to" ng-if="!recipientType">
<i class="icon icon-svg abs-v-center icon-bitcoinlogoplain"></i>
<div copy-to-clipboard="toAddress" ng-if="!paypro" class="ellipsis">
<contact ng-if="!toName" address="{{toAddress}}"></contact>
<span class="m15l size-14" ng-if="toName">{{toName}}</span>
<div copy-to-clipboard="tx.toAddress" ng-if="!tx.paypro" class="ellipsis">
<contact ng-if="!tx.toName" address="{{tx.toAddress}}"></contact>
<span class="m15l size-14" ng-if="tx.toName">{{tx.toName}}</span>
</div>
<div ng-if="paypro" ng-click="openPPModal(paypro)" class="m15l size-14 w100p pointer">
<i ng-show="paypro.verified && paypro.caTrusted" class="ion-locked" style="color:green"></i>
<i ng-show="!paypro.caTrusted" class="ion-unlocked" style="color:red"></i>
<span class="ellipsis" ng-show="!toName">{{paypro.domain || paypro.toAddress}}</span>
<span ng-show="toName">{{toName}}</span>
<div ng-if="tx.paypro" ng-click="openPPModal(tx.paypro)" class="m15l size-14 w100p pointer">
<i ng-show="tx.paypro.verified && tx.paypro.caTrusted" class="ion-locked" style="color:green"></i>
<i ng-show="!tx.paypro.caTrusted" class="ion-unlocked" style="color:red"></i>
<span class="ellipsis" ng-show="!tx.toName">{{tx.paypro.domain || tx.paypro.toAddress}}</span>
<span ng-show="tx.toName">{{tx.toName}}</span>
</div>
<!-- <contact ng-if="!tx.hasMultiplesOutputs" class="ellipsis" address="{{toAddress}}"></contact>
<!-- <contact ng-if="!tx.hasMultiplesOutputs" class="ellipsis" address="{{tx.toAddress}}"></contact>
<span ng-if="tx.hasMultiplesOutputs" translate>Multiple recipients</span> -->
</span>
<div class="wallet" ng-if="recipientType == 'wallet'">
<i class="icon big-icon-svg">
<img src="img/icon-wallet.svg" ng-class="{'wallet-background-color-default': !toColor}" ng-style="{'background-color': toColor}" class="bg"/>
</i>
<div copy-to-clipboard="toAddress" class="ellipsis">
<contact ng-if="!toName" address="{{toAddress}}"></contact>
<span ng-if="toName" class="wallet-name">{{toName}}</span>
<div copy-to-clipboard="tx.toAddress" class="ellipsis">
<contact ng-if="!tx.toName" address="{{tx.toAddress}}"></contact>
<span ng-if="tx.toName" class="wallet-name">{{tx.toName}}</span>
</div>
</div>
<div ng-if="recipientType == 'contact' && !isChromeApp" class="gravatar-contact toggle" ng-click="toggleAddress()">
<gravatar class="send-gravatar" name="{{toName}}" height="30" width="30" email="{{toEmail}}"></gravatar>
<span ng-if="toName && !showAddress">{{toName}}</span>
<span ng-if="toName && showAddress">{{toAddress}}</span>
<gravatar class="send-gravatar" name="{{tx.toName}}" height="30" width="30" email="{{toEmail}}"></gravatar>
<span ng-if="tx.toName && !showAddress">{{tx.toName}}</span>
<span ng-if="tx.toName && showAddress">{{tx.toAddress}}</span>
</div>
</div>
<a class="item item-icon-right" ng-hide="!useSendMax && (insufficientFunds || noMatchingWallet)" ng-click="showWalletSelector()">
<a class="item item-icon-right" ng-hide="!wallets" ng-click="showWalletSelector()">
<span class="label" translate>From</span>
<div class="wallet" ng-if="wallet">
<i class="icon big-icon-svg">
@ -77,46 +77,46 @@
</div>
<i class="icon bp-arrow-right"></i>
</a>
<div class="item item-icon-right" ng-if="!insufficientFunds && !noMatchingWallet" ng-click="chooseFeeLevel()">
<span class="label">{{'Fee:' | translate}} {{feeLevel | translate}}</span>
<span class="m10l">{{fee || '...'}}</span>
<div class="item item-icon-right" ng-if="wallet" ng-click="chooseFeeLevel(tx, wallet)">
<span class="label">{{'Fee:' | translate}} {{tx.feeLevelName | translate}}</span>
<span class="m10l">{{tx.txp[wallet.id].feeStr || '...'}}</span>
<span class="item-note m10l">
<span>{{feeFiat || '...'}}&nbsp;<span class="fee-rate" ng-if="feeRateStr" translate>- {{feeRateStr}} of the transaction</span></span>
<span>{{tx.txp[wallet.id].alternativeFeeStr || '...'}}&nbsp;
<span class="fee-rate" ng-if="tx.txp[wallet.id].feeRatePerStr"> &middot;
<i class="ion-alert-circled warn" ng-show="tx.txp[wallet.id].feeToHigh"></i> &nbsp;
<span class="fee-rate" ng-class="{'warn':tx.txp[wallet.id].feeToHigh}" translate> {{tx.txp[wallet.id].feeRatePerStr}} of the sending amount </span>
</span>
</span>
</span>
<i class="icon bp-arrow-right"></i>
</div>
<a class="item item-icon-right" ng-if="!insufficientFunds && !noMatchingWallet" ng-click="showDescriptionPopup()">
<a class="item item-icon-right" ng-if="wallet" ng-click="showDescriptionPopup(tx)">
<span class="label" translate>Add Memo</span>
<span class="item-note m10l">
{{description}}
{{tx.description}}
</span>
<i class="icon bp-arrow-right"></i>
</a>
<div class="text-center" ng-show="noMatchingWallet">
<span class="badge badge-energized" translate>No wallets available</span>
</div>
<div class="text-center" ng-show="insufficientFunds">
<span class="badge badge-energized" translate>Insufficient funds</span>
<div class="text-center" ng-show="noWalletMessage">
<span class="badge badge-energized">{{noWalletMessage}}</span>
</div>
</div>
</div>
</ion-content>
<click-to-accept
ng-click="approve(statusChangeHandler)"
ng-if="!isCordova"
ng-click="approve(tx, wallet, statusChangeHandler)"
ng-if="!isCordova || isWindowsPhoneApp"
click-send-status="sendStatus"
has-wallet-chosen="wallet"
insufficient-funds="insufficientFunds"
no-matching-wallet="noMatchingWallet">
is-disabled="!wallet">
{{buttonText}}
</click-to-accept>
<slide-to-accept
ng-if="isCordova && (wallet && !insufficientFunds && !noMatchingWallet)"
slide-on-confirm="onConfirm()"
ng-if="isCordova && !isWindowsPhoneApp && wallet"
slide-on-confirm="approve(tx, wallet, statusChangeHandler)"
slide-send-status="sendStatus"
has-wallet-chosen="wallet"
insufficient-funds="insufficientFunds"
no-matching-wallet="noMatchingWallet">
is-disabled="!wallet">
{{buttonText}}
</slide-to-accept>
<slide-to-accept-success
@ -125,14 +125,14 @@
slide-success-hide-on-confirm="true">
<span ng-show="wallet.m == 1 && (wallet.canSign() || wallet.isPrivKeyExternal())" translate>Payment Sent</span>
<span ng-show="wallet.m > 1 && (wallet.canSign() || wallet.isPrivKeyExternal())" translate>Proposal Created</span>
<span ng-show="!wallet.canSign() && !wallet.isPrivKeyExternal()" translate>Transaction created</span>
<span ng-show="!wallet.canSign() && !wallet.isPrivKeyExternal()" translate>Transaction Created</span>
</slide-to-accept-success>
<wallet-selector
wallet-selector-title="walletSelectorTitle"
wallet-selector-wallets="wallets"
wallet-selector-selected-wallet="wallet"
wallet-selector-show="showWallets"
wallet-selector-show="walletSelector"
wallet-selector-on-select="onWalletSelect">
</wallet-selector>

View file

@ -1,14 +1,13 @@
<ion-view id="copayers-invitation">
<ion-view id="copayers-invitation" hide-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>
<ion-nav-title>{{wallet.name}}</ion-nav-title>
<ion-nav-buttons side="secondary">
<button class="button-share ng-hide" ng-show="isCordova && secret" ng-click="shareSecret()">
<i class="icon"
<i class="icon"
ng-class="{
'ion-ios-upload-outline': shareIcon == 'iOS',
'ion-ios-upload-outline': shareIcon == 'iOS',
'ion-android-share-alt': shareIcon != 'iOS'
}"></i>
</button>
@ -34,7 +33,7 @@
</div>
</div>
<button ng-if="secret" class="button-cancel" ng-click="showDeletePopup()">
<span translate>Cancel invitation</span>
<span translate>Cancel invitation</span>
</button>
</div>
</div>

View file

@ -1,4 +1,4 @@
<ion-view id="export">
<ion-view id="export" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>{{'Export wallet' | translate}}</ion-nav-title>
<ion-nav-back-button>

View file

@ -1,4 +1,4 @@
<ion-view id="glidera">
<ion-view id="glidera" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>
@ -13,7 +13,7 @@
Refresh
</button>
</ion-nav-buttons>
<ion-content scroll="false" class="ng-hide" ng-show="!account.token">
<ion-content scroll="false" class="ng-hide" ng-show="!account.token">
<div class="integration-onboarding">
<div class="integration-onboarding-logo">
<img src="img/glidera-logo.png">

View file

@ -1,4 +1,4 @@
<ion-view>
<ion-view show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>

View file

@ -1,4 +1,4 @@
<ion-view id="import" class="settings">
<ion-view id="import" class="settings" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>{{'Import Wallet' | translate}}</ion-nav-title>
<ion-nav-back-button>

View file

@ -1,21 +1,16 @@
<div class="list card">
<div class="item item-icon-right item-heading">
<span translate>Cards</span>
<a ui-sref="tabs.bitpayCardIntro"><i class="icon ion-ios-plus-empty list-add-button"></i></a>
</div>
<div>
<a ng-repeat="card in bitpayCardItems track by $index"
ui-sref="tabs.bitpayCard({id:card.id})"
class="item item-sub item-icon-left item-big-icon-left item-icon-right">
<i class="icon big-icon-svg">
<div class="bg icon-bitpay-card"></div>
</i>
<span>BitPay Visa&reg; Card ({{card.lastFourDigits}})</span>
<p>{{card.balance ? card.currencySymbol + card.balance : 'Add funds to get started'|translate}} {{card.updatedOn ? (' &middot; ' + (card.updatedOn * 1000 | amTimeAgo)) : ''}}</p>
<i class="icon bp-arrow-right"></i>
</a>
</div>
<div class="item item-icon-right item-heading">
<span translate>Cards</span>
<a ui-sref="tabs.bitpayCardIntro"><i class="icon ion-ios-plus-empty list-add-button"></i></a>
</div>
<div>
<a ng-repeat="card in bitpayCardItems track by $index"
ui-sref="tabs.bitpayCard({id:card.id})"
class="item item-sub item-icon-left item-big-icon-left item-icon-right">
<i class="icon big-icon-svg">
<div class="bg icon-bitpay-card"></div>
</i>
<span>BitPay Visa&reg; Card ({{card.lastFourDigits}})</span>
<p>{{card.balance ? card.currencySymbol + card.balance : 'Add funds to get started'|translate}} {{card.updatedOn ? (' &middot; ' + (card.updatedOn * 1000 | amTimeAgo)) : ''}}</p>
<i class="icon bp-arrow-right"></i>
</a>
</div>

View file

@ -1,5 +1,4 @@
<div class="list card" ng-controller="buyAndSellCardController">
<div ng-controller="buyAndSellCardController">
<div class="item item-sub item-icon-right item-heading">
<span translate>Buy bitcoin</span>
<a ui-sref="tabs.buyandsell"><i class="icon ion-ios-plus-empty list-add-button"></i></a>

View file

@ -1,4 +1,4 @@
<button ng-disabled="!hasWalletChosen || insufficientFunds || noMatchingWallet" class="click-to-accept__button button button-standard button-primary" ng-class="{disable: sendStatus}">
<button ng-disabled="isDisabled" class="click-to-accept__button button button-standard button-primary" ng-class="{disable: sendStatus}">
<span ng-if="!sendStatus">
<ng-transclude></ng-transclude>
</span>

View file

@ -1,19 +1,18 @@
<div class="list card" ng-controller="homeIntegrationsController">
<div class="item item-icon-right item-heading" ng-click="toggle()" >
<span translate>Services</span>
<i class="icon bp-arrow-up" ng-show="!hide"></i>
<i class="icon bp-arrow-down" ng-show="hide"></i>
</div>
<div ng-show="!hide">
<div ng-repeat="service in services track by $index">
<a ui-sref="{{service.sref}}" class="item item-sub item-icon-left item-big-icon-left item-icon-right next-step">
<i class="icon big-icon-svg">
<div class="bg {{service.icon}}"></div>
</i>
<span>{{service.title || service.name}}</span>
<i class="icon bp-arrow-right"></i>
</a>
</div>
<div ng-controller="homeIntegrationsController">
<div class="item item-icon-right item-heading" ng-click="toggle()" >
<span translate>Services</span>
<i class="icon bp-arrow-up" ng-show="!hide"></i>
<i class="icon bp-arrow-down" ng-show="hide"></i>
</div>
<div ng-show="!hide">
<div ng-repeat="service in services track by $index">
<a ui-sref="{{service.sref}}" class="item item-sub item-icon-left item-big-icon-left item-icon-right next-step">
<i class="icon big-icon-svg">
<div class="bg {{service.icon}}"></div>
</i>
<span>{{service.title || service.name}}</span>
<i class="icon bp-arrow-right"></i>
</a>
</div>
</div>
</div>

View file

@ -1,19 +1,18 @@
<div class="list card" ng-controller="nextStepsController">
<div class="item item-icon-right item-heading" ng-click="toggle()" >
<span translate>Next steps</span>
<i class="icon bp-arrow-up" ng-show="!hide"></i>
<i class="icon bp-arrow-down" ng-show="hide"></i>
</div>
<div ng-show="!hide">
<div ng-repeat="service in services track by $index">
<a ui-sref="{{service.sref}}" class="item item-sub item-icon-left item-big-icon-left item-icon-right next-step">
<i class="icon big-icon-svg">
<div class="bg {{service.icon}}"></div>
</i>
<span>{{service.title || service.name}}</span>
<i class="icon bp-arrow-right"></i>
</a>
</div>
<div ng-controller="nextStepsController">
<div class="item item-icon-right item-heading" ng-click="toggle()" >
<span translate>Next steps</span>
<i class="icon bp-arrow-up" ng-show="!hide"></i>
<i class="icon bp-arrow-down" ng-show="hide"></i>
</div>
<div ng-show="!hide">
<div ng-repeat="service in services track by $index">
<a ui-sref="{{service.sref}}" class="item item-sub item-icon-left item-big-icon-left item-icon-right next-step">
<i class="icon big-icon-svg">
<div class="bg {{service.icon}}"></div>
</i>
<span>{{service.title || service.name}}</span>
<i class="icon bp-arrow-right"></i>
</a>
</div>
</div>
</div>

View file

@ -1,6 +1,6 @@
<div
class="slide-success__background"
ng-class="{'fill-screen': fillScreen}">
ng-class="{'fill-screen': fillScreen, 'slide-success__windows-background': isWindowsPhoneApp}">
</div>
<div ng-disabled="wallet" class="slide-success__content">

View file

@ -26,6 +26,11 @@
<i class="icon"><img src="img/icon-warning.png" width="20px"></i>
<span class="comment" translate>Low fees</span>
</div>
<div class="low-fees" ng-if="btx.lowAmount">
<i class="icon"><img src="img/icon-warning.png" width="20px"></i>
<span class="comment" translate>Amount too low to spend</span>
</div>
</div>
<div ng-show="btx.action == 'sent'" class="ellipsis">

View file

@ -1,4 +1,4 @@
<ion-view id="join" class="settings">
<ion-view id="join" class="settings" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>

View file

@ -1,4 +1,4 @@
<ion-view class="settings">
<ion-view class="settings" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>{{'Startup Lock' | translate}}</ion-nav-title>
<ion-nav-back-button>

View file

@ -1,13 +1,13 @@
<ion-modal-view id="settings-fee" class="settings" ng-controller="preferencesFeeController" ng-init="init()">
<ion-modal-view id="settings-fee" class="settings" ng-controller="preferencesFeeController" >
<ion-header-bar align-title="center" class="bar-royal">
<button class="button button-clear" ng-click="hideModal()">
Close
</button>
<div class="title">
{{'Bitcoin Network Fee Policy'|translate}}
</div>
<button class="button button-clear" ng-click="chooseNewFee()">
OK
</button>
</ion-header-bar>
<ion-content>
<ion-content ng-init="init(network)">
<div class="settings-explanation">
<div class="estimates">
<div>
@ -20,6 +20,7 @@
<span class="fee-rate" ng-if="feePerSatByte">{{feePerSatByte}} satoshis/byte</span>
<span ng-if="loadingFee">...</span>
</div>
<div ng-if="network!='livenet'">[{{network}}]</span>
</div>
</div>
<div class="fee-policies">
@ -27,8 +28,5 @@
{{level|translate}}
</ion-radio>
</div>
<div class="m20t">
<button class="button button-standard button-primary" ng-click="chooseNewFee()" translate>Save</button>
</div>
</ion-content>
</ion-modal-view>

View file

@ -9,51 +9,51 @@
<div class="list">
<div class="item head">
<div class="amount-label">
<div class="amount">{{displayAmount || '...'}} <span class="unit">{{displayUnit}}</span></div>
<div class="alternative">{{alternativeAmountStr || '...'}}</div>
<div class="amount">{{tx.amountValueStr || '...'}} <span class="unit">{{tx.amountUnitStr}}</span></div>
<div class="alternative">{{tx.alternativeAmountStr || '...'}}</div>
</div>
</div>
<div class="info">
<div class="item single-line" ng-if="paypro.domain">
<div class="item single-line" ng-if="tx.paypro.domain">
<span class="label">{{'Pay To'|translate}}</span>
<span class="item-note">
{{paypro.domain}}
{{tx.paypro.domain}}
</span>
</div>
<div class="item single-line" ng-if="paypro.toAddress">
<div class="item single-line" ng-if="tx.paypro.toAddress">
<span class="label">{{'Address'|translate}}</span>
<span class="item-note m10l ellipsis">
{{paypro.toAddress}}
{{tx.paypro.toAddress}}
</span>
</div>
<div class="item">
<span class="label">{{'Certified by'|translate}}</span>
<span class="item-note w100p">
<span ng-show="paypro.caTrusted">
<span ng-show="tx.paypro.caTrusted">
<i class="ion-locked" style="color:green"></i>
{{paypro.caName}} {{'(Trusted)' | translate}}</span>
{{tx.paypro.caName}} {{'(Trusted)' | translate}}</span>
</span>
<span ng-show="!paypro.caTrusted">
<span ng-show="paypro.selfSigned">
<span ng-show="!tx.paypro.caTrusted">
<span ng-show="tx.paypro.selfSigned">
<i class="ion-unlocked" style="color:red"></i> <span translate>Self-signed Certificate</span>
</span>
<span ng-show="!paypro.selfSigned">
<i class="ion-locked" style="color:yellow"></i>{{paypro.caName}}<br>
<span ng-show="!tx.paypro.selfSigned">
<i class="ion-locked" style="color:yellow"></i>{{tx.paypro.caName}}<br>
<span translate>WARNING: UNTRUSTED CERTIFICATE</span>
</span>
</span>
</span>
</div>
<div class="item" ng-if="paypro.memo">
<div class="item" ng-if="tx.paypro.memo">
<span class="label">{{'Memo'|translate}}</span>
<span class="item-note w100p">
{{paypro.memo}}
{{tx.paypro.memo}}
</span>
</div>
<div class="item single-line" ng-if="paypro.expires">
<div class="item single-line" ng-if="tx.paypro.expires">
<span class="label">{{'Expires'|translate}}</span>
<span class="item-note">
{{paypro.expires * 1000 | amTimeAgo }}
{{tx.paypro.expires * 1000 | amTimeAgo }}
</span>
</div>
</div>

View file

@ -1,6 +1,6 @@
<ion-modal-view id="pin" ng-controller="pinController">
<ion-header-bar align-title="center" class="bar-royal">
<button ng-if="action != 'check'" class="button button-back button-clear" ng-click="hideModal()">
<button ng-if="action != 'check'" class="button button-back button-clear" ng-click="hideModal()" translate>
Close
</button>
</ion-header-bar>

View file

@ -19,7 +19,7 @@
<span translate>Sending</span>
</div>
<div class="amount-label">
<div class="amount">{{displayAmount}} <span class="unit">{{displayUnit}}</span></div>
<div class="amount">{{tx.amountValueStr}} <span class="unit">{{tx.amountUnitStr}}</span></div>
<div class="alternative" ng-show="tx.alternativeAmountStr">{{tx.alternativeAmountStr}}</div>
</div>
@ -97,10 +97,11 @@
{{tx.message}}
</span>
</div>
<div class="item single-line">
<span class="label" translate>Fee</span>
<span class="item-note">
{{tx.feeStr}}
<div class="item">
<span class="label">{{'Fee:' | translate}} {{tx.feeLevelStr | translate}}</span>
<span class="m10l">{{tx.feeStr || '...'}}</span>
<span class="item-note m10l">
<span>{{tx.feeFiatStr || '...'}}&nbsp;<span class="fee-rate" ng-if="tx.feeRateStr" translate>- {{tx.feeRateStr}} of the transaction</span></span>
</span>
</div>
@ -169,13 +170,13 @@
<click-to-accept
ng-click="onConfirm(statusChangeHandler)"
ng-if="tx.pendingForUs && canSign && !paymentExpired && !isCordova"
ng-if="tx.pendingForUs && canSign && !paymentExpired && (!isCordova || isWindowsPhoneApp)"
click-send-status="sendStatus"
has-wallet-chosen="true">
{{buttonText}}
</click-to-accept>
<slide-to-accept
ng-if="tx.pendingForUs && canSign && !paymentExpired && isCordova"
ng-if="tx.pendingForUs && canSign && !paymentExpired && isCordova && !isWindowsPhoneApp"
slide-on-confirm="onConfirm()"
slide-send-status="sendStatus"
has-wallet-chosen="true">
@ -184,6 +185,6 @@
<slide-to-accept-success
slide-success-show="sendStatus === 'success'"
slide-success-on-confirm="onSuccessConfirm()">
{{'Payment Sent' | translate}}
{{successText}}
</slide-to-accept-success>
</ion-modal-view>

View file

@ -1,48 +0,0 @@
<div
class="topbar-container"
ng-include="'views/includes/topbar.html'"
ng-init="titleSection='Choose wallet'; closeToHome = true">
</div>
<div class="content p20v row payment-uri" ng-controller="paymentUriController as payment">
<div class="large-12 columns" ng-init="payment.init()">
<div class="panel text-center" ng-if="!payment.uri">
<h1 translate>Bitcoin URI is NOT valid!</h1>
</div>
<div ng-if="payment.uri" ng-init="payment.getWallets(payment.uri.network)">
<h1 translate>Make a payment to</h1>
<div class="panel size-14">
<div class="ellipsis"><b translate>Address</b>: {{payment.uri.address.toString()}}</div>
<div ng-show="payment.uri.amount"><b translate>Amount</b>: {{payment.uri.amount}}</div>
<div ng-show="payment.uri.message"><b translate>Message</b>: {{payment.uri.message}}</div>
<div ng-show="payment.uri.network == 'testnet'"><b translate>Network</b>: {{payment.uri.network}}</div>
</div>
<div ng-if="!wallets || !wallets.length">
<div class="box-notification">
<span class="text-warning">
<b translate>There are no wallets to make this payment</b>
<span ng-show="payment.uri.network == 'testnet'">[testnet]</span>
</span>
</div>
</div>
<div ng-if="wallets.length">
<h2 translate>Select a wallet</h2>
<ul class="no-bullet">
<li class="panel" ng-repeat="w in wallets">
<a ng-click="payment.selectWallet(w.id)">
<div class="avatar-wallet"
ng-class="{'wallet-background-color-default': !w.color}" ng-style="{'background-color':w.color}">
<i class="icon-wallet size-21"></i>
</div>
<div class="ellipsis">{{w.name || w.id}}</div>
<div class="size-12">{{w.m}} of {{w.n}}
<span ng-show="w.network=='testnet'">[Testnet]</span>
</div>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>

View file

@ -1,4 +1,4 @@
<ion-view class="settings">
<ion-view class="settings" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>
{{'Wallet Settings'|translate}}
@ -18,7 +18,7 @@
</span>
<i class="icon bp-arrow-right"></i>
</a>
<a class="item item-icon-right" ui-sref="tabs.preferences.preferencesColor">
<a ng-if="!isWindowsPhoneApp" class="item item-icon-right" ui-sref="tabs.preferences.preferencesColor">
<span translate>Color</span>
<span class="item-note">
<span class="settings-color-block"

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