Merge sprint 19.

# Conflicts:
#	src/js/controllers/tab-home.js
This commit is contained in:
Brendon Duncan 2018-07-13 21:19:28 +12:00
commit 0bb4de9bd1
49 changed files with 4115 additions and 3815 deletions

View file

@ -136,6 +136,7 @@ module.exports = function(grunt) {
},
angular: {
src: [
'src/shim/shim.js',
'bower_components/qrcode-generator/js/qrcode.js',
'bower_components/qrcode-generator/js/qrcode_UTF8.js',
'bower_components/moment/min/moment-with-locales.js',
@ -164,6 +165,7 @@ module.exports = function(grunt) {
src: [
'src/js/app.js',
'src/js/routes.js',
'src/js/decorators/*.js',
'src/js/directives/*.js',
'!src/js/directives/*.spec.js',

View file

@ -24,9 +24,9 @@
"windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c",
"pushSenderId": "1036948132229",
"description": "A Secure Bitcoin Wallet",
"version": "4.12.0",
"fullVersion": "4.12-rc1",
"androidVersion": "412000",
"version": "4.12.2",
"fullVersion": "4.12-rc3",
"androidVersion": "412200",
"_extraCSS": "",
"_enabledExtensions": {
"coinbase": false,

View file

@ -72,9 +72,9 @@
<plugin name="cordova-plugin-queries-schemes" spec="~0.1.5" />
<plugin name="cordova-plugin-firebase" spec="https://github.com/arnesson/cordova-plugin-firebase.git" />
<plugin name="cordova-plugin-wkwebview-inputfocusfix" spec="https://github.com/onderceylan/cordova-plugin-wkwebview-inputfocusfix.git" />
<plugin name="cordova-plugin-nativeaudio" spec="^3.0.9" />
<!-- Changes in error descriptions may break the use of cordova-plugin-secure-storage -->
<plugin name="cordova-plugin-secure-storage" spec="2.6.8" />
<plugin name="cordova-plugin-media" spec="~5.0.2">
<variable name="KEEP_AVAUDIOSESSION_ALWAYS_ACTIVE" value="NO" />
</plugin>
<!-- Supported Platforms -->
<engine name="ios" spec="~4.5.3" />
<engine name="android" spec="~6.3.0" />

View file

@ -1,23 +1,23 @@
Secure bitcoin on your own terms with an open source, multisignature wallet from BitPay.
Copay users can hold funds individually or share finances securely with other users with multisignature wallets, which prevent unauthorized payments by requiring multiple approvals. Here are some ways Copay can be used with others:
To save for vacations or joint purchases with friends
To track family spending and allowances
To manage business, club, or organization funds and expenses
We built the following features into this version of Copay for a bitcoin wallet that doesn't compromise on security or accessibility:
Multiple wallet creation and management in-app
Intuitive multisignature security for personal or shared wallets
Easy spending proposal flow for shared wallets and group payments
Hierarchical deterministic (HD) address generation and wallet backups
Device-based security: all private keys are stored locally, not in the cloud
Support for Bitcoin testnet wallets
Synchronous access across all major mobile and desktop platforms
Payment protocol (BIP70-BIP73) support: easily-identifiable payment requests and verifiably secure bitcoin payments
Support for 150+ currency pricing options and unit denomination in BTC or bits
Email notifications for payments and transfers
Customizable wallet naming and background colors
9 supported languages (EN, CS, FR, DE, IT, ES, JA, PL, RU)
Copay is free and open source software run on non-proprietary servers, so there's no need to rely on any company for continuous support. Anyone can review or contribute to Copay's source code on GitHub (https://github.com/bitpay/copay).
Secure bitcoin on your own terms with an open source, multisignature wallet from BitPay.
Copay users can hold funds individually or share finances securely with other users with multisignature wallets, which prevent unauthorized payments by requiring multiple approvals. Here are some ways Copay can be used with others:
To save for vacations or joint purchases with friends
To track family spending and allowances
To manage business, club, or organization funds and expenses
We built the following features into this version of Copay for a bitcoin wallet that doesn't compromise on security or accessibility:
Multiple wallet creation and management in-app
Intuitive multisignature security for personal or shared wallets
Easy spending proposal flow for shared wallets and group payments
Hierarchical deterministic (HD) address generation and wallet backups
Device-based security: all private keys are stored locally, not in the cloud
Support for Bitcoin testnet wallets
Synchronous access across all major mobile and desktop platforms
Payment protocol (BIP70-BIP73) support: easily-identifiable payment requests and verifiably secure bitcoin payments
Support for 150+ currency pricing options and unit denomination in BTC or bits
Email notifications for payments and transfers
Customizable wallet naming and background colors
9 supported languages (EN, CS, FR, DE, IT, ES, JA, PL, RU)
Copay is free and open source software run on non-proprietary servers, so there's no need to rely on any company for continuous support. Anyone can review or contribute to Copay's source code on GitHub (https://github.com/bitpay/copay).

View file

@ -1 +1 @@

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: Catalan\n"
"Language: ca\n"
"PO-Revision-Date: 2018-06-22T04:02:43+0000\n"
"PO-Revision-Date: 2018-07-04 09:26\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "Compte"
msgid "Account Number"
msgstr "Número de compte"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr "Transaccions instantànies amb comissions baixes"
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "Comptes"

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: Czech\n"
"Language: cs\n"
"PO-Revision-Date: 2018-06-22T04:02:46+0000\n"
"PO-Revision-Date: 2018-07-04 09:26\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "Účet"
msgid "Account Number"
msgstr "Číslo účtu"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr "Okamžité transakce s nízkou platbou"
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "Účty"

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: German\n"
"Language: de\n"
"PO-Revision-Date: 2018-06-22T04:02:49+0000\n"
"PO-Revision-Date: 2018-07-04 03:57\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "Benutzerkonto"
msgid "Account Number"
msgstr "Kontonummer"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr ""
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "Konten"

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: Spanish\n"
"Language: es\n"
"PO-Revision-Date: 2018-06-22T04:02:57+0000\n"
"PO-Revision-Date: 2018-07-04 09:27\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "Cuenta"
msgid "Account Number"
msgstr "Número de cuenta"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr "Transacciones instantáneas con comisiones bajas"
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "Cuentas"

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: Persian\n"
"Language: fa\n"
"PO-Revision-Date: 2018-06-22T04:02:53+0000\n"
"PO-Revision-Date: 2018-07-04 09:27\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "حساب"
msgid "Account Number"
msgstr "شماره حساب"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr "معاملات فوری با پرداخت کم"
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "حساب ها"

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: French\n"
"Language: fr\n"
"PO-Revision-Date: 2018-06-22T04:02:48+0000\n"
"PO-Revision-Date: 2018-07-04 09:26\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "Compte"
msgid "Account Number"
msgstr "Numéro de compte"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr "Transactions instantanées à bas frais"
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "Comptes"

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: Italian\n"
"Language: it\n"
"PO-Revision-Date: 2018-06-22T04:02:50+0000\n"
"PO-Revision-Date: 2018-07-04 09:27\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "Conto"
msgid "Account Number"
msgstr "Numero del Conto"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr "Transazioni istantanee con commissioni basse"
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "Account"

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: Japanese\n"
"Language: ja\n"
"PO-Revision-Date: 2018-06-22T04:02:51+0000\n"
"PO-Revision-Date: 2018-07-04 09:27\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "ポケット"
msgid "Account Number"
msgstr "ポケット番号"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr "僅かな手数料で即時決済"
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "アカウント一覧"
@ -631,7 +635,7 @@ msgstr "翻訳に協力"
#: src/js/controllers/confirm.js:130
msgid "Copay only supports Bitcoin Cash using new version numbers addresses"
msgstr "Copay のビットコインキャッシュはビットコインと完全に異なる別通貨なので、アドレスの頭文字が異なります。"
msgstr "のビットコインキャッシュはビットコインと完全に異なる別通貨なので、アドレスの頭文字が異なります。"
#: src/js/services/bwcError.js:62
msgid "Copayer already in this wallet"
@ -2225,7 +2229,7 @@ msgstr "正しい順序で各単語をタップしてください。"
#: src/js/services/bwcError.js:101
msgid "Please upgrade Copay to perform this action"
msgstr "この操作を実行するにはCopayを最新バージョンに更新してください"
msgstr "この操作を実行するにはを最新バージョンに更新してください"
#: www/views/walletDetails.html:142
#: www/views/walletDetails.html:62

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: Korean\n"
"Language: ko\n"
"PO-Revision-Date: 2018-06-22T04:02:52+0000\n"
"PO-Revision-Date: 2018-07-04 09:27\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "계정"
msgid "Account Number"
msgstr "계정 번호"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr "낮은 수수료로 빠른 송금을"
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "계정들"

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: Dutch\n"
"Language: nl\n"
"PO-Revision-Date: 2018-06-22T04:02:48+0000\n"
"PO-Revision-Date: 2018-07-04 09:26\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "Account"
msgid "Account Number"
msgstr "Account Nummer"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr "Directe transacties tegen lage kosten"
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "Accounts"

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: Polish\n"
"Language: pl\n"
"PO-Revision-Date: 2018-06-22T04:02:54+0000\n"
"PO-Revision-Date: 2018-07-04 03:58\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "Konto"
msgid "Account Number"
msgstr "Numer konta"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr ""
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "Konta"

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: Portuguese, Brazilian\n"
"Language: pt\n"
"PO-Revision-Date: 2018-06-22T04:02:55+0000\n"
"PO-Revision-Date: 2018-07-04 09:27\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "Conta"
msgid "Account Number"
msgstr "Número de conta"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr "Transações instantâneas com taxas baixas"
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "Contas"

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: Russian\n"
"Language: ru\n"
"PO-Revision-Date: 2018-06-22T04:02:56+0000\n"
"PO-Revision-Date: 2018-07-04 09:27\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "Учётная запись"
msgid "Account Number"
msgstr "Номер учётной записи"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr "Мгновенные транзакции с низкой оплатой"
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "Аккаунты"

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: Swedish\n"
"Language: sv\n"
"PO-Revision-Date: 2018-06-22T04:02:58+0000\n"
"PO-Revision-Date: 2018-07-04 03:58\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "Konto"
msgid "Account Number"
msgstr "Kontonummer"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr ""
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "Konton"
@ -369,7 +373,7 @@ msgstr ""
#: www/views/tab-home.html:98
#: www/views/tab-settings.html:115
msgid "Bitcoin Cash Wallets"
msgstr ""
msgstr "Bitcoin Cash plånböcker"
#: www/views/modals/chooseFeeLevel.html:4
#: www/views/preferencesFee.html:4

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: Vietnamese\n"
"Language: vi\n"
"PO-Revision-Date: 2018-06-22T04:02:59+0000\n"
"PO-Revision-Date: 2018-07-04 03:58\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -54,67 +54,71 @@ msgstr "A total of {{amountAboveMaxSizeStr}} were excluded. The maximum size all
#: src/js/controllers/confirm.js:395
msgid "A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided."
msgstr ""
msgstr "A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided."
#: src/js/controllers/preferencesAbout.js:6
#: www/views/tab-settings.html:156
msgid "About"
msgstr ""
msgstr "About"
#: src/js/controllers/modals/txpDetails.js:62
#: src/js/controllers/tx-details.js:79
msgid "Accepted"
msgstr ""
msgstr "Accepted"
#: www/views/preferencesInformation.html:72
msgid "Account"
msgstr ""
msgstr "Account"
#: www/views/join.html:72
#: www/views/tab-create-personal.html:45
#: www/views/tab-create-shared.html:74
#: www/views/tab-import-hardware.html:19
msgid "Account Number"
msgstr "Account Number"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr ""
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr ""
msgstr "Accounts"
#: www/views/bitpayCard.html:56
msgid "Activity"
msgstr ""
msgstr "Activity"
#: src/js/services/bitpayAccountService.js:83
msgid "Add Account"
msgstr ""
msgstr "Add Account"
#: src/js/services/bitpayAccountService.js:69
msgid "Add BitPay Account?"
msgstr ""
msgstr "Add BitPay Account?"
#: www/views/addressbook.add.html:4
#: www/views/addressbook.html:22
msgid "Add Contact"
msgstr ""
msgstr "Add Contact"
#: www/views/bitpayCard.html:28
msgid "Add Funds"
msgstr ""
msgstr "Add Funds"
#: www/views/confirm.html:94
msgid "Add Memo"
msgstr ""
msgstr "Add Memo"
#: www/views/join.html:87
#: www/views/tab-create-personal.html:59
#: www/views/tab-create-shared.html:88
msgid "Add a password"
msgstr ""
msgstr "Add a password"
#: www/views/includes/accountSelector.html:27
msgid "Add account"
msgstr ""
msgstr "Add account"
#: www/views/join.html:90
#: www/views/tab-create-personal.html:62

View file

@ -11,7 +11,7 @@ msgstr ""
"Last-Translator: emilold\n"
"Language-Team: Chinese Simplified\n"
"Language: zh\n"
"PO-Revision-Date: 2018-06-22T04:02:44+0000\n"
"PO-Revision-Date: 2018-07-04 03:57\n"
#: www/views/modals/paypro.html:34
msgid "(Trusted)"
@ -77,6 +77,10 @@ msgstr "帐户"
msgid "Account Number"
msgstr "帐号"
#: www/views/tab-home.html:61
msgid "Instant transactions with low fees"
msgstr ""
#: www/views/preferencesBitpayServices.html:23
msgid "Accounts"
msgstr "帐户"

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, walletService, platformInfo, lodash, configService, $stateParams, $window, $state, $log, profileService, bitcore, bitcoreCash, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService, feeService, bwcError, txConfirmNotification, externalLinkService, firebaseEventsService) {
angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, walletService, platformInfo, lodash, configService, $stateParams, $window, $state, $log, profileService, bitcore, bitcoreCash, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService, feeService, bwcError, txConfirmNotification, externalLinkService, firebaseEventsService, soundService) {
var countDown = null;
var FEE_TOO_HIGH_LIMIT_PER = 15;
@ -287,7 +287,10 @@ angular.module('copayApp.controllers').controller('confirmController', function(
tx.amountValueStr = tx.amountStr.split(' ')[0];
tx.amountUnitStr = tx.amountStr.split(' ')[1];
txFormatService.formatAlternativeStr(wallet.coin, tx.toAmount, function(v) {
var parts = v.split(' ');
tx.alternativeAmountStr = v;
tx.alternativeAmountValueStr = parts[0];
tx.alternativeAmountUnitStr = (parts.length > 0) ? parts[1] : '';
});
}
@ -426,6 +429,8 @@ angular.module('copayApp.controllers').controller('confirmController', function(
function showSendMaxWarning(wallet, sendMaxInfo) {
var feeAlternative = '',
msg = '';
function verifyExcludedUtxos() {
var warningMsg = [];
@ -443,9 +448,18 @@ angular.module('copayApp.controllers').controller('confirmController', function(
return warningMsg.join('\n');
};
var msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees.", {
fee: txFormatService.formatAmountStr(wallet.coin, sendMaxInfo.fee)
});
feeAlternative = txFormatService.formatAlternativeStr(wallet.coin, sendMaxInfo.fee);
if (feeAlternative) {
msg = gettextCatalog.getString("{{feeAlternative}} will be deducted for bitcoin networking fees ({{fee}}).", {
fee: txFormatService.formatAmountStr(wallet.coin, sendMaxInfo.fee),
feeAlternative: feeAlternative
});
} else {
msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees).", {
fee: txFormatService.formatAmountStr(wallet.coin, sendMaxInfo.fee)
});
}
var warningMsg = verifyExcludedUtxos();
if (!lodash.isEmpty(warningMsg))
@ -624,10 +638,11 @@ angular.module('copayApp.controllers').controller('confirmController', function(
(processName == 'sendingTx' && !$scope.wallet.canSign() && !$scope.wallet.isPrivKeyExternal())
) && !isOn) {
$scope.sendStatus = 'success';
if (config.soundsEnabled && $scope.wallet.coin == 'bch') {
var audio = new Audio('misc/bch_sent.mp3');
audio.play();
if ($state.current.name === "tabs.send.confirm") { // XX SP: Otherwise all open wallets on other devices play this sound if you have been in a send flow before on that device.
soundService.play('misc/payment_sent.mp3');
}
firebaseEventsService.logEvent('sent_bitcoin', { coin: $scope.wallet.coin });
$timeout(function() {
$scope.$digest();

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('tabHomeController',
function($rootScope, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, storageService, txpModalService, appConfigService, startupService, addressbookService, bwcError, nextStepsService, buyAndSellService, homeIntegrationsService, bitpayCardService, pushNotificationsService, timeService, bitcoincomService, pricechartService, firebaseEventsService, servicesService, shapeshiftService, $ionicNavBarDelegate, signVerifyMessageService) {
function($rootScope, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, bannerService, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, storageService, txpModalService, appConfigService, startupService, addressbookService, feedbackService, bwcError, nextStepsService, buyAndSellService, homeIntegrationsService, bitpayCardService, pushNotificationsService, timeService, bitcoincomService, pricechartService, firebaseEventsService, servicesService, shapeshiftService, $ionicNavBarDelegate, signVerifyMessageService) {
var wallet;
var listeners = [];
var notifications = [];
@ -15,9 +15,19 @@ angular.module('copayApp.controllers').controller('tabHomeController',
$scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
$scope.isNW = platformInfo.isNW;
$scope.showServices = false;
$scope.bannerIsLoading = true;
$scope.bannerImageUrl = '';
$scope.bannerUrl = '';
$scope.$on("$ionicView.afterEnter", function() {
startupService.ready();
bannerService.getBanner(function (banner) {
$scope.bannerImageUrl = banner.imageURL;
$scope.bannerUrl = banner.url;
$scope.bannerIsLoading = false;
});
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
@ -117,8 +127,8 @@ angular.module('copayApp.controllers').controller('tabHomeController',
externalLinkService.open(url, optIn, title, message, okText, cancelText);
};
$scope.openStore = function() {
externalLinkService.open('https://store.bitcoin.com/', false);
$scope.openBannerUrl = function() {
externalLinkService.open($scope.bannerUrl, false);
};
$scope.openNotificationModal = function(n) {

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('tabReceiveController', function($rootScope, $scope, $timeout, $log, $ionicModal, $state, $ionicHistory, $ionicPopover, storageService, platformInfo, walletService, profileService, configService, lodash, gettextCatalog, popupService, bwcError, bitcoinCashJsService, $ionicNavBarDelegate, txFormatService) {
angular.module('copayApp.controllers').controller('tabReceiveController', function($rootScope, $scope, $timeout, $log, $ionicModal, $state, $ionicHistory, $ionicPopover, storageService, platformInfo, walletService, profileService, configService, lodash, gettextCatalog, popupService, bwcError, bitcoinCashJsService, $ionicNavBarDelegate, txFormatService, soundService, clipboardService) {
var listeners = [];
$scope.bchAddressType = { type: 'cashaddr' };
@ -15,22 +15,6 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
var config;
var soundLoaded = false;
var nativeAudioAvailable = (window.plugins && window.plugins.NativeAudio);
if (nativeAudioAvailable) {
window.plugins.NativeAudio.preloadSimple('received', 'misc/coin_received.mp3', function (msg) {
$log.debug('Receive sound loaded.');
soundLoaded = true;
}, function (error) {
$log.debug('Error loading receive sound.');
$log.debug(error);
});
} else {
$log.debug('isNW: Using HTML5-Audio instead of native audio');
soundLoaded = true;
}
$scope.displayBalanceAsFiat = true;
$scope.requestSpecificAmount = function() {
@ -74,6 +58,12 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
paymentSubscriptionObj.addr = $scope.addr
}
try {
clipboardService.copyToClipboard($scope.wallet.coin == 'bch' && $scope.bchAddressType.type == 'cashaddr' ? 'bitcoincash:' + $scope.addr : $scope.addr);
} catch (error) {
$log.debug("Error copying to clipboard:");
$log.debug(error);
}
// create subscription
var msg = JSON.stringify(paymentSubscriptionObj);
currentAddressSocket.onopen = function (event) {
@ -143,21 +133,21 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
for (var i = 0; i < data.x.out.length; i++) {
if (data.x.out[i].addr == watchAddress) {
$scope.paymentReceivedAmount = txFormatService.formatAmount(data.x.out[i].value, 'full');
$scope.paymentReceivedAlternativeAmount = ''; // For when a subsequent payment is received.
txFormatService.formatAlternativeStr($scope.wallet.coin, data.x.out[i].value, function(alternativeStr){
if (alternativeStr) {
$scope.paymentReceivedAlternativeAmount = alternativeStr;
}
});
}
}
$scope.paymentReceivedCoin = $scope.wallet.coin;
$scope.$apply(function () {
if (config.soundsEnabled && soundLoaded) {
$log.debug('Play sound.');
if (nativeAudioAvailable) {
window.plugins.NativeAudio.play('received');
} else {
new Audio('misc/coin_received.ogg').play(); // NW.js has no mp3 support
}
} else {
$log.debug('Sound is disabled.');
}
if ($state.current.name === "tabs.receive") {
soundService.play('misc/payment_received.mp3');
}
$scope.$apply(function () {
$scope.showingPaymentReceived = true;
});
}

View file

@ -122,8 +122,11 @@ angular.module('copayApp.controllers').controller('tabScanController', function(
scannerService.openSettings();
};
$scope.reactivationCount = 0;
$scope.attemptToReactivate = function(){
scannerService.reinitialize();
scannerService.reinitialize(function(){
$scope.reactivationCount++;
});
};
$scope.toggleLight = function(){

View file

@ -76,8 +76,11 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
var walletList = [];
lodash.each(walletsToTransfer, function(v) {
var displayBalanceAsFiat =
v.status.alternativeBalanceAvailable &&
config.wallet.settings.priceDisplay === 'fiat';
// BD got v.status as undefined here once during development, just
// after creating a new wallet.
v.status &&
v.status.alternativeBalanceAvailable &&
config.wallet.settings.priceDisplay === 'fiat';
walletList.push({
color: v.color,

View file

@ -0,0 +1,15 @@
angular.module('copayApp')
.config(['$provide', '$logProvider', function($provide, $logProvider) {
// expose a provider to reach debugEnabled in $log
$provide.value('$logProvider', $logProvider);
}])
.decorator('$log', ['$logProvider', '$delegate', function($logProvider, $delegate) {
// override $log.debug to display in Chrome
$delegate.debug = function () {
if ($logProvider.debugEnabled()) {
$delegate.info.apply($delegate, arguments);
}
};
return $delegate;
}]);

View file

@ -1,38 +1,26 @@
'use strict';
angular.module('copayApp.directives')
.directive('copyToClipboard', function(platformInfo, nodeWebkitService, gettextCatalog, ionicToast, clipboard) {
.directive('copyToClipboard', function(clipboardService, ionicToast, gettextCatalog) {
return {
restrict: 'A',
scope: {
copyToClipboard: '=copyToClipboard'
},
link: function(scope, elem, attrs, ctrl) {
var isCordova = platformInfo.isCordova;
var isChromeApp = platformInfo.isChromeApp;
var isNW = platformInfo.isNW;
elem.bind('mouseover', function() {
elem.css('cursor', 'pointer');
});
var msg = gettextCatalog.getString('Copied to clipboard');
elem.bind('click', function() {
var data = scope.copyToClipboard;
if (!data) return;
clipboardService.copyToClipboard(data);
if (isCordova) {
cordova.plugins.clipboard.copy(data);
} else if (isNW) {
nodeWebkitService.writeToClipboard(data);
} else if (clipboard.supported) {
clipboard.copyText(data);
} else {
// No supported
return;
}
scope.$apply(function() {
var msg = gettextCatalog.getString('Copied to clipboard');
scope.$apply(function () {
ionicToast.show(msg, 'bottom', false, 1000);
});
});
}
}

View file

@ -0,0 +1,78 @@
'use strict';
angular.module('copayApp.services').factory('bannerService', function ($http, $log) {
// Export
var root = {};
// Constant
var API_URL = 'https://bwscash.bitcoin.com/bws/api/v1/marketing';
// Variable
var hasFetched = false;
var banners = [];
var defaultBanner = {
id: 'default-banner',
imageURL: 'img/banner-store.png',
url: 'https://store.bitcoin.com/',
isLocal: true
};
// Private methods
var fetchSettings = function (cb) {
var req = {
method: 'GET',
url: API_URL+'/settings',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
};
$http(req).then(function (response) {
$log.info('Get banner settings: SUCCESS');
banners = response.data;
return cb(true);
}, function (error) {
$log.error('Get banner settings: ERROR ' + error.statusText);
return cb(false);
});
};
root.getBanner = function (cb) {
// If not fetch get the banner
if (!hasFetched) {
hasFetched = true;
// If never fetch, lets fetch
fetchSettings(function (isSuccess) {
root.getBanner(cb);
});
// If fetch, and got banners, lets have a look
} else if (banners.length > 0) {
var selectedBanners = [];
for(var i in banners) {
var banner = banners[i];
// Generate the URL for the banner
var fileName = banner.image.substring(0, banner.image.lastIndexOf('.'));
var extension = banner.image.substring(banner.image.lastIndexOf('.')+1);
banner.imageURL = API_URL +'/banners/'+fileName+"/"+extension;
// Add the banner
selectedBanners.push(banners[i]);
}
// If no banner activated, return the default one
if (selectedBanners.length == 0) {
return cb(defaultBanner);
} else {
return cb(selectedBanners[Math.floor(Math.random()*banners.length)]);
}
} else {
return cb(defaultBanner);
}
};
return root;
});

View file

@ -0,0 +1,24 @@
'use strict';
angular.module('copayApp.services').factory('clipboardService', function ($http, $log, platformInfo, nodeWebkitService, gettextCatalog, ionicToast, clipboard) {
var root = {};
root.copyToClipboard = function (data) {
if (!data) return;
$log.debug("Copy '"+data+"' to clipboard");
if (platformInfo.isCordova) {
cordova.plugins.clipboard.copy(data);
} else if (platformInfo.isNW) {
nodeWebkitService.writeToClipboard(data);
} else if (clipboard.supported) {
clipboard.copyText(data);
} else {
// No supported
return;
}
};
return root;
});

View file

@ -103,6 +103,7 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
_completeInitialization(status, callback);
});
} else {
isAvailable = true; // XX SP: Availability can change after permissions are granted after being denied.
_completeInitialization(status, callback);
}
});

View file

@ -0,0 +1,44 @@
'use strict';
angular.module('copayApp.services').service('soundService', function($log, $timeout, platformInfo, configService) {
var root = {};
/**
* Play a sound (when enabled in the configuration) using the Cordova Media-plugin (on Mobile) or html5-audio (on Desktop) relative to the www-root
* Make sure there is a .ogg file as well for NW.js (desktop) implementation
* @param soundFile
*/
root.play = function(soundFile) {
configService.whenAvailable(function(config) {
if (config.soundsEnabled) {
if (platformInfo.isCordova) {
if (platformInfo.isAndroid) {
var p = window.location.pathname;
var device_path = p.substring(0, p.lastIndexOf('/'));
soundFile = device_path + '/' + soundFile;
}
var audio = new Media(soundFile,
function () {
$log.debug("playAudio(bch_sent):Audio Success");
},
function (err) {
$log.debug("playAudio():Audio Error: " + err);
}
);
audio.play({playAudioWhenScreenIsLocked: false}); // XX SP: "Locked" is also the mute switch in iOS
} else {
if (platformInfo.isNW) {
soundFile = soundFile.substring(0, soundFile.lastIndexOf('.')) + ".ogg";
$log.debug("Playing .ogg file ("+soundFile+"), as NW.js has no mp3 support");
}
new Audio(soundFile).play();
}
}
});
};
return root;
});

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.services')
.factory('storageService', function(appConfigService, logHeader, fileStorageService, localStorageService, sjcl, $log, lodash, platformInfo, secureStorageService, $timeout) {
.factory('storageService', function(appConfigService, logHeader, fileStorageService, localStorageService, sjcl, $log, lodash, platformInfo, $timeout) {
var root = {};
var storage;
@ -121,11 +121,7 @@ angular.module('copayApp.services')
root.storeProfile = function(profile, cb) {
var profileString = profile.toObj();
if (platformInfo.isNW) {
storage.set('profile', profileString, cb);
} else {
secureStorageService.set('profile', profileString, cb);
}
storage.set('profile', profileString, cb);
};
/**
@ -205,48 +201,19 @@ angular.module('copayApp.services')
* @param {getProfileCallback} cb
*/
root.getProfile = function(cb) {
if (platformInfo.isNW) {
storage.get('profile', function(getErr, getStr) {
_onOldProfileRetrieved(getErr, getStr, cb);
});
return
}
secureStorageService.get('profile', function(secureErr, secureStr) {
var secureProfile;
var oldProfile;
if (secureErr) {
return cb(secureErr, null);
storage.get('profile', function(getErr, getStr) {
if (getErr) {
cb(getErr, null);
return;
}
if (secureStr) {
try {
secureProfile = Profile.fromString(secureStr);
$log.debug('profile: ' + JSON.stringify(secureProfile));
} catch (e) {
$log.error(e);
return cb(e, null);
}
if (!getStr) {
cb(null, null);
return;
}
storage.get('profile', function(getErr, getStr) {
_onOldProfileRetrieved(getErr, getStr, function(oldErr, oldProfile){
if (oldErr) {
return cb(oldErr, null);
}
if (!oldProfile) {
if (secureProfile) {
return cb(null, secureProfile);
} else {
// No profiles found. No errors either.
return cb(null, null);
}
}
_migrateProfiles(oldProfile, secureProfile, cb);
});
});
var profile = Profile.fromString(getStr);
cb(null, profile);
});
};

View file

@ -72,11 +72,19 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
var config = configService.getSync().wallet.settings;
var val = function() {
var v1 = parseFloat((rateService.toFiat(satoshis, config.alternativeIsoCode, coin)).toFixed(2));
v1 = $filter('formatFiatAmount')(v1);
var fiatAmount = rateService.toFiat(satoshis, config.alternativeIsoCode, coin);
var roundedStr = fiatAmount.toFixed(2);
var roundedNum = parseFloat(roundedStr);
var subcent = roundedNum === 0 && fiatAmount > 0;
var lessThanPrefix = '';
if (subcent) {
roundedNum = 0.01;
lessThanPrefix = '< ';
}
var v1 = $filter('formatFiatAmount')(roundedNum);
if (!v1) return null;
return v1 + ' ' + config.alternativeIsoCode;
return lessThanPrefix + v1 + ' ' + config.alternativeIsoCode;
};
// Async version

View file

@ -0,0 +1,68 @@
describe('txFormatService', function(){
var configServiceMock,
rateServiceMock,
txFormatService;
beforeEach(function(){
module('ngLodash');
module('bwcModule');
module('copayApp.filters');
module('copayApp.services');
configServiceMock = {
getSync: jasmine.createSpy()
};
rateServiceMock = {
isAvailable: jasmine.createSpy(),
toFiat: jasmine.createSpy()
};
module(function($provide) {
$provide.value('configService', configServiceMock);
$provide.value('rateService', rateServiceMock);
});
inject(function($injector){
txFormatService = $injector.get('txFormatService');
});
});
it('formatAlternativeStr 0.49 cents.', function() {
configServiceMock.getSync.and.returnValue({
wallet: {
settings: {
alternativeIsoCode: 'USD'
}
}
});
rateServiceMock.isAvailable.and.returnValue(true);
rateServiceMock.toFiat.and.returnValue(0.00499);
var formatted = txFormatService.formatAlternativeStr('bch', 123);
expect(formatted).toBe('< 0.01 USD');
});
it('formatAlternativeStr 0.5 cents.', function() {
configServiceMock.getSync.and.returnValue({
wallet: {
settings: {
alternativeIsoCode: 'USD'
}
}
});
rateServiceMock.isAvailable.and.returnValue(true);
rateServiceMock.toFiat.and.returnValue(0.005);
var formatted = txFormatService.formatAlternativeStr('bch', 123);
expect(formatted).toBe('0.01 USD');
});
});

View file

@ -1,14 +1,15 @@
qrcode {
&.qr-icon {
position: relative;
&.qr-overlay {
&::before {
content: "";
background-size: 100% 100%;
display: block;
margin-left: calc(50% - 22px);
left: 88px;
margin-top: 88px;
width: 44px;
height: 44px;
position: absolute;
position:absolute;
}
&--bch::before {
background-image: url('../img/qr-overlay-bch.png');

View file

@ -36,6 +36,11 @@
.amount-label{
line-height: 30px;
.amount{
font-size: 16px;
color: #9B9B9B;
font-family: "Roboto-Light";
}
.alternative {
font-size: 38px;
margin-bottom: .5rem;
@ -43,11 +48,6 @@
font-family: "Roboto-Light";
}
}
.alternative {
font-size: 16px;
font-family: "Roboto-Light";
color: #9B9B9B;
}
}
}
.item {

View file

@ -59,6 +59,9 @@
}
}
&-banner {
svg {
margin: 40px auto 40px;
}
padding: 0;
&__img {
width: 100%;

11
src/shim/shim.js Normal file
View file

@ -0,0 +1,11 @@
//---------------------------------------------------------------------
//
// Add components what are missing in old JavaScript Engine
//
//---------------------------------------------------------------------
if (!ArrayBuffer['isView']) {
ArrayBuffer.isView = function(a) {
return a !== null && typeof(a) === "object" && a['buffer'] instanceof ArrayBuffer;
};
}

BIN
www/misc/payment_sent.ogg Normal file

Binary file not shown.

View file

@ -16,8 +16,8 @@
<span translate ng-if="tx.sendMax">Sending maximum amount</span>
</div>
<div class="amount-label">
<div class="alternative">{{tx.alternativeAmountValueStr || '...'}} <span class="unit">{{tx.alternativeAmountUnitStr}}</span></div>
<div class="amount">{{tx.amountValueStr || '...'}} <span class="unit">{{tx.amountUnitStr}}</span></div>
<div class="alternative">{{tx.alternativeAmountStr || '...'}}</div>
</div>
</div>
<div class="info">
@ -77,9 +77,9 @@
</a>
<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="m10l">{{tx.txp[wallet.id].alternativeFeeStr || '...'}}</span>
<span class="item-note m10l">
<span>{{tx.txp[wallet.id].alternativeFeeStr || '...'}}&nbsp;
<span>{{tx.txp[wallet.id].feeStr || '...'}}&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>

View file

@ -41,7 +41,6 @@
<div class="subheader" ng-if="walletsBtc[0] && walletsBch[0]" translate>
<div translate>Bitcoin Core (BTC)</div>
<div translate class="subtitle">Slow transactions with high fees</div>
</div>
<a
ng-repeat="wallet in walletsBtc track by $index"

View file

@ -71,7 +71,6 @@
<div class="list card">
<div class="item item-icon-right item-heading">
<div translate>Bitcoin Core (BTC)</div>
<div translate class="subtitle">Slow transactions with high fees</div>
<a ui-sref="tabs.add"><i class="icon ion-ios-plus-empty list-add-button"></i></a>
</div>
<div>
@ -90,9 +89,11 @@
<div class="ng-hide list card" ng-show="showServices && (walletsBch[0] || walletsBtc[0])" ng-include="'views/includes/services.html'"></div>
<div class="list card card-banner">
<a ng-click="openStore()">
<img class="card-banner__img" src="img/banner-store.png"/>
<ion-spinner ng-if="bannerIsLoading"></ion-spinner>
<a ng-if="!bannerIsLoading" ng-click="openBannerUrl()">
<img class="card-banner__img" ng-src="{{bannerImageUrl}}"/>
</a>
</div>
<div class="ng-hide list card" ng-show="nextStepsItems.length>0 && !isWindowsPhoneApp" ng-include="'views/includes/community.html'"></div>

View file

@ -41,16 +41,16 @@
</button>
</span>
<qrcode class="qr-icon qr-icon--{{ wallet.coin }}" ng-if="addr" size="220" data="{{ protocolHandler }}:{{addr}}" color="#334"></qrcode>
<qrcode class="qr-overlay qr-overlay--{{ wallet.coin }}" ng-if="addr" size="220" data="{{ protocolHandler }}:{{addr}}" color="#334"></qrcode>
<div class="address-label">
<span class="ellipsis">{{addr}}</span>
<ion-spinner ng-show="!addr" class="spinner-dark" icon="crescent"></ion-spinner>
</div>
<div>
<button ng-show="addr" class="button-address" ng-click="setAddress(true)">
<span translate>Generate new address</span>
</button><br/>
</div>
</div>
<div>
<button ng-show="addr" class="button-address" ng-click="setAddress(true)">
<span translate>Generate new address</span>
</button><br/>
</div>
</div>
<!-- animation for payment received -->
@ -61,7 +61,8 @@
</svg>
<p class="success animated fadeIn">
<br/>Payment Received!
<span class="payment-received-amount">{{ paymentReceivedAmount }} <span class="payment-received-currency">{{ paymentReceivedCoin }}</span></span>
<span ng-if="!(displayBalanceAsFiat && paymentReceivedAlternativeAmount)" class="payment-received-amount">{{ paymentReceivedAmount }} <span class="payment-received-currency">{{ paymentReceivedCoin }}</span></span>
<span ng-if="displayBalanceAsFiat && paymentReceivedAlternativeAmount" class="payment-received-amount">{{ paymentReceivedAlternativeAmount }}</span></span>
Return To Address<br/>
</p>
</div>

View file

@ -16,7 +16,7 @@
<div class="zero-state-description" translate>You can scan bitcoin addresses, payment requests, paper wallets, and more.</div>
<div class="zero-state-cta">
<div class="ng-hide zero-state-tldr" ng-show="!currentState || currentState === scannerStates.unauthorized" translate>Enable the camera to get started.</div>
<div class="ng-hide zero-state-tldr" ng-show="currentState === scannerStates.denied" translate>Enable camera access in your device settings to get started.</div>
<div class="ng-hide zero-state-tldr" ng-show="currentState === scannerStates.denied || reactivationCount > 2" translate>Enable camera access in your device settings to get started.</div>
<div class="ng-hide zero-state-tldr" ng-show="currentState === scannerStates.unavailable" translate>Please connect a camera to get started.</div>
<button ng-show="!currentState || currentState === scannerStates.unauthorized" class="ng-hide button button-standard button-primary" ng-click="authorize()" translate>Allow Camera Access</button>
<button ng-show="currentState === scannerStates.denied && canOpenSettings" class="ng-hide button button-standard button-primary" ng-click="openSettings()" translate>Open Settings</button>