From 236a93d69d9780b94ec04173d021f7c7806b630c Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 16 Jul 2018 17:21:15 +0200 Subject: [PATCH 001/240] share transaction after sending on mobile + share explorer url on desktop. --- i18n/po/template.pot | 1 + src/js/controllers/confirm.js | 14 ++++++- src/js/directives/slideToAcceptSuccess.js | 11 ++++-- .../views/includes/slideToAcceptSuccess.scss | 38 +++++++++++++------ www/img/icon-share-white.svg | 17 +++++++++ www/views/confirm.html | 1 + www/views/includes/slideToAcceptSuccess.html | 7 +++- 7 files changed, 71 insertions(+), 18 deletions(-) create mode 100644 www/img/icon-share-white.svg diff --git a/i18n/po/template.pot b/i18n/po/template.pot index 66a2e7ca8..2ee53ec78 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -648,6 +648,7 @@ msgstr "" #: src/js/controllers/copayers.js:79 #: src/js/controllers/export.js:193 +#: src/js/controllers/confirm.js:41 #: www/views/includes/copyToClipboard.html:4 msgid "Copied to clipboard" msgstr "" diff --git a/src/js/controllers/confirm.js b/src/js/controllers/confirm.js index 03af26fd1..c8c889263 100644 --- a/src/js/controllers/confirm.js +++ b/src/js/controllers/confirm.js @@ -1,11 +1,12 @@ '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, soundService) { +angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, ionicToast, 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, clipboardService) { var countDown = null; var FEE_TOO_HIGH_LIMIT_PER = 15; var tx = {}; + var lastTxId = ""; // Config Related values var config = configService.getSync(); @@ -31,6 +32,16 @@ angular.module('copayApp.controllers').controller('confirmController', function( }, 10); } + $scope.shareTransaction = function() { + var explorerTxUrl = 'https://explorer.bitcoin.com/'+tx.coin+'/tx/'+lastTxId; + if (platformInfo.isCordova) { + var text = 'Take a look at this Bitcoin transaction here: '+explorerTxUrl; + window.plugins.socialsharing.share(text, null, null, null); + } else { + ionicToast.show(gettextCatalog.getString('Copied to clipboard'), 'bottom', false, 3000); + clipboardService.copyToClipboard(explorerTxUrl); + } + }; $scope.showWalletSelector = function() { $scope.walletSelector = true; @@ -612,6 +623,7 @@ angular.module('copayApp.controllers').controller('confirmController', function( txConfirmNotification.subscribe(wallet, { txid: txp.txid }); + lastTxId = txp.txid; } }, onSendStatusChange); }; diff --git a/src/js/directives/slideToAcceptSuccess.js b/src/js/directives/slideToAcceptSuccess.js index fbd588bfe..ec6321b95 100644 --- a/src/js/directives/slideToAcceptSuccess.js +++ b/src/js/directives/slideToAcceptSuccess.js @@ -9,12 +9,12 @@ angular.module('copayApp.directives') scope: { isShown: '=slideSuccessShow', onConfirm: '&slideSuccessOnConfirm', - hideOnConfirm: '=slideSuccessHideOnConfirm' + hideOnConfirm: '=slideSuccessHideOnConfirm', + onShare: '=slideSuccessOnShare', }, link: function(scope, element, attrs) { - - scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP; - + scope.isCordova = platformInfo.isCordova; + scope.hasShareFunction = typeof scope.onShare === 'function'; var elm = element[0]; elm.style.display = 'none'; scope.$watch('isShown', function() { @@ -32,6 +32,9 @@ angular.module('copayApp.directives') elm.style.display = 'none'; } }; + scope.onShareButtonClick = function() { + scope.onShare(); + } } }; }); diff --git a/src/sass/views/includes/slideToAcceptSuccess.scss b/src/sass/views/includes/slideToAcceptSuccess.scss index 68312c7a4..724363b0d 100644 --- a/src/sass/views/includes/slideToAcceptSuccess.scss +++ b/src/sass/views/includes/slideToAcceptSuccess.scss @@ -12,12 +12,6 @@ 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; @@ -40,9 +34,11 @@ slide-to-accept-success { &__content { position: relative; z-index: 1; - margin-top: -20vh; + margin-top: -10vh; > img { + width: 45vw; + max-width: 166px; margin-bottom: 1.8rem; -webkit-transform: translateY(5rem); transform: translateY(5rem); @@ -59,7 +55,7 @@ slide-to-accept-success { &__header { color: #FFFFFF; - font-size: 26px; + font-size: 29px; -webkit-transform: translateY(5rem); transform: translateY(5rem); opacity: 0; @@ -72,6 +68,26 @@ slide-to-accept-success { opacity: 1; } } + &__share { + transition: transform $duration ease, opacity $duration ease; + transition-delay: 1000ms; + opacity: 0; + margin-top: 15vh; + span { + color: #FFF; + font-size: 22px; + height: 28px; + } + img { + height: 28px; + width: auto; + vertical-align: bottom; + margin-right: 4px; + } + &.reveal { + opacity: 0.79; + } + } } &__footer { @@ -98,11 +114,11 @@ slide-to-accept-success { &__btn { display: block; color: #FFFFFF; - font-size: 18px; + font-size: 22px; font-weight: 600; letter-spacing: 2.86px; - padding: 1rem 0 1.1rem; - border-top: 1px solid rgba(255, 255, 255, .45); + padding: 2rem 0 2.1rem; + border-top: 1px solid rgba(255, 255, 255, 0.25); cursor: pointer; } } diff --git a/www/img/icon-share-white.svg b/www/img/icon-share-white.svg new file mode 100644 index 000000000..eeb3e7b6b --- /dev/null +++ b/www/img/icon-share-white.svg @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/www/views/confirm.html b/www/views/confirm.html index e54837f34..afc8f9e40 100644 --- a/www/views/confirm.html +++ b/www/views/confirm.html @@ -120,6 +120,7 @@ Payment Sent Proposal Created diff --git a/www/views/includes/slideToAcceptSuccess.html b/www/views/includes/slideToAcceptSuccess.html index 923eab25c..ed4ebfa77 100644 --- a/www/views/includes/slideToAcceptSuccess.html +++ b/www/views/includes/slideToAcceptSuccess.html @@ -1,13 +1,16 @@
+ ng-class="{'fill-screen': fillScreen}">
- +
Payment Sent
+
+
+
+
+ Receive +
+
+
+
+ Buy Bitcoin +
+
+ Send +
+
+
+
From cc907251f75493d0ab0f5536a1fd9f924e56322b Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 18 Jul 2018 17:20:59 +1200 Subject: [PATCH 007/240] Removing unused services, and making string translatable. --- i18n/po/template.pot | 4 ++++ src/js/controllers/amount.js | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index a23e5dbae..45a4fbd44 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -3699,3 +3699,7 @@ msgstr "" #: www/views/includes/walletInfo.html:18 msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "" + +#: src/js/controllers/amount.js:49 +msgid "Address doesn\'t contain currency information, please make sure you are sending the correct currency." +msgstr "" \ No newline at end of file diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 52695e829..1c91baf97 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('amountController', function($scope, $filter, $timeout, $ionicModal, $ionicScrollDelegate, $ionicHistory, storageService, walletService, gettextCatalog, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, txFormatService, ongoingProcess, popupService, bwcError, payproService, profileService, bitcore, amazonService, nodeWebkitService) { +angular.module('copayApp.controllers').controller('amountController', function($scope, $filter, $timeout, $ionicModal, $ionicScrollDelegate, $ionicHistory, walletService, gettextCatalog, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, txFormatService, ongoingProcess, popupService, profileService, nodeWebkitService) { var _id; var unitToSatoshi; @@ -46,7 +46,7 @@ angular.module('copayApp.controllers').controller('amountController', function($ if (data.stateParams.noPrefix) { $scope.showWarningMessage = data.stateParams.noPrefix != 0; if ($scope.showWarningMessage) { - var message = 'Address doesn\'t contain currency information, please make sure you are sending the correct currency.'; + var message = gettextCatalog.getString('Address doesn\'t contain currency information, please make sure you are sending the correct currency.'); popupService.showAlert('', message, function() {}, 'Ok'); } } From 0db57f997a6d8e374544a445ce050be5734a1508 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 18 Jul 2018 18:24:49 +1200 Subject: [PATCH 008/240] First successful test, instantiating an amountController. --- src/js/controllers/amount.spec.js | 55 ++++++++++++++++++++ src/js/services/secureStorageService.spec.js | 6 +-- src/js/services/storageService.spec.js | 4 +- test/karma.conf.js | 2 + 4 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 src/js/controllers/amount.spec.js diff --git a/src/js/controllers/amount.spec.js b/src/js/controllers/amount.spec.js new file mode 100644 index 000000000..1e5342cd9 --- /dev/null +++ b/src/js/controllers/amount.spec.js @@ -0,0 +1,55 @@ +fdescribe('amountController', function(){ + var $controller, + $rootScope, + platformInfo, + $stateParams; + + + + beforeEach(function(){ + module('ngLodash'); + module('copayApp.controllers'); + + platformInfo = { + isChromeApp: false, + isAndroid: false, + isIos: true + }; + + $stateParams = {}; + + inject(function(_$controller_, _$rootScope_){ + // The injector unwraps the underscores (_) from around the parameter names when matching + $controller = _$controller_; + $rootScope = _$rootScope_; + }); + + + + }); + + it('something', function() { + var $scope = $rootScope.$new(); + var amountController = $controller('amountController', { + configService: {}, + gettextCatalog: {}, + $ionicHistory: {}, + $ionicModal: {}, + $ionicScrollDelegate: {}, + nodeWebkitService: {}, + ongoingProcess: {}, + platformInfo: platformInfo, + profileService: {}, + popupService: {}, + rateService: {}, + $scope: $scope, + $state: {}, + $stateParams: $stateParams, + txFormatService: {}, + walletService: {} + }); + + expect(true).toBe(true); + }); + +}); \ No newline at end of file diff --git a/src/js/services/secureStorageService.spec.js b/src/js/services/secureStorageService.spec.js index abfa5d947..8d2842a6a 100644 --- a/src/js/services/secureStorageService.spec.js +++ b/src/js/services/secureStorageService.spec.js @@ -1,4 +1,4 @@ -describe('secureStorageService in browser', function(){ +xdescribe('secureStorageService in browser', function(){ var localStorage, sss; @@ -100,7 +100,7 @@ describe('secureStorageService in browser', function(){ }); -describe('secureStorageService on desktop', function(){ +xdescribe('secureStorageService on desktop', function(){ var desktopSss, sss; @@ -202,7 +202,7 @@ describe('secureStorageService on desktop', function(){ }); -describe('secureStorageService on mobile', function(){ +xdescribe('secureStorageService on mobile', function(){ var mobileSss, sss; diff --git a/src/js/services/storageService.spec.js b/src/js/services/storageService.spec.js index 493678b97..cc3ad285d 100644 --- a/src/js/services/storageService.spec.js +++ b/src/js/services/storageService.spec.js @@ -414,7 +414,7 @@ xdescribe('storageService on desktop', function(){ }); -describe('storageService on desktop using local storage', function(){ +xdescribe('storageService on desktop using local storage', function(){ var appConfig, localStorageServiceMock, log, @@ -614,7 +614,7 @@ describe('storageService on desktop using local storage', function(){ }); -describe('storageService on mobile', function(){ +xdescribe('storageService on mobile', function(){ var appConfig, expectedOldProfileSavedToSecure, expectedOldProfileMergedWithSecure, diff --git a/test/karma.conf.js b/test/karma.conf.js index 002d40c91..b4f64af73 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -17,6 +17,8 @@ module.exports = function(config) { files: [ 'node_modules/angular/angular.js', + 'bitanalytics/bitanalytics-0.1.0.js', + // From Gruntfile.js 'bower_components/qrcode-generator/js/qrcode.js', 'bower_components/qrcode-generator/js/qrcode_UTF8.js', From 8725adb959f38d971cfecf05bbd067b6baf3186b Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 18 Jul 2018 09:02:52 +0200 Subject: [PATCH 009/240] sent-successful svg --- www/img/icon-sent-successful.svg | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 www/img/icon-sent-successful.svg diff --git a/www/img/icon-sent-successful.svg b/www/img/icon-sent-successful.svg new file mode 100644 index 000000000..070357ddf --- /dev/null +++ b/www/img/icon-sent-successful.svg @@ -0,0 +1,10 @@ + + + + + + From 3df28360819870a3ab62a858f9866f1407aafe8a Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 18 Jul 2018 20:26:20 +1200 Subject: [PATCH 010/240] Successful test with amountController, using beforeEnter. --- src/js/controllers/amount.spec.js | 62 +++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/src/js/controllers/amount.spec.js b/src/js/controllers/amount.spec.js index 1e5342cd9..ed64da836 100644 --- a/src/js/controllers/amount.spec.js +++ b/src/js/controllers/amount.spec.js @@ -1,7 +1,12 @@ -fdescribe('amountController', function(){ - var $controller, +describe('amountController', function(){ + var configCache, + configService, + $controller, + $ionicHistory, $rootScope, platformInfo, + profileService, + rateService, $stateParams; @@ -10,12 +15,34 @@ fdescribe('amountController', function(){ module('ngLodash'); module('copayApp.controllers'); + configCache = { + wallet: { + settings: { + + } + } + }; + + + configService = jasmine.createSpyObj(['getDefaults','getSync']); + configService.getDefaults.and.returnValue({ + bitcoinCashAlias: 'bch', + bitcoinAlias: 'btc' + }); + configService.getSync.and.returnValue(configCache); + + $ionicHistory = jasmine.createSpyObj(['backView']); + platformInfo = { isChromeApp: false, isAndroid: false, isIos: true }; + profileService = jasmine.createSpyObj(['getWallets']); + + rateService = jasmine.createSpyObj(['fromFiat', 'whenAvailable']); + $stateParams = {}; inject(function(_$controller_, _$rootScope_){ @@ -28,20 +55,30 @@ fdescribe('amountController', function(){ }); - it('something', function() { + it('receives fromWalletId and toAddress.', function() { + + var backView = { + stateName: 'ignoreme' + }; + $ionicHistory.backView.and.returnValue(backView); + profileService.getWallets.and.returnValue([{}]); + rateService.fromFiat.and.returnValue(12); // satoshis or coins? + var $scope = $rootScope.$new(); + + var amountController = $controller('amountController', { - configService: {}, + configService: configService, gettextCatalog: {}, - $ionicHistory: {}, + $ionicHistory: $ionicHistory, $ionicModal: {}, $ionicScrollDelegate: {}, nodeWebkitService: {}, ongoingProcess: {}, platformInfo: platformInfo, - profileService: {}, + profileService: profileService, popupService: {}, - rateService: {}, + rateService: rateService, $scope: $scope, $state: {}, $stateParams: $stateParams, @@ -49,7 +86,16 @@ fdescribe('amountController', function(){ walletService: {} }); - expect(true).toBe(true); + var data = { + stateParams: { + fromWalletId: 'fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b', + toAddress: 'qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s' + } + }; + $scope.$emit('$ionicView.beforeEnter', data); + + expect($scope.fromWalletId).toBe('fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b'); + expect($scope.toAddress).toBe('qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s'); }); }); \ No newline at end of file From 8c124fba196e611f486bdd0d3ae10fa6ef0637a0 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 18 Jul 2018 11:29:03 +0200 Subject: [PATCH 011/240] price display translation --- i18n/po/template.pot | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index a23e5dbae..df01cecec 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -197,6 +197,10 @@ msgstr "" msgid "Alternative Currency" msgstr "" +#: www/views/tab-settings.html:75 +msgid "Price Display" +msgstr "" + #: src/js/controllers/buyAmazon.js:98 msgid "Amazon.com is not available at this moment. Please try back later." msgstr "" From b8bab036e6e2b7f1008e050ea762e9a65a6e7613 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 18 Jul 2018 11:54:41 +0200 Subject: [PATCH 012/240] wallet to wallet transfer views + css --- .../controllers/walletToWalletController.js | 14 ++++++ src/js/routes.js | 9 ++++ src/sass/views/views.scss | 1 + src/sass/views/wallet-to-wallet-transfer.scss | 18 ++++++++ www/views/tab-send.html | 2 +- www/views/wallet-to-wallet-transfer.html | 43 +++++++++++++++++++ 6 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/js/controllers/walletToWalletController.js create mode 100644 src/sass/views/wallet-to-wallet-transfer.scss create mode 100644 www/views/wallet-to-wallet-transfer.html diff --git a/src/js/controllers/walletToWalletController.js b/src/js/controllers/walletToWalletController.js new file mode 100644 index 000000000..7cb467b67 --- /dev/null +++ b/src/js/controllers/walletToWalletController.js @@ -0,0 +1,14 @@ +'use strict'; + +angular.module('copayApp.controllers').controller('walletToWalletController', function($scope, $rootScope, $log, profileService, configService) { + + $scope.$on("$ionicView.enter", function(event, data) { + $scope.walletsBch = profileService.getWallets({coin: 'bch'}); + $scope.walletsBtc = profileService.getWallets({coin: 'btc'}); + configService.whenAvailable(function(config) { + $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; + }); + }); + + +}); \ No newline at end of file diff --git a/src/js/routes.js b/src/js/routes.js index 8277314e5..72397d872 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -295,6 +295,15 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } } }) + .state('tabs.send.wallet-to-wallet', { + url: '/wallet-to-wallet', + views: { + 'tab-send@tabs': { + controller: 'walletToWalletController', + templateUrl: 'views/wallet-to-wallet-transfer.html' + } + } + }) .state('tabs.send.confirm', { url: '/confirm/:recipientType/:toAddress/:toName/:toAmount/:toEmail/:toColor/:description/:coin/:useSendMax/:fromWalletId/:displayAddress/:requiredFeeRate', views: { diff --git a/src/sass/views/views.scss b/src/sass/views/views.scss index d4ed735ed..538787901 100644 --- a/src/sass/views/views.scss +++ b/src/sass/views/views.scss @@ -8,6 +8,7 @@ @import "tab-receive"; @import "tab-scan"; @import "tab-send"; +@import "wallet-to-wallet-transfer"; @import "tab-settings"; @import "wallet-colors"; @import "walletBalance"; diff --git a/src/sass/views/wallet-to-wallet-transfer.scss b/src/sass/views/wallet-to-wallet-transfer.scss new file mode 100644 index 000000000..e909258cf --- /dev/null +++ b/src/sass/views/wallet-to-wallet-transfer.scss @@ -0,0 +1,18 @@ +#wallet-to-wallet-transfer { + .header, .list { + font-size: 12px; + margin: 20px 14px; + + .title { + font-size: 20px; + font-weight: bold; + color: $v-dark-gray; + } + .subtitle { + font-size: 12px; + line-height: 1.5em; + font-weight: 300; + color: $v-dark-gray; + } + } +} \ No newline at end of file diff --git a/www/views/tab-send.html b/www/views/tab-send.html index 8b39808db..b5d556214 100644 --- a/www/views/tab-send.html +++ b/www/views/tab-send.html @@ -22,7 +22,7 @@
- diff --git a/www/views/wallet-to-wallet-transfer.html b/www/views/wallet-to-wallet-transfer.html new file mode 100644 index 000000000..2eb2bcb11 --- /dev/null +++ b/www/views/wallet-to-wallet-transfer.html @@ -0,0 +1,43 @@ + + + {{'Wallet to wallet transfer' | translate}} + + +
+
+ Choose your origin wallet +
+
+ This is where the Bitcoin will be taken out from. +
+
+
+
+
Bitcoin Cash (BCH)
+
Instant transactions with low fees
+ +
+
+ + + +
+
+ +
+
+
Bitcoin Core (BTC)
+ +
+
+ + + +
+
+
+
\ No newline at end of file From 450b80b03fd6ab9701a481b383cedeadd658cdd4 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 18 Jul 2018 16:30:54 +0200 Subject: [PATCH 013/240] send/receive buttons on walletDetails + currencySymbolService --- src/js/controllers/walletDetails.js | 24 ++- src/js/services/currencySymbolService.js | 194 +++++++++++++++++++++++ src/sass/shame.scss | 4 + src/sass/views/walletDetails.scss | 24 ++- www/views/tab-home.html | 26 +-- www/views/walletDetails.html | 42 +++-- 6 files changed, 280 insertions(+), 34 deletions(-) create mode 100644 src/js/services/currencySymbolService.js diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 24237f6c9..24e4a5a20 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -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, feeService, appConfigService, rateService) { +angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicHistory, profileService, lodash, configService, currencySymbolService, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService, appConfigService, rateService) { var HISTORY_SHOW_LIMIT = 10; var currentTxHistoryPage = 0; @@ -12,6 +12,13 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.isAndroid = platformInfo.isAndroid; $scope.isIOS = platformInfo.isIOS; + $scope.currencySymbols = { + 'EUR': '€', + 'GBP': '£', + 'USD': '$', + 'YEN' : '' + }; + var channel = "firebase"; if (platformInfo.isNW) { channel = "ga"; @@ -256,6 +263,11 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun return !tx.confirmations || tx.confirmations === 0; }; + $scope.currencySymbol = function(code) { + var symbol = currencySymbolService.getCurrencySymbol(code); + return symbol?symbol:""; + }; + $scope.showMore = function() { $timeout(function() { currentTxHistoryPage++; @@ -315,16 +327,16 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun } scrollPos = scrollPos || 0; - var amountHeight = 210 - scrollPos; + var amountHeight = 230 - scrollPos; if (amountHeight < 80) { amountHeight = 80; } var contentMargin = amountHeight; - if (contentMargin > 210) { - contentMargin = 210; + if (contentMargin > 230) { + contentMargin = 230; } - var amountScale = (amountHeight / 210); + var amountScale = (amountHeight / 230); if (amountScale < 0.5) { amountScale = 0.5; } @@ -342,7 +354,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun top = TOP_BALANCE_BUTTON; } - var amountTop = ((amountScale - 0.7) / 0.7) * top; + var amountTop = ((amountScale - 0.85) / 0.85) * top; if (amountTop < -10) { amountTop = -10; } diff --git a/src/js/services/currencySymbolService.js b/src/js/services/currencySymbolService.js new file mode 100644 index 000000000..b4cf0c2ac --- /dev/null +++ b/src/js/services/currencySymbolService.js @@ -0,0 +1,194 @@ +'use strict'; + +angular.module('copayApp.services').factory('currencySymbolService', function($log) { + var root = {}; + root.currencySymbols = { + 'AED': 'د.إ', + 'AFN': '؋', + 'ALL': 'L', + 'AMD': '֏', + 'ANG': 'ƒ', + 'AOA': 'Kz', + 'ARS': '$', + 'AUD': '$', + 'AWG': 'ƒ', + 'AZN': '₼', + 'BAM': 'KM', + 'BBD': '$', + 'BDT': '৳', + 'BGN': 'лв', + 'BHD': '.د.ب', + 'BIF': 'FBu', + 'BMD': '$', + 'BND': '$', + 'BOB': '$b', + 'BRL': 'R$', + 'BSD': '$', + 'BTC': '฿', + 'BTN': 'Nu.', + 'BWP': 'P', + 'BYR': 'Br', + 'BYN': 'Br', + 'BZD': 'BZ$', + 'CAD': '$', + 'CDF': 'FC', + 'CHF': 'CHF', + 'CLP': '$', + 'CNY': '¥', + 'COP': '$', + 'CRC': '₡', + 'CUC': '$', + 'CUP': '₱', + 'CVE': '$', + 'CZK': 'Kč', + 'DJF': 'Fdj', + 'DKK': 'kr', + 'DOP': 'RD$', + 'DZD': 'دج', + 'EEK': 'kr', + 'EGP': '£', + 'ERN': 'Nfk', + 'ETB': 'Br', + 'ETH': 'Ξ', + 'EUR': '€', + 'FJD': '$', + 'FKP': '£', + 'GBP': '£', + 'GEL': '₾', + 'GGP': '£', + 'GHC': '₵', + 'GHS': 'GH₵', + 'GIP': '£', + 'GMD': 'D', + 'GNF': 'FG', + 'GTQ': 'Q', + 'GYD': '$', + 'HKD': '$', + 'HNL': 'L', + 'HRK': 'kn', + 'HTG': 'G', + 'HUF': 'Ft', + 'IDR': 'Rp', + 'ILS': '₪', + 'IMP': '£', + 'INR': '₹', + 'IQD': 'ع.د', + 'IRR': '﷼', + 'ISK': 'kr', + 'JEP': '£', + 'JMD': 'J$', + 'JOD': 'JD', + 'JPY': '¥', + 'KES': 'KSh', + 'KGS': 'лв', + 'KHR': '៛', + 'KMF': 'CF', + 'KPW': '₩', + 'KRW': '₩', + 'KWD': 'KD', + 'KYD': '$', + 'KZT': 'лв', + 'LAK': '₭', + 'LBP': '£', + 'LKR': '₨', + 'LRD': '$', + 'LSL': 'M', + 'LTC': 'Ł', + 'LTL': 'Lt', + 'LVL': 'Ls', + 'LYD': 'LD', + 'MAD': 'MAD', + 'MDL': 'lei', + 'MGA': 'Ar', + 'MKD': 'ден', + 'MMK': 'K', + 'MNT': '₮', + 'MOP': 'MOP$', + 'MRO': 'UM', + 'MRU': 'UM', + 'MUR': '₨', + 'MVR': 'Rf', + 'MWK': 'MK', + 'MXN': '$', + 'MYR': 'RM', + 'MZN': 'MT', + 'NAD': '$', + 'NGN': '₦', + 'NIO': 'C$', + 'NOK': 'kr', + 'NPR': '₨', + 'NZD': '$', + 'OMR': '﷼', + 'PAB': 'B/.', + 'PEN': 'S/.', + 'PGK': 'K', + 'PHP': '₱', + 'PKR': '₨', + 'PLN': 'zł', + 'PYG': 'Gs', + 'QAR': '﷼', + 'RMB': '¥', + 'RON': 'lei', + 'RSD': 'Дин.', + 'RUB': '₽', + 'RWF': 'R₣', + 'SAR': '﷼', + 'SBD': '$', + 'SCR': '₨', + 'SDG': 'ج.س.', + 'SEK': 'kr', + 'SGD': '$', + 'SHP': '£', + 'SLL': 'Le', + 'SOS': 'S', + 'SRD': '$', + 'SSP': '£', + 'STD': 'Db', + 'STN': 'Db', + 'SVC': '$', + 'SYP': '£', + 'SZL': 'E', + 'THB': '฿', + 'TJS': 'SM', + 'TMT': 'T', + 'TND': 'د.ت', + 'TOP': 'T$', + 'TRL': '₤', + 'TRY': '₺', + 'TTD': 'TT$', + 'TVD': '$', + 'TWD': 'NT$', + 'TZS': 'TSh', + 'UAH': '₴', + 'UGX': 'USh', + 'USD': '$', + 'UYU': '$U', + 'UZS': 'лв', + 'VEF': 'Bs', + 'VND': '₫', + 'VUV': 'VT', + 'WST': 'WS$', + 'XAF': 'FCFA', + 'XBT': 'Ƀ', + 'XCD': '$', + 'XOF': 'CFA', + 'XPF': '₣', + 'YER': '﷼', + 'ZAR': 'R', + 'ZWD': 'Z$' + }; + + root.getCurrencySymbol = function(code) { + code = code.toUpperCase(); + + if (root.currencySymbols[code]) { + $log.debug("Currency symbol for "+code+" found"); + return root.currencySymbols[code]; + } + $log.debug("Currency symbol for "+code+" not found"); + return false; + }; + + return root; + +}); \ No newline at end of file diff --git a/src/sass/shame.scss b/src/sass/shame.scss index 07ac2dedf..5a17c5f1f 100644 --- a/src/sass/shame.scss +++ b/src/sass/shame.scss @@ -233,6 +233,10 @@ input[type=number] { font-size: 24px; } +.size-25 { + font-size: 25px; +} + .size-28 { font-size: 28px; } diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index 9e651f871..2d1aa1343 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -153,7 +153,7 @@ ion-content { &.collapsible { - margin-top: 210px; + margin-top: 230px; } padding-top: 0; @@ -190,12 +190,32 @@ transform: translateY(100px); } } + + .buttons { + margin-bottom: 0; + margin-top: 6px; + >.col { + padding: 5px 10px; + margin-bottom: 0; + } + .button { + border: 2px solid; + border-radius: 47px; + padding: 0 15px 0 15px; + text-align: center; + width: 100%; + font-size: 19px; + font-weight: bolder; + min-height: 45px; + line-height: 45px; + } + } } .amount { width: 100%; text-align: center; color: #fff; - height: 210px; + height: 230px; padding-top: 40px; display: block; align-items: center; diff --git a/www/views/tab-home.html b/www/views/tab-home.html index 85b7692f7..618f2e128 100644 --- a/www/views/tab-home.html +++ b/www/views/tab-home.html @@ -23,20 +23,22 @@
-
-
- Receive -
-
-
-
- Buy Bitcoin -
-
- Send -
+
+
+ Receive
+
+
+ Buy Bitcoin +
+
+ Send +
+
+
diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index 0f9e4961c..820930a95 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -34,7 +34,7 @@ on-hold="hideToggle()" ng-style="{'transform': amountScale}" ng-class="{amount__balance: amountIsCollapsible}"> - {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}} + {{currencySymbol(status.alternativeIsoCode)}} {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}}
- {{status.totalBalanceStr}} + {{status.totalBalanceStr}}
- {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}} + {{currencySymbol(status.alternativeIsoCode)}} {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}}
@@ -86,11 +86,25 @@   - {{status.spendableBalanceAlternative}} {{status.alternativeIsoCode}} + {{currencySymbol(status.alternativeIsoCode)}} {{status.spendableBalanceAlternative}} {{status.alternativeIsoCode}}
- +
+
+
+ Receive +
+
+
+
+ Buy Bitcoin +
+
+ Send +
+
+
@@ -127,12 +141,12 @@ on-hold="hideToggle()" ng-style="{'transform': amountScale}" ng-class="{amount__balance: amountIsCollapsible}"> - {{status.totalBalanceStr}} + {{status.totalBalanceStr}}
- {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}} + {{currencySymbol(status.alternativeIsoCode)}} {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}}
@@ -142,9 +156,9 @@ on-hold="hideToggle()" ng-style="{'transform': amountScale}" ng-class="{amount__balance: amountIsCollapsible}"> - {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}} + {{currencySymbol(status.alternativeIsoCode)}} {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}}
{{status.totalBalanceStr}} @@ -156,7 +170,7 @@ ng-show="!updateStatusError && wallet.balanceHidden && !wallet.scanning" on-hold="hideToggle()"> [Balance Hidden] -
+
Tap and hold to show
@@ -166,7 +180,7 @@ class="amount__balance" ng-show="!updateStatusError && wallet.scanning"> [Scanning Funds] -
+
Please wait
@@ -180,7 +194,7 @@   - {{status.spendableBalanceAlternative}} {{status.alternativeIsoCode}} + {{currencySymbol(status.alternativeIsoCode)}} {{status.spendableBalanceAlternative}} {{status.alternativeIsoCode}}
@@ -189,7 +203,7 @@
From 46906352e5c1d4bab852192e14af7b2cbdc77c60 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 18 Jul 2018 16:47:15 +0200 Subject: [PATCH 014/240] remove "add wallet" buttons --- www/views/wallet-to-wallet-transfer.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/www/views/wallet-to-wallet-transfer.html b/www/views/wallet-to-wallet-transfer.html index 2eb2bcb11..68a61fe2d 100644 --- a/www/views/wallet-to-wallet-transfer.html +++ b/www/views/wallet-to-wallet-transfer.html @@ -15,7 +15,6 @@
Bitcoin Cash (BCH)
Instant transactions with low fees
-
Date: Wed, 18 Jul 2018 16:48:57 +0200 Subject: [PATCH 015/240] wallet selection stub --- src/js/controllers/walletToWalletController.js | 3 +++ www/views/wallet-to-wallet-transfer.html | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/js/controllers/walletToWalletController.js b/src/js/controllers/walletToWalletController.js index 7cb467b67..77d2218e6 100644 --- a/src/js/controllers/walletToWalletController.js +++ b/src/js/controllers/walletToWalletController.js @@ -10,5 +10,8 @@ angular.module('copayApp.controllers').controller('walletToWalletController', fu }); }); + $scope.useWallet = function(wallet) { + // Do something with selected wallet + }; }); \ No newline at end of file diff --git a/www/views/wallet-to-wallet-transfer.html b/www/views/wallet-to-wallet-transfer.html index 68a61fe2d..f172f40da 100644 --- a/www/views/wallet-to-wallet-transfer.html +++ b/www/views/wallet-to-wallet-transfer.html @@ -19,7 +19,7 @@ From 3cedfa5146d0e732f66eb21ef48ada651f625286 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 19 Jul 2018 12:19:08 +1200 Subject: [PATCH 016/240] Added $scope.fundsAreInsufficient for triggering UI. --- src/js/controllers/amount.js | 52 +++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 1c91baf97..3d8422447 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -7,6 +7,8 @@ angular.module('copayApp.controllers').controller('amountController', function($ var satToUnit; var unitDecimals; var satToBtc; + var spendableAmountInSatoshis = null; + var SMALL_FONT_SIZE_LIMIT = 10; var LENGTH_EXPRESSION_LIMIT = 19; var LENGTH_BEFORE_COMMA_EXPRESSION_LIMIT = 8; @@ -22,6 +24,10 @@ angular.module('copayApp.controllers').controller('amountController', function($ $scope.amountModel = { amount: 0 }; + // Use insufficient for logic, as when the amount is invalid, funds being + // either sufficent or insufficient doesn't make sense. + $scope.fundsAreInsufficient = false; + $scope.isChromeApp = platformInfo.isChromeApp; $scope.isAndroid = platformInfo.isAndroid; $scope.isIos = platformInfo.isIOS; @@ -134,6 +140,12 @@ angular.module('copayApp.controllers').controller('amountController', function($ }); altUnitIndex = 0; + + if ($scope.fromWalletId) { + var fromWallet = profileService.getWallet($scope.fromWalletId); + console.log('got fromWallet.'); + updateSpendableAmountInSatoshisFromWallet(fromWallet); + } }; // Go to... @@ -387,24 +399,41 @@ angular.module('copayApp.controllers').controller('amountController', function($ var a = fromFiat(result); if (a) { - $scope.alternativeAmount = txFormatService.formatAmount(a * unitToSatoshi, true); - $scope.allowSend = lodash.isNumber(a) && a > 0 + var amountInSatoshis = a * unitToSatoshi; + $scope.fundsAreInsufficient = !!$scope.fromWalletId + && spendableAmountInSatoshis !== null + && spendableAmountInSatoshis < amountInSatoshis; + + $scope.alternativeAmount = txFormatService.formatAmount(amountInSatoshis, true); + $scope.allowSend = lodash.isNumber(a) + && a > 0 && (!$scope.shapeshiftOrderId - || (a >= $scope.minShapeshiftAmount && a <= $scope.maxShapeshiftAmount)); + || (a >= $scope.minShapeshiftAmount && a <= $scope.maxShapeshiftAmount)) + && !$scope.fundsAreInsufficient; } else { if (result) { $scope.alternativeAmount = 'N/A'; } else { $scope.alternativeAmount = null; } + $scope.fundsAreInsufficient = false; $scope.allowSend = false; } } else { + $scope.fundsAreInsufficient = !!$scope.fromWalletId + && spendableAmountInSatoshis !== null + && spendableAmountInSatoshis < result * unitToSatoshi; + $scope.alternativeAmount = $filter('formatFiatAmount')(toFiat(result)); - $scope.allowSend = lodash.isNumber(result) && result > 0 + $scope.allowSend = lodash.isNumber(result) + && result > 0 && (!$scope.shapeshiftOrderId - || (result >= $scope.minShapeshiftAmount && result <= $scope.maxShapeshiftAmount)); + || (result >= $scope.minShapeshiftAmount && result <= $scope.maxShapeshiftAmount)) + && !$scope.fundsAreInsufficient; } + + } else { + $scope.fundsAreInsufficient = false; } }; @@ -584,6 +613,7 @@ angular.module('copayApp.controllers').controller('amountController', function($ $timeout(function() { $scope.$apply(); }); + rateService.updateRates(); }); } @@ -632,5 +662,15 @@ angular.module('copayApp.controllers').controller('amountController', function($ updateUnitUI(); $scope.close(); }); - }; + }; + + function updateSpendableAmountInSatoshisFromWallet(wallet) { + if (wallet.status) { + spendableAmountInSatoshis = wallet.status.spendableAmount; + } else if (fromWallet.cachedStatus) { + spendableAmountInSatoshis = wallet.cachedStatus.spendableAmount; + } else { + spendableAmountInSatoshis = null; + } + } }); From 61f1603bbffb90bd7661a31990821af9b77a1e13 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 19 Jul 2018 12:52:37 +1200 Subject: [PATCH 017/240] Added $scope.availableFunds. --- src/js/controllers/amount.js | 51 +++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 3d8422447..1ea8f3104 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -3,11 +3,14 @@ angular.module('copayApp.controllers').controller('amountController', function($scope, $filter, $timeout, $ionicModal, $ionicScrollDelegate, $ionicHistory, walletService, gettextCatalog, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, txFormatService, ongoingProcess, popupService, profileService, nodeWebkitService) { var _id; + var availableFundsInCrypto = ''; + var availableFundsInFiat = ''; + var availableSatoshis = null; var unitToSatoshi; var satToUnit; var unitDecimals; var satToBtc; - var spendableAmountInSatoshis = null; + var SMALL_FONT_SIZE_LIMIT = 10; var LENGTH_EXPRESSION_LIMIT = 19; @@ -23,6 +26,8 @@ angular.module('copayApp.controllers').controller('amountController', function($ var fixedUnit; $scope.amountModel = { amount: 0 }; + $scope.availableFunds = ''; + // Use insufficient for logic, as when the amount is invalid, funds being // either sufficent or insufficient doesn't make sense. @@ -143,8 +148,7 @@ angular.module('copayApp.controllers').controller('amountController', function($ if ($scope.fromWalletId) { var fromWallet = profileService.getWallet($scope.fromWalletId); - console.log('got fromWallet.'); - updateSpendableAmountInSatoshisFromWallet(fromWallet); + updateAvailableFundsFromWallet(fromWallet); } }; @@ -281,12 +285,15 @@ angular.module('copayApp.controllers').controller('amountController', function($ if (availableUnits[unitIndex].isFiat) { altUnitIndex = altUnitIndex == 0 && availableUnits.length > 2 ? 1 : 0; + $scope.availableFunds = availableFundsInFiat || availableFundsInCrypto; } else { altUnitIndex = lodash.findIndex(availableUnits, { isFiat: true }); + $scope.availableFunds = availableFundsInCrypto; } + console.log('availableFunds: ' + $scope.availableFunds); updateUnitUI(); }; @@ -401,8 +408,8 @@ angular.module('copayApp.controllers').controller('amountController', function($ if (a) { var amountInSatoshis = a * unitToSatoshi; $scope.fundsAreInsufficient = !!$scope.fromWalletId - && spendableAmountInSatoshis !== null - && spendableAmountInSatoshis < amountInSatoshis; + && availableSatoshis !== null + && availableSatoshis < amountInSatoshis; $scope.alternativeAmount = txFormatService.formatAmount(amountInSatoshis, true); $scope.allowSend = lodash.isNumber(a) @@ -421,8 +428,8 @@ angular.module('copayApp.controllers').controller('amountController', function($ } } else { $scope.fundsAreInsufficient = !!$scope.fromWalletId - && spendableAmountInSatoshis !== null - && spendableAmountInSatoshis < result * unitToSatoshi; + && availableSatoshis !== null + && availableSatoshis < result * unitToSatoshi; $scope.alternativeAmount = $filter('formatFiatAmount')(toFiat(result)); $scope.allowSend = lodash.isNumber(result) @@ -664,13 +671,33 @@ angular.module('copayApp.controllers').controller('amountController', function($ }); }; - function updateSpendableAmountInSatoshisFromWallet(wallet) { + function updateAvailableFundsFromWallet(wallet) { if (wallet.status) { - spendableAmountInSatoshis = wallet.status.spendableAmount; - } else if (fromWallet.cachedStatus) { - spendableAmountInSatoshis = wallet.cachedStatus.spendableAmount; + availableFundsInCrypto = wallet.status.spendableBalanceStr; + availableSatoshis = wallet.status.spendableAmount; + if (wallet.status.alternativeBalanceAvailable) { + availableFundsInFiat = wallet.status.spendableBalanceAlternative + ' ' + wallet.status.alternativeIsoCode; + } else { + availableFundsInFiat = ''; + } + + } else if (wallet.cachedStatus) { + + if (wallet.cachedStatus.alternativeBalanceAvailable) { + availableFundsInFiat = wallet.cachedStatus.spendableBalanceAlternative + ' ' + wallet.cachedStatus.alternativeIsoCode; + } else { + availableFundsInFiat = ''; + } + availableFundsInCrypto = wallet.cachedStatus.spendableBalanceStr; + availableSatoshis = wallet.cachedStatus.spendableAmount; + } else { - spendableAmountInSatoshis = null; + + availableFundsInFiat = ''; + availableFundsInCrypto = ''; + availableSatoshis = null; } + + } }); From 9e7566b171fb7c46876b1a2adcb9bb705170147f Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 19 Jul 2018 12:58:17 +1200 Subject: [PATCH 018/240] Removing log statements. --- src/js/controllers/amount.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 1ea8f3104..92d54c281 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -293,7 +293,6 @@ angular.module('copayApp.controllers').controller('amountController', function($ $scope.availableFunds = availableFundsInCrypto; } - console.log('availableFunds: ' + $scope.availableFunds); updateUnitUI(); }; @@ -697,7 +696,5 @@ angular.module('copayApp.controllers').controller('amountController', function($ availableFundsInCrypto = ''; availableSatoshis = null; } - - } }); From d2d1511e61ed4cad15add697488d7d9c37ff766b Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 19 Jul 2018 13:03:29 +1200 Subject: [PATCH 019/240] Added some additional validation checks on wallet status. --- src/js/controllers/amount.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 92d54c281..7684aaac6 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -671,7 +671,7 @@ angular.module('copayApp.controllers').controller('amountController', function($ }; function updateAvailableFundsFromWallet(wallet) { - if (wallet.status) { + if (wallet.status && wallet.status.isValid) { availableFundsInCrypto = wallet.status.spendableBalanceStr; availableSatoshis = wallet.status.spendableAmount; if (wallet.status.alternativeBalanceAvailable) { @@ -680,7 +680,7 @@ angular.module('copayApp.controllers').controller('amountController', function($ availableFundsInFiat = ''; } - } else if (wallet.cachedStatus) { + } else if (wallet.cachedStatus && wallet.status.isValid) { if (wallet.cachedStatus.alternativeBalanceAvailable) { availableFundsInFiat = wallet.cachedStatus.spendableBalanceAlternative + ' ' + wallet.cachedStatus.alternativeIsoCode; From 19bec8a09aac2079abc632db721a1f4cdeaf5632 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 19 Jul 2018 15:34:53 +1200 Subject: [PATCH 020/240] Fixed update of available funds text. --- src/js/controllers/amount.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 7684aaac6..40a5f63ce 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -696,5 +696,12 @@ angular.module('copayApp.controllers').controller('amountController', function($ availableFundsInCrypto = ''; availableSatoshis = null; } + + if (availableUnits[unitIndex].isFiat) { + $scope.availableFunds = availableFundsInFiat || availableFundsInCrypto; + } else { + $scope.availableFunds = availableFundsInCrypto; + } + } }); From 0076ff26e6c637a553991ea0d109cb72dcccb1b0 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 19 Jul 2018 16:24:24 +1200 Subject: [PATCH 021/240] Currency of available funds changes when the currency of the amount is changed. --- src/js/controllers/amount.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 40a5f63ce..c705dc24b 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -665,11 +665,31 @@ angular.module('copayApp.controllers').controller('amountController', function($ availableUnits[altUnitIndex].name = newAltCurrency.isoCode; availableUnits[altUnitIndex].shortName = newAltCurrency.isoCode; fiatCode = newAltCurrency.isoCode; + updateAvailableFiatIfNeeded(); updateUnitUI(); $scope.close(); }); }; + + function updateAvailableFiatIfNeeded() { + if ($scope.fromWalletId && availableSatoshis !== null) { + availableFundsInFiat = ''; + $scope.availableFunds = availableFundsInCrypto; + var coin = availableUnits[altUnitIndex].isFiat ? availableUnits[unitIndex].id : availableUnits[altUnitIndex].id; + txFormatService.formatAlternativeStr(coin, availableSatoshis, function formatCallback(formatted){ + if (formatted) { + availableFundsInFiat = formatted; + if (availableUnits[unitIndex].isFiat) { + $scope.availableFunds = availableFundsInFiat; + } else { + $scope.availableFunds = availableFundsInCrypto; + } + } + }); + } + } + function updateAvailableFundsFromWallet(wallet) { if (wallet.status && wallet.status.isValid) { availableFundsInCrypto = wallet.status.spendableBalanceStr; From 0a96ae0dbedb9b421114283c6f847af2a4024dd2 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 19 Jul 2018 17:06:53 +1200 Subject: [PATCH 022/240] Added $scope.isRequestingSpecifcAmount. --- src/js/controllers/amount.js | 4 ++++ www/index.html | 7 +++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 52695e829..f4e789436 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -26,6 +26,8 @@ angular.module('copayApp.controllers').controller('amountController', function($ $scope.isAndroid = platformInfo.isAndroid; $scope.isIos = platformInfo.isIOS; + $scope.isRequestingSpecificAmount = false; + $scope.$on('$ionicView.leave', function() { angular.element($window).off('keydown'); }); @@ -51,6 +53,8 @@ angular.module('copayApp.controllers').controller('amountController', function($ } } + $scope.isRequestingSpecificAmount = !!data.stateParams.id; + var config = configService.getSync().wallet.settings; function setAvailableUnits() { diff --git a/www/index.html b/www/index.html index 4c73317e3..ecc2d923c 100644 --- a/www/index.html +++ b/www/index.html @@ -11,9 +11,8 @@ - Bitcoin.com Wallet - Bitcoin.com Wallet - - + Bitcoin.com Wallet + @@ -31,7 +30,7 @@ - + From 4f4bee27f1c53eef16b541542bae2c8fac586fe5 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 19 Jul 2018 09:59:56 +0200 Subject: [PATCH 023/240] Options also translatable --- i18n/po/template.pot | 10 ++++++++++ src/js/controllers/tab-settings.js | 2 +- www/views/preferencesPriceDisplay.html | 6 +++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index df01cecec..aed2220fd 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -201,6 +201,16 @@ msgstr "" msgid "Price Display" msgstr "" +#: src/js/controllers/tab-settings.js:19 +#: www/views/preferencesPriceDisplay.html:12 +msgid "Fiat" +msgstr "" + +#: src/js/controllers/tab-settings.js:19 +#: www/views/preferencesPriceDisplay.html:15 +msgid "Cryptocurrency" +msgstr "" + #: src/js/controllers/buyAmazon.js:98 msgid "Amazon.com is not available at this moment. Please try back later." msgstr "" diff --git a/src/js/controllers/tab-settings.js b/src/js/controllers/tab-settings.js index 4d0636d53..494d63cc5 100644 --- a/src/js/controllers/tab-settings.js +++ b/src/js/controllers/tab-settings.js @@ -16,7 +16,7 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct isoCode: config.wallet.settings.alternativeIsoCode }; - $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; + $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay === 'crypto' ? gettextCatalog.getString('Cryptocurrency') : gettextCatalog.getString('Fiat'); // TODO move this to a generic service bitpayAccountService.getAccounts(function(err, data) { diff --git a/www/views/preferencesPriceDisplay.html b/www/views/preferencesPriceDisplay.html index a4c12d273..31a9eb9b9 100644 --- a/www/views/preferencesPriceDisplay.html +++ b/www/views/preferencesPriceDisplay.html @@ -1,7 +1,7 @@ - {{'Price display'|translate}} + {{'Price Display'|translate}} @@ -9,10 +9,10 @@
- fiat + Fiat - cryptocurrency + Cryptocurrency
From 54478a4848619d538f694f04eabf862e8b19397f Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 19 Jul 2018 10:23:10 +0200 Subject: [PATCH 024/240] translation + faster transition --- i18n/po/template.pot | 4 ++++ src/sass/views/includes/slideToAcceptSuccess.scss | 2 +- www/views/includes/slideToAcceptSuccess.html | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index 4bab9ff2e..33616a9c5 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -2159,6 +2159,10 @@ msgstr "" msgid "Payment Sent" msgstr "" +#: www/views/includes/slideToAcceptSuccess.html:12 +msgid "Share this transaction" +msgstr "" + #: www/views/modals/txp-details.html:32 msgid "Payment accepted, but not yet broadcasted" msgstr "" diff --git a/src/sass/views/includes/slideToAcceptSuccess.scss b/src/sass/views/includes/slideToAcceptSuccess.scss index 724363b0d..f64dd6154 100644 --- a/src/sass/views/includes/slideToAcceptSuccess.scss +++ b/src/sass/views/includes/slideToAcceptSuccess.scss @@ -70,7 +70,7 @@ slide-to-accept-success { } &__share { transition: transform $duration ease, opacity $duration ease; - transition-delay: 1000ms; + transition-delay: 600ms; opacity: 0; margin-top: 15vh; span { diff --git a/www/views/includes/slideToAcceptSuccess.html b/www/views/includes/slideToAcceptSuccess.html index ed4ebfa77..9995001ae 100644 --- a/www/views/includes/slideToAcceptSuccess.html +++ b/www/views/includes/slideToAcceptSuccess.html @@ -9,7 +9,7 @@ Payment Sent
From b1befb1e7335c200541dd74f352517db78be60d2 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 19 Jul 2018 13:10:25 +0200 Subject: [PATCH 025/240] wallet Details buttons --- src/js/controllers/walletDetails.js | 2 ++ src/sass/views/walletDetails.scss | 16 ++++++++-------- www/views/walletDetails.html | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 24e4a5a20..b3fea717e 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -365,6 +365,8 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var t = amountTop; $scope.altAmountOpacity = (amountHeight - 100) / 80; + + $scope.buttonsOpacity = (amountHeight - 150)/80; $window.requestAnimationFrame(function() { $scope.amountHeight = amountHeight + 'px'; $scope.contentMargin = contentMargin + 'px'; diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index 2d1aa1343..6be9e6cf2 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -147,13 +147,13 @@ background-color: transparent; } } - .nav-bar-block, .bar { - background-color: inherit !important; - } + //.nav-bar-block, .bar { + //background-color: inherit !important; + //} ion-content { &.collapsible { - margin-top: 230px; + margin-top: 210px; } padding-top: 0; @@ -193,7 +193,7 @@ .buttons { margin-bottom: 0; - margin-top: 6px; + margin-top: 30px; >.col { padding: 5px 10px; margin-bottom: 0; @@ -206,8 +206,8 @@ width: 100%; font-size: 19px; font-weight: bolder; - min-height: 45px; - line-height: 45px; + min-height: 40px; + line-height: 40px; } } } @@ -215,7 +215,7 @@ width: 100%; text-align: center; color: #fff; - height: 230px; + height: 210px; padding-top: 40px; display: block; align-items: center; diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index 820930a95..e2c0f7342 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -90,7 +90,7 @@
-
+
Receive From 700d2c8a2398e4a38b476030e5b693756a7b23db Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 19 Jul 2018 13:19:56 +0200 Subject: [PATCH 026/240] currency symbol --- src/js/services/currencySymbolService.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/services/currencySymbolService.js b/src/js/services/currencySymbolService.js index b4cf0c2ac..14565a1a5 100644 --- a/src/js/services/currencySymbolService.js +++ b/src/js/services/currencySymbolService.js @@ -179,10 +179,11 @@ angular.module('copayApp.services').factory('currencySymbolService', function($l }; root.getCurrencySymbol = function(code) { + if (!code) + return false; code = code.toUpperCase(); if (root.currencySymbols[code]) { - $log.debug("Currency symbol for "+code+" found"); return root.currencySymbols[code]; } $log.debug("Currency symbol for "+code+" not found"); From 442f6ba4dffc247696975e884265964ad5e54448 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 19 Jul 2018 13:20:38 +0200 Subject: [PATCH 027/240] currency symbol --- src/js/services/currencySymbolService.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/services/currencySymbolService.js b/src/js/services/currencySymbolService.js index 14565a1a5..029cd9eb2 100644 --- a/src/js/services/currencySymbolService.js +++ b/src/js/services/currencySymbolService.js @@ -179,8 +179,8 @@ angular.module('copayApp.services').factory('currencySymbolService', function($l }; root.getCurrencySymbol = function(code) { - if (!code) - return false; + if (!code) return false; + code = code.toUpperCase(); if (root.currencySymbols[code]) { From 8ddfffd56b4dca5458545e5421cb467cdeb45be4 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 19 Jul 2018 15:21:27 +0200 Subject: [PATCH 028/240] amount on custom amount screen --- src/js/controllers/customAmount.js | 10 ++++++- src/sass/views/custom-amount.scss | 43 +++++++++++++++++++++++------- www/views/customAmount.html | 34 ++++++++++++++++++----- 3 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/js/controllers/customAmount.js b/src/js/controllers/customAmount.js index f6b96c22c..86bfa250f 100644 --- a/src/js/controllers/customAmount.js +++ b/src/js/controllers/customAmount.js @@ -64,6 +64,14 @@ angular.module('copayApp.controllers').controller('customAmountController', func var currency = parsedAmount.currency; $scope.amountUnitStr = parsedAmount.amountUnitStr; + configService.whenAvailable(function (config) { + $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; + + $timeout(function () { + $scope.$apply(); + }); + }); + if (currency != 'BTC' && currency != 'BCH') { // Convert to BTC or BCH var config = configService.getSync().wallet.settings; @@ -71,7 +79,7 @@ angular.module('copayApp.controllers').controller('customAmountController', func var btcParsedAmount = txFormatService.parseAmount($scope.wallet.coin, amountUnit, $scope.wallet.coin); $scope.amountBtc = btcParsedAmount.amount; - $scope.altAmountStr = btcParsedAmount.amountUnitStr; + $scope.altAmountStr = btcParsedAmount.amountUnitStr.toUpperCase(); } else { $scope.amountBtc = amount; // BTC or BCH $scope.altAmountStr = txFormatService.formatAlternativeStr($scope.wallet.coin, parsedAmount.amountSat); diff --git a/src/sass/views/custom-amount.scss b/src/sass/views/custom-amount.scss index b9bf65459..17973101d 100644 --- a/src/sass/views/custom-amount.scss +++ b/src/sass/views/custom-amount.scss @@ -26,16 +26,10 @@ height: 100%; .qr-code { text-align: center; - margin-top: 24vh; - margin-bottom: 7vh; - @media(max-height: 800px) { - margin-top: 18vh; - } - @media(max-height: 700px) { - margin-top: 14vh; - } - @media(max-height: 600px) { - margin-top: 8vh; + margin-top: 6px; + qrcode canvas { + height: 30vh; + max-height: 220px; } } .info { @@ -91,5 +85,34 @@ .address-types { text-align: center; } + + .amount { + margin-top: 20vh; + margin-bottom: 4vh; + @media(max-height: 800px) { + margin-top: 12vh; + margin-bottom: 6vh; + } + @media(max-height: 700px) { + margin-top: 10vh; + margin-bottom: 4vh; + } + @media(max-height: 600px) { + margin-top: 6vh; + margin-bottom: 2vh; + + } + width: 100%; + text-align: center; + //padding-top: 30px; + display: block; + align-items: center; + justify-content: center; + + &-alternative { + line-height: 36px; + } + } + } } diff --git a/www/views/customAmount.html b/www/views/customAmount.html index a4f2d57c9..73f107d82 100644 --- a/www/views/customAmount.html +++ b/www/views/customAmount.html @@ -11,7 +11,7 @@
-
-
-
+
Address {{address}} From 95faffa7698347a323cbaa53cdafa0c0dea25929 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 19 Jul 2018 15:30:29 +0200 Subject: [PATCH 029/240] remove bold font style --- www/views/customAmount.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/views/customAmount.html b/www/views/customAmount.html index 73f107d82..cac2bbc7d 100644 --- a/www/views/customAmount.html +++ b/www/views/customAmount.html @@ -34,13 +34,13 @@
- {{amountUnitStr}} + {{amountUnitStr}}
{{altAmountStr}}
- {{altAmountStr}} + {{altAmountStr}}
{{amountUnitStr}}
From 00ca5cded6f082052f6edbb34a0fc54cf6e211e5 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 19 Jul 2018 15:41:06 +0200 Subject: [PATCH 030/240] uppercase pipes --- src/js/controllers/customAmount.js | 2 +- www/views/customAmount.html | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/controllers/customAmount.js b/src/js/controllers/customAmount.js index 86bfa250f..a3916a2d4 100644 --- a/src/js/controllers/customAmount.js +++ b/src/js/controllers/customAmount.js @@ -79,7 +79,7 @@ angular.module('copayApp.controllers').controller('customAmountController', func var btcParsedAmount = txFormatService.parseAmount($scope.wallet.coin, amountUnit, $scope.wallet.coin); $scope.amountBtc = btcParsedAmount.amount; - $scope.altAmountStr = btcParsedAmount.amountUnitStr.toUpperCase(); + $scope.altAmountStr = btcParsedAmount.amountUnitStr; } else { $scope.amountBtc = amount; // BTC or BCH $scope.altAmountStr = txFormatService.formatAlternativeStr($scope.wallet.coin, parsedAmount.amountSat); diff --git a/www/views/customAmount.html b/www/views/customAmount.html index cac2bbc7d..849487735 100644 --- a/www/views/customAmount.html +++ b/www/views/customAmount.html @@ -36,11 +36,11 @@
{{amountUnitStr}}
- {{altAmountStr}} + {{altAmountStr | uppercase}}
- {{altAmountStr}} + {{altAmountStr | uppercase}}
{{amountUnitStr}}
@@ -80,7 +80,7 @@
Amount - {{amountUnitStr}} - {{altAmountStr}} + {{amountUnitStr}} - {{altAmountStr | uppercase}}
From a0b4f10ca9a91f91389752efcc7ef8bc9041ceab Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 19 Jul 2018 15:43:32 +0200 Subject: [PATCH 031/240] reverted IDE spacings --- www/views/customAmount.html | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/www/views/customAmount.html b/www/views/customAmount.html index 849487735..a71a60202 100644 --- a/www/views/customAmount.html +++ b/www/views/customAmount.html @@ -11,7 +11,7 @@
-
-
-
+
Address {{address}} From c3cded5cb06ca88e32b50d5b33dbf583e450fbba Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 19 Jul 2018 15:50:09 +0200 Subject: [PATCH 032/240] wallet to wallet (sub)title --- src/js/controllers/walletToWalletController.js | 4 ++++ www/views/wallet-to-wallet-transfer.html | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/js/controllers/walletToWalletController.js b/src/js/controllers/walletToWalletController.js index 77d2218e6..eaf8f7700 100644 --- a/src/js/controllers/walletToWalletController.js +++ b/src/js/controllers/walletToWalletController.js @@ -2,6 +2,10 @@ angular.module('copayApp.controllers').controller('walletToWalletController', function($scope, $rootScope, $log, profileService, configService) { + // TODO: change according to which screen this is, origin/destination + $scope.headerTitle = gettextCatalog.getString('Choose your origin wallet'); + $scope.headerSubtitle = gettextCatalog.getString('This is where the Bitcoin will be taken out from.'); + $scope.$on("$ionicView.enter", function(event, data) { $scope.walletsBch = profileService.getWallets({coin: 'bch'}); $scope.walletsBtc = profileService.getWallets({coin: 'btc'}); diff --git a/www/views/wallet-to-wallet-transfer.html b/www/views/wallet-to-wallet-transfer.html index f172f40da..87ae6f6ed 100644 --- a/www/views/wallet-to-wallet-transfer.html +++ b/www/views/wallet-to-wallet-transfer.html @@ -5,10 +5,10 @@
- Choose your origin wallet + {{headerTitle}}
- This is where the Bitcoin will be taken out from. + {{headerSubtitle}}
From 3952df634324176ccdbdb7652ac1992a48264dea Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 19 Jul 2018 17:07:30 +0200 Subject: [PATCH 033/240] forked the cordova media plugin to remove unused permissions --- app-template/config-template.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-template/config-template.xml b/app-template/config-template.xml index 1c7f5a30a..2f8e3db04 100644 --- a/app-template/config-template.xml +++ b/app-template/config-template.xml @@ -72,7 +72,7 @@ - + From cb82aa7253bf55bea89d5eebedda69b9809686d0 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 19 Jul 2018 17:11:24 +0200 Subject: [PATCH 034/240] OSX clipboard bugfix --- src/js/services/clipboardService.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/js/services/clipboardService.js b/src/js/services/clipboardService.js index ff28cd2e7..c53a1b764 100644 --- a/src/js/services/clipboardService.js +++ b/src/js/services/clipboardService.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.services').factory('clipboardService', function ($http, $log, platformInfo, nodeWebkitService, gettextCatalog, ionicToast, clipboard) { +angular.module('copayApp.services').factory('clipboardService', function ($http, $log, $timeout, platformInfo,nodeWebkitService, gettextCatalog, ionicToast, clipboard) { var root = {}; root.copyToClipboard = function (data) { @@ -26,7 +26,9 @@ angular.module('copayApp.services').factory('clipboardService', function ($http, cb(text); }) } else if (platformInfo.isNW) { - cb(nodeWebkitService.readFromClipboard()); + $timeout(function() { + cb(nodeWebkitService.readFromClipboard()); + },0); } else { navigator.clipboard.readText() .then(text => { From 1d5e116fa92f96bf99ab576d910de61e058f193f Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 19 Jul 2018 17:13:19 +0200 Subject: [PATCH 035/240] spacing --- src/js/services/clipboardService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/services/clipboardService.js b/src/js/services/clipboardService.js index 2420d45e9..b9851d626 100644 --- a/src/js/services/clipboardService.js +++ b/src/js/services/clipboardService.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.services').factory('clipboardService', function ($http, $log, $timeout, platformInfo,nodeWebkitService, gettextCatalog, ionicToast, clipboard) { +angular.module('copayApp.services').factory('clipboardService', function ($http, $log, $timeout, platformInfo, nodeWebkitService, gettextCatalog, ionicToast, clipboard) { var root = {}; root.copyToClipboard = function (data) { From da853bcbf685f0bcec66a108bea5b2ecf3772730 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 20 Jul 2018 18:15:25 +1200 Subject: [PATCH 036/240] Placeholders for buttons. --- www/views/shapeshift.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/www/views/shapeshift.html b/www/views/shapeshift.html index 27be00fbd..48664ebba 100644 --- a/www/views/shapeshift.html +++ b/www/views/shapeshift.html @@ -13,6 +13,8 @@
No available wallets to convert between. + +
From 713bbf71ca4efd4f04e6e551f0c005c070b26003 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Fri, 20 Jul 2018 10:06:59 +0200 Subject: [PATCH 037/240] currency format service + remove bottom amount in request amount screen --- src/js/services/txFormatService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/services/txFormatService.js b/src/js/services/txFormatService.js index ebcb3886a..1932ebd2a 100644 --- a/src/js/services/txFormatService.js +++ b/src/js/services/txFormatService.js @@ -201,7 +201,7 @@ angular.module('copayApp.services').factory('txFormatService', function($filter, var alternativeIsoCode = config.alternativeIsoCode; // If fiat currency - if (currency != 'BCH' && currency != 'BTC' && currency != 'sat') { + if (currency && currency.toUpperCase() != 'BCH' && currency.toUpperCase() != 'BTC' && currency != 'sat') { amountUnitStr = $filter('formatFiatAmount')(amount) + ' ' + currency; amountSat = rateService.fromFiat(amount, currency, coin).toFixed(0); } else if (currency == 'sat') { From 1153830d05724322aa89444273c70a23e072ba77 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Fri, 20 Jul 2018 10:41:09 +0200 Subject: [PATCH 038/240] custom amount screen --- www/views/customAmount.html | 6 ------ 1 file changed, 6 deletions(-) diff --git a/www/views/customAmount.html b/www/views/customAmount.html index a71a60202..d6a474d1a 100644 --- a/www/views/customAmount.html +++ b/www/views/customAmount.html @@ -71,12 +71,6 @@ {{address}}
-
- Amount - - {{amountUnitStr}} - {{altAmountStr | uppercase}} - -
From 893dbe5c6fa4a8fad918adda1e036c8a95656700 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 23 Jul 2018 14:20:46 +0200 Subject: [PATCH 039/240] "has no funds" case --- .../controllers/walletToWalletController.js | 24 ++++--- src/js/services/profileService.js | 7 ++ src/sass/views/wallet-to-wallet-transfer.scss | 69 ++++++++++++++++--- www/views/wallet-to-wallet-transfer.html | 24 +++++-- 4 files changed, 103 insertions(+), 21 deletions(-) diff --git a/src/js/controllers/walletToWalletController.js b/src/js/controllers/walletToWalletController.js index eaf8f7700..e12b78837 100644 --- a/src/js/controllers/walletToWalletController.js +++ b/src/js/controllers/walletToWalletController.js @@ -1,14 +1,23 @@ 'use strict'; -angular.module('copayApp.controllers').controller('walletToWalletController', function($scope, $rootScope, $log, profileService, configService) { - - // TODO: change according to which screen this is, origin/destination - $scope.headerTitle = gettextCatalog.getString('Choose your origin wallet'); - $scope.headerSubtitle = gettextCatalog.getString('This is where the Bitcoin will be taken out from.'); +angular.module('copayApp.controllers').controller('walletToWalletController', function($scope, $rootScope, $log, configService, gettextCatalog, profileService) { $scope.$on("$ionicView.enter", function(event, data) { - $scope.walletsBch = profileService.getWallets({coin: 'bch'}); - $scope.walletsBtc = profileService.getWallets({coin: 'btc'}); + $scope.type = 'origin'; + $scope.coin = 'bch'; + $scope.walletsEmpty = []; + $scope.isPaymentRequest = true; + + if ($scope.type === 'origin') { + $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); + $scope.walletsEmpty = profileService.getWallets({coin: $scope.coin, hasNoFunds: true}); + } else if ($scope.type === 'destination') { + $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to'); + } + + $scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: $scope.type==='origin'}); + $scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: $scope.type==='origin'}); + configService.whenAvailable(function(config) { $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; }); @@ -17,5 +26,4 @@ angular.module('copayApp.controllers').controller('walletToWalletController', fu $scope.useWallet = function(wallet) { // Do something with selected wallet }; - }); \ No newline at end of file diff --git a/src/js/services/profileService.js b/src/js/services/profileService.js index 4f8710c28..c86d263f2 100644 --- a/src/js/services/profileService.js +++ b/src/js/services/profileService.js @@ -847,6 +847,13 @@ angular.module('copayApp.services') }); } + if (opts.hasNoFunds) { + ret = lodash.filter(ret, function(w) { + if (!w.status) return; + return (w.status.availableBalanceSat === 0); + }); + } + if (opts.minAmount) { ret = lodash.filter(ret, function(w) { if (!w.status) return; diff --git a/src/sass/views/wallet-to-wallet-transfer.scss b/src/sass/views/wallet-to-wallet-transfer.scss index e909258cf..7328a2891 100644 --- a/src/sass/views/wallet-to-wallet-transfer.scss +++ b/src/sass/views/wallet-to-wallet-transfer.scss @@ -1,18 +1,71 @@ #wallet-to-wallet-transfer { - .header, .list { + .header--request { + padding: 30px 24px; + width: 100%; + height: 139px; + background-color: #fff; + &__title { + width: 46px; + height: 20px; + font-size: 16px; + font-weight: 600; + letter-spacing: -0.4px; + color: #000000; + } + &__amount { + font-size: 29px; + font-weight: 600; + letter-spacing: -0.7px; + color: #000000; + margin: 11px 0 2px; + } + &__amount-alt { + opacity: 0.45; + font-size: 16px; + font-weight: 600; + letter-spacing: -0.4px; + color: #000000; + } + } + .wallets-header { font-size: 12px; - margin: 20px 14px; + margin: 20px 14px 0px; .title { - font-size: 20px; + font-size: 16px; font-weight: bold; color: $v-dark-gray; + margin-bottom: -12px; } - .subtitle { - font-size: 12px; - line-height: 1.5em; - font-weight: 300; - color: $v-dark-gray; + } + .card { + font-size: 12px; + margin: 20px 14px 0px; + + .item-heading { + font-weight: 600; + } + + &-insufficient { + .wallet { + opacity: 0.4; + + } + .item-heading { + font-size: 12px; + >div { + display: inline-block; + vertical-align: text-bottom; + } + } + &__dot { + display: inline-block; + width: 16px; + height: 16px; + background-color: #ec5959; + border-radius: 8px; + margin: 2px 6px 2px 2px; + } } } } \ No newline at end of file diff --git a/www/views/wallet-to-wallet-transfer.html b/www/views/wallet-to-wallet-transfer.html index 87ae6f6ed..2055e4845 100644 --- a/www/views/wallet-to-wallet-transfer.html +++ b/www/views/wallet-to-wallet-transfer.html @@ -3,13 +3,15 @@ {{'Wallet to wallet transfer' | translate}} -
+
+
Paying
+
$37.42 USD
+
0.04580000 BCH
+
+
{{headerTitle}}
-
- {{headerSubtitle}} -
@@ -25,7 +27,7 @@
-
+
Bitcoin Core (BTC)
@@ -37,5 +39,17 @@
+ +
+
+
Insufficient funds
+
+
+ + + +
+
\ No newline at end of file From 7aecd2306f64188e45be5f4a65a491a08f9f70c1 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 23 Jul 2018 14:48:25 +0200 Subject: [PATCH 040/240] comments & show wallets --- src/js/controllers/walletToWalletController.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/js/controllers/walletToWalletController.js b/src/js/controllers/walletToWalletController.js index e12b78837..d83f93ddb 100644 --- a/src/js/controllers/walletToWalletController.js +++ b/src/js/controllers/walletToWalletController.js @@ -3,10 +3,10 @@ angular.module('copayApp.controllers').controller('walletToWalletController', function($scope, $rootScope, $log, configService, gettextCatalog, profileService) { $scope.$on("$ionicView.enter", function(event, data) { - $scope.type = 'origin'; - $scope.coin = 'bch'; + $scope.type = 'origin'; // origin || destination + $scope.coin = false; // Wallets to show (for destination screen) $scope.walletsEmpty = []; - $scope.isPaymentRequest = true; + $scope.isPaymentRequest = true; // Show price-header if ($scope.type === 'origin') { $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); @@ -15,8 +15,12 @@ angular.module('copayApp.controllers').controller('walletToWalletController', fu $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to'); } - $scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: $scope.type==='origin'}); - $scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: $scope.type==='origin'}); + if (!$scope.coin || $scope.coin === 'bch') { + $scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: $scope.type==='origin'}); + } + if (!$scope.coin || $scope.coin === 'btc') { + $scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: $scope.type === 'origin'}); + } configService.whenAvailable(function(config) { $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; From 3604ee3c3cc66a9233ed2c8af5091563f605565e Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 23 Jul 2018 16:58:32 +0200 Subject: [PATCH 041/240] routes, renames and flow --- src/js/controllers/sendFlowController.js | 69 ++++++++++++++++++ src/js/controllers/tab-send.js | 73 +++++++++---------- .../controllers/walletToWalletController.js | 33 --------- src/js/routes.js | 22 +++++- src/js/services/incomingData.js | 6 +- src/sass/views/views.scss | 2 +- ...er.scss => wallet-origin-destination.scss} | 2 +- www/views/tab-send.html | 54 +++++++------- ...er.html => wallet-origin-destination.html} | 4 +- 9 files changed, 160 insertions(+), 105 deletions(-) create mode 100644 src/js/controllers/sendFlowController.js delete mode 100644 src/js/controllers/walletToWalletController.js rename src/sass/views/{wallet-to-wallet-transfer.scss => wallet-origin-destination.scss} (97%) rename www/views/{wallet-to-wallet-transfer.html => wallet-origin-destination.html} (96%) diff --git a/src/js/controllers/sendFlowController.js b/src/js/controllers/sendFlowController.js new file mode 100644 index 000000000..ec29d7b50 --- /dev/null +++ b/src/js/controllers/sendFlowController.js @@ -0,0 +1,69 @@ +'use strict'; + +angular.module('copayApp.controllers').controller('sendFlowController', function($scope, $rootScope, $state, $stateParams, $log, configService, gettextCatalog, profileService) { + + var unitToSatoshi; + var satToUnit; + var unitDecimals; + var satToBtc; + + $scope.$on("$ionicView.beforeEnter", function(event, data) { + var config = configService.getSync().wallet.settings; + + $scope.specificAmount = $scope.specificAlternativeAmount = ''; + unitToSatoshi = config.unitToSatoshi; + satToUnit = 1 / unitToSatoshi; + satToBtc = 1 / 100000000; + unitDecimals = config.unitDecimals; + + // in SAT ALWAYS + if ($stateParams.toAmount) { + $scope.requestAmount = (($stateParams.toAmount) * satToUnit).toFixed(unitDecimals); + $scope.isPaymentRequest = true; + } + + console.log(data, $stateParams); + + $scope.params = $stateParams; + }); + + $scope.$on("$ionicView.enter", function(event, data) { + console.log(data, $stateParams); + $scope.type = data.stateParams.fromWalletId ? 'destination' : 'origin'; // origin || destination + $scope.coin = false; // Wallets to show (for destination screen) + $scope.walletsEmpty = []; + // Show price-header + + if ($scope.type === 'origin') { + $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); + $scope.walletsEmpty = profileService.getWallets({coin: $scope.coin, hasNoFunds: true}); + } else if ($scope.type === 'destination') { + + $scope.fromWallet = profileService.getWallet(data.stateParams.fromWalletId); + $scope.coin = $scope.fromWallet.coin; + + $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to'); + } + + if (!$scope.coin || $scope.coin === 'bch') { + $scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: $scope.type==='origin'}); + } + if (!$scope.coin || $scope.coin === 'btc') { + $scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: $scope.type === 'origin'}); + } + + configService.whenAvailable(function(config) { + $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; + }); + }); + + $scope.useWallet = function(wallet) { + if ($scope.type === 'origin') { + $scope.params['fromWalletId'] = wallet.id; + $state.transitionTo('tabs.send.destination', $scope.params); + } else { + $scope.params['toWalletId'] = wallet.id; + $state.transitionTo('tabs.send.amount', $scope.params); + } + }; +}); \ No newline at end of file diff --git a/src/js/controllers/tab-send.js b/src/js/controllers/tab-send.js index 99265457d..8e0ae6b92 100644 --- a/src/js/controllers/tab-send.js +++ b/src/js/controllers/tab-send.js @@ -54,42 +54,42 @@ angular.module('copayApp.controllers').controller('tabSendController', function( updateList(); }); }); - - var wallets; - var walletsBch; - var walletsBtc; - var walletToWalletFrom = false; - - $scope.onWalletSelect = function(wallet) { - if (!$scope.walletToWalletFrom) { - $scope.walletToWalletFrom = wallet; - if (wallet.coin === 'bch') { - $scope.showWalletsBch = true; - } else if (wallet.coin === 'btc') { - $scope.showWalletsBtc = true; - } - $scope.walletSelectorTitleTo = gettextCatalog.getString('Send to'); - } else { - $ionicLoading.show(); - walletService.getAddress(wallet, true, function(err, addr) { - $ionicLoading.hide(); - return $state.transitionTo('tabs.send.amount', { - displayAddress: $scope.walletToWalletFrom.coin === 'bch' ? bitcoinCashJsService.translateAddresses(addr).cashaddr : addr, - recipientType: 'wallet', - fromWalletId: $scope.walletToWalletFrom.id, - toAddress: addr, - coin: $scope.walletToWalletFrom.coin - }); - }); - - } - }; - - $scope.showWalletSelector = function() { - $scope.walletToWalletFrom = false; - $scope.walletSelectorTitleFrom = gettextCatalog.getString('Send from'); - $scope.showWallets = true; - }; + // + // var wallets; + // var walletsBch; + // var walletsBtc; + // var walletToWalletFrom = false; + // + // $scope.onWalletSelect = function(wallet) { + // if (!$scope.walletToWalletFrom) { + // $scope.walletToWalletFrom = wallet; + // if (wallet.coin === 'bch') { + // $scope.showWalletsBch = true; + // } else if (wallet.coin === 'btc') { + // $scope.showWalletsBtc = true; + // } + // $scope.walletSelectorTitleTo = gettextCatalog.getString('Send to'); + // } else { + // $ionicLoading.show(); + // walletService.getAddress(wallet, true, function(err, addr) { + // $ionicLoading.hide(); + // return $state.transitionTo('tabs.send.amount', { + // displayAddress: $scope.walletToWalletFrom.coin === 'bch' ? bitcoinCashJsService.translateAddresses(addr).cashaddr : addr, + // recipientType: 'wallet', + // fromWalletId: $scope.walletToWalletFrom.id, + // toAddress: addr, + // coin: $scope.walletToWalletFrom.coin + // }); + // }); + // + // } + // }; + // + // $scope.showWalletSelector = function() { + // $scope.walletToWalletFrom = false; + // $scope.walletSelectorTitleFrom = gettextCatalog.getString('Send from'); + // $scope.showWallets = true; + // }; $scope.findContact = function(search) { @@ -133,7 +133,6 @@ angular.module('copayApp.controllers').controller('tabSendController', function( }; var updateHasFunds = function() { - $scope.hasFunds = false; var index = 0; lodash.each($scope.wallets, function(w) { diff --git a/src/js/controllers/walletToWalletController.js b/src/js/controllers/walletToWalletController.js deleted file mode 100644 index d83f93ddb..000000000 --- a/src/js/controllers/walletToWalletController.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('walletToWalletController', function($scope, $rootScope, $log, configService, gettextCatalog, profileService) { - - $scope.$on("$ionicView.enter", function(event, data) { - $scope.type = 'origin'; // origin || destination - $scope.coin = false; // Wallets to show (for destination screen) - $scope.walletsEmpty = []; - $scope.isPaymentRequest = true; // Show price-header - - if ($scope.type === 'origin') { - $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); - $scope.walletsEmpty = profileService.getWallets({coin: $scope.coin, hasNoFunds: true}); - } else if ($scope.type === 'destination') { - $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to'); - } - - if (!$scope.coin || $scope.coin === 'bch') { - $scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: $scope.type==='origin'}); - } - if (!$scope.coin || $scope.coin === 'btc') { - $scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: $scope.type === 'origin'}); - } - - configService.whenAvailable(function(config) { - $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; - }); - }); - - $scope.useWallet = function(wallet) { - // Do something with selected wallet - }; -}); \ No newline at end of file diff --git a/src/js/routes.js b/src/js/routes.js index 72397d872..29273d444 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -270,6 +270,24 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } } }) + .state('tabs.send.origin', { + url: '/origin/:thirdParty/:amount/:toAddress/:toWalletId', + views: { + 'tab-send@tabs': { + controller: 'sendFlowController', + templateUrl: 'views/wallet-origin-destination.html', + } + } + }) + .state('tabs.send.destination', { + url: '/destination/:fromWalletId/:thirdParty/:amount', + views: { + 'tab-send@tabs': { + controller: 'sendFlowController', + templateUrl: 'views/wallet-origin-destination.html', + } + } + }) .state('tabs.settings', { url: '/settings', views: { @@ -299,7 +317,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr url: '/wallet-to-wallet', views: { 'tab-send@tabs': { - controller: 'walletToWalletController', + controller: 'sendFlowController', templateUrl: 'views/wallet-to-wallet-transfer.html' } } @@ -1273,7 +1291,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr if (screen.width < 768 && platformInfo.isCordova) screen.lockOrientation('portrait'); - if (ionic.Platform.isAndroid() && StatusBar) { + if (ionic.Platform.isAndroid() && platformInfo.isCordova && StatusBar) { StatusBar.backgroundColorByHexString('#000000'); } diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index 1bb87b49c..fc3d37d47 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -83,7 +83,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat // Timeout is required to enable the "Back" button $timeout(function() { if (amount) { - $state.transitionTo('tabs.send.confirm', { + $state.transitionTo('tabs.send.origin', { toAmount: amount, toAddress: addr, displayAddress: originalAddress ? originalAddress : addr, @@ -102,8 +102,10 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat params['minShapeshiftAmount'] = shapeshiftData.minAmount; params['maxShapeshiftAmount'] = shapeshiftData.maxAmount; params['shapeshiftOrderId'] = shapeshiftData.orderId; + $state.transitionTo('tabs.send.amount', params); + } else { + $state.transitionTo('tabs.send.origin', params); } - $state.transitionTo('tabs.send.amount', params); } }, 100); } diff --git a/src/sass/views/views.scss b/src/sass/views/views.scss index 538787901..e1a122dbb 100644 --- a/src/sass/views/views.scss +++ b/src/sass/views/views.scss @@ -8,7 +8,7 @@ @import "tab-receive"; @import "tab-scan"; @import "tab-send"; -@import "wallet-to-wallet-transfer"; +@import "wallet-origin-destination"; @import "tab-settings"; @import "wallet-colors"; @import "walletBalance"; diff --git a/src/sass/views/wallet-to-wallet-transfer.scss b/src/sass/views/wallet-origin-destination.scss similarity index 97% rename from src/sass/views/wallet-to-wallet-transfer.scss rename to src/sass/views/wallet-origin-destination.scss index 7328a2891..94fbe8e4b 100644 --- a/src/sass/views/wallet-to-wallet-transfer.scss +++ b/src/sass/views/wallet-origin-destination.scss @@ -1,4 +1,4 @@ -#wallet-to-wallet-transfer { +#wallet-origin-destination { .header--request { padding: 30px 24px; width: 100%; diff --git a/www/views/tab-send.html b/www/views/tab-send.html index b5d556214..a1e8a778f 100644 --- a/www/views/tab-send.html +++ b/www/views/tab-send.html @@ -105,31 +105,31 @@
- - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/views/wallet-to-wallet-transfer.html b/www/views/wallet-origin-destination.html similarity index 96% rename from www/views/wallet-to-wallet-transfer.html rename to www/views/wallet-origin-destination.html index 2055e4845..66f5852ad 100644 --- a/www/views/wallet-to-wallet-transfer.html +++ b/www/views/wallet-origin-destination.html @@ -1,4 +1,4 @@ - + {{'Wallet to wallet transfer' | translate}} @@ -6,7 +6,7 @@
Paying
$37.42 USD
-
0.04580000 BCH
+
0.04580000 BCH {{requestAmount}}
From ac91282c13ac419f15513106df9eb1f52adc114c Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 23 Jul 2018 12:02:04 -0700 Subject: [PATCH 042/240] Indicative change for view. --- www/views/amount.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/views/amount.html b/www/views/amount.html index af4e9d55c..8361dcd73 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -27,7 +27,7 @@
-
-
- +
From fd4adbfb57be5d678a5319c9e17f4e2c75b771b4 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Tue, 24 Jul 2018 14:24:22 +0200 Subject: [PATCH 045/240] wallet to wallet case worked out. Also fixed routes and some css --- src/js/controllers/amount.js | 27 +-- src/js/controllers/confirm.js | 174 +++++++++--------- src/js/controllers/sendFlowController.js | 31 ++-- src/js/routes.js | 42 ++--- src/js/services/incomingData.js | 4 +- src/sass/views/wallet-origin-destination.scss | 5 +- 6 files changed, 152 insertions(+), 131 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 52695e829..c8383a510 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -31,7 +31,6 @@ angular.module('copayApp.controllers').controller('amountController', function($ }); $scope.$on("$ionicView.beforeEnter", function(event, data) { - initCurrencies(); if (data.stateParams.shapeshiftOrderId && data.stateParams.shapeshiftOrderId.length > 0) { @@ -42,6 +41,7 @@ angular.module('copayApp.controllers').controller('amountController', function($ // To get the wallet from with the new flow $scope.fromWalletId = data.stateParams.fromWalletId; + $scope.toWalletId = data.stateParams.toWalletId; if (data.stateParams.noPrefix) { $scope.showWarningMessage = data.stateParams.noPrefix != 0; @@ -156,10 +156,10 @@ angular.module('copayApp.controllers').controller('amountController', function($ $scope.toEmail = data.stateParams.toEmail; $scope.toColor = data.stateParams.toColor; - if (!$scope.nextStep && !data.stateParams.toAddress) { - $log.error('Bad params at amount') - throw ('bad params'); - } + // if (!$scope.nextStep && !data.stateParams.toAddress) { + // $log.error('Bad params at amount') + // throw ('bad params'); + // } var reNr = /^[1234567890\.]$/; var reOp = /^[\*\+\-\/]$/; @@ -198,8 +198,8 @@ angular.module('copayApp.controllers').controller('amountController', function($ $scope.resetAmount(); // in SAT ALWAYS - if ($stateParams.toAmount) { - $scope.amountModel.amount = (($stateParams.toAmount) * satToUnit).toFixed(unitDecimals); + if ($stateParams.amount) { + $scope.amountModel.amount = (($stateParams.amount) * satToUnit).toFixed(unitDecimals); } $scope.processAmount(); @@ -461,7 +461,8 @@ angular.module('copayApp.controllers').controller('amountController', function($ currency: unit.id.toUpperCase(), coin: coin, useSendMax: $scope.useSendMax, - fromWalletId: $scope.fromWalletId + fromWalletId: $scope.fromWalletId, + toWalletId: $scope.toWalletId }); } else { var amount = _amount; @@ -474,7 +475,7 @@ angular.module('copayApp.controllers').controller('amountController', function($ var confirmData = { recipientType: $scope.recipientType, - toAmount: amount, + amount: amount, toAddress: $scope.toAddress, displayAddress: $scope.displayAddress || $scope.toAddress, toName: $scope.toName, @@ -482,14 +483,14 @@ angular.module('copayApp.controllers').controller('amountController', function($ toColor: $scope.toColor, coin: coin, useSendMax: $scope.useSendMax, - fromWalletId: $scope.fromWalletId + fromWalletId: $scope.fromWalletId, + toWalletId: $scope.toWalletId }; if ($scope.shapeshiftOrderId) { var shapeshiftOrderUrl = 'https://www.shapeshift.io/#/status/'; shapeshiftOrderUrl += $scope.shapeshiftOrderId; confirmData.description = shapeshiftOrderUrl; - confirmData.fromWalletId = $scope.fromWalletId; if (confirmData.useSendMax) { var wallet = lodash.find(profileService.getWallets({ coin: coin }), @@ -500,10 +501,10 @@ angular.module('copayApp.controllers').controller('amountController', function($ var balance = parseFloat(wallet.cachedBalance.substring(0, wallet.cachedBalance.length-4)); if (balance < $scope.minShapeshiftAmount * 1.04) { confirmData.useSendMax = false; - confirmData.toAmount = $scope.minShapeshiftAmount * unitToSatoshi; + confirmData.amount = $scope.minShapeshiftAmount * unitToSatoshi; } else if (balance > $scope.maxShapeshiftAmount) { confirmData.useSendMax = false; - confirmData.toAmount = $scope.maxShapeshiftAmount * unitToSatoshi * 0.99; + confirmData.amount = $scope.maxShapeshiftAmount * unitToSatoshi * 0.99; } } } diff --git a/src/js/controllers/confirm.js b/src/js/controllers/confirm.js index 03af26fd1..74b86e623 100644 --- a/src/js/controllers/confirm.js +++ b/src/js/controllers/confirm.js @@ -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, soundService) { +angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, $ionicLoading, 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; @@ -68,82 +68,95 @@ angular.module('copayApp.controllers').controller('confirmController', function( }); }; + var setWalletSelector = function(coin, network, minAmount, cb) { + + // no min amount? (sendMax) => look for no empty wallets + minAmount = minAmount || 1; + + $scope.wallets = profileService.getWallets({ + onlyComplete: true, + network: network, + coin: coin + }); + + if (tx.fromWalletId) { + $scope.wallets = lodash.filter($scope.wallets, function (w) { + return w.id == tx.fromWalletId; + }); + } + + + if (!$scope.wallets || !$scope.wallets.length) { + setNoWallet(gettextCatalog.getString('No wallets available'), true); + 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(gettextCatalog.getString('Insufficient confirmed funds'), true); + } + $scope.wallets = lodash.clone(filteredWallets); + return cb(); + } + }); + }); + }; $scope.$on("$ionicView.beforeEnter", function(event, data) { - - function setWalletSelector(coin, network, minAmount, cb) { - - // no min amount? (sendMax) => look for no empty wallets - minAmount = minAmount || 1; - - $scope.wallets = profileService.getWallets({ - onlyComplete: true, - network: network, - coin: coin - }); - - if (tx.fromWalletId) { - $scope.wallets = lodash.filter($scope.wallets, function(w) { - return w.id == tx.fromWalletId; - }); - } - - - - if (!$scope.wallets || !$scope.wallets.length) { - setNoWallet(gettextCatalog.getString('No wallets available'), true); - 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(gettextCatalog.getString('Insufficient confirmed funds'), true); - } - $scope.wallets = lodash.clone(filteredWallets); - return cb(); - } - }); - }); - }; - // Setup $scope var B = data.stateParams.coin == 'bch' ? bitcoreCash : bitcore; var networkName; + $scope.fromWallet = profileService.getWallet(data.stateParams.fromWalletId); + $scope.recipientType = null; + try { - networkName = (new B.Address(data.stateParams.toAddress)).network.name; - } catch(e) { + if (data.stateParams.toWalletId) { + $scope.recipientType = 'wallet'; // set type to wallet-to-wallet + $ionicLoading.show(); + var wallet = profileService.getWallet(data.stateParams.toWalletId); + walletService.getAddress(wallet, true, function (err, addr) { + $ionicLoading.hide(); + data.stateParams.toAddress = addr; + networkName = (new B.Address(data.stateParams.toAddress)).network.name; + vanityTx(networkName, data); + }); + } else if (data.stateParams.toAddress) { + networkName = (new B.Address(data.stateParams.toAddress)).network.name; + vanityTx(networkName, data); + } + } catch (e) { var message = gettextCatalog.getString('Invalid address'); var backText = gettextCatalog.getString('Go back'); var learnText = gettextCatalog.getString('Learn more'); - popupService.showConfirm(null, message, backText, learnText, function(back) { + popupService.showConfirm(null, message, backText, learnText, function (back) { $ionicHistory.nextViewOptions({ disableAnimate: true, historyRoot: true }); - $state.go('tabs.send').then(function() { + $state.go('tabs.send').then(function () { $ionicHistory.clearHistory(); if (!back) { var url = 'https://support.bitpay.com/hc/en-us/articles/115004671663'; @@ -153,27 +166,24 @@ angular.module('copayApp.controllers').controller('confirmController', function( }); return; } - + }); + var vanityTx = function(networkName, data) { // Grab stateParams tx = { - toAmount: parseInt(data.stateParams.toAmount), + amount: parseInt(data.stateParams.amount), sendMax: data.stateParams.useSendMax == 'true' ? true : false, fromWalletId: data.stateParams.fromWalletId, toAddress: data.stateParams.toAddress, - displayAddress: data.stateParams.displayAddress, - 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, + recipientType: $scope.recipientType || null, toName: data.stateParams.toName, toEmail: data.stateParams.toEmail, toColor: data.stateParams.toColor, network: networkName, - coin: data.stateParams.coin, + coin: $scope.fromWallet.coin, txp: {}, }; @@ -188,12 +198,10 @@ angular.module('copayApp.controllers').controller('confirmController', function( // Other Scope vars $scope.isCordova = isCordova; - $scope.isWindowsPhoneApp = isWindowsPhoneApp; $scope.showAddress = false; - $scope.walletSelectorTitle = gettextCatalog.getString('Send from'); - setWalletSelector(tx.coin, tx.network, tx.toAmount, function(err) { + setWalletSelector(tx.coin, tx.network, tx.amount, function(err) { if (err) { return exitWithError('Could not update wallets'); } @@ -207,7 +215,7 @@ angular.module('copayApp.controllers').controller('confirmController', function( $scope.displayBalanceAsFiat = walletConfig.settings.priceDisplay === 'fiat'; - }); + }; function getSendMaxInfo(tx, wallet, cb) { @@ -231,7 +239,7 @@ angular.module('copayApp.controllers').controller('confirmController', function( return setSendError(msg); } - if (tx.toAmount > Number.MAX_SAFE_INTEGER) { + if (tx.amount > Number.MAX_SAFE_INTEGER) { var msg = gettextCatalog.getString('Amount too big'); $log.warn(msg); return setSendError(msg); @@ -241,7 +249,7 @@ angular.module('copayApp.controllers').controller('confirmController', function( txp.outputs = [{ 'toAddress': tx.toAddress, - 'amount': tx.toAmount, + 'amount': tx.amount, 'message': tx.description }]; @@ -280,13 +288,13 @@ angular.module('copayApp.controllers').controller('confirmController', function( $scope.tx = tx; function updateAmount() { - if (!tx.toAmount) return; + if (!tx.amount) return; // Amount - tx.amountStr = txFormatService.formatAmountStr(wallet.coin, tx.toAmount); + tx.amountStr = txFormatService.formatAmountStr(wallet.coin, tx.amount); tx.amountValueStr = tx.amountStr.split(' ')[0]; tx.amountUnitStr = tx.amountStr.split(' ')[1]; - txFormatService.formatAlternativeStr(wallet.coin, tx.toAmount, function(v) { + txFormatService.formatAlternativeStr(wallet.coin, tx.amount, function(v) { var parts = v.split(' '); tx.alternativeAmountStr = v; tx.alternativeAmountValueStr = parts[0]; @@ -342,7 +350,7 @@ angular.module('copayApp.controllers').controller('confirmController', function( } tx.sendMaxInfo = sendMaxInfo; - tx.toAmount = tx.sendMaxInfo.amount; + tx.amount = tx.sendMaxInfo.amount; updateAmount(); ongoingProcess.set('calculatingFee', false); $timeout(function() { @@ -393,7 +401,7 @@ angular.module('copayApp.controllers').controller('confirmController', function( function useSelectedWallet() { if (!$scope.useSendMax) { - showAmount(tx.toAmount); + showAmount(tx.amount); } $scope.onWalletSelect($scope.wallet); diff --git a/src/js/controllers/sendFlowController.js b/src/js/controllers/sendFlowController.js index ec29d7b50..d6f2b707d 100644 --- a/src/js/controllers/sendFlowController.js +++ b/src/js/controllers/sendFlowController.js @@ -6,34 +6,34 @@ angular.module('copayApp.controllers').controller('sendFlowController', function var satToUnit; var unitDecimals; var satToBtc; + var nextStep = ''; $scope.$on("$ionicView.beforeEnter", function(event, data) { var config = configService.getSync().wallet.settings; - + $scope.params = $stateParams; $scope.specificAmount = $scope.specificAlternativeAmount = ''; unitToSatoshi = config.unitToSatoshi; satToUnit = 1 / unitToSatoshi; satToBtc = 1 / 100000000; unitDecimals = config.unitDecimals; - // in SAT ALWAYS - if ($stateParams.toAmount) { - $scope.requestAmount = (($stateParams.toAmount) * satToUnit).toFixed(unitDecimals); + if ($scope.params.amount) { + console.log("is Payment Request", $scope.params.amount); + + $scope.requestAmount = (($stateParams.amount) * satToUnit).toFixed(unitDecimals); $scope.isPaymentRequest = true; } - - console.log(data, $stateParams); - - $scope.params = $stateParams; }); $scope.$on("$ionicView.enter", function(event, data) { console.log(data, $stateParams); - $scope.type = data.stateParams.fromWalletId ? 'destination' : 'origin'; // origin || destination + $scope.type = data.stateParams && data.stateParams['fromWalletId'] ? 'destination' : 'origin'; // origin || destination $scope.coin = false; // Wallets to show (for destination screen) $scope.walletsEmpty = []; // Show price-header + console.log("current type: "+$scope.type); + if ($scope.type === 'origin') { $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); $scope.walletsEmpty = profileService.getWallets({coin: $scope.coin, hasNoFunds: true}); @@ -57,13 +57,22 @@ angular.module('copayApp.controllers').controller('sendFlowController', function }); }); + function getNextStep() { + if (!$scope.params.toWalletId && !$scope.params.toAddress) { + return 'tabs.send.destination'; + } else if (!$scope.params.amount) { + return 'tabs.send.amount'; + } else { + return 'tabs.send.confirm'; + } + } + $scope.useWallet = function(wallet) { if ($scope.type === 'origin') { $scope.params['fromWalletId'] = wallet.id; - $state.transitionTo('tabs.send.destination', $scope.params); } else { $scope.params['toWalletId'] = wallet.id; - $state.transitionTo('tabs.send.amount', $scope.params); } + $state.transitionTo(getNextStep(), $scope.params); }; }); \ No newline at end of file diff --git a/src/js/routes.js b/src/js/routes.js index 29273d444..de77a34d5 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -270,24 +270,6 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } } }) - .state('tabs.send.origin', { - url: '/origin/:thirdParty/:amount/:toAddress/:toWalletId', - views: { - 'tab-send@tabs': { - controller: 'sendFlowController', - templateUrl: 'views/wallet-origin-destination.html', - } - } - }) - .state('tabs.send.destination', { - url: '/destination/:fromWalletId/:thirdParty/:amount', - views: { - 'tab-send@tabs': { - controller: 'sendFlowController', - templateUrl: 'views/wallet-origin-destination.html', - } - } - }) .state('tabs.settings', { url: '/settings', views: { @@ -305,7 +287,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr */ .state('tabs.send.amount', { - url: '/amount/:recipientType/:toAddress/:toName/:toEmail/:toColor/:coin/:fixedUnit/:fromWalletId/:minShapeshiftAmount/:maxShapeshiftAmount/:shapeshiftOrderId/:displayAddress/:noPrefix', + url: '/amount/:recipientType/:toAddress/:toName/:toEmail/:toColor/:coin/:fixedUnit/:fromWalletId/:toWalletId/:minShapeshiftAmount/:maxShapeshiftAmount/:shapeshiftOrderId/:displayAddress/:noPrefix', views: { 'tab-send@tabs': { controller: 'amountController', @@ -318,12 +300,30 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr views: { 'tab-send@tabs': { controller: 'sendFlowController', - templateUrl: 'views/wallet-to-wallet-transfer.html' + templateUrl: 'views/wallet-origin-destination.html' + } + } + }) + .state('tabs.send.origin', { + url: '/origin/:thirdParty/:amount/:toAddress/:toWalletId', + views: { + 'tab-send@tabs': { + controller: 'sendFlowController', + templateUrl: 'views/wallet-origin-destination.html', + } + } + }) + .state('tabs.send.destination', { + url: '/destination/:thirdParty/:amount/:fromWalletId', + views: { + 'tab-send@tabs': { + controller: 'sendFlowController', + templateUrl: 'views/wallet-origin-destination.html', } } }) .state('tabs.send.confirm', { - url: '/confirm/:recipientType/:toAddress/:toName/:toAmount/:toEmail/:toColor/:description/:coin/:useSendMax/:fromWalletId/:displayAddress/:requiredFeeRate', + url: '/confirm/:thirdParty/:amount/:fromWalletId/:toWalletId/:toAddress', views: { 'tab-send@tabs': { controller: 'confirmController', diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index fc3d37d47..e12cc2255 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -84,7 +84,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat $timeout(function() { if (amount) { $state.transitionTo('tabs.send.origin', { - toAmount: amount, + amount: amount, toAddress: addr, displayAddress: originalAddress ? originalAddress : addr, description: message, @@ -409,7 +409,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat function handlePayPro(payProDetails, coin) { var stateParams = { - toAmount: payProDetails.amount, + amount: payProDetails.amount, toAddress: payProDetails.toAddress, description: payProDetails.memo, paypro: payProDetails, diff --git a/src/sass/views/wallet-origin-destination.scss b/src/sass/views/wallet-origin-destination.scss index 94fbe8e4b..1c6016862 100644 --- a/src/sass/views/wallet-origin-destination.scss +++ b/src/sass/views/wallet-origin-destination.scss @@ -28,7 +28,6 @@ } } .wallets-header { - font-size: 12px; margin: 20px 14px 0px; .title { @@ -43,7 +42,11 @@ margin: 20px 14px 0px; .item-heading { + .subtitle { + font-size: 12px; + } font-weight: 600; + } &-insufficient { From 91dac0f54c460fb7d41cb8c5a523847454df7050 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 24 Jul 2018 12:37:51 -0700 Subject: [PATCH 046/240] Refactored Enter Amount controller to follow latest Angular 1 guidelines. --- src/js/controllers/amount.js | 471 +++++++++++++++--------------- src/js/routes.js | 7 + www/views/amount.html | 50 ++-- www/views/modals/altCurrency.html | 18 +- 4 files changed, 276 insertions(+), 270 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index f4e789436..1bc492136 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -1,62 +1,166 @@ 'use strict'; -angular.module('copayApp.controllers').controller('amountController', function($scope, $filter, $timeout, $ionicModal, $ionicScrollDelegate, $ionicHistory, storageService, walletService, gettextCatalog, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, txFormatService, ongoingProcess, popupService, bwcError, payproService, profileService, bitcore, amazonService, nodeWebkitService) { +angular.module('copayApp.controllers').controller('amountController', amountController); + +function amountController(configService, $filter, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $stateParams, $timeout, txFormatService, platformInfo, popupService, profileService, walletService, $window) { + var vm = this; + + vm.allowSend = false; + vm.altCurrencyList = []; + vm.alternativeAmount = ''; + vm.alternativeUnit = ''; + vm.amountModel = { amount: 0 }; + vm.fromWalletId = ''; + vm.globalResult = ''; + vm.isRequestingSpecificAmount = false; + vm.listComplete = false; + vm.lastUsedPopularList = []; + vm.maxShapeshiftAmount = 0; + vm.minShapeshiftAmount = 0; + vm.shapeshiftOrderId = ''; + vm.unit = ''; + + vm.changeUnit = changeUnit; + vm.close = close; + vm.findCurrency = findCurrency; + vm.finish = finish; + vm.goBack = goBack; + vm.loadMore = loadMore; + vm.openPopup = openPopup; + vm.pushDigit = pushDigit; + vm.removeDigit = removeDigit; + vm.save = save; + vm.sendMax = sendMax; + + $scope.$on('$ionicView.beforeEnter', onBeforeEnter); + $scope.$on('$ionicView.leave', onLeave); - var _id; - var unitToSatoshi; - var satToUnit; - var unitDecimals; - var satToBtc; - var SMALL_FONT_SIZE_LIMIT = 10; var LENGTH_EXPRESSION_LIMIT = 19; var LENGTH_BEFORE_COMMA_EXPRESSION_LIMIT = 8; var LENGTH_AFTER_COMMA_EXPRESSION_LIMIT = 8; - var isNW = platformInfo.isNW; - var unitIndex = 0; + var _id; + var altCurrencyModal = null; var altUnitIndex = 0; var availableUnits = []; + var displayAddress = null; var fiatCode; - var fixedUnit; + var hasMaxAmount = true; + var isNW = platformInfo.isNW; + var isAndroid = platformInfo.isAndroid; + var isIos = platformInfo.isIOS; + var lastUsedAltCurrencyList = []; + var nextStep = null; + var unitToSatoshi; + var recipientType = null; + var satToUnit; + var showMenu = false; + var showWarningMessage = false; + var toAddress = ''; + var toColor = null; + var toEmail = null; + var toName = null; + var unitDecimals; + var unitIndex = 0; + var useSendMax = false; - $scope.amountModel = { amount: 0 }; - - $scope.isChromeApp = platformInfo.isChromeApp; - $scope.isAndroid = platformInfo.isAndroid; - $scope.isIos = platformInfo.isIOS; - - $scope.isRequestingSpecificAmount = false; - - $scope.$on('$ionicView.leave', function() { + function onLeave() { angular.element($window).off('keydown'); - }); - - $scope.$on("$ionicView.beforeEnter", function(event, data) { + } + function onBeforeEnter(event, data) { + initCurrencies(); if (data.stateParams.shapeshiftOrderId && data.stateParams.shapeshiftOrderId.length > 0) { - $scope.minShapeshiftAmount = parseFloat(data.stateParams.minShapeshiftAmount); - $scope.maxShapeshiftAmount = parseFloat(data.stateParams.maxShapeshiftAmount); - $scope.shapeshiftOrderId = data.stateParams.shapeshiftOrderId; + vm.minShapeshiftAmount = parseFloat(data.stateParams.minShapeshiftAmount); + vm.maxShapeshiftAmount = parseFloat(data.stateParams.maxShapeshiftAmount); + vm.shapeshiftOrderId = data.stateParams.shapeshiftOrderId; } // To get the wallet from with the new flow - $scope.fromWalletId = data.stateParams.fromWalletId; + vm.fromWalletId = data.stateParams.fromWalletId; if (data.stateParams.noPrefix) { - $scope.showWarningMessage = data.stateParams.noPrefix != 0; - if ($scope.showWarningMessage) { + showWarningMessage = data.stateParams.noPrefix != 0; + if (showWarningMessage) { var message = 'Address doesn\'t contain currency information, please make sure you are sending the correct currency.'; popupService.showAlert('', message, function() {}, 'Ok'); } } - $scope.isRequestingSpecificAmount = !!data.stateParams.id; - + vm.isRequestingSpecificAmount = !!data.stateParams.id; var config = configService.getSync().wallet.settings; + // Go to... + _id = data.stateParams.id; // Optional (BitPay Card ID or Wallet ID) + nextStep = data.stateParams.nextStep; + + setAvailableUnits(); + updateUnitUI(); + + if ($ionicHistory.backView().stateName == 'tabs.receive') { + hasMaxAmount = false; + } + + showMenu = $ionicHistory.backView() && ($ionicHistory.backView().stateName == 'tabs.send' || $ionicHistory.backView().stateName == 'tabs.bitpayCard'); + recipientType = data.stateParams.recipientType || null; + toAddress = data.stateParams.toAddress; + displayAddress = data.stateParams.displayAddress; + toName = data.stateParams.toName; + toEmail = data.stateParams.toEmail; + toColor = data.stateParams.toColor; + + if (!nextStep && !data.stateParams.toAddress) { + $log.error('Bad params at amount') + throw ('bad params'); + } + + var reNr = /^[1234567890\.]$/; + var reOp = /^[\*\+\-\/]$/; + + if (!isAndroid && !isIos) { + var disableKeys = angular.element($window).on('keydown', function(e) { + if (!e.key) return; + if (e.which === 8) { // you can add others here inside brackets. + if (!altCurrencyModal) { + e.preventDefault(); + vm.removeDigit(); + } + } + + if (e.key.match(reNr)) { + vm.pushDigit(e.key); + } else if (e.key.match(reOp)) { + pushOperator(e.key); + } else if (e.keyCode === 86) { + if (e.ctrlKey || e.metaKey) processClipboard(); + } else if (e.keyCode === 13) vm.finish(); + + $timeout(function() { + $scope.$apply(); + }); + }); + } + + unitToSatoshi = config.unitToSatoshi; + satToUnit = 1 / unitToSatoshi; + unitDecimals = config.unitDecimals; + + resetAmount(); + + // in SAT ALWAYS + if ($stateParams.toAmount) { + vm.amountModel.amount = (($stateParams.toAmount) * satToUnit).toFixed(unitDecimals); + } + + processAmount(); + + $timeout(function() { + $ionicScrollDelegate.resize(); + }, 10); + function setAvailableUnits() { var defaults = configService.getDefaults(); var configCache = configService.getSync(); @@ -139,82 +243,10 @@ angular.module('copayApp.controllers').controller('amountController', function($ altUnitIndex = 0; }; + }; - // Go to... - _id = data.stateParams.id; // Optional (BitPay Card ID or Wallet ID) - $scope.nextStep = data.stateParams.nextStep; - - setAvailableUnits(); - updateUnitUI(); - - $scope.hasMaxAmount = true; - if ($ionicHistory.backView().stateName == 'tabs.receive') { - $scope.hasMaxAmount = false; - } - - $scope.showMenu = $ionicHistory.backView() && ($ionicHistory.backView().stateName == 'tabs.send' || $ionicHistory.backView().stateName == 'tabs.bitpayCard'); - $scope.recipientType = data.stateParams.recipientType || null; - $scope.toAddress = data.stateParams.toAddress; - $scope.displayAddress = data.stateParams.displayAddress; - $scope.toName = data.stateParams.toName; - $scope.toEmail = data.stateParams.toEmail; - $scope.toColor = data.stateParams.toColor; - - if (!$scope.nextStep && !data.stateParams.toAddress) { - $log.error('Bad params at amount') - throw ('bad params'); - } - - var reNr = /^[1234567890\.]$/; - var reOp = /^[\*\+\-\/]$/; - - if (!$scope.isAndroid && !$scope.isIos) { - var disableKeys = angular.element($window).on('keydown', function(e) { - if (!e.key) return; - if (e.which === 8) { // you can add others here inside brackets. - if (!$scope.altCurrencyModal) { - e.preventDefault(); - $scope.removeDigit(); - } - } - - if (e.key.match(reNr)) { - $scope.pushDigit(e.key); - } else if (e.key.match(reOp)) { - $scope.pushOperator(e.key); - } else if (e.keyCode === 86) { - if (e.ctrlKey || e.metaKey) processClipboard(); - } else if (e.keyCode === 13) $scope.finish(); - - $timeout(function() { - $scope.$apply(); - }); - }); - } - - $scope.specificAmount = $scope.specificAlternativeAmount = ''; - $scope.isCordova = platformInfo.isCordova; - unitToSatoshi = config.unitToSatoshi; - satToUnit = 1 / unitToSatoshi; - satToBtc = 1 / 100000000; - unitDecimals = config.unitDecimals; - - $scope.resetAmount(); - - // in SAT ALWAYS - if ($stateParams.toAmount) { - $scope.amountModel.amount = (($stateParams.toAmount) * satToUnit).toFixed(unitDecimals); - } - - $scope.processAmount(); - - $timeout(function() { - $ionicScrollDelegate.resize(); - }, 10); - }); - - $scope.goBack = function() { - if ($scope.shapeshiftOrderId) { + function goBack() { + if (vm.shapeshiftOrderId) { $state.go('tabs.send').then(function() { $ionicHistory.clearHistory(); $state.go('tabs.home').then(function() { @@ -227,8 +259,8 @@ angular.module('copayApp.controllers').controller('amountController', function($ } function paste(value) { - $scope.amountModel.amount = value; - $scope.processAmount(); + vm.amountModel.amount = value; + processAmount(); $timeout(function() { $scope.$apply(); }); @@ -240,29 +272,22 @@ angular.module('copayApp.controllers').controller('amountController', function($ if (value && evaluate(value) > 0) paste(evaluate(value)); }; - $scope.sendMax = function() { - $scope.useSendMax = true; - $scope.finish(); - }; - - $scope.toggleAlternative = function() { - if ($scope.amountModel.amount && isExpression($scope.amountModel.amount)) { - var amount = evaluate(format($scope.amountModel.amount)); - $scope.globalResult = '= ' + processResult(amount); - } + function sendMax() { + useSendMax = true; + finish(); }; function updateUnitUI() { - $scope.unit = availableUnits[unitIndex].shortName; - $scope.alternativeUnit = availableUnits[altUnitIndex].shortName; + vm.unit = availableUnits[unitIndex].shortName; + vm.alternativeUnit = availableUnits[altUnitIndex].shortName; - $scope.processAmount(); - $log.debug('Update unit coin @amount unit:' + $scope.unit + " alternativeUnit:" + $scope.alternativeUnit); + processAmount(); + $log.debug('Update unit coin @amount unit:' + vm.unit + " alternativeUnit:" + vm.alternativeUnit); }; - $scope.changeUnit = function() { + function changeUnit() { - $scope.amountModel.amount = '0'; + vm.amountModel.amount = '0'; if (fixedUnit) return; @@ -282,59 +307,35 @@ angular.module('copayApp.controllers').controller('amountController', function($ updateUnitUI(); }; - - $scope.changeAlternativeUnit = function() { - - // Do nothing is fiat is not main unit - if (!availableUnits[unitIndex].isFiat) return; - - var nextCoin = lodash.findIndex(availableUnits, function(x) { - if (x.isFiat) return false; - if (x.id == availableUnits[altUnitIndex].id) return false; - return true; - }); - - if (nextCoin >= 0) { - altUnitIndex = nextCoin; - updateUnitUI(); - } - }; - - function checkFontSize() { - if ($scope.amountModel.amount && $scope.amountModel.amount.length >= SMALL_FONT_SIZE_LIMIT) $scope.smallFont = true; - else $scope.smallFont = false; - }; - - $scope.pushDigit = function(digit) { - if ($scope.amountModel.amount && digit != '.') { - var amountSplitByComma = $scope.amountModel.amount.split('.'); + function pushDigit(digit) { + if (vm.amountModel.amount && digit != '.') { + var amountSplitByComma = vm.amountModel.amount.split('.'); if (amountSplitByComma.length > 1 && amountSplitByComma[1].length >= LENGTH_AFTER_COMMA_EXPRESSION_LIMIT) return; if (amountSplitByComma.length == 1 && amountSplitByComma[0].length >= LENGTH_BEFORE_COMMA_EXPRESSION_LIMIT) return; } - if ($scope.amountModel.amount && $scope.amountModel.amount.length >= LENGTH_EXPRESSION_LIMIT) return; - if ($scope.amountModel.amount.indexOf('.') > -1 && digit == '.') return; - if ($scope.amountModel.amount == '0' && digit == '0') return; - if (availableUnits[unitIndex].isFiat && $scope.amountModel.amount.indexOf('.') > -1 && $scope.amountModel.amount[$scope.amountModel.amount.indexOf('.') + 2]) return; + if (vm.amountModel.amount && vm.amountModel.amount.length >= LENGTH_EXPRESSION_LIMIT) return; + if (vm.amountModel.amount.indexOf('.') > -1 && digit == '.') return; + if (vm.amountModel.amount == '0' && digit == '0') return; + if (availableUnits[unitIndex].isFiat && vm.amountModel.amount.indexOf('.') > -1 && vm.amountModel.amount[vm.amountModel.amount.indexOf('.') + 2]) return; - if ($scope.amountModel.amount == '0' && digit != '.') { - $scope.amountModel.amount = ''; + if (vm.amountModel.amount == '0' && digit != '.') { + vm.amountModel.amount = ''; } - if ($scope.amountModel.amount == '' && digit == '.') { - $scope.amountModel.amount = '0'; + if (vm.amountModel.amount == '' && digit == '.') { + vm.amountModel.amount = '0'; } - $scope.amountModel.amount = ($scope.amountModel.amount + digit).replace('..', '.'); - checkFontSize(); - $scope.processAmount(); + vm.amountModel.amount = (vm.amountModel.amount + digit).replace('..', '.'); + processAmount(); }; - $scope.pushOperator = function(operator) { - if (!$scope.amountModel.amount || $scope.amountModel.amount.length == 0) return; - $scope.amountModel.amount = _pushOperator($scope.amountModel.amount); + function pushOperator(operator) { + if (!vm.amountModel.amount || vm.amountModel.amount.length == 0) return; + vm.amountModel.amount = pushOperator(vm.amountModel.amount); - function _pushOperator(val) { + function pushOperator(val) { if (!isOperator(lodash.last(val))) { return val + operator; } else { @@ -353,61 +354,59 @@ angular.module('copayApp.controllers').controller('amountController', function($ return regex.test(val); }; - $scope.removeDigit = function() { - $scope.amountModel.amount = ($scope.amountModel.amount).toString().slice(0, -1); - $scope.processAmount(); - checkFontSize(); - }; + function removeDigit() { + vm.amountModel.amount = (vm.amountModel.amount).toString().slice(0, -1); + processAmount(); + } - $scope.resetAmount = function() { - $scope.amountModel.amount = $scope.alternativeAmount = $scope.globalResult = ''; - $scope.allowSend = false; - checkFontSize(); - }; + function resetAmount() { + vm.amountModel.amount = vm.alternativeAmount = vm.globalResult = ''; + vm.allowSend = false; + } - $scope.openPopup = function() { + function openPopup() { $ionicModal.fromTemplateUrl('views/modals/altCurrency.html', { scope: $scope }).then(function(modal) { - $scope.altCurrencyModal = modal; - $scope.altCurrencyModal.show(); + altCurrencyModal = modal; + altCurrencyModal.show(); }); + } + + function close() { + altCurrencyModal.remove(); + altCurrencyModal = null; }; - $scope.close = function() { - $scope.altCurrencyModal.remove(); - $scope.altCurrencyModal = false; - }; - - $scope.processAmount = function() { - var formatedValue = format($scope.amountModel.amount); + function processAmount() { + var formatedValue = format(vm.amountModel.amount); var result = evaluate(formatedValue); if (lodash.isNumber(result)) { - $scope.globalResult = isExpression($scope.amountModel.amount) ? '= ' + processResult(result) : ''; + vm.globalResult = isExpression(vm.amountModel.amount) ? '= ' + processResult(result) : ''; if (availableUnits[unitIndex].isFiat) { var a = fromFiat(result); if (a) { - $scope.alternativeAmount = txFormatService.formatAmount(a * unitToSatoshi, true); - $scope.allowSend = lodash.isNumber(a) && a > 0 - && (!$scope.shapeshiftOrderId - || (a >= $scope.minShapeshiftAmount && a <= $scope.maxShapeshiftAmount)); + vm.alternativeAmount = txFormatService.formatAmount(a * unitToSatoshi, true); + vm.allowSend = lodash.isNumber(a) && a > 0 + && (!vm.shapeshiftOrderId + || (a >= vm.minShapeshiftAmount && a <= vm.maxShapeshiftAmount)); } else { if (result) { - $scope.alternativeAmount = 'N/A'; + vm.alternativeAmount = 'N/A'; } else { - $scope.alternativeAmount = null; + vm.alternativeAmount = null; } - $scope.allowSend = false; + vm.allowSend = false; } } else { - $scope.alternativeAmount = $filter('formatFiatAmount')(toFiat(result)); - $scope.allowSend = lodash.isNumber(result) && result > 0 - && (!$scope.shapeshiftOrderId - || (result >= $scope.minShapeshiftAmount && result <= $scope.maxShapeshiftAmount)); + vm.alternativeAmount = $filter('formatFiatAmount')(toFiat(result)); + vm.allowSend = lodash.isNumber(result) && result > 0 + && (!vm.shapeshiftOrderId + || (result >= vm.minShapeshiftAmount && result <= vm.maxShapeshiftAmount)); } } }; @@ -448,24 +447,24 @@ angular.module('copayApp.controllers').controller('amountController', function($ return result.replace('x', '*'); }; - $scope.finish = function() { + function finish() { function finish() { var unit = availableUnits[unitIndex]; - var _amount = evaluate(format($scope.amountModel.amount)); + var _amount = evaluate(format(vm.amountModel.amount)); var coin = unit.id; if (unit.isFiat) { coin = availableUnits[altUnitIndex].id; } - if ($scope.nextStep) { - $state.transitionTo($scope.nextStep, { + if (nextStep) { + $state.transitionTo(nextStep, { id: _id, - amount: $scope.useSendMax ? null : _amount, + amount: useSendMax ? null : _amount, currency: unit.id.toUpperCase(), coin: coin, - useSendMax: $scope.useSendMax, - fromWalletId: $scope.fromWalletId + useSendMax: useSendMax, + fromWalletId: vm.fromWalletId }); } else { var amount = _amount; @@ -477,52 +476,52 @@ angular.module('copayApp.controllers').controller('amountController', function($ } var confirmData = { - recipientType: $scope.recipientType, + recipientType: recipientType, toAmount: amount, - toAddress: $scope.toAddress, - displayAddress: $scope.displayAddress || $scope.toAddress, - toName: $scope.toName, - toEmail: $scope.toEmail, - toColor: $scope.toColor, + toAddress: toAddress, + displayAddress: displayAddress || toAddress, + toName: toName, + toEmail: toEmail, + toColor: toColor, coin: coin, - useSendMax: $scope.useSendMax, - fromWalletId: $scope.fromWalletId + useSendMax: useSendMax, + fromWalletId: vm.fromWalletId }; - if ($scope.shapeshiftOrderId) { + if (vm.shapeshiftOrderId) { var shapeshiftOrderUrl = 'https://www.shapeshift.io/#/status/'; - shapeshiftOrderUrl += $scope.shapeshiftOrderId; + shapeshiftOrderUrl += vm.shapeshiftOrderId; confirmData.description = shapeshiftOrderUrl; - confirmData.fromWalletId = $scope.fromWalletId; + confirmData.fromWalletId = vm.fromWalletId; if (confirmData.useSendMax) { var wallet = lodash.find(profileService.getWallets({ coin: coin }), function(w) { - return w.id == $scope.fromWalletId; + return w.id == vm.fromWalletId; }); var balance = parseFloat(wallet.cachedBalance.substring(0, wallet.cachedBalance.length-4)); - if (balance < $scope.minShapeshiftAmount * 1.04) { + if (balance < vm.minShapeshiftAmount * 1.04) { confirmData.useSendMax = false; - confirmData.toAmount = $scope.minShapeshiftAmount * unitToSatoshi; - } else if (balance > $scope.maxShapeshiftAmount) { + confirmData.toAmount = vm.minShapeshiftAmount * unitToSatoshi; + } else if (balance > vm.maxShapeshiftAmount) { confirmData.useSendMax = false; - confirmData.toAmount = $scope.maxShapeshiftAmount * unitToSatoshi * 0.99; + confirmData.toAmount = vm.maxShapeshiftAmount * unitToSatoshi * 0.99; } } } $state.transitionTo('tabs.send.confirm', confirmData); } - $scope.useSendMax = null; + useSendMax = null; } - if ($scope.showWarningMessage) { - var u = $scope.unit == 'BCH' || $scope.unit == 'BTC' ? $scope.unit : $scope.alternativeUnit; + if (showWarningMessage) { + var u = vm.unit == 'BCH' || vm.unit == 'BTC' ? vm.unit : vm.alternativeUnit; var message = 'Are you sure you want to send ' + u.toUpperCase() + '?'; popupService.showConfirm(message, '', 'Yes', 'No', function(res) { if (!res) { - $scope.useSendMax = null; + useSendMax = null; return; }; finish(); @@ -566,10 +565,10 @@ angular.module('copayApp.controllers').controller('amountController', function($ }]; rateService.whenAvailable(function() { - $scope.listComplete = false; + vm.listComplete = false; var idx = lodash.indexBy(unusedCurrencyList, 'isoCode'); - var idx2 = lodash.indexBy($scope.lastUsedAltCurrencyList, 'isoCode'); + var idx2 = lodash.indexBy(lastUsedAltCurrencyList, 'isoCode'); var idx3 = lodash.indexBy(popularCurrencyList, 'isoCode'); var alternatives = rateService.listAlternatives(true); @@ -582,8 +581,8 @@ angular.module('copayApp.controllers').controller('amountController', function($ } }); - $scope.altCurrencyList = completeAlternativeList.slice(0, 10); - $scope.lastUsedPopularList = lodash.unique(lodash.union($scope.lastUsedAltCurrencyList, popularCurrencyList), 'isoCode'); + vm.altCurrencyList = completeAlternativeList.slice(0, 10); + vm.lastUsedPopularList = lodash.unique(lodash.union(lastUsedAltCurrencyList, popularCurrencyList), 'isoCode'); $timeout(function() { $scope.$apply(); @@ -591,19 +590,19 @@ angular.module('copayApp.controllers').controller('amountController', function($ }); } - $scope.loadMore = function() { + function loadMore() { $timeout(function() { - $scope.altCurrencyList = completeAlternativeList.slice(0, next); + vm.altCurrencyList = completeAlternativeList.slice(0, next); next += 10; - $scope.listComplete = $scope.altCurrencyList.length >= completeAlternativeList.length; + vm.listComplete = vm.altCurrencyList.length >= completeAlternativeList.length; $scope.$broadcast('scroll.infiniteScrollComplete'); }, 100); }; - $scope.findCurrency = function(search) { + function findCurrency(search) { if (!search) initCurrencies(); - var list = lodash.unique(lodash.union(completeAlternativeList, lodash.union($scope.lastUsedAltCurrencyList, popularCurrencyList)), 'isoCode'); - $scope.altCurrencyList = lodash.filter(list, function(item) { + var list = lodash.unique(lodash.union(completeAlternativeList, lodash.union(lastUsedAltCurrencyList, popularCurrencyList)), 'isoCode'); + vm.altCurrencyList = lodash.filter(list, function(item) { var val = item.name var val2 = item.isoCode; return lodash.includes(val.toLowerCase(), search.toLowerCase()) || lodash.includes(val2.toLowerCase(), search.toLowerCase()); @@ -613,7 +612,7 @@ angular.module('copayApp.controllers').controller('amountController', function($ }); }; - $scope.save = function(newAltCurrency) { + function save(newAltCurrency) { var opts = { wallet: { settings: { @@ -634,7 +633,7 @@ angular.module('copayApp.controllers').controller('amountController', function($ availableUnits[altUnitIndex].shortName = newAltCurrency.isoCode; fiatCode = newAltCurrency.isoCode; updateUnitUI(); - $scope.close(); + close(); }); }; -}); +} diff --git a/src/js/routes.js b/src/js/routes.js index 8277314e5..286b27ab1 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -291,6 +291,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr views: { 'tab-send@tabs': { controller: 'amountController', + controllerAs: 'vm', templateUrl: 'views/amount.html' } } @@ -699,6 +700,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr views: { 'tab-receive@tabs': { controller: 'amountController', + controllerAs: 'vm', templateUrl: 'views/amount.html' } } @@ -845,6 +847,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr views: { 'tab-home@tabs': { controller: 'amountController', + controllerAs: 'vm', templateUrl: 'views/amount.html' } } @@ -910,6 +913,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr views: { 'tab-home@tabs': { controller: 'amountController', + controllerAs: 'vm', templateUrl: 'views/amount.html' } } @@ -1029,6 +1033,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr views: { 'tab-home@tabs': { controller: 'amountController', + controllerAs: 'vm', templateUrl: 'views/amount.html' } }, @@ -1081,6 +1086,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr views: { 'tab-home@tabs': { controller: 'amountController', + controllerAs: 'vm', templateUrl: 'views/amount.html' } }, @@ -1137,6 +1143,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr views: { 'tab-home@tabs': { controller: 'amountController', + controllerAs: 'vm', templateUrl: 'views/amount.html' } } diff --git a/www/views/amount.html b/www/views/amount.html index e803ffc10..90187ef59 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -3,37 +3,37 @@ {{'Enter amount' | translate}} - +
-
- Minimum amount: {{minShapeshiftAmount}}
- Maximum amount: {{maxShapeshiftAmount}}
+
+ Minimum amount: {{vm.minShapeshiftAmount}}
+ Maximum amount: {{vm.maxShapeshiftAmount}}
- {{ amountModel.amount || 0 }}{{unit}} + ng-class="{long: vm.amountModel.amount.length > 5, 'very-long': vm.amountModel.amount.length > 10}"> + {{ vm.amountModel.amount || 0 }}{{vm.unit}}
- {{globalResult}} {{unit}} + {{vm.globalResult}} {{vm.unit}}
- {{alternativeAmount || '0.00'}} {{alternativeUnit}} + {{vm.alternativeAmount || '0.00'}} {{vm.alternativeUnit}}
-
+
- -
@@ -11,23 +11,23 @@
-
- {{lastUsedAltCurrency.name}} {{lastUsedAltCurrency.isoCode}} +
+ {{lastUsedAltCurrency.name}} {{lastUsedAltCurrency.isoCode}}
-
{{altCurrency.name}} {{altCurrency.isoCode}} +
{{altCurrency.name}} {{altCurrency.isoCode}}
From bba85794ac3599127b6627f11baaf6d435ee30a5 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 25 Jul 2018 16:17:42 +0900 Subject: [PATCH 047/240] BitAnalytics 0.2.1 with GA --- Gruntfile.js | 2 +- ...{bitanalytics-0.1.0.js => bitanalytics.js} | 260 +++++++++++++++--- src/js/controllers/addressbookAdd.js | 6 +- src/js/controllers/confirm.js | 6 +- .../controllers/preferencesNotifications.js | 6 +- src/js/controllers/tab-receive.js | 6 +- src/js/controllers/walletDetails.js | 6 +- src/js/routes.js | 6 +- src/js/services/profileService.js | 6 +- www/index.html | 7 +- 10 files changed, 245 insertions(+), 66 deletions(-) rename bitanalytics/{bitanalytics-0.1.0.js => bitanalytics.js} (97%) diff --git a/Gruntfile.js b/Gruntfile.js index 342cc85e7..eb4bb2eb0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -163,7 +163,7 @@ module.exports = function(grunt) { }, bitanalytics: { src: [ - 'bitanalytics/bitanalytics-0.1.0.js' + 'bitanalytics/bitanalytics.js' ], dest: 'www/lib/bitanalytics.js' }, diff --git a/bitanalytics/bitanalytics-0.1.0.js b/bitanalytics/bitanalytics.js similarity index 97% rename from bitanalytics/bitanalytics-0.1.0.js rename to bitanalytics/bitanalytics.js index db149e481..c8c0d8870 100644 --- a/bitanalytics/bitanalytics-0.1.0.js +++ b/bitanalytics/bitanalytics.js @@ -6276,7 +6276,7 @@ var ClickAction = /** @class */ (function (_super) { }(action_1.default)); exports.default = ClickAction; -},{"../action":4,"../log-event":15,"../log-event-handlers":14}],6:[function(require,module,exports){ +},{"../action":4,"../log-event":16,"../log-event-handlers":15}],6:[function(require,module,exports){ "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; @@ -6315,7 +6315,7 @@ var BitAnalytics = /** @class */ (function () { exports.default = BitAnalytics; BitAnalytics.main(); -},{"./action-factory":2,"./action-handlers":3,"./channels/adjust-channel":9,"./channels/mixpanel-channel":12,"./log-event":15,"./log-event-handlers":14}],7:[function(require,module,exports){ +},{"./action-factory":2,"./action-handlers":3,"./channels/adjust-channel":9,"./channels/mixpanel-channel":12,"./log-event":16,"./log-event-handlers":15}],7:[function(require,module,exports){ "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; @@ -6560,11 +6560,10 @@ var FirebaseChannel = /** @class */ (function (_super) { var keys = Object.keys(params); var keysLength = keys.length; var sanitized = {}; - for (var i = 0; i < keysLength; i++) { - var key = keys[i]; + keys.map(function (key) { var cleanKey = key.replace('-', '_').replace(/[\W]+/g, ''); sanitized[cleanKey] = params[key]; - } + }); return sanitized; }; return FirebaseChannel; @@ -6588,13 +6587,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); var channel_1 = __importDefault(require("../channel")); +var ga_1 = __importDefault(require("../external-libs/ga")); var GoogleAnalyticsChannel = /** @class */ (function (_super) { __extends(GoogleAnalyticsChannel, _super); function GoogleAnalyticsChannel(name, config) { var _this = _super.call(this, name) || this; - _this.dataLayer = null; _this.gaInstance = null; - _this.trackingId = ''; _this.eventLabels = ['id']; if (!config.trackingId) { throw new Error('[BitAnalytics] Google Analytics config is missing tracking ID.'); @@ -6602,8 +6600,12 @@ var GoogleAnalyticsChannel = /** @class */ (function (_super) { if (config.eventLabels) { _this.eventLabels = config.eventLabels; } - _this.trackingId = config.trackingId; - _this.setUpGa(); + _this.gaInstance = new ga_1.default({ + trackID: config.trackingId, + appVersion: config.appVersion, + appName: config.appName || 'App' + }); + _this.isReady = true; return _this; } /** @@ -6612,49 +6614,26 @@ var GoogleAnalyticsChannel = /** @class */ (function (_super) { * */ GoogleAnalyticsChannel.prototype.postEvent = function (name, params) { - // Default Google Analytics Events - // https://developers.google.com/analytics/devguides/collection/gtagjs/events - // Useful to convert to these, or start with these? if (this.isReady) { - params.event_category = name; + var category = name; + var action = name; + var label = name; + var value = params['value'] || ''; for (var _i = 0, _a = this.eventLabels; _i < _a.length; _i++) { var eventLabel = _a[_i]; if (params[eventLabel]) { - params.event_label = params[eventLabel]; + label = params[eventLabel]; break; } } - this.gtag('event', name, params); + this.gaInstance.event(category, action, label, value); } }; - /** - * - * Private methods - * - */ - /** - * Mimics function in the tracking snippet - */ - GoogleAnalyticsChannel.prototype.gtag = function () { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i] = arguments[_i]; - } - console.log(arguments); - window.dataLayer.push(arguments); - }; - GoogleAnalyticsChannel.prototype.setUpGa = function () { - // From what GA recommends to insert into page - window.dataLayer = window.dataLayer || []; - this.gtag('js', new Date()); - this.gtag('config', this.trackingId); - this.isReady = true; - }; return GoogleAnalyticsChannel; }(channel_1.default)); exports.default = GoogleAnalyticsChannel; -},{"../channel":8}],12:[function(require,module,exports){ +},{"../channel":8,"../external-libs/ga":14}],12:[function(require,module,exports){ "use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || @@ -6746,6 +6725,207 @@ exports.default = MixpanelChannel; },{}],14:[function(require,module,exports){ "use strict"; +/* + * name: nwjs-analytics -Node-Webkit Google Analytics integration + * version: 1.0.2 + * github: https://github.com/Daaru00/nwjs-analytics + */ +function GA(opt) { + this.apiVersion = opt.apiVersion || '1'; + this.trackID = opt.trackID || 'UA-XXXXXXXX-X'; + this.clientID = opt.clientID || null; + this.userID = opt.userID || null; + this.appName = opt.appName || 'App'; + this.appVersion = opt.appVersion || '1.0.0'; + this.debug = opt.debug || false; + this.performanceTracking = opt.performanceTracking || true; + this.errorTracking = opt.errorTracking || true; + this.userLanguage = opt.userLanguage || "en"; + this.currency = opt.currency || "EUR"; + this.lastScreenName = opt.lastScreenName || ''; +} +GA.prototype.sendRequest = function (data, callback) { + var ga = this; + if (!this.clientID || this.clientID == null) + this.clientID = this.generateClientID(); + if (!this.userID || this.userID == null) + this.userID = this.generateClientID(); + var postData = "v=" + this.apiVersion + + "&an=" + this.appName + + "&av=" + this.appVersion + + "&tid=" + this.trackID + + "&cid=" + this.clientID + + "&sr=" + this.getScreenResolution() + + "&vp=" + this.getViewportSize(); + Object.keys(data).forEach(function (key) { + var val = data[key]; + if (typeof val != "undefined") + postData += "&" + key + "=" + val; + }); + var http = new XMLHttpRequest(); + var url = "https://www.google-analytics.com"; + if (!this.debug) + url += "/collect"; + else + url += "/debug/collect"; + http.open("GET", url + "?" + postData, true); + http.onreadystatechange = function () { + if (ga.debug) + console.log(http.response); + if (http.readyState == 4 && http.status == 200) { + if (callback) + callback(true); + } + else { + if (callback) + callback(false); + } + }; + http.send(); +}; +GA.prototype.generateClientID = function () { + var id = ""; + var possibilities = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (var i = 0; i < 5; i++) + id += possibilities.charAt(Math.floor(Math.random() * possibilities.length)); + return id; +}; +GA.prototype.getScreenResolution = function () { + return screen.width + "x" + screen.height; +}; +GA.prototype.getColorDept = function () { + return screen.colorDepth + "-bits"; +}; +GA.prototype.getUserAgent = function () { + return navigator.userAgent; +}; +GA.prototype.getViewportSize = function () { + return window.screen.availWidth + "x" + window.screen.availHeight; +}; +/* + * Measurement Protocol + * [https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide] + */ +GA.prototype.screenView = function (screename) { + var data = { + 't': 'screenview', + 'cd': screename + }; + this.sendRequest(data); + this.lastScreenName = screename; +}; +GA.prototype.event = function (category, action, label, value) { + var data = { + 't': 'event', + 'ec': category, + 'ea': action, + }; + if (label) { + data['el'] = label; + } + if (value) { + data['ev'] = value; + } + if (this.lastScreenName) { + data['cd'] = this.lastScreenName; + } + this.sendRequest(data); +}; +GA.prototype.exception = function (msg, fatal) { + var data = { + 't': 'exception', + 'exd': msg, + 'exf': fatal || 0 + }; + this.sendRequest(data); +}; +GA.prototype.timing = function (category, variable, time, label) { + var data = { + 't': 'timing', + 'utc': category, + 'utv': variable, + 'utt': time, + 'utl': label, + }; + this.sendRequest(data); +}, + GA.prototype.ecommerce = { + transactionID: false, + generateTransactionID: function () { + var id = ""; + var possibilities = "0123456789"; + for (var i = 0; i < 5; i++) + id += possibilities.charAt(Math.floor(Math.random() * possibilities.length)); + return id; + }, + transaction: function (total, items) { + var t_id = ""; + if (!this.ecommerce.transactionID) + t_id = this.ecommerce.generateTransactionID(); + else + t_id = this.ecommerce.transactionID; + var data = { + 't': 'transaction', + 'ti': t_id, + 'tr': total, + 'cu': this.currency, + }; + this.sendRequest(data); + items.forEach(function (item) { + var data = { + 't': 'item', + 'ti': t_id, + 'in': item.name, + 'ip': item.price, + 'iq': item.qty, + 'ic': item.id, + 'cu': this.currency + }; + this.sendRequest(data); + }); + } + }, + GA.prototype.custom = function (data) { + this.sendRequest(data); + }; +module.exports = GA; +/* + * Performance Tracking + */ +/*window.addEventListener("load", function() { + + if(ga.performanceTracking) { + setTimeout(function() { + var timing = window.performance.timing; + var userTime = timing.loadEventEnd - timing.navigationStart; + ga.timing("performance", "pageload", userTime); + }, 0); + } + +}, false);*/ +/* + * Error Reporting + */ +/*window.onerror = function (msg, url, lineNo, columnNo, error) { + var message = [ + 'Message: ' + msg, + 'Line: ' + lineNo, + 'Column: ' + columnNo, + 'Error object: ' + JSON.stringify(error) + ].join(' - '); + + if(ga.errorTracking) + { + setTimeout(function() { + ga.exception(message.toString()); + }, 0); + } + + return false; +};*/ + +},{}],15:[function(require,module,exports){ +"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -6865,7 +7045,7 @@ var LogEventHandlers = /** @class */ (function () { }()); exports.default = LogEventHandlers; -},{"./channel-factory":7}],15:[function(require,module,exports){ +},{"./channel-factory":7}],16:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var LogEvent = /** @class */ (function () { diff --git a/src/js/controllers/addressbookAdd.js b/src/js/controllers/addressbookAdd.js index 9529d943e..e33f85725 100644 --- a/src/js/controllers/addressbookAdd.js +++ b/src/js/controllers/addressbookAdd.js @@ -36,9 +36,9 @@ angular.module('copayApp.controllers').controller('addressbookAddController', fu addressbook.address = translated.legacy; } - var channel = "firebase"; - if (platformInfo.isNW) { - channel = "ga"; + var channel = "ga"; + if (platformInfo.isCordova) { + channel = "firebase"; } var log = new window.BitAnalytics.LogEvent("contact_created", [{ "coin": $scope.addressbookEntry.coin diff --git a/src/js/controllers/confirm.js b/src/js/controllers/confirm.js index 03af26fd1..3c0465891 100644 --- a/src/js/controllers/confirm.js +++ b/src/js/controllers/confirm.js @@ -643,9 +643,9 @@ angular.module('copayApp.controllers').controller('confirmController', function( soundService.play('misc/payment_sent.mp3'); } - var channel = "firebase"; - if (platformInfo.isNW) { - channel = "ga"; + var channel = "ga"; + if (platformInfo.isCordova) { + channel = "firebase"; } var log = new window.BitAnalytics.LogEvent("transfer_success", [{ "coin": $scope.wallet.coin, diff --git a/src/js/controllers/preferencesNotifications.js b/src/js/controllers/preferencesNotifications.js index edfb983b5..a9a833ff7 100644 --- a/src/js/controllers/preferencesNotifications.js +++ b/src/js/controllers/preferencesNotifications.js @@ -76,9 +76,9 @@ angular.module('copayApp.controllers').controller('preferencesNotificationsContr emailService.updateEmail(opts); - var channel = "firebase"; - if (platformInfo.isNW) { - channel = "ga"; + var channel = "ga"; + if (platformInfo.isCordova) { + channel = "firebase"; } var log = new window.BitAnalytics.LogEvent("settings_email_notification_toggle", [{ "toggle": $scope.emailNotifications.value diff --git a/src/js/controllers/tab-receive.js b/src/js/controllers/tab-receive.js index 8f25412ec..29acb10d8 100644 --- a/src/js/controllers/tab-receive.js +++ b/src/js/controllers/tab-receive.js @@ -145,9 +145,9 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi } $scope.paymentReceivedCoin = $scope.wallet.coin; - var channel = "firebase"; - if (platformInfo.isNW) { - channel = "ga"; + var channel = "ga"; + if (platformInfo.isCordova) { + channel = "firebase"; } var log = new window.BitAnalytics.LogEvent("transfer_success", [{ "coin": $scope.wallet.coin, diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 24237f6c9..241bbce89 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -12,9 +12,9 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.isAndroid = platformInfo.isAndroid; $scope.isIOS = platformInfo.isIOS; - var channel = "firebase"; - if (platformInfo.isNW) { - channel = "ga"; + var channel = "ga"; + if (platformInfo.isCordova) { + channel = "firebase"; } var log = new window.BitAnalytics.LogEvent("wallet_details_open", [], [channel]); window.BitAnalytics.LogEventHandlers.postEvent(log); diff --git a/src/js/routes.js b/src/js/routes.js index 8277314e5..665294b9b 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -1184,9 +1184,9 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }); - var channel = "firebase"; - if (platformInfo.isNW) { - channel = "ga"; + var channel = "ga"; + if (platformInfo.isCordova) { + channel = "firebase"; } // Send a log to test diff --git a/src/js/services/profileService.js b/src/js/services/profileService.js index 4f8710c28..e79b809f6 100644 --- a/src/js/services/profileService.js +++ b/src/js/services/profileService.js @@ -427,9 +427,9 @@ angular.module('copayApp.services') }, function(err, secret) { if (err) return bwcError.cb(err, gettextCatalog.getString('Error creating wallet'), cb); - var channel = "firebase"; - if (platformInfo.isNW) { - channel = "ga"; + var channel = "ga"; + if (platformInfo.isCordova) { + channel = "firebase"; } var log = new window.BitAnalytics.LogEvent("wallet_created", [{ "coin": opts.coin diff --git a/www/index.html b/www/index.html index 4c73317e3..ecc2d923c 100644 --- a/www/index.html +++ b/www/index.html @@ -11,9 +11,8 @@ - Bitcoin.com Wallet - Bitcoin.com Wallet - - + Bitcoin.com Wallet + @@ -31,7 +30,7 @@ - + From 029ac7dd39379b565668e6b6e4db1fbf57810842 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 25 Jul 2018 16:26:06 +0900 Subject: [PATCH 048/240] Fix the QRCode scan. --- src/js/controllers/addressbookAdd.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/js/controllers/addressbookAdd.js b/src/js/controllers/addressbookAdd.js index 9529d943e..89d648b46 100644 --- a/src/js/controllers/addressbookAdd.js +++ b/src/js/controllers/addressbookAdd.js @@ -21,6 +21,9 @@ angular.module('copayApp.controllers').controller('addressbookAddController', fu $timeout(function() { var form = addressbookForm; if (data && form) { + if (data.result) { + data = data.result; + } data = data.replace(/^bitcoin(cash)?:/, ''); form.address.$setViewValue(data); form.address.$isValid = true; From 4093e2ba71dff9800d24d3fcdff5980bb58c5dcf Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 25 Jul 2018 11:26:33 +0200 Subject: [PATCH 049/240] refactored the sendFlowController --- src/js/controllers/sendFlowController.js | 53 +++++++++--------------- 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/src/js/controllers/sendFlowController.js b/src/js/controllers/sendFlowController.js index d6f2b707d..400751867 100644 --- a/src/js/controllers/sendFlowController.js +++ b/src/js/controllers/sendFlowController.js @@ -2,75 +2,60 @@ angular.module('copayApp.controllers').controller('sendFlowController', function($scope, $rootScope, $state, $stateParams, $log, configService, gettextCatalog, profileService) { - var unitToSatoshi; - var satToUnit; - var unitDecimals; - var satToBtc; - var nextStep = ''; - $scope.$on("$ionicView.beforeEnter", function(event, data) { var config = configService.getSync().wallet.settings; $scope.params = $stateParams; - $scope.specificAmount = $scope.specificAlternativeAmount = ''; - unitToSatoshi = config.unitToSatoshi; - satToUnit = 1 / unitToSatoshi; - satToBtc = 1 / 100000000; - unitDecimals = config.unitDecimals; - - if ($scope.params.amount) { - console.log("is Payment Request", $scope.params.amount); - + if ($scope.params.amount) { // There is an amount, so presume that it a payment request + $scope.specificAmount = $scope.specificAlternativeAmount = ''; + var unitToSatoshi = config.unitToSatoshi; + var satToUnit = 1 / unitToSatoshi; + var satToBtc = 1 / 100000000; + var unitDecimals = config.unitDecimals; $scope.requestAmount = (($stateParams.amount) * satToUnit).toFixed(unitDecimals); $scope.isPaymentRequest = true; } }); $scope.$on("$ionicView.enter", function(event, data) { - console.log(data, $stateParams); + configService.whenAvailable(function(config) { + $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; + }); + $scope.type = data.stateParams && data.stateParams['fromWalletId'] ? 'destination' : 'origin'; // origin || destination $scope.coin = false; // Wallets to show (for destination screen) - $scope.walletsEmpty = []; - // Show price-header - - console.log("current type: "+$scope.type); + $scope.walletsEmpty = []; // empty wallets for origin screen if ($scope.type === 'origin') { $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); $scope.walletsEmpty = profileService.getWallets({coin: $scope.coin, hasNoFunds: true}); } else if ($scope.type === 'destination') { - $scope.fromWallet = profileService.getWallet(data.stateParams.fromWalletId); - $scope.coin = $scope.fromWallet.coin; - + $scope.coin = $scope.fromWallet.coin; // Only show wallets with the select origin wallet coin $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to'); } - if (!$scope.coin || $scope.coin === 'bch') { + if (!$scope.coin || $scope.coin === 'bch') { // if no specific coin is set or coin is set to bch $scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: $scope.type==='origin'}); } - if (!$scope.coin || $scope.coin === 'btc') { + if (!$scope.coin || $scope.coin === 'btc') { // if no specific coin is set or coin is set btc $scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: $scope.type === 'origin'}); } - - configService.whenAvailable(function(config) { - $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; - }); }); function getNextStep() { - if (!$scope.params.toWalletId && !$scope.params.toAddress) { + if (!$scope.params.toWalletId && !$scope.params.toAddress) { // If we have no toAddress or fromWallet return 'tabs.send.destination'; - } else if (!$scope.params.amount) { + } else if (!$scope.params.amount) { // If we have no amount return 'tabs.send.amount'; - } else { + } else { // If we do have them return 'tabs.send.confirm'; } } $scope.useWallet = function(wallet) { - if ($scope.type === 'origin') { + if ($scope.type === 'origin') { // we're on the origin screen, set wallet to send from $scope.params['fromWalletId'] = wallet.id; - } else { + } else { // we're on the destination screen, set wallet to send to $scope.params['toWalletId'] = wallet.id; } $state.transitionTo(getNextStep(), $scope.params); From b88329fbb36811ce77cb5e783682e9e7dfdc9faa Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 25 Jul 2018 15:07:15 +0200 Subject: [PATCH 050/240] removed unused vars + changes for wallet to wallet transfer --- src/js/controllers/confirm.js | 109 +++++++++++------------ www/views/confirm.html | 4 +- www/views/wallet-origin-destination.html | 1 + 3 files changed, 54 insertions(+), 60 deletions(-) diff --git a/src/js/controllers/confirm.js b/src/js/controllers/confirm.js index 74b86e623..b57d2ca08 100644 --- a/src/js/controllers/confirm.js +++ b/src/js/controllers/confirm.js @@ -10,16 +10,10 @@ angular.module('copayApp.controllers').controller('confirmController', function( // 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; //custom fee flag var usingCustomFee = false; @@ -31,7 +25,6 @@ angular.module('copayApp.controllers').controller('confirmController', function( }, 10); } - $scope.showWalletSelector = function() { $scope.walletSelector = true; refresh(); @@ -45,7 +38,6 @@ 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() { @@ -125,49 +117,8 @@ angular.module('copayApp.controllers').controller('confirmController', function( }); }; $scope.$on("$ionicView.beforeEnter", function(event, data) { - // Setup $scope + $scope.fromWallet = profileService.getWallet(data.stateParams.fromWalletId); // Wallet to send from - var B = data.stateParams.coin == 'bch' ? bitcoreCash : bitcore; - var networkName; - $scope.fromWallet = profileService.getWallet(data.stateParams.fromWalletId); - $scope.recipientType = null; - - try { - if (data.stateParams.toWalletId) { - $scope.recipientType = 'wallet'; // set type to wallet-to-wallet - $ionicLoading.show(); - var wallet = profileService.getWallet(data.stateParams.toWalletId); - walletService.getAddress(wallet, true, function (err, addr) { - $ionicLoading.hide(); - data.stateParams.toAddress = addr; - networkName = (new B.Address(data.stateParams.toAddress)).network.name; - vanityTx(networkName, data); - }); - } else if (data.stateParams.toAddress) { - networkName = (new B.Address(data.stateParams.toAddress)).network.name; - vanityTx(networkName, data); - } - } catch (e) { - var message = gettextCatalog.getString('Invalid address'); - var backText = gettextCatalog.getString('Go back'); - var learnText = gettextCatalog.getString('Learn more'); - popupService.showConfirm(null, message, backText, learnText, function (back) { - $ionicHistory.nextViewOptions({ - disableAnimate: true, - historyRoot: true - }); - $state.go('tabs.send').then(function () { - $ionicHistory.clearHistory(); - if (!back) { - var url = 'https://support.bitpay.com/hc/en-us/articles/115004671663'; - externalLinkService.open(url); - } - }); - }); - return; - } - }); - var vanityTx = function(networkName, data) { // Grab stateParams tx = { amount: parseInt(data.stateParams.amount), @@ -179,10 +130,10 @@ angular.module('copayApp.controllers').controller('confirmController', function( // Vanity tx info (not in the real tx) recipientType: $scope.recipientType || null, - toName: data.stateParams.toName, - toEmail: data.stateParams.toEmail, - toColor: data.stateParams.toColor, - network: networkName, + toName: null, + toEmail: null, + toColor: null, + network: false, coin: $scope.fromWallet.coin, txp: {}, }; @@ -192,10 +143,52 @@ angular.module('copayApp.controllers').controller('confirmController', function( tx.feeRate = parseInt(data.stateParams.requiredFeeRate); } - if (tx.coin && tx.coin == 'bch') { + if (tx.coin && tx.coin === 'bch') { tx.feeLevel = 'normal'; } + var B = data.stateParams.coin === 'bch' ? bitcoreCash : bitcore; + var networkName; + $scope.recipientType = null; + try { + if (data.stateParams.toWalletId) { // There is a toWalletId, so we presume this is a wallet-to-wallet transfer + $scope.recipientType = 'wallet'; // set transaction type to wallet-to-wallet + $ionicLoading.show(); + + var toWallet = profileService.getWallet(data.stateParams.toWalletId); + tx.toColor = toWallet.color; + tx.toName = toWallet.name; + + // We need an address to send to, so we ask the walletService to create a new address for the toWallet. + walletService.getAddress(toWallet, true, function (err, addr) { + $ionicLoading.hide(); + tx.toAddress = addr; + networkName = (new B.Address(tx.toAddress)).network.name; + tx.network = networkName; + setupTx(tx); + }); + } else { // This is a Wallet-to-address transfer + networkName = (new B.Address(tx.toAddress)).network.name; + tx.network = networkName; + setupTx(tx); + } + } catch (e) { + var message = gettextCatalog.getString('Invalid address'); + popupService.showAlert(null, message, function () { + $ionicHistory.nextViewOptions({ + disableAnimate: true, + historyRoot: true + }); + $state.go('tabs.send').then(function () { + $ionicHistory.clearHistory(); + }); + }); + return; + } + }); + + var setupTx = function(networkName, data) { + // Other Scope vars $scope.isCordova = isCordova; $scope.showAddress = false; @@ -410,19 +403,19 @@ angular.module('copayApp.controllers').controller('confirmController', function( function setButtonText(isMultisig, isPayPro) { if (isPayPro) { - if (isCordova && !isWindowsPhoneApp) { + if (isCordova) { $scope.buttonText = gettextCatalog.getString('Slide to pay'); } else { $scope.buttonText = gettextCatalog.getString('Click to pay'); } } else if (isMultisig) { - if (isCordova && !isWindowsPhoneApp) { + if (isCordova) { $scope.buttonText = gettextCatalog.getString('Slide to accept'); } else { $scope.buttonText = gettextCatalog.getString('Click to accept'); } } else { - if (isCordova && !isWindowsPhoneApp) { + if (isCordova) { $scope.buttonText = gettextCatalog.getString('Slide to send'); } else { $scope.buttonText = gettextCatalog.getString('Click to send'); diff --git a/www/views/confirm.html b/www/views/confirm.html index e54837f34..1def16389 100644 --- a/www/views/confirm.html +++ b/www/views/confirm.html @@ -105,13 +105,13 @@ {{buttonText}} diff --git a/www/views/wallet-origin-destination.html b/www/views/wallet-origin-destination.html index 66f5852ad..32ac73e59 100644 --- a/www/views/wallet-origin-destination.html +++ b/www/views/wallet-origin-destination.html @@ -1,6 +1,7 @@ {{'Wallet to wallet transfer' | translate}} +
From d51ab2bf79f732a9b5d4eb0a4e6404840367f993 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 25 Jul 2018 15:11:14 +0200 Subject: [PATCH 051/240] only change button color if it contains an address --- www/views/tab-send.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/views/tab-send.html b/www/views/tab-send.html index 8b39808db..999b940df 100644 --- a/www/views/tab-send.html +++ b/www/views/tab-send.html @@ -15,7 +15,7 @@
-
+ ng-if="!item.isWallet && item.recipientType != 'wallet'" ng-click="sendToContact(item)"> diff --git a/www/views/wallet-origin-destination.html b/www/views/wallet-origin-destination.html index 32ac73e59..4bdbde22b 100644 --- a/www/views/wallet-origin-destination.html +++ b/www/views/wallet-origin-destination.html @@ -1,13 +1,13 @@ - {{'Wallet to wallet transfer' | translate}} + {{sendFlowTitle}}
Paying
-
$37.42 USD
-
0.04580000 BCH {{requestAmount}}
+
$... USD
+
{{requestAmount}} {{coin.toUpperCase()}}
From decd0a123e1931aaea4ad2763e9fdd10f5701024 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 25 Jul 2018 16:43:45 -0700 Subject: [PATCH 055/240] Buttons on keypad have correct colour in new Enter Amount screen. --- src/js/routes.js | 2 +- src/sass/views/amountNew.scss | 484 ++++++++++++++++++++++++++++++++++ src/sass/views/views.scss | 1 + www/views/amountNew.html | 85 ++++++ 4 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 src/sass/views/amountNew.scss create mode 100644 www/views/amountNew.html diff --git a/src/js/routes.js b/src/js/routes.js index 286b27ab1..5900b88da 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -701,7 +701,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr 'tab-receive@tabs': { controller: 'amountController', controllerAs: 'vm', - templateUrl: 'views/amount.html' + templateUrl: 'views/amountNew.html' } } }) diff --git a/src/sass/views/amountNew.scss b/src/sass/views/amountNew.scss new file mode 100644 index 000000000..cbffe4929 --- /dev/null +++ b/src/sass/views/amountNew.scss @@ -0,0 +1,484 @@ +#view-amount-new { + @extend .deflash-blue; + .recipient-label { + font-size: 14px; + padding-bottom: 0; + color: $v-mid-gray; + } + .item-no-bottom-border + .item { + border-top: 0; + } + .icon-bitpay-card { + background-image: url("../img/icon-bitpay.svg"); + } + .icon-amazon { + background-image: url("../img/icon-amazon.svg"); + } + @media(max-width: 480px) { + .bitcoin-address { + .icon { + left: 8px; + font-size: 24px; + } + .big-icon-svg { + left:5px; + & > .bg{ + width:30px; + height:30px; + box-shadow: none; + } + } + font-size: 13px; + padding-left: 48px; + } + } + @media(max-width: 320px) { + .bitcoin-address { + & > span:last-child { + margin-left: -2px; + } + } + } + .send-gravatar { + left: 11px; + position: absolute; + top: 10px; + } + .amount span input { + display: inline; + width: 120px; + } + .amount-pane-recipient { + position: absolute; + top: 95px; + bottom: 0; + width: 100%; + background-color: #fff; + padding: 0 16px; + + .amount-bar { + padding: 24px 0; + font-size: 18px; + @media(max-height: 480px) { + padding: 0px; + } + @media(max-width: 320px) { + padding: 0px; + } + .title { + float: left; + padding-top: 10px; + color: $v-dark-gray; + font-weight: bold; + @media(max-height: 480px) { + padding: 0px; + } + } + @media(max-height: 480px) { + padding-top: 3px; + } + } + .amount { + display: flex; + flex-direction: column; + justify-content: center; + flex-grow: 1; + position: absolute; + bottom: 254px; + top: 66px; + .light { + color: $v-light-gray; + } + @media(max-height: 480px) { + top: 45px; + } + @media(max-width: 320px) { + bottom: 276px; + top: 60px; + & > div { + display: inline-block; + } + & > div:first-child { + display: inherit; + } + } + } + } + .amount-pane-no-recipient { + position: absolute; + top: 0; + bottom: 0; + width: 100%; + background-color: #fff; + padding: 0 16px; + + .amount-bar { + padding: 24px 0; + font-size: 18px; + .title { + padding-top: 10px; + color: $v-dark-gray; + font-weight: bold; + .limits { + margin-top: 10px; + color: $v-light-gray; + font-size: 12px; + } + .select { + margin: 10px 1px; + } + } + } + .amount { + display: flex; + flex-direction: column; + justify-content: center; + flex-grow: 1; + position: absolute; + bottom: 254px; + top: 66px; + .light { + color: $v-light-gray; + } + } + } + .amount { + padding-top: 10px; + padding-bottom: 10px; + .icon-toggle { + font-size: 1.2em; + width: auto; + margin: 0.8em auto; + border: 1px solid $v-subtle-gray; + color: $v-dark-gray; + border-radius: 3px; + padding: 0 10px; + cursor: pointer; + @media(max-height: 280px) { + margin: 0.1em auto; + } + } + &__editable { + &--minimize { + font-size: 22px; + } + &--standard { + font-size: 42px; + @media(max-height: 480px) { + font-size: 26px; + padding-top: 10px; + } + } + &--placeholder { + color: $v-light-gray; + } + } + &__number { + color: $v-dark-gray; + } + &__currency-toggle { + border: 1px solid $v-subtle-gray; + color: $v-dark-gray; + border-radius: 3px; + padding: 0 10px; + cursor: pointer; + font-size: .6em; + position: relative; + top: -3px; + line-height: 1; + @media(max-width: 320px) { + line-height: 30px; + height: 30px; + } + } + &__currency-toggle-mobile { + border: 1px solid $v-subtle-gray; + color: $v-dark-gray; + border-radius: 3px; + cursor: pointer; + position: relative; + line-height: 1; + @media(max-width: 320px) { + line-height: 30px; + height: 30px; + } + } + &__results { + &--minimize { + font-size: 12px; + } + &--standard { + font-size: 18px; + padding: 10px 0; + } + &--placeholder { + color: $v-light-gray; + } + } + &__result { + color: $v-light-gray; + font-size: .9em; + //margin-bottom: -.9em; TODO matias + line-height: 1; + @media(max-height: 480px) { + margin-bottom: 0; + } + } + &__result-equiv { + color: $v-mid-gray; + font-size: 1.2em; + @media(max-height: 480px) { + margin-top: 0; + font-size: 16px; + } + } + } + + .scroll-content { + display: flex; + flex-direction: column; + + .send-amount { + flex: 1 1 auto; + display: flex; + flex-direction: column; + justify-content: center; + + .send-amount-tool { + flex: 0 1 auto; + + .send-amount-tool-input { + text-align: center; + position: relative; + padding: 10px 30px; + + .text-selectable { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + } + + .primary-amount { + input, .unit, .primary-amount-display { + font-size: 1.8em; + + @media (min-width: 375px) { + font-size: 2.1em; + } + + @media (min-width: 414px) { + font-size: 2.4em; + } + } + + &.long { + input, .unit, .primary-amount-display { + font-size: 1.6em; + + @media (min-width: 375px) { + font-size: 1.8em; + } + + @media (min-width: 414px) { + font-size: 2em; + } + } + } + + &.very-long { + input, .unit, .primary-amount-display { + font-size: 0.9em; + + @media (min-width: 375px) { + font-size: 1.3em; + } + + @media (min-width: 414px) { + font-size: 1.4em; + } + } + } + + + input { + border:0; + padding:0; + white-space:normal; + background:none; + line-height:1; + box-sizing:content-box; + display: inline-block; + vertical-align: middle; + margin: 0; + height: 1em; + margin-right: 5px; + font-family: 'ProximaNova'; + + @media (min-width: 375px) { + } + + @media (min-width: 414px) { + } + } + + .unit, + .primary-amount-display { + display: inline-block; + vertical-align: middle; + line-height: 1em; + } + + .unit { + font-weight: bold; + } + + .primary-amount-display { + margin-right: 5px; + word-break: break-all; + } + } + + .switch-currencies { + position: absolute; + right: 0; + top: 50%; + transform: translate(0, -50%); + padding: 15px; + + img { + width: 18px; + } + } + } + + .send-amount-actions { + margin-top: 15px; + display: flex; + align-items: center; + justify-content: center; + + .button { + flex: 1 1 auto; + line-height: 1.2em; + + + .button { + margin-left: 10px; + } + + span { + display: flex; + align-items: center; + justify-content: center; + } + } + } + } + } + + .button { + &.no-margin { + margin: 0; + } + } + + .notification-warning { + display: block; + padding: .75rem 1.25rem; + color: #856404; + background-color: #fff3cd; + border: 1px solid #ffeeba; + line-height: 1.4em; + margin-bottom: 20px; + } + + .keypad-container { + position: relative; + //flex: 0 1 196px; + + @media (min-height: 667px) { + //flex: 0 1 224px; + } + + .keypad { + text-align: center; + font-size: 18px; + font-weight: lighter; + position: absolute; + bottom: 0; + width: 100%; + color: $v-text-primary-color; + + @media (min-height: 667px) { + font-size: 24px; + } + + .row { + padding: 0 !important; + margin: 0 !important; + } + + .col { + line-height: 38px; + + @media (min-height: 667px) { + line-height: 45px; + } + } + + .row { + &:last-child { + .col { + padding-bottom: 10px; + } + } + } + + .operator { + background-color: $v-subtle-gray; + font-weight: normal; + cursor: pointer; + + &:active { + background-color: $v-light-gray; + } + } + + .operator-send { + font-weight: bolder; + color: #fff; + background-color: $positive; + font-size: 36px; + cursor: pointer; + + &:active { + background-color: #eaeaea; + } + } + + .digit{ + cursor: pointer; + background-color: #000; + border: 1px solid #262424; + transition: all 0.1s ease; + + + &:active { + background-color: $v-dark-gray; + } + } + + @media(max-height: 480px) { + font-size: 12px; + + } + } + } + } + background: #494949; + + ion-content { + margin-bottom: constant(safe-area-inset-bottom); /* iOS 11.0 */ + margin-bottom: env(safe-area-inset-bottom); /* iOS 11.2 */ + } +} \ No newline at end of file diff --git a/src/sass/views/views.scss b/src/sass/views/views.scss index d4ed735ed..387b68597 100644 --- a/src/sass/views/views.scss +++ b/src/sass/views/views.scss @@ -1,6 +1,7 @@ @import "tabs"; @import "add"; @import "amount"; +@import "amountNew"; @import "confirm"; @import "copayers"; @import "starting"; diff --git a/www/views/amountNew.html b/www/views/amountNew.html new file mode 100644 index 000000000..6fe58eb9b --- /dev/null +++ b/www/views/amountNew.html @@ -0,0 +1,85 @@ + + + + {{'Enter amount (New)' | translate}} + + + + + +
+ +
+
+ Minimum amount: {{vm.minShapeshiftAmount}}
+ Maximum amount: {{vm.maxShapeshiftAmount}}
+
+
+
+
+ {{ vm.amountModel.amount || 0 }}{{vm.unit}} +
+ {{vm.globalResult}} {{vm.unit}} +
+ {{vm.alternativeAmount || '0.00'}} {{vm.alternativeUnit}} +
+
+
+
+ + +
+
+
+
+ +
+
+ +
+
7
+
8
+
9
+
+ +
+
4
+
5
+
6
+
+ +
+
1
+
2
+
3
+
+ +
+
.
+
0
+
+
+
+
+ + +
+
\ No newline at end of file From e539f0e713bb095dfa8f0869e983ba575df49a99 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 25 Jul 2018 17:32:07 -0700 Subject: [PATCH 056/240] Space around amount, and added "Not Enough Funds" warning. --- src/sass/views/amountNew.scss | 9 +++++++ www/views/amountNew.html | 45 ++++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/sass/views/amountNew.scss b/src/sass/views/amountNew.scss index cbffe4929..0d1798e25 100644 --- a/src/sass/views/amountNew.scss +++ b/src/sass/views/amountNew.scss @@ -244,6 +244,15 @@ flex-direction: column; justify-content: center; + .send-amount-header-footer { + flex: 1 1 auto; + min-height: 10px; + + .warning { + text-align:center; + } + } + .send-amount-tool { flex: 0 1 auto; diff --git a/www/views/amountNew.html b/www/views/amountNew.html index 6fe58eb9b..e6b11750c 100644 --- a/www/views/amountNew.html +++ b/www/views/amountNew.html @@ -1,7 +1,7 @@ - {{'Enter amount (New)' | translate}} + {{'Enter Amount' | translate}} @@ -9,10 +9,12 @@
-
-
- Minimum amount: {{vm.minShapeshiftAmount}}
- Maximum amount: {{vm.maxShapeshiftAmount}}
+
+
@@ -26,21 +28,26 @@
-
- - + -
+
+
+
+ +
From 91daae9f7a8fbe046b65218c3d3e364512cfbbc2 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 25 Jul 2018 18:19:21 -0700 Subject: [PATCH 057/240] Improved amount card layout. Darker background under card. --- src/sass/views/amountNew.scss | 19 ++++++++++++------- www/views/amountNew.html | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/sass/views/amountNew.scss b/src/sass/views/amountNew.scss index 0d1798e25..894135725 100644 --- a/src/sass/views/amountNew.scss +++ b/src/sass/views/amountNew.scss @@ -246,10 +246,14 @@ .send-amount-header-footer { flex: 1 1 auto; - min-height: 10px; + min-height: 20px; .warning { - text-align:center; + color: #b7664d; + font-family: 'ProximaNova-Semibold'; + font-size: 12pt; + padding: 0 6px 6px 6px; + text-align: center; } } @@ -269,6 +273,8 @@ } .primary-amount { + color: #333; + font-family: 'ProximaNova-Semibold'; input, .unit, .primary-amount-display { font-size: 1.8em; @@ -338,16 +344,15 @@ line-height: 1em; } - .unit { - font-weight: bold; - } - .primary-amount-display { margin-right: 5px; word-break: break-all; } } + .alternative-amount { + color: #6F6F70; + } .switch-currencies { position: absolute; right: 0; @@ -484,7 +489,7 @@ } } } - background: #494949; + background: #f2f2f2; ion-content { margin-bottom: constant(safe-area-inset-bottom); /* iOS 11.0 */ diff --git a/www/views/amountNew.html b/www/views/amountNew.html index e6b11750c..b9b6c08b2 100644 --- a/www/views/amountNew.html +++ b/www/views/amountNew.html @@ -5,7 +5,7 @@ - +
From 507fb21862c4a1b0eb95742fb5e6d537275efc94 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 25 Jul 2018 18:46:42 -0700 Subject: [PATCH 058/240] Available funds. --- src/js/controllers/amount.js | 2 ++ src/sass/views/amountNew.scss | 42 +++++++++++++++++++++-------------- www/views/amountNew.html | 20 ++++++++++------- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 1bc492136..1d4cb41f1 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -10,6 +10,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i vm.alternativeAmount = ''; vm.alternativeUnit = ''; vm.amountModel = { amount: 0 }; + vm.availableFunds = '251.00 USD'; vm.fromWalletId = ''; vm.globalResult = ''; vm.isRequestingSpecificAmount = false; @@ -409,6 +410,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i || (result >= vm.minShapeshiftAmount && result <= vm.maxShapeshiftAmount)); } } + console.log('allowSend: ', vm.allowSend); }; function processResult(val) { diff --git a/src/sass/views/amountNew.scss b/src/sass/views/amountNew.scss index 894135725..37de78665 100644 --- a/src/sass/views/amountNew.scss +++ b/src/sass/views/amountNew.scss @@ -365,27 +365,35 @@ } } } + } + } - .send-amount-actions { - margin-top: 15px; + .send-amount-extras { + display: flex; + flex: 0 0 auto; + flex-direction: row-reverse; + align-items: center; + justify-content: space-between; + margin: 0 14px; + + .extra { + display: inline-block; + flex: 0 1 auto; + font-size: 12pt; + } + + .button { + flex: 0 1 auto; + line-height: 1.2em; + + + .button { + margin-left: 10px; + } + + span { display: flex; align-items: center; justify-content: center; - - .button { - flex: 1 1 auto; - line-height: 1.2em; - - + .button { - margin-left: 10px; - } - - span { - display: flex; - align-items: center; - justify-content: center; - } - } } } } diff --git a/www/views/amountNew.html b/www/views/amountNew.html index b9b6c08b2..9d44e9e8d 100644 --- a/www/views/amountNew.html +++ b/www/views/amountNew.html @@ -35,20 +35,24 @@
-
- - +
+ Available Funds: {{vm.availableFunds}} +
+
+
From d87c2e65f75c3ab8bb97a0e916d5ba1576f12875 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 26 Jul 2018 15:04:42 +0200 Subject: [PATCH 059/240] Wallet buttons max width + Receive in viewing wallet. --- src/js/controllers/tab-receive.js | 10 +++++++--- src/js/controllers/walletDetails.js | 11 +++++++++++ src/js/routes.js | 2 +- src/sass/views/tab-home.scss | 4 ++-- src/sass/views/walletDetails.scss | 4 ++-- www/views/walletDetails.html | 2 +- 6 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/js/controllers/tab-receive.js b/src/js/controllers/tab-receive.js index 8f25412ec..dcb2d1166 100644 --- a/src/js/controllers/tab-receive.js +++ b/src/js/controllers/tab-receive.js @@ -233,10 +233,14 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi if (!$scope.wallets[0]) return; - // select first wallet if no wallet selected previously - var selectedWallet = checkSelectedWallet($scope.wallet, $scope.wallets); + var selectedWallet = null; + if (data.stateParams.walletId) { // from walletDetails + selectedWallet = checkSelectedWallet(profileService.getWallet(data.stateParams.walletId), $scope.wallets); + } else { + // select first wallet if no wallet selected previously + selectedWallet = checkSelectedWallet($scope.wallet, $scope.wallets); + } $scope.onWalletSelect(selectedWallet); - $scope.showShareButton = platformInfo.isCordova ? (platformInfo.isIOS ? 'iOS' : 'Android') : null; listeners = [ diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index b3fea717e..2436da730 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -483,4 +483,15 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun function rgbToHex(r, g, b) { return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); } + + $scope.goToReceive = function() { + $state.go('tabs.home', { + walletId: $scope.wallet.id + }).then(function () { + $ionicHistory.clearHistory(); + $state.go('tabs.receive', { + walletId: $scope.wallet.id + }); + }); + }; }); diff --git a/src/js/routes.js b/src/js/routes.js index 8277314e5..f00679727 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -236,7 +236,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }) .state('tabs.receive', { - url: '/receive', + url: '/receive/:walletId', views: { 'tab-receive': { controller: 'tabReceiveController', diff --git a/src/sass/views/tab-home.scss b/src/sass/views/tab-home.scss index 69cdfde4d..680738a3b 100644 --- a/src/sass/views/tab-home.scss +++ b/src/sass/views/tab-home.scss @@ -70,8 +70,8 @@ } } .buttons { - margin-bottom: 0; - margin-top: 6px; + margin: 6px auto 0px; + max-width: 600px; >.col { padding: 5px 10px; margin-bottom: 0; diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index 6be9e6cf2..d03530cbb 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -192,8 +192,8 @@ } .buttons { - margin-bottom: 0; - margin-top: 30px; + max-width: 600px; + margin: 30px auto 0; >.col { padding: 5px 10px; margin-bottom: 0; diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index e2c0f7342..2da69fb6e 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -92,7 +92,7 @@
-
+
Receive
From b2ed16bf2198e48a6eb0ce9c85e4ba4d1b5b0858 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 26 Jul 2018 19:38:29 +0200 Subject: [PATCH 060/240] mobile support for send/receive buttons and some refactors to fix the navigation flow. --- src/js/controllers/walletDetails.js | 17 ++++++++++++++++- src/sass/views/tab-home.scss | 6 +++--- src/sass/views/walletDetails.scss | 12 ++++-------- www/views/walletDetails.html | 24 +++++++++++++++++++++--- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 2436da730..c824cbfda 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -483,7 +483,14 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun function rgbToHex(r, g, b) { return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); } - + $scope.goToSend = function() { + $state.go('tabs.home', { + walletId: $scope.wallet.id + }).then(function () { + $ionicHistory.clearHistory(); + $state.go('tabs.send'); + }); + }; $scope.goToReceive = function() { $state.go('tabs.home', { walletId: $scope.wallet.id @@ -494,4 +501,12 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun }); }); }; + $scope.goToBuy = function() { + $state.go('tabs.home', { + walletId: $scope.wallet.id + }).then(function () { + $ionicHistory.clearHistory(); + $state.go('tabs.buyandsell'); + }); + }; }); diff --git a/src/sass/views/tab-home.scss b/src/sass/views/tab-home.scss index 680738a3b..708ff4fad 100644 --- a/src/sass/views/tab-home.scss +++ b/src/sass/views/tab-home.scss @@ -70,7 +70,7 @@ } } .buttons { - margin: 6px auto 0px; + margin: 6px auto -12px; max-width: 600px; >.col { padding: 5px 10px; @@ -84,8 +84,8 @@ width: 100%; font-size: 19px; font-weight: bolder; - min-height: 45px; - line-height: 45px; + min-height: auto; + line-height: 36px; } } .wallet-coin-logo { diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index d03530cbb..0536a5735 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -132,10 +132,6 @@ position: relative; height: 100%; height: calc(100% - env(safe-area-inset-bottom) * 2); - - &.status-bar { - margin-top: 20px; - } } .bar-header { border: 0; @@ -191,9 +187,9 @@ } } - .buttons { + .send-receive-buttons { max-width: 600px; - margin: 30px auto 0; + margin: 45px auto 0; >.col { padding: 5px 10px; margin-bottom: 0; @@ -206,8 +202,8 @@ width: 100%; font-size: 19px; font-weight: bolder; - min-height: 40px; - line-height: 40px; + min-height: auto; + line-height: 36px; } } } diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index 2da69fb6e..85aace8fc 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -90,21 +90,23 @@
-
+ +
Receive
-
+
Buy Bitcoin
-
+
Send
+
@@ -212,6 +214,22 @@
+
+
+
+ Receive +
+
+
+
+ Buy Bitcoin +
+
+ Send +
+
+
+
From 8fd0b76a44d5acaeeedae7261a2d073f87b292a9 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 26 Jul 2018 13:09:16 -0700 Subject: [PATCH 061/240] Change Currency button appearance. --- src/sass/views/amountNew.scss | 34 +++++++++++++++------ www/img/icon-alternative-currency-black.svg | 22 +++++++++++++ www/views/amountNew.html | 10 +++--- 3 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 www/img/icon-alternative-currency-black.svg diff --git a/src/sass/views/amountNew.scss b/src/sass/views/amountNew.scss index 37de78665..5229f9b56 100644 --- a/src/sass/views/amountNew.scss +++ b/src/sass/views/amountNew.scss @@ -251,7 +251,7 @@ .warning { color: #b7664d; font-family: 'ProximaNova-Semibold'; - font-size: 12pt; + font-size: 12px; padding: 0 6px 6px 6px; text-align: center; } @@ -371,24 +371,38 @@ .send-amount-extras { display: flex; flex: 0 0 auto; + /* So that if only one item is present, it appears on the right. */ flex-direction: row-reverse; + font-size: 12px; align-items: center; justify-content: space-between; margin: 0 14px; - .extra { - display: inline-block; + .extra, + button.extra { + color: #000; + display: flex; flex: 0 1 auto; - font-size: 12pt; } - .button { - flex: 0 1 auto; - line-height: 1.2em; + button.extra { + background: none; + border: none; + font-family: 'ProximaNova'; + font-size: 14px; + line-height: normal; + min-height: auto; + min-width: auto; + padding: 0; + } - + .button { - margin-left: 10px; - } + .button .icon:before { + font-size: 14px; + line-height: normal; + } + + + .button { span { display: flex; diff --git a/www/img/icon-alternative-currency-black.svg b/www/img/icon-alternative-currency-black.svg new file mode 100644 index 000000000..e9b175256 --- /dev/null +++ b/www/img/icon-alternative-currency-black.svg @@ -0,0 +1,22 @@ + + + + 3A719124-019D-470F-908A-5D61F117A295 + Created with sketchtool. + + + + + + + + + + + + + + + + + diff --git a/www/views/amountNew.html b/www/views/amountNew.html index 9d44e9e8d..753074e54 100644 --- a/www/views/amountNew.html +++ b/www/views/amountNew.html @@ -35,18 +35,20 @@
+
-
Available Funds: {{vm.availableFunds}} -
- +
+ -
- Available Funds: {{vm.availableFunds}} +
+ Available Funds: {{vm.availableFunds}}
From e344fd567b3e1bbc74727dc5a1a898ffb85aa1cf Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 27 Jul 2018 17:45:12 +0900 Subject: [PATCH 063/240] Add translations and remove googletagmanager --- i18n/po/ca/template-ca.po | 128 +++++++++- i18n/po/cs/template-cs.po | 130 +++++++++- i18n/po/de/template-de.po | 412 +++++++++++++++++++++----------- i18n/po/es-ES/template-es-ES.po | 130 +++++++++- i18n/po/fa/template-fa.po | 130 +++++++++- i18n/po/fr/template-fr.po | 134 ++++++++++- i18n/po/it/template-it.po | 130 +++++++++- i18n/po/ja/template-ja.po | 140 ++++++++++- i18n/po/ko/template-ko.po | 130 +++++++++- i18n/po/nl/template-nl.po | 130 +++++++++- i18n/po/pl/template-pl.po | 194 ++++++++++++--- i18n/po/pt-BR/template-pt-BR.po | 130 +++++++++- i18n/po/ru/template-ru.po | 130 +++++++++- i18n/po/sv-SE/template-sv-SE.po | 128 +++++++++- i18n/po/vi/template-vi.po | 401 ++++++++++++++++++++----------- i18n/po/zh-CN/template-zh-CN.po | 164 +++++++++++-- www/index.html | 7 +- 17 files changed, 2351 insertions(+), 397 deletions(-) diff --git a/i18n/po/ca/template-ca.po b/i18n/po/ca/template-ca.po index 515fcd814..088cd870b 100644 --- a/i18n/po/ca/template-ca.po +++ b/i18n/po/ca/template-ca.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Catalan\n" "Language: ca\n" -"PO-Revision-Date: 2018-07-04 09:26\n" +"PO-Revision-Date: 2018-07-27 08:43\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -437,6 +437,7 @@ msgid "Buy & Sell Bitcoin" msgstr "Compra & ven bitcoins" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "Compra bitcoins" @@ -619,10 +620,14 @@ msgstr "S'està connectant a Glidera..." msgid "Connection reset by peer" msgstr "Connexió reiniciada per un parell" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "Contactes" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "Adreces utilitzades freqüentment desades" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "Continua" @@ -823,7 +828,7 @@ msgstr "Crea una cartera compartida" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "Crea una cartera bitcoin" @@ -2533,6 +2538,14 @@ msgstr "Cerca transaccions" msgid "Search or enter bitcoin address" msgstr "Cerca o introdueix l'adreça bitcoin" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "Porta-retalls" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "Teniu el porta-retalls buit" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "Cerca transaccions" @@ -2591,9 +2604,70 @@ msgid "Send by email" msgstr "Envia per correu electrònic" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "Envia des de" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "Enviat a" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "Enganxa des del porta-retalls" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "Enganxa l'adreça" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "Transferència de cartera a cartera" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "Escaneja el codi QR" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Envia bitcoins més ràpid!" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Envia bitcoins més ràpid!" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "Deseu adreces utilitzades freqüentment i envieu-los bitcoins d'un sol toc" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "Afegiu el primer contacte" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "La vostra cartera bitcoin és buida" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "Per començar, compreu Bitcoin Cash (BCH) o Bitcoin Core (BTC), o compartiu la vostra adreça." + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "Podeu rebre bitcoins des de qualsevol cartera o servei." + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "Per començar, heu de crear una cartera bitcoin i obtenir uns quants bitcoins." + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "Compra Bitcoins ara" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "Mostra la meva adreça" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "Envia import màxim" @@ -3636,3 +3710,51 @@ msgstr "{{updatingTxHistoryProgress}} transaccions descarregades" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "{{wallet.m}}-de-{{wallet.n}}" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "Shapeshift" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "Comunitat" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "Reddit de Bitcoin Cash" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "Twitter de Bitcoin.com" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "Explora Bitcoin.com" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "Jocs de Bitcoin Cash" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "Notícies" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "Xarxa minera" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "Eines" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "Gràfiques de preus del bitcoin" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "Bitcoin Cash gratis" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "Les vostres carteres bitcoin ja estan a punt!" + diff --git a/i18n/po/cs/template-cs.po b/i18n/po/cs/template-cs.po index 14fe7faf2..5a3702736 100644 --- a/i18n/po/cs/template-cs.po +++ b/i18n/po/cs/template-cs.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Czech\n" "Language: cs\n" -"PO-Revision-Date: 2018-07-04 09:26\n" +"PO-Revision-Date: 2018-07-27 08:43\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -437,6 +437,7 @@ msgid "Buy & Sell Bitcoin" msgstr "Koupit & prodat Bitcoin" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "Koupit Bitcoin" @@ -619,10 +620,14 @@ msgstr "Připojování ke Glidera..." msgid "Connection reset by peer" msgstr "Připojení přerušeno druhou stranou" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "Kontakty" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "Uložené, často používané adresy" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "Pokračovat" @@ -823,7 +828,7 @@ msgstr "Vytvořit sdílenou peněženku" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "Vytvořit Bitcoin peněženku" @@ -2533,6 +2538,14 @@ msgstr "Vyhledávání transakcí" msgid "Search or enter bitcoin address" msgstr "Vyhledat nebo zadat Bitcoin adresu" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "Schránka" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "Vaše schránka je prázdná" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "Vyhledávání transakcí" @@ -2591,9 +2604,70 @@ msgid "Send by email" msgstr "Poslat e-mailem" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "Odeslat z" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "Odeslat" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "Vložit ze schránky" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "Vložit adresu" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "Převod mezi peněženkami" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "Skenovat QR kód" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Odesílejte Bitcoin rychleji!" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Odesílejte Bitcoin rychleji!" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "Uložte si často používané adresy a posílejte jim Bitcoin jedním kliknutím" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "Přidat váš první kontakt" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "Vaše Bitcoin peněženka je prázdná" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "Chcete-li začít, nakupte Bitcoin Cash (BCH) nebo Bitcoin Core (BTC), nebo sdílejte svou adresu." + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "Můžete přijímat Bitcoin z jakékoliv peněženky nebo služby." + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "Chcete-li začít, budete muset vytvořit Bitcoin peněženku a Bitcoin získat." + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "Koupit Bitcoin" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "Zobrazit mou adresu" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "Odeslat maximální částku" @@ -3030,7 +3104,7 @@ msgstr "Chcete-li začít, nakupte Bitcoin nebo sdílejte svou adresu. Můžete #: www/views/tab-send.html:33 msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." -msgstr "Chcete-li začít, budete muset vytvořit Bitcoin peněženku a Bitcoin získat." +msgstr "Chcete-li začít, budete potřebovat vytvořit Bitcoin peněženku a získat Bitcoin." #: src/js/services/bitpayAccountService.js:73 msgid "To {{reason}} you must first add your BitPay account - {{email}}" @@ -3636,3 +3710,51 @@ msgstr "{{updatingTxHistoryProgress}} transakcí staženo" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "{{wallet.m}}-z-{{wallet.n}}" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "Shapeshift" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "Komunita" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "Bitcoin Cash Reddit" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "Bitcoin.com Twitter" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "Prohlédnout Bitcoin.com" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "Bitcoin Cash hry" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "Novinky" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "Těžební pool" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "Nástroje" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "Grafy cen Bitcoinu" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "Bitcoin Cash zdarma" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "Vaše Bitcoin peněženka je připravena!" + diff --git a/i18n/po/de/template-de.po b/i18n/po/de/template-de.po index 7b3a50d69..18d8ca0f4 100644 --- a/i18n/po/de/template-de.po +++ b/i18n/po/de/template-de.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: German\n" "Language: de\n" -"PO-Revision-Date: 2018-07-04 03:57\n" +"PO-Revision-Date: 2018-07-27 08:44\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -79,7 +79,7 @@ msgstr "Kontonummer" #: www/views/tab-home.html:61 msgid "Instant transactions with low fees" -msgstr "" +msgstr "Sofortige Transaktionen mit niedrigen Gebühren" #: www/views/preferencesBitpayServices.html:23 msgid "Accounts" @@ -226,7 +226,7 @@ msgstr "Betrag zu hoch" #: www/views/includes/walletHistory.html:31 msgid "Amount too low to spend" -msgstr "Betrag ist zu niedrig zum ausgeben" +msgstr "Betrag ist zu niedrig zum Ausgeben" #: src/js/controllers/tab-home.js:147 msgid "An update to this app is available. For your security, please update to the latest version." @@ -384,7 +384,7 @@ msgstr "Bitcoin-Netzwerk Gebührenübersicht" #: www/views/tab-home.html:83 #: www/views/tab-settings.html:107 msgid "Bitcoin Core Wallets" -msgstr "Bitcoin Core Brieftaschen" +msgstr "Bitcoin Core Wallets" #: src/js/services/incomingData.js:151 msgid "Bitcoin cash Payment" @@ -437,6 +437,7 @@ msgid "Buy & Sell Bitcoin" msgstr "Kaufe & Verkaufe Bitcoins" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "Kaufe Bitcoins" @@ -547,7 +548,7 @@ msgstr "Schließen" #: www/views/includes/cash.html:2 #: www/views/preferencesInformation.html:17 msgid "Coin" -msgstr "" +msgstr "Münze" #: www/views/preferences.html:22 msgid "Color" @@ -563,7 +564,7 @@ msgstr "Beenden Sie den Backupvorgang um diese Option zu nutzen" #: www/views/bitpayCard.html:93 msgid "Completed" -msgstr "" +msgstr "Abgeschlossen" #: src/js/controllers/buyAmazon.js:311 #: src/js/controllers/buyMercadoLibre.js:305 @@ -584,7 +585,7 @@ msgstr "Bestätigen & Beenden" #: www/views/buyAmazon.html:90 msgid "Confirm purchase" -msgstr "" +msgstr "Kauf bestätigen" #: www/views/modals/pin.html:10 msgid "Confirm your PIN" @@ -619,10 +620,14 @@ msgstr "Verbinde mit Glidera..." msgid "Connection reset by peer" msgstr "Verbindung von Peer zurückgesetzt" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "Kontakte" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "Gespeicherte häufig verwendete Adressen" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "Weiter" @@ -633,7 +638,7 @@ msgstr "Übersetzungen beitragen" #: src/js/controllers/confirm.js:130 msgid "Copay only supports Bitcoin Cash using new version numbers addresses" -msgstr "" +msgstr "Copay unterstützt nur Bitcoin Cash bei Adressen welche das neue Adressformat benutzen" #: src/js/services/bwcError.js:62 msgid "Copayer already in this wallet" @@ -675,7 +680,7 @@ msgstr "In die Zwischenablage kopieren" #: src/js/controllers/buyMercadoLibre.js:102 msgid "Could not access Gift Card Service" -msgstr "" +msgstr "Der Geschenkkartendienst konnte nicht erreicht werden" #: www/views/tab-import-phrase.html:2 msgid "Could not access the wallet at the server. Please check:" @@ -683,7 +688,7 @@ msgstr "Kein Zugriff auf Wallet des Servers. Überprüfen Sie bitte:" #: src/js/controllers/buyAmazon.js:102 msgid "Could not access to Amazon.com" -msgstr "" +msgstr "Auf Amazon.com konnte nicht zugegriffen werden" #: src/js/services/profileService.js:511 msgid "Could not access wallet" @@ -707,13 +712,13 @@ msgstr "Adresse konnte nicht erstellt werden" #: src/js/controllers/topup.js:92 msgid "Could not create the invoice" -msgstr "" +msgstr "Die Rechnung konnte nicht erstellt werden" #: src/js/controllers/buyAmazon.js:164 #: src/js/controllers/buyMercadoLibre.js:164 #: src/js/controllers/topup.js:142 msgid "Could not create transaction" -msgstr "" +msgstr "Transaktion konnte nicht erstellt werden" #: src/js/services/profileService.js:350 msgid "Could not create using the specified extended private key" @@ -737,7 +742,7 @@ msgstr "Zahlungsvorschlag konnte nicht gelöscht werden" #: src/js/controllers/cashScan.js:117 msgid "Could not duplicate" -msgstr "" +msgstr "Konnte nicht duplizieren" #: src/js/services/feeService.js:73 msgid "Could not get dynamic fee" @@ -749,13 +754,13 @@ msgstr "Dynamische Gebühr für den Level konnte nicht abgerufen werden: {{feeLe #: src/js/controllers/modals/feeLevels.js:112 msgid "Could not get fee levels" -msgstr "" +msgstr "Gebühren konnten nicht abgerufen werden" #: src/js/controllers/buyAmazon.js:122 #: src/js/controllers/buyMercadoLibre.js:122 #: src/js/controllers/topup.js:100 msgid "Could not get the invoice" -msgstr "" +msgstr "Konnte die Rechnung nicht erhalten" #: src/js/controllers/bitpayCard.js:66 msgid "Could not get transactions" @@ -800,7 +805,7 @@ msgstr "Zahlung kann nicht gesendet werden" #: src/js/controllers/buyMercadoLibre.js:318 #: src/js/controllers/topup.js:299 msgid "Could not send transaction" -msgstr "" +msgstr "Transaktion konnte nicht gesendet werden" #: www/views/walletDetails.html:210 msgid "Could not update transaction history" @@ -823,7 +828,7 @@ msgstr "Erstelle eine gemeinsame Wallet" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "Erstelle Bitcoin-Wallet" @@ -859,7 +864,7 @@ msgstr "Aktuelle Gebühr für dieses Verfahren" #: src/js/services/feeService.js:15 msgid "Custom" -msgstr "" +msgstr "Eigene" #: www/views/customAmount.html:9 msgid "Custom Amount" @@ -867,7 +872,7 @@ msgstr "Benutzerdefinierter Betrag" #: src/js/controllers/preferencesFee.js:85 msgid "Custom Fee" -msgstr "" +msgstr "Eigene Gebühr" #: www/views/modals/mercadolibre-card-details.html:56 #: www/views/modals/txp-details.html:87 @@ -944,11 +949,11 @@ msgstr "Herunterladen" #: www/views/cashScan.html:37 msgid "Duplicate for BCH" -msgstr "Duplikat für BCH" +msgstr "Für BCH duplizieren" #: src/js/services/onGoingProcess.js:49 msgid "Duplicating wallet..." -msgstr "" +msgstr "Wallet wird dupliziert..." #: www/views/addresses.html:19 msgid "Each bitcoin wallet can generate billions of addresses from your 12-word backup. A new address is automatically generated and shown each time you receive a payment." @@ -960,7 +965,7 @@ msgstr "Wirtschaftlich" #: www/views/onboarding/collectEmail.html:27 msgid "Edit" -msgstr "" +msgstr "Bearbeiten" #: www/views/addressbook.add.html:29 #: www/views/addressbook.view.html:22 @@ -977,7 +982,7 @@ msgstr "Obergrenze für leere Adressen erreicht. Neue Adressen können nicht gen #: www/views/preferencesCash.html:17 msgid "Enable Bitcoin Cash wallet creation and operation within the App." -msgstr "" +msgstr "Bitcoin Cash-Wallet-Erstellung und Betrieb innerhalb der App aktivieren." #: www/views/tab-scan.html:19 msgid "Enable camera access in your device settings to get started." @@ -993,7 +998,7 @@ msgstr "Pushbenachrichtigungen aktivieren" #: www/views/preferencesNotifications.html:33 msgid "Enable sound" -msgstr "" +msgstr "Sound einschalten" #: www/views/tab-scan.html:18 msgid "Enable the camera to get started." @@ -1014,11 +1019,11 @@ msgstr "Eingabe des Zwei-Faktor-Authentifizierungs-Codes für das BitPay Konto" #: www/views/amount.html:4 msgid "Enter amount" -msgstr "" +msgstr "Betrag eingeben" #: www/views/modals/chooseFeeLevel.html:41 msgid "Enter custom fee" -msgstr "" +msgstr "Eigene Gebühr eingeben" #: src/js/services/walletService.js:1029 msgid "Enter new spending password" @@ -1032,7 +1037,7 @@ msgstr "Wiederherstellungsphrase eingeben (BIP39)" #: www/views/onboarding/collectEmail.html:13 msgid "Enter your email" -msgstr "" +msgstr "Ihre E-Mail Adresse eingeben" #: www/views/backup.html:69 msgid "Enter your password" @@ -1123,12 +1128,12 @@ msgstr "Fehler beim bestätigen" #: src/js/controllers/buyAmazon.js:179 msgid "Error creating gift card" -msgstr "" +msgstr "Fehler beim Erstellen der Geschenkkarte" #: src/js/controllers/buyAmazon.js:94 #: src/js/controllers/buyMercadoLibre.js:94 msgid "Error creating the invoice" -msgstr "" +msgstr "Fehler beim Erstellen der Rechnung" #: src/js/services/profileService.js:412 msgid "Error creating wallet" @@ -1136,17 +1141,17 @@ msgstr "Fehler beim Erstellen des Wallets" #: src/js/controllers/confirm.js:296 msgid "Error getting SendMax information" -msgstr "" +msgstr "Fehler beim Abrufen der SendMax Informationen" #: src/js/controllers/buyAmazon.js:136 #: src/js/controllers/buyMercadoLibre.js:136 #: src/js/controllers/topup.js:114 msgid "Error in Payment Protocol" -msgstr "" +msgstr "Fehler im Zahlungsprotokoll" #: src/js/controllers/bitpayCardIntro.js:14 msgid "Error pairing BitPay Account" -msgstr "" +msgstr "Fehler beim Paaren mit BitPay Konto" #: src/js/controllers/paperWallet.js:41 msgid "Error scanning funds:" @@ -1201,7 +1206,7 @@ msgstr "Erweiterte öffentliche Schlüssel" #: src/js/services/onGoingProcess.js:20 msgid "Extracting Wallet information..." -msgstr "" +msgstr "Wallet-Informationen extrahieren..." #: src/js/controllers/export.js:115 #: src/js/controllers/export.js:126 @@ -1222,16 +1227,16 @@ msgstr "Gebühr" #: www/views/modals/chooseFeeLevel.html:75 msgid "Fee level" -msgstr "" +msgstr "Gebührenstufe" #: src/js/controllers/modals/feeLevels.js:100 msgid "Fee level is not defined" -msgstr "" +msgstr "Gebührenstufe ist nicht definiert" #: www/views/confirm.html:79 #: www/views/modals/txp-details.html:99 msgid "Fee:" -msgstr "" +msgstr "Gebühr:" #: src/js/controllers/feedback/send.js:23 msgid "Feedback could not be submitted. Please try again later." @@ -1243,7 +1248,7 @@ msgstr "BitPay Konto abrufen..." #: src/js/services/onGoingProcess.js:21 msgid "Fetching payment information" -msgstr "" +msgstr "Zahlungsinformationen abrufen" #: www/views/export.html:14 #: www/views/import.html:16 @@ -1252,7 +1257,7 @@ msgstr "Datei/Text" #: www/views/preferencesLogs.html:17 msgid "Filter setting" -msgstr "" +msgstr "Filtereinstellungen" #: src/js/services/fingerprintService.js:43 #: src/js/services/fingerprintService.js:48 @@ -1285,7 +1290,7 @@ msgstr "Von BitPay Konto" #: www/views/tab-import-phrase.html:57 msgid "From Hardware Wallet" -msgstr "" +msgstr "Aus der Hardware-Wallet" #: www/views/tab-export-qrCode.html:5 msgid "From the destination device, go to Add wallet > Import wallet and scan this QR code" @@ -1301,7 +1306,7 @@ msgstr "Betrag gefunden:" #: www/views/topup.html:49 msgid "Funds to be added" -msgstr "" +msgstr "Hinzuzufügende Mittel" #: www/views/paperWallet.html:51 msgid "Funds transferred" @@ -1309,7 +1314,7 @@ msgstr "Betrag übermittelt" #: www/views/topup.html:103 msgid "Funds were added to debit card" -msgstr "" +msgstr "Mittel wurden auf die Debitkarte hinzugefügt" #: www/views/paperWallet.html:22 msgid "Funds will be transferred to" @@ -1317,7 +1322,7 @@ msgstr "Beträge werden überwiesen an" #: www/views/tab-receive.html:51 msgid "Generate new address" -msgstr "" +msgstr "Neue Adresse generieren" #: src/js/services/onGoingProcess.js:22 msgid "Generating .csv file..." @@ -1338,7 +1343,7 @@ msgstr "Erhalten Sie Neuigkeiten und Aktualisierungen von BitPay" #: www/views/onboarding/welcome.html:8 msgctxt "button" msgid "Get started" -msgstr "" +msgstr "Loslegen" #: www/views/bitpayCard.html:49 msgid "Get started" @@ -1355,20 +1360,20 @@ msgstr "Entgelt-Level werden abgerufen..." #: www/views/buyAmazon.html:43 #: www/views/buyMercadoLibre.html:42 msgid "Gift Card" -msgstr "Geschenk-Karte" +msgstr "Geschenkkarte" #: www/views/modals/mercadolibre-card-details.html:30 #: www/views/modals/mercadolibre-card-details.html:35 msgid "Gift Card is not available to use anymore" -msgstr "" +msgstr "Geschenkkarte kann nicht mehr verwendet werden" #: src/js/controllers/buyAmazon.js:204 msgid "Gift card expired" -msgstr "" +msgstr "Geschenkkarte ist abgelaufen" #: www/views/buyAmazon.html:111 msgid "Gift card generated and ready to use." -msgstr "" +msgstr "Geschenkkarte generiert und einsatzbereit." #: src/js/controllers/bitpayCard.js:114 #: src/js/controllers/bitpayCard.js:124 @@ -1414,7 +1419,7 @@ msgstr "Hardware-Wallet" #: src/js/controllers/create.js:180 #: src/js/controllers/join.js:145 msgid "Hardware wallets are not yet supported with Bitcoin Cash" -msgstr "" +msgstr "Bitcoin Cash unterstützt keine Hardware-Wallets" #: www/views/tab-settings.html:20 msgid "Help & Support" @@ -1531,7 +1536,7 @@ msgstr "Wenn Sie einen Screenshot erstellen, kann die Sicherung von anderen Apps #: www/views/tab-import-hardware.html:42 #: www/views/tab-import-phrase.html:80 msgid "Import" -msgstr "" +msgstr "Import" #: www/views/import.html:3 msgid "Import Wallet" @@ -1580,13 +1585,13 @@ msgstr "QR code hat falsches Format" #: src/js/services/bwcError.js:113 msgid "Incorrect network address" -msgstr "" +msgstr "Falsche Netzwerkadresse" #: src/js/controllers/confirm.js:114 #: src/js/controllers/confirm.js:306 #: src/js/services/bwcError.js:44 msgid "Insufficient confirmed funds" -msgstr "" +msgstr "Unzureichende bestätigte Mittel" #: src/js/controllers/topup.js:165 #: src/js/controllers/topup.js:177 @@ -1606,7 +1611,7 @@ msgstr "Ungültig" #: src/js/controllers/buyMercadoLibre.js:137 #: src/js/controllers/topup.js:115 msgid "Invalid URL" -msgstr "" +msgstr "Ungültige URL" #: src/js/controllers/create.js:186 #: src/js/controllers/import.js:345 @@ -1635,7 +1640,7 @@ msgstr "Einladung um eine {{appName}} Wallet zu teilen" #: www/views/mercadoLibreCards.html:20 #: www/views/modals/mercadolibre-card-details.html:48 msgid "Invoice expired" -msgstr "" +msgstr "Rechnung abgelaufen" #: src/js/controllers/feedback/send.js:79 msgid "Is there anything we could do better?" @@ -1696,7 +1701,7 @@ msgstr "Letzter Monat" #: www/views/preferencesCash.html:18 #: www/views/tx-details.html:94 msgid "Learn more" -msgstr "" +msgstr "Weitere Informationen" #: www/views/backup.html:43 msgid "Let's verify your backup phrase." @@ -1737,15 +1742,15 @@ msgstr "Zeitsperre aktiv. Bitte auf die Entfernung des Zahlungsvorschlags warten #: www/views/includes/logOptions.html:3 msgid "Log options" -msgstr "" +msgstr "Log-Optionen" #: www/views/modals/bitpay-card-confirmation.html:14 msgid "Log out" -msgstr "" +msgstr "Abmelden" #: www/views/addresses.html:87 msgid "Low amount inputs" -msgstr "" +msgstr "Zu wenige Inputs" #: www/views/includes/walletHistory.html:27 msgid "Low fees" @@ -1777,11 +1782,11 @@ msgstr "Notiz" #: www/views/mercadoLibre.html:6 msgid "Mercado Livre Brazil Gift Cards" -msgstr "" +msgstr "Mercado Livre Brazil Geschenkkarten" #: src/js/controllers/buyMercadoLibre.js:98 msgid "Mercadolibre Gift Card Service is not available at this moment. Please try back later." -msgstr "" +msgstr "Der MercadoLibre Geschenkkarten-Dienst ist aktuell nicht erreichbar. Bitte versuchen Sie es später noch einmal." #: www/views/modals/txp-details.html:131 msgid "Merchant Message" @@ -1791,7 +1796,7 @@ msgstr "Händlernachricht" #: www/views/buyMercadoLibre.html:54 #: www/views/topup.html:63 msgid "Miner Fee" -msgstr "" +msgstr "Miner-Gebühr" #: src/js/services/bwcError.js:134 msgid "Missing parameter" @@ -1828,13 +1833,13 @@ msgstr "Hinweis: Um eine Brieftasche aus einer 3rd-Party-Software zu importieren #: www/views/preferences.html:15 #: www/views/preferencesAlias.html:17 msgid "Name" -msgstr "" +msgstr "Name" #: www/views/buyAmazon.html:49 #: www/views/buyMercadoLibre.html:48 #: www/views/topup.html:56 msgid "Network Cost" -msgstr "" +msgstr "Netzwerkkosten" #: src/js/services/bwcError.js:47 msgid "Network error" @@ -1863,7 +1868,7 @@ msgstr "Keine Wallet" #: src/js/controllers/buyAmazon.js:115 #: src/js/controllers/buyMercadoLibre.js:115 msgid "No access key defined" -msgstr "" +msgstr "Kein Zugangsschlüssel definiert" #: www/views/onboarding/backupRequest.html:5 msgid "No backup, no bitcoin." @@ -1875,7 +1880,7 @@ msgstr "Noch keine Kontakte" #: www/views/preferencesLogs.html:16 msgid "No entries for this log level" -msgstr "" +msgstr "Keine Einträge für dieses Log-level" #: www/views/preferencesExternal.html:12 msgid "No hardware information available." @@ -1896,7 +1901,7 @@ msgstr "Keine aktuellen Transaktionen" #: src/js/controllers/buyAmazon.js:44 #: src/js/controllers/topup.js:47 msgid "No signing proposal: No private key" -msgstr "" +msgstr "Kein Unterzeichnungsvorschlag: Privater Schlüssel nicht vorhanden" #: www/views/walletDetails.html:204 msgid "No transactions yet" @@ -1915,7 +1920,7 @@ msgstr "Kein Wallet ausgewählt" #: src/js/controllers/confirm.js:85 #: src/js/controllers/topup.js:265 msgid "No wallets available" -msgstr "" +msgstr "Keine Wallets verfügbar" #: www/views/paperWallet.html:45 msgid "No wallets available to receive funds" @@ -1923,19 +1928,19 @@ msgstr "Keine Wallet verfügbar um Guthaben zu erhalten" #: www/views/cashScan.html:15 msgid "No wallets eligible for Bitcoin Cash support" -msgstr "" +msgstr "Keine Wallet zur Unterstützung von Bitcoin Cash geeignet" #: src/js/controllers/cashScan.js:58 msgid "Non BIP44 wallet" -msgstr "" +msgstr "Wallet ist keine BIP44-Wallet" #: www/views/cashScan.html:46 msgid "Non eligible BTC wallets" -msgstr "" +msgstr "Keine geeigneten BTC Wallets" #: src/js/services/feeService.js:12 msgid "Normal" -msgstr "" +msgstr "Normal" #: src/js/services/bwcError.js:80 msgid "Not authorized" @@ -1964,7 +1969,7 @@ msgstr "Notiz" #: www/views/backup.html:19 msgid "Note: if this BCH wallet was duplicated from a BTC wallet, they share the same recovery phrase." -msgstr "" +msgstr "Hinweis: Wenn diese BCH-Wallet aus einer BTC-Wallet dupliziert wurde, verwenden sie die selbe Wiederherstellungsphrase." #: www/views/modals/wallets.html:25 msgid "Notice: only 1-1 (single signature) wallets can be used for sell bitcoin" @@ -1977,15 +1982,15 @@ msgstr "Benachrichtigungen" #: www/views/onboarding/collectEmail.html:9 msgid "Notifications by email" -msgstr "" +msgstr "Benachrichtigungen per e-Mail" #: www/views/tx-details.html:117 msgid "Notify me if confirmed" -msgstr "" +msgstr "Benachrichtige mich sobald sie bestätigt ist" #: www/views/preferencesNotifications.html:24 msgid "Notify me when transactions are confirmed" -msgstr "" +msgstr "Benachrichtigen Sie mich, wenn Transaktionen bestätigt werden" #: www/views/includes/backupNeededPopup.html:8 msgid "Now is a good time to backup your wallet. If this device is lost, it is impossible to access your funds without a backup." @@ -2004,7 +2009,7 @@ msgstr "Jetzt ist die perfekte Zeit um Ihre Umgebung zu überprüfen. In der Nä #: src/js/services/popupService.js:72 #: www/views/modals/chooseFeeLevel.html:6 msgid "OK" -msgstr "" +msgstr "Okay" #: www/views/modals/tx-status.html:12 #: www/views/modals/tx-status.html:24 @@ -2023,7 +2028,7 @@ msgstr "Oh nein!" #: src/js/controllers/buyMercadoLibre.js:306 msgid "Ok" -msgstr "" +msgstr "Okay" #: www/views/tab-home.html:39 msgid "On this screen you can see all your wallets, accounts, and assets." @@ -2053,7 +2058,7 @@ msgstr "Öffne GitHub Projekt" #: src/js/controllers/bitpayCard.js:123 #: src/js/controllers/tx-details.js:192 msgid "Open Explorer" -msgstr "" +msgstr "Explorer öffnen" #: www/views/tab-scan.html:22 msgid "Open Settings" @@ -2069,11 +2074,11 @@ msgstr "Öffne Website" #: src/js/controllers/preferencesCash.js:32 msgid "Open bitcoincash.org?" -msgstr "" +msgstr "bitcoincash.org öffnen?" #: src/js/controllers/cashScan.js:18 msgid "Open the recovery tool." -msgstr "" +msgstr "Öffnen Sie das Recovery-Tool." #: www/views/tab-receive.html:27 msgid "Open wallet" @@ -2081,7 +2086,7 @@ msgstr "Öffne Wallet" #: www/views/includes/incomingDataMenu.html:19 msgid "Open website" -msgstr "" +msgstr "Öffne Website" #: www/views/bitpayCardIntro.html:34 msgid "Order the BitPay Card" @@ -2173,7 +2178,7 @@ msgstr "Zahlung akzeptiert. Sie wird durch Glidera übermittelt. Falls ein Probl #: src/js/services/incomingData.js:152 msgid "Payment address was translated to new Bitcoin Cash address format:" -msgstr "" +msgstr "Die Zahlungsadresse wurde auf das neue Bitcoin Cash-Adressformat übersetzt:" #: www/views/modals/txp-details.html:107 msgid "Payment details" @@ -2186,7 +2191,7 @@ msgstr "Zahlungsanforderung" #: www/views/mercadoLibreCards.html:22 #: www/views/modals/mercadolibre-card-details.html:39 msgid "Pending" -msgstr "" +msgstr "Ausstehend" #: www/views/proposals.html:4 msgid "Pending Proposals" @@ -2232,7 +2237,7 @@ msgstr "Bitte Copay aktualisieren, um diese Aktion auszuführen" #: www/views/walletDetails.html:142 #: www/views/walletDetails.html:62 msgid "Please wait" -msgstr "" +msgstr "Bitte warten" #: src/js/controllers/import.js:238 msgid "Please, select your backup file" @@ -2240,7 +2245,7 @@ msgstr "Bitte die Sicherungsdatei wählen" #: www/views/bitpayCard.html:81 msgid "Pre-Auth Holds" -msgstr "" +msgstr "Pre-Auth Holds" #: www/views/tab-settings.html:40 msgid "Preferences" @@ -2264,7 +2269,7 @@ msgstr "höchste Priorität" #: www/views/includes/incomingDataMenu.html:80 msgid "Private Key" -msgstr "" +msgstr "Privater Schlüssel" #: src/js/controllers/paperWallet.js:136 msgid "Private key encrypted. Enter password" @@ -2298,11 +2303,11 @@ msgstr "Vorschläge" #: src/js/controllers/buyAmazon.js:282 msgid "Purchase Amount is limited to {{limitPerDay}} {{currency}} per day" -msgstr "" +msgstr "Kaufbetrag beschränkt ist auf {{limitPerDay}} {{currency}} pro Tag beschränkt" #: src/js/controllers/buyMercadoLibre.js:281 msgid "Purchase amount must be a value between 50 and 2000" -msgstr "" +msgstr "Kaufbetrag muss ein Wert zwischen 50 und 2000 sein" #: www/views/onboarding/notifications.html:3 msgid "Push Notifications" @@ -2340,11 +2345,11 @@ msgstr "Mehr anzeigen" #: src/js/controllers/preferences.js:65 #: src/js/controllers/tx-details.js:54 msgid "Read more in our Wiki" -msgstr "" +msgstr "Lesen Sie mehr in unserem Wiki" #: src/js/controllers/cashScan.js:61 msgid "Read only wallet" -msgstr "" +msgstr "Schreibgeschützte Wallet" #: www/views/tab-receive.html:3 #: www/views/tabs.html:7 @@ -2353,7 +2358,7 @@ msgstr "Empfangen" #: www/views/customAmount.html:44 msgid "Receive in" -msgstr "" +msgstr "Erhalten in" #: www/views/includes/walletHistory.html:24 #: www/views/tx-details.html:18 @@ -2402,7 +2407,7 @@ msgstr "Wallet wiederherstellen..." #: www/views/modals/mercadolibre-card-details.html:22 msgid "Redeem now" -msgstr "" +msgstr "Jetzt einlösen" #: src/js/controllers/modals/txpDetails.js:63 #: src/js/controllers/tx-details.js:80 @@ -2420,7 +2425,7 @@ msgstr "Release-Informationen" #: www/views/addressbook.view.html:36 #: www/views/modals/mercadolibre-card-details.html:69 msgid "Remove" -msgstr "" +msgstr "Entfernen" #: src/js/controllers/preferencesBitpayServices.js:7 msgid "Remove BitPay Account?" @@ -2503,7 +2508,7 @@ msgstr "Adresse auf neue Beträge überprüfen" #: www/views/modals/fingerprintCheck.html:11 msgid "Scan again" -msgstr "" +msgstr "Erneut scannen" #: src/js/services/fingerprintService.js:56 msgid "Scan your fingerprint please" @@ -2511,7 +2516,7 @@ msgstr "Scannen Sie bitte Ihren Fingerabdruck" #: www/views/preferencesCash.html:23 msgid "Scan your wallets for Bitcoin Cash" -msgstr "" +msgstr "Durchsuche deine Wallet nach Bitcoin Cash" #: src/js/services/onGoingProcess.js:30 msgid "Scanning Wallet funds..." @@ -2519,7 +2524,7 @@ msgstr "Prüfe Wallet auf neue Beträge..." #: www/views/includes/walletList.html:11 msgid "Scanning funds..." -msgstr "" +msgstr "Mittel werden durchsucht..." #: www/views/includes/screenshotWarningModal.html:7 msgid "Screenshots are not secure" @@ -2533,6 +2538,14 @@ msgstr "Transaktionen durchsuchen" msgid "Search or enter bitcoin address" msgstr "Bitcoin-Adresse suchen oder eingeben" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "Zwischenablage" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "Ihre Zwischenablage ist leer" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "Transaktionen durchsuchen" @@ -2547,7 +2560,7 @@ msgstr "Sicherheit" #: www/views/modals/mercadolibre-card-details.html:64 msgid "See invoice" -msgstr "" +msgstr "Rechnung einsehen" #: www/views/tab-import-file.html:7 msgid "Select a backup file" @@ -2591,16 +2604,77 @@ msgid "Send by email" msgstr "Per E-Mail versenden" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "Senden von" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "Senden an" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "Aus Zwischenablage einfügen" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "Adresse einfügen" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "Wallet-zu-Wallet-Überweisung" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "QR-Code scannen" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Versenden Sie Bitcoin schneller!" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Versenden Sie Bitcoin schneller!" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "Speichern Sie häufig verwendete Adressen und senden Sie ihnen Bitcoin mit nur einem Tippen" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "Ihren ersten Kontakt hinzufügen" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "Ihre Bitcoin-Wallet ist leer" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "Um loszulegen, können sie entweder Bitcoin Cash (BCH) oder Bitcoin Core (BTC) kaufen, oder uns ihre Adresse mitteilen." + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "Sie können Bitcoin von jeder Wallet oder jedem Dienst erhalten." + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "Um loszulegen müssen Sie eine Bitcoin-Wallet erstellen und einige Bitcoins erhalten." + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "Bitcoin jetzt kaufen" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "Meine Adresse anzeigen" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "Sende max. Betrag" #: www/views/includes/incomingDataMenu.html:46 msgid "Send payment to this address" -msgstr "" +msgstr "Zahlung an diese Adresse senden" #: www/views/feedback/rateApp.html:17 msgid "Send us feedback instead" @@ -2651,11 +2725,11 @@ msgstr "Antwort des Servers konnte nicht verifiziert werden" #: src/js/controllers/buyAmazon.js:97 #: src/js/controllers/buyMercadoLibre.js:97 msgid "Service not available" -msgstr "" +msgstr "Dienst ist nicht verfügbar" #: www/views/includes/homeIntegrations.html:3 msgid "Services" -msgstr "" +msgstr "Dienste" #: www/views/preferencesLogs.html:3 msgid "Session Log" @@ -2671,7 +2745,7 @@ msgstr "Passwort einrichten" #: src/js/controllers/preferencesFee.js:85 msgid "Set your own fee in satoshis/byte" -msgstr "" +msgstr "Legen Sie Ihre eigene Gebühr in Satoshis/byte fest" #: www/views/tab-settings.html:3 #: www/views/tabs.html:19 @@ -2747,24 +2821,24 @@ msgstr "Überspringen" #: src/js/controllers/confirm.js:371 #: src/js/controllers/modals/txpDetails.js:47 msgid "Slide to accept" -msgstr "" +msgstr "Zum Akzeptieren schieben" #: www/views/buyAmazon.html:96 msgid "Slide to buy" -msgstr "" +msgstr "Zum Kaufen schieben" #: src/js/controllers/confirm.js:365 msgid "Slide to pay" -msgstr "" +msgstr "Zum Bezahlen schieben" #: src/js/controllers/confirm.js:377 #: src/js/controllers/modals/txpDetails.js:40 msgid "Slide to send" -msgstr "" +msgstr "Zum Senden schieben" #: www/views/cashScan.html:56 msgid "Some of your wallets are not eligible for Bitcoin Cash support. You can try to access BCH funds from these wallets using the" -msgstr "" +msgstr "Einige ihrer Wallets sind nicht zur Unterstützung von Bitcoin Cash geeignet. Sie können versuchen, auf BCH Mittel von diesen Wallets aus zuzugreifen indem Sie die" #: src/js/controllers/create.js:88 #: src/js/controllers/join.js:71 @@ -2785,7 +2859,7 @@ msgstr "Berechtigungscode erforderlich" #: www/views/walletDetails.html:173 msgid "Spending this balance will need significant Bitcoin network fees" -msgstr "" +msgstr "Die Ausgabe dieses Betrages wird signifikante Bitcoin-Netzwerkgebühren erfordern" #: www/views/tab-send.html:28 msgid "Start sending bitcoin" @@ -2798,11 +2872,11 @@ msgstr "Sperre des Startups" #: www/views/mercadoLibreCards.html:21 #: www/views/modals/mercadolibre-card-details.html:42 msgid "Still pending" -msgstr "" +msgstr "Noch ausstehend" #: www/views/topup.html:101 msgid "Success" -msgstr "" +msgstr "Erfolg" #: src/js/services/feeService.js:14 msgid "Super Economy" @@ -2810,7 +2884,7 @@ msgstr "Niedrigste Priorität" #: www/views/preferencesCash.html:11 msgid "Support Bitcoin Cash" -msgstr "" +msgstr "Bitcoin Cash unterstützen" #: www/views/paperWallet.html:7 msgid "Sweep" @@ -2840,7 +2914,7 @@ msgstr "Anzeigen durch tippen und halten" #: www/views/includes/walletInfo.html:3 msgid "Tap to recreate" -msgstr "" +msgstr "Zum neu erstellen tippen" #: www/views/includes/walletInfo.html:4 msgid "Tap to retry" @@ -2860,11 +2934,11 @@ msgstr "Nutzungsbedingungen" #: www/views/tab-create-personal.html:118 #: www/views/tab-import-phrase.html:68 msgid "Testnet" -msgstr "" +msgstr "Testnet" #: www/views/includes/incomingDataMenu.html:61 msgid "Text" -msgstr "" +msgstr "Text" #: src/js/controllers/feedback/send.js:27 #: src/js/controllers/feedback/send.js:76 @@ -2999,7 +3073,7 @@ msgstr "Diese Wiederherstellungsphrase entstand mit einem Passwort. Zur Wiederhe #: www/views/tx-details.html:91 msgid "This transaction amount is too small compared to current Bitcoin network fees. Spending these funds will need a Bitcoin network fee cost comparable to the funds itself." -msgstr "" +msgstr "Dieser Betrag ist zu klein im Vergleich zu den aktuellen Bitcoin-Netzwerk-Gebühren. Diese Mittel benötigen eine Bitcoin Netzwerk Gebühr mit vergleichbarer Größe zu den Mitteln." #: www/views/tx-details.html:87 msgid "This transaction could take a long time to confirm or could be dropped due to the low fees set by the sender" @@ -3034,15 +3108,15 @@ msgstr "Um loszulegen müssen Sie eine Bitcoin-Wallet erstellen und einige Bitco #: src/js/services/bitpayAccountService.js:73 msgid "To {{reason}} you must first add your BitPay account - {{email}}" -msgstr "" +msgstr "Zum {{reason}} müssen Sie erst Ihr BitPay-Konto hinzufügen - {{email}}" #: src/js/services/onGoingProcess.js:48 msgid "Top up in progress..." -msgstr "" +msgstr "Aufladung in Bearbeitung..." #: src/js/controllers/topup.js:206 msgid "Top up {{amountStr}} to debit card ({{cardLastNumber}})" -msgstr "" +msgstr "Laden sie {{amountStr}} auf die Debitkarte ({{cardLastNumber}}) auf" #: www/views/buyAmazon.html:61 #: www/views/buyMercadoLibre.html:60 @@ -3061,7 +3135,7 @@ msgstr "Gesamtanzahl der Copayer" #: www/views/addresses.html:81 msgid "Total wallet inputs" -msgstr "" +msgstr "Alle Wallet-Inputs" #: src/js/services/fingerprintService.js:63 #: src/js/services/fingerprintService.js:68 @@ -3074,7 +3148,7 @@ msgstr "Transaktion" #: www/views/confirm.html:126 msgid "Transaction Created" -msgstr "" +msgstr "Transaktion erstellt" #: www/views/preferencesAdvanced.html:29 #: www/views/preferencesHistory.html:3 @@ -3089,11 +3163,11 @@ msgstr "Transaktion wurde bereits übermittelt" #: src/js/controllers/buyMercadoLibre.js:301 #: src/js/controllers/topup.js:281 msgid "Transaction has not been created" -msgstr "" +msgstr "Transaktion wurde nicht erstellt" #: www/views/topup.html:104 msgid "Transaction initiated" -msgstr "" +msgstr "Transaktion eingeleitet" #: src/js/controllers/tx-details.js:119 msgid "Transaction not available at this time" @@ -3106,11 +3180,11 @@ msgstr "Transaktion konnte nicht gefunden werden" #: www/views/modals/chooseFeeLevel.html:55 msgid "Transactions without fee are not supported." -msgstr "" +msgstr "Transaktionen ohne Gebühren werden nicht unterstützt." #: src/js/controllers/paperWallet.js:109 msgid "Transfer to" -msgstr "" +msgstr "Transfer zu" #: www/views/tab-send.html:67 msgid "Transfer to Wallet" @@ -3130,7 +3204,7 @@ msgstr "Wiederherstellungsphrase eingeben (in der Regel 12 Wörter)" #: src/js/controllers/backup.js:75 msgid "Uh oh..." -msgstr "" +msgstr "Oh oh..." #: www/views/tx-details.html:100 msgid "Unconfirmed" @@ -3178,12 +3252,12 @@ msgstr "Überprüfe Wiederherstellungsphrase..." #: www/views/modals/fingerprintCheck.html:4 msgid "Verify your identity" -msgstr "" +msgstr "Bestätigen Sie Ihre Identität" #: www/views/preferencesAbout.html:14 #: www/views/preferencesExternal.html:25 msgid "Version" -msgstr "" +msgstr "Version" #: www/views/tab-export-file.html:69 msgid "View" @@ -3200,7 +3274,7 @@ msgstr "Nutzungsbedingungen anzeigen" #: src/js/controllers/bitpayCard.js:122 #: src/js/controllers/tx-details.js:191 msgid "View Transaction on Explorer.Bitcoin.com" -msgstr "" +msgstr "Transaktion auf Explorer.Bitcoin.com ansehen" #: src/js/controllers/tab-home.js:148 msgid "View Update" @@ -3212,7 +3286,7 @@ msgstr "Auf blockchain anzeigen" #: www/views/mercadoLibre.html:26 msgid "Visit mercadolivre.com.br →" -msgstr "" +msgstr "Besuchen Sie mercadolivre.com.br →" #: www/views/walletDetails.html:182 msgid "WARNING: Key derivation is not working on this device/wallet. Actions cannot be performed on this wallet." @@ -3274,7 +3348,7 @@ msgstr "Wallet-Informationen" #: www/views/addresses.html:76 msgid "Wallet Inputs" -msgstr "" +msgstr "Wallet-Inputs" #: www/views/join.html:26 msgid "Wallet Invitation" @@ -3328,7 +3402,7 @@ msgstr "Wallet exstiert bereits" #: src/js/services/profileService.js:516 msgid "Wallet already in {{appName}}" -msgstr "" +msgstr "Wallet bereits in {{appName}}" #: www/views/includes/walletActivity.html:6 msgid "Wallet created" @@ -3379,7 +3453,7 @@ msgstr "Wallet ist nicht beim Wallet-Service registiert. Neu erzeugen mit \"Neue #: www/views/backup.html:12 msgid "Wallet recovery phrase not available" -msgstr "" +msgstr "Wallet Wiederherstellungsphrase ist nicht verfügbar" #: src/js/services/bwcError.js:50 msgid "Wallet service not found" @@ -3387,7 +3461,7 @@ msgstr "Wallet-Dienst nicht gefunden" #: www/views/tab-home.html:69 msgid "Wallets" -msgstr "" +msgstr "Wallets" #: src/js/controllers/addressbookView.js:36 #: src/js/controllers/modals/txpDetails.js:153 @@ -3424,7 +3498,7 @@ msgstr "Wir sind immer auf der Suche nach Möglichkeiten um {{appName}} zu verbe #: www/views/includes/incomingDataMenu.html:6 msgid "Website" -msgstr "" +msgstr "Webseite" #: www/views/preferencesLanguage.html:16 msgid "We’re always looking for translation contributions! You can make corrections or help to make this app available in your native language by joining our community on Crowdin." @@ -3504,11 +3578,11 @@ msgstr "Sie kontrollieren Ihre Bitcoins." #: www/views/modals/chooseFeeLevel.html:64 msgid "You should not set a fee higher than {{maxFeeRecommended}} satoshis/byte." -msgstr "" +msgstr "Sie sollten keine Gebühren welche höher als {{maxFeeRecommended}} satoshis/byte sind wählen." #: www/views/modals/bitpay-card-confirmation.html:5 msgid "You will need to log back for fill in your BitPay Card." -msgstr "" +msgstr "Sie müssen sich wieder anmelden um ihre BitPay-Karte aufzufüllen." #: www/views/preferencesNotifications.html:34 msgid "You'll receive email notifications about payments sent and received from your wallets." @@ -3516,12 +3590,12 @@ msgstr "Sie erhalten E-Mail Benachrichtigungen über gesendete und empfangen Zah #: www/views/bitpayCard.html:50 msgid "Your BitPay Card is ready. Add funds to your card to start using it at stores and ATMs worldwide." -msgstr "" +msgstr "Ihre BitPay-Karte ist bereit. Fügen sie Mittel auf Ihre Karte hinzu um diesen bei Geschäften und Geldautomaten weltweit zu benutzen." #: www/views/mercadoLibre.html:57 #: www/views/mercadoLibreCards.html:6 msgid "Your Gift Cards" -msgstr "" +msgstr "Ihre Geschenkkarten" #: www/views/includes/confirmBackupPopup.html:6 msgid "Your bitcoin wallet is backed up!" @@ -3533,7 +3607,7 @@ msgstr "Ihre Bitcoin-Wallet ist fertig!" #: www/views/modals/chooseFeeLevel.html:61 msgid "Your fee is lower than recommended." -msgstr "" +msgstr "Ihre Gebühren sind niedriger als empfohlen." #: www/views/feedback/send.html:42 msgid "Your ideas, feedback, or comments" @@ -3554,11 +3628,11 @@ msgstr "Passwort" #: www/views/buyAmazon.html:102 msgid "Your purchase could not be completed" -msgstr "" +msgstr "Ihre Bestellung konnte nicht abgeschlossen werden" #: www/views/buyAmazon.html:105 msgid "Your purchase was added to the list of pending" -msgstr "" +msgstr "Ihre Bestellung wurde in die Liste der anstehenden Bestellungen hinzugefügt" #: www/views/onboarding/backupRequest.html:10 msgid "Your wallet is never saved to cloud storage or standard device backups." @@ -3580,7 +3654,7 @@ msgstr "[Guthaben versteckt]" #: www/views/walletDetails.html:141 #: www/views/walletDetails.html:61 msgid "[Scanning Funds]" -msgstr "" +msgstr "[Mittel werden durchsucht...]" #: src/js/controllers/bitpayCardIntro.js:11 msgid "add your BitPay Visa card(s)" @@ -3604,15 +3678,15 @@ msgstr "in Arbeit..." #: www/views/cashScan.html:57 msgid "recovery tool." -msgstr "" +msgstr "Wiederherstellungstool." #: src/js/controllers/buyAmazon.js:239 msgid "{{amountStr}} for Amazon.com Gift Card" -msgstr "" +msgstr "{{amountStr}} für Amazon.com Geschenkkarte" #: src/js/controllers/buyMercadoLibre.js:237 msgid "{{amountStr}} for Mercado Livre Brazil Gift Card" -msgstr "" +msgstr "{{amountStr}} für Mercado Livre Brazil Geschenkkarte" #: www/views/preferencesBwsUrl.html:21 msgid "{{appName}} depends on Bitcore Wallet Service (BWS) for blockchain information, networking and Copayer synchronization. The default configuration points to https://bws.bitpay.com (BitPay's public BWS instance)." @@ -3624,7 +3698,7 @@ msgstr "{{fee}} wird für Bitcoin-Netzwerkgebühren abgezogen werden." #: www/views/confirm.html:85 msgid "{{tx.txp[wallet.id].feeRatePerStr}} of the sending amount" -msgstr "" +msgstr "{{tx.txp[wallet.id].feeRatePerStr}} von dem sendenden Betrag" #: www/views/walletDetails.html:218 msgid "{{updatingTxHistoryProgress}} transactions downloaded" @@ -3636,3 +3710,51 @@ msgstr "{{updatingTxHistoryProgress}} Transaktionen werden heruntergeladen" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "{{wallet.m}}-von-{{wallet.n}}" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "Shapeshift" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "Community" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "Bitcoin Cash Reddit" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "Bitcoin.com Twitter" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "Erkunden Sie Bitcoin.com" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "Bitcoin Cash Spiele" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "Neuigkeiten" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "Mining-Pool" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "Werkzeuge" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "Bitcoin Preis-Charts" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "Kostenloses Bitcoin Cash" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "Ihre Bitcoin-Wallet ist fertig!" + diff --git a/i18n/po/es-ES/template-es-ES.po b/i18n/po/es-ES/template-es-ES.po index e062b4d1e..9338825d0 100644 --- a/i18n/po/es-ES/template-es-ES.po +++ b/i18n/po/es-ES/template-es-ES.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Spanish\n" "Language: es\n" -"PO-Revision-Date: 2018-07-04 09:27\n" +"PO-Revision-Date: 2018-07-27 08:44\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -437,6 +437,7 @@ msgid "Buy & Sell Bitcoin" msgstr "Comprar & Vender Bitcoin" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "Comprar Bitcoin" @@ -619,10 +620,14 @@ msgstr "Conectando a Glidera..." msgid "Connection reset by peer" msgstr "Conexión re establecida" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "Contactos" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "Direcciones frecuentes guardadas" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "Continuar" @@ -823,7 +828,7 @@ msgstr "Crear billetera compartida" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "Crear billetera" @@ -2533,6 +2538,14 @@ msgstr "Buscar transacciones" msgid "Search or enter bitcoin address" msgstr "Buscar o introducir dirección bitcoin" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "Portapapeles" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "Portapapeles vacío" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "Buscar transacciones" @@ -2591,9 +2604,70 @@ msgid "Send by email" msgstr "Enviar por correo electrónico" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "Enviar desde" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "Enviar a" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "Pegar portapapeles" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "Pegar dirección" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "Billetera a billetera" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "Escanear código QR" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "¡Envía Bitcoin más rápido!" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "¡Envía Bitcoin más rápido!" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "Guardar las direcciones que usas frecuentemente y envía Bitcoin en un click" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "Añadie tu primer contacto" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "Tu billetera Bitcoin está vacía" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "Para empezar, compra Bitcoin Cash (BCH) o Bitcoin Core (BTC), o comparte tu dirección." + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "Puedes recibir bitcoin desde cualquier billetera o servicio." + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "Para empezar, necesitarás crear una billetera y obtener bitcoins." + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "Comprar Bitcoin ahora" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "Ver mi dirección" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "Enviar la máxima cantidad" @@ -3030,7 +3104,7 @@ msgstr "Para empezar, compra bitcoin o comparte tu dirección. Puedes recibir bi #: www/views/tab-send.html:33 msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." -msgstr "Para empezar, necesitarás crear una billetera y obtener bitcoins." +msgstr "Para empezar, necesitarás crear una billetera y obtener Bitcoins." #: src/js/services/bitpayAccountService.js:73 msgid "To {{reason}} you must first add your BitPay account - {{email}}" @@ -3636,3 +3710,51 @@ msgstr "{{updatingTxHistoryProgress}} transacciones descargadas" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "{{wallet.m}}-de-{{wallet.n}}" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "Shapeshift - Cambia BTC a BCH" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "Comunidad" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "Reddit de Bitcoin Cash" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "Twitter de Bitcoin.com" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "Explora Bitcoin.com" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "Juegos de Bitcoin Cash" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "Noticias" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "Minería en la nube" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "Herramientas" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "Gráfica de precios Bitcoin" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "Bitcoin Cash gratis" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "¡Tus billeteras Bitcoin están listas!" + diff --git a/i18n/po/fa/template-fa.po b/i18n/po/fa/template-fa.po index aa4b76892..148ac9147 100644 --- a/i18n/po/fa/template-fa.po +++ b/i18n/po/fa/template-fa.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Persian\n" "Language: fa\n" -"PO-Revision-Date: 2018-07-04 09:27\n" +"PO-Revision-Date: 2018-07-27 08:44\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -437,6 +437,7 @@ msgid "Buy & Sell Bitcoin" msgstr "خرید & فروش بیتکوین" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "خرید بیتکوین" @@ -619,10 +620,14 @@ msgstr "در حال اتصال به Glidera..." msgid "Connection reset by peer" msgstr "ریست اتصال توسط همکار" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "تماسها" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "آدرس های ذخیره شده که اغلب مورد استفاده قرار گرفته اند" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "ادامه" @@ -823,7 +828,7 @@ msgstr "ایجاد کیف پول مشترک" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "ایجاد کیف پول بیتکوین" @@ -2533,6 +2538,14 @@ msgstr "جستجوی تراکنش ها" msgid "Search or enter bitcoin address" msgstr "جستجو و یا وارد کردن آدرس بیتکوین" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "کلیپ بورد" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "کلیپ بورد شما خالی است" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "جستجوی تراکنش ها" @@ -2591,9 +2604,70 @@ msgid "Send by email" msgstr "ارسال توسط ایمیل" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "ارسال از" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "ارسال به" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "جای گذاری کلیپ برد" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "جای گذاری آدرس" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "انتقال پول از کیف پول به کیف پول" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "اسکن کد QR" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "ارسال سریع تر بیت کوین!" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "ارسال سریع تر بیت کوین!" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "ذخیره آدرس های اغلب استفاده شده و ارسال بیت کوین به آنها تنها با یک ضربه" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "اولین تماس خود اضافه کنید" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "کیف پول بیت کوین شما خالی است" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "برای شروع، Bitcoin Cash (BCH) یا Bitcoin Core (BTC) بخرید و یا آدرس خود را به اشتراک بگذارید." + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "شما می توانید از هر کیف پول و یا خدمات بیت کوین دریافت کنید." + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "برای شروع، شما نیاز دارید که یک کیف پول ایجاد کنید و مقداری بیتکوین تهیه کنید." + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "خرید بیت کوین همین الان" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "نمایش آدرس من" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "ارسال حداکثر مقدار" @@ -3030,7 +3104,7 @@ msgstr "برای شروع، بیتکوین بخرید و یا آدرس خود ر #: www/views/tab-send.html:33 msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." -msgstr "برای شروع، شما نیاز دارید که یک کیف پول ایجاد کنید و مقداری بیتکوین تهیه کنید." +msgstr "برای شروع، شما نیاز دارید که یک کیف پول بیت کوین ایجاد کرده و مقداری بیت کوین تهیه کنید." #: src/js/services/bitpayAccountService.js:73 msgid "To {{reason}} you must first add your BitPay account - {{email}}" @@ -3636,3 +3710,51 @@ msgstr "{{updatingTxHistoryProgress}} تراکنش دانلود شد" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "{{wallet.m}} از {{wallet.n}}" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "Shapeshift" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "جامعه" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "Bitcoin Cash Reddit" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "توییتر Bitcoin.com" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "کاوش Bitcoin.com" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "Bitcoin Cash Games" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "اخبار" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "استخر استخراج" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "ابزارها" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "نمودارهای قیمت بیت کوین" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "رایگان Bitcoin Cash" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "کیف پول بیت کوین شما آماده است!" + diff --git a/i18n/po/fr/template-fr.po b/i18n/po/fr/template-fr.po index f49b0fe55..d87845d47 100644 --- a/i18n/po/fr/template-fr.po +++ b/i18n/po/fr/template-fr.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: French\n" "Language: fr\n" -"PO-Revision-Date: 2018-07-04 09:26\n" +"PO-Revision-Date: 2018-07-27 08:44\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -79,7 +79,7 @@ msgstr "Numéro de compte" #: www/views/tab-home.html:61 msgid "Instant transactions with low fees" -msgstr "Transactions instantanées à bas frais" +msgstr "Instant transactions with low fees" #: www/views/preferencesBitpayServices.html:23 msgid "Accounts" @@ -437,6 +437,7 @@ msgid "Buy & Sell Bitcoin" msgstr "Acheter & vendre des bitcoins" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "Acheter des bitcoins" @@ -619,10 +620,14 @@ msgstr "Connexion à Glidera..." msgid "Connection reset by peer" msgstr "Connexion réinitialisée par un pair" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "Contacts" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "Adresses fréquemment utilisées enregistrées" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "Continuer" @@ -823,7 +828,7 @@ msgstr "Créer un portefeuille partagé" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "Créer un portefeuille bitcoin" @@ -993,7 +998,7 @@ msgstr "Autoriser les notifications" #: www/views/preferencesNotifications.html:33 msgid "Enable sound" -msgstr "Activer le son" +msgstr "Malayu" #: www/views/tab-scan.html:18 msgid "Enable the camera to get started." @@ -2533,6 +2538,14 @@ msgstr "Rechercher des transactions" msgid "Search or enter bitcoin address" msgstr "Recherchez ou saisissez une adresse bitcoin" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "Presse-papiers" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "Votre presse-papiers est vide" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "Rechercher des transactions" @@ -2591,9 +2604,70 @@ msgid "Send by email" msgstr "Envoyer par e-mail" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "Envoyer à partir de" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "Envoyer à" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "Coller le contenu du presse-papiers" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "Coller l'adresse" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "Transfert de portefeuille à portefeuille" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "Numérisez le code QR" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Envoyez des Bitcoin plus vite !" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Envoyez des Bitcoin plus vite !" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "Enregistrez les adresses fréquemment utilisées et envoyez-leurs des Bitcoins en un seul geste" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "Ajoutez votre premier contact" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "Votre portefeuille Bitcoin est vide" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "Pour commencer, achetez des Bitcoins Cash (BCH) ou des Bitcoins Core (BTC), ou partagez votre adresse." + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "Vous pouvez recevoir des Bitcoins de n'importe quel portefeuille ou service." + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "Pour commencer, vous aurez besoin de créer un portefeuille bitcoin et d'obtenir quelques bitcoins." + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "Acheter des Bitcoins maintenant" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "Afficher mon adresse" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "Envoyer le montant maximal" @@ -3030,7 +3104,7 @@ msgstr "Pour commencer, achetez des bitcoins ou partagez votre adresse. Vous pou #: www/views/tab-send.html:33 msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." -msgstr "Pour commencer, vous aurez besoin de créer un portefeuille bitcoin et d'obtenir quelques bitcoins." +msgstr "Pour commencer, vous aurez besoin de créer un portefeuille Bitcoin et d'obtenir quelques bitcoins." #: src/js/services/bitpayAccountService.js:73 msgid "To {{reason}} you must first add your BitPay account - {{email}}" @@ -3636,3 +3710,51 @@ msgstr "{{updatingTxHistoryProgress}} transactions téléchargées" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "{{wallet.m}}-sur-{{wallet.n}}" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "Shapeshift" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "Communauté" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "Reddit Bitcoin Cash" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "Twitter Bitcoin.com" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "Explorez Bitcoin.com" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "Jeux Bitcoin Cash" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "Nouvelles" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "Coopératives de mineurs" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "Outils" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "Graphiques du prix du Bitcoin" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "Bitcoin Cash Gratuit" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "Vos portefeuilles bitcoin sont prêts !" + diff --git a/i18n/po/it/template-it.po b/i18n/po/it/template-it.po index 1fcaea030..62738bf84 100644 --- a/i18n/po/it/template-it.po +++ b/i18n/po/it/template-it.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Italian\n" "Language: it\n" -"PO-Revision-Date: 2018-07-04 09:27\n" +"PO-Revision-Date: 2018-07-27 08:44\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -437,6 +437,7 @@ msgid "Buy & Sell Bitcoin" msgstr "Comprare & Vendere Bitcoin" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "Acquista Bitcoin" @@ -619,10 +620,14 @@ msgstr "Connessione a Glidera..." msgid "Connection reset by peer" msgstr "Connessione ripristinata dall'utente" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "Contatti" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "Indirizzi più utilizzati salvati" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "Continua" @@ -823,7 +828,7 @@ msgstr "Creare portafoglio condiviso" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "Creare portafoglio bitcoin" @@ -2533,6 +2538,14 @@ msgstr "Cerca Transazioni" msgid "Search or enter bitcoin address" msgstr "Cerca o inserisci indirizzo bitcoin" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "Appunti" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "Gli appunti sono vuoti" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "Ricerca transazioni" @@ -2591,9 +2604,70 @@ msgid "Send by email" msgstr "Invia via email" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "Inviata Da" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "Invia a" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "Incolla appunti" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "Incolla indirizzo" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "Trasferimento da portafoglio a portafoglio" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "Scansiona codice QR" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Invia Bitcoin più velocemente!" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Invia Bitcoin più velocemente!" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "Salva gli indirizzi più utilizzati e invia Bitcoin con un solo tocco" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "Aggiungi il tuo primo contatto" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "Il tuo portafoglio Bitcoin è vuoto" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "Per iniziare, acquista Bitcoin Cash (BCH) o Bitcoin Core (BTC), oppure condividi il tuo indirizzo." + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "Puoi ricevere Bitcoin da qualsiasi portafoglio o servizio." + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "Per iniziare, è necessario che tu crei un portafoglio bitcoin e ottenerne qualcuno." + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "Acquista subito Bitcoin" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "Visualizza il mio indirizzo" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "Inviare l'importo massimo" @@ -3030,7 +3104,7 @@ msgstr "Per iniziare, acquista bitcoin o condividi il tuo indirizzo. È possibil #: www/views/tab-send.html:33 msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." -msgstr "Per iniziare, è necessario che tu crei un portafoglio bitcoin e ottenerne qualcuno." +msgstr "Per iniziare, devi creare un portafoglio bitcoin e ottenere qualche bitcoin." #: src/js/services/bitpayAccountService.js:73 msgid "To {{reason}} you must first add your BitPay account - {{email}}" @@ -3636,3 +3710,51 @@ msgstr "{{updatingTxHistoryProgress}} transazioni scaricate" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "{{wallet.m}}-di-{{wallet.n}}" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "Shapeshift" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "Community" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "Bitcoin Cash su Reddit" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "Bitcoin.com su Twitter" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "Esplora Bitcoin.com" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "Giochi Bitcoin Cash" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "News" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "Mining Pool" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "Strumenti" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "Tabella prezzi Bitcoin" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "Bitcoin Cash gratis" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "I tuoi portafogli Bitcoin sono pronti!" + diff --git a/i18n/po/ja/template-ja.po b/i18n/po/ja/template-ja.po index e7df99c1b..30668ce9d 100644 --- a/i18n/po/ja/template-ja.po +++ b/i18n/po/ja/template-ja.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Japanese\n" "Language: ja\n" -"PO-Revision-Date: 2018-07-04 09:27\n" +"PO-Revision-Date: 2018-07-27 08:44\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -406,7 +406,7 @@ msgstr "ビットコインは世界で
最も安全な仮想通貨。" #: www/views/preferencesFee.html:11 msgid "Bitcoin transactions include a fee collected by miners on the network." -msgstr "ビットコインの取引はネットワークの安全を守る「採掘者」と呼ばれる者達に送る手数料が含まれます。" +msgstr "ビットコインの取引はネットワークの安全を守る「採掘者」と呼ばれる方達に送る手数料が含まれます。" #: www/views/buyAmazon.html:108 msgid "Bought {{amountUnitStr}}" @@ -439,6 +439,7 @@ msgid "Buy & Sell Bitcoin" msgstr "ビットコインの購入&売却" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "ビットコインを購入" @@ -621,10 +622,14 @@ msgstr "Glidera に接続中…" msgid "Connection reset by peer" msgstr "接続がピアによってリセットされました" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "連絡先" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "よく使う保存済みのアドレス" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "続ける" @@ -825,7 +830,7 @@ msgstr "共有ウォレットを作成" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "ビットコインウォレット作成" @@ -995,7 +1000,7 @@ msgstr "プッシュ通知を有効化" #: www/views/preferencesNotifications.html:33 msgid "Enable sound" -msgstr "音を有効にする" +msgstr "サウンドを有効にする" #: www/views/tab-scan.html:18 msgid "Enable the camera to get started." @@ -1348,7 +1353,7 @@ msgstr "始めよう" #: www/views/addressbook.html:20 msgid "Get started by adding your first one." -msgstr "初めての連絡先を追加しましょう。" +msgstr "連絡先を追加しましょう。" #: src/js/services/onGoingProcess.js:23 msgid "Getting fee levels..." @@ -1873,7 +1878,7 @@ msgstr "バックアップは非常に重要です!" #: www/views/addressbook.html:19 msgid "No contacts yet" -msgstr "連絡先が無い" +msgstr "連絡先はありません" #: www/views/preferencesLogs.html:16 msgid "No entries for this log level" @@ -2006,7 +2011,7 @@ msgstr "今一度周りの環境をよく見てみましょう。隠しカメラ #: src/js/services/popupService.js:72 #: www/views/modals/chooseFeeLevel.html:6 msgid "OK" -msgstr "わかりました" +msgstr "OK" #: www/views/modals/tx-status.html:12 #: www/views/modals/tx-status.html:24 @@ -2535,6 +2540,14 @@ msgstr "取引を検索" msgid "Search or enter bitcoin address" msgstr "連絡先検索かビットコインアドレスを指定" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "クリップボード" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "クリップボードは空です" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "取引を検索" @@ -2593,9 +2606,70 @@ msgid "Send by email" msgstr "メールで送信" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "ここから送金" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "送金先:" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "クリップボードからペースト" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "アドレスをペースト" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "ウォレット間送金" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "QRコードを読み取る" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "ビットコイン送金をより高速に!" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "ビットコイン送金をより高速に!" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "よく使うアドレスを保存すればワンタップでビットコインを送金できます" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "最初の連絡先を追加" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "ビットコインウォレットが空です" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "始めるには、Bitcoin Cash (BCH) または Bitcoin Core (BTC) を購入するか、あなたのアドレスを共有してください。" + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "どのウォレットやサービスからでもビットコインを受け取ることができます。" + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "はじめに、ビットコインウォレットを作成し、ビットコインを入手する必要があります。" + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "今すぐビットコインを購入" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "自分のアドレスを表示" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "全残高を送金" @@ -3450,7 +3524,7 @@ msgstr "送金発生時のメール通知はどのメールアドレスで受け #: www/views/addresses.html:19 msgid "Why?" -msgstr "なぜ?" +msgstr "なぜですか?" #: www/views/feedback/rateApp.html:10 msgid "Would you be willing to rate {{appName}} in the app store?" @@ -3642,3 +3716,51 @@ msgstr "{{updatingTxHistoryProgress}} 個の取引ダウンロード済み" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "{{wallet.m}} の{{wallet.n}}" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "Shapeshift" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "コミュニティ" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "Bitcoin Cash Reddit" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "Bitcoin.com Twitter" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "Bitcoin.com を参照" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "Bitcoin Cash ゲーム" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "ニュース" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "マイニングプール" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "ツール" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "ビットコインの価格チャート" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "無料 Bitcoin Cash" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "ビットコインウォレットが完成しました!" + diff --git a/i18n/po/ko/template-ko.po b/i18n/po/ko/template-ko.po index cee1be7d1..6285496ae 100644 --- a/i18n/po/ko/template-ko.po +++ b/i18n/po/ko/template-ko.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Korean\n" "Language: ko\n" -"PO-Revision-Date: 2018-07-04 09:27\n" +"PO-Revision-Date: 2018-07-27 08:44\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -437,6 +437,7 @@ msgid "Buy & Sell Bitcoin" msgstr "비트코인 구매 & 판매" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "비트코인 구매" @@ -619,10 +620,14 @@ msgstr "Glidera에 연결 중..." msgid "Connection reset by peer" msgstr "연결 실패" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "연락처" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "자주 사용하는 저장된 주소" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "계속하기" @@ -823,7 +828,7 @@ msgstr "공유 지갑 만들기" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "비트코인 지갑 만들기" @@ -2533,6 +2538,14 @@ msgstr "거래 기록 검색하기" msgid "Search or enter bitcoin address" msgstr "비트코인 주소를 찾거나 작성" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "클립보드" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "클립보드가 비어 있습니다" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "거래 기록 검색" @@ -2591,9 +2604,70 @@ msgid "Send by email" msgstr "이메일로 보내기" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "출처" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "보내기" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "클립보드 붙여넣기" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "주소 붙여넣기" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "지갑 간 전송" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "QR 코드 스캔" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "비트코인 속성 전송!" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "비트코인 속성 전송!" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "자주 사용하는 주소를 저장하고 한 번의 탭으로 저장된 주소에 비트코인 전송" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "첫 번째 연락처 추가" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "비트코인 지갑이 비어 있습니다" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "시작하려면 비트코인 캐시(BCH) 또는 비트코인 코어(BTC)를 구매하거나 주소를 공유합니다." + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "지갑 또는 서비스에서 비트코인을 받을 수 있습니다." + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "시작하시기 위해선 비트코인 지갑을 생성하시거나 비트코인을 구매하세요." + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "지금 비트코인 구매" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "내 주소 보기" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "최대 수량 보내기" @@ -3030,7 +3104,7 @@ msgstr "시작하시려면 비트코인을 구매하시거나 주소를 등록 #: www/views/tab-send.html:33 msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." -msgstr "시작하시기 위해선 비트코인 지갑을 생성하시거나 비트코인을 구매하세요." +msgstr "시작하려면 비트코인 지갑을 만들고 비트코인을 구매하십시오." #: src/js/services/bitpayAccountService.js:73 msgid "To {{reason}} you must first add your BitPay account - {{email}}" @@ -3636,3 +3710,51 @@ msgstr "{{updatingTxHistoryProgress}} 거래 내역 다운로드 완료" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "{{wallet.n}}의 {{wallet.m}}" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "Shapeshift" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "커뮤니티" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "BCH Reddit" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "Bitcoin.com 트위터" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "Bitcoin.com 탐색" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "BCH 게임" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "소식" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "채굴 풀" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "도구" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "비트코인 가격 차트" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "무료 BCH" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "비트코인 지갑이 완료되었습니다!" + diff --git a/i18n/po/nl/template-nl.po b/i18n/po/nl/template-nl.po index 754c41e21..b66d0b892 100644 --- a/i18n/po/nl/template-nl.po +++ b/i18n/po/nl/template-nl.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Dutch\n" "Language: nl\n" -"PO-Revision-Date: 2018-07-04 09:26\n" +"PO-Revision-Date: 2018-07-27 08:44\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -437,6 +437,7 @@ msgid "Buy & Sell Bitcoin" msgstr "Koop & Verkoop Bitcoin" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "Bitcoin kopen" @@ -619,10 +620,14 @@ msgstr "Verbinding maken met Glidera..." msgid "Connection reset by peer" msgstr "Verbinding is gereset door peer" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "Contactpersonen" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "Opgeslagen veelgebruikte adressen" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "Ga verder" @@ -823,7 +828,7 @@ msgstr "Gedeelde Portemonnee Aanmaken" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "Bitcoin portemonnee aanmaken" @@ -2533,6 +2538,14 @@ msgstr "Doorzoek Transacties" msgid "Search or enter bitcoin address" msgstr "Zoeken of bitcoin adres invullen" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "Klembord" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "Uw klembord is leeg" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "Doorzoek transacties" @@ -2591,9 +2604,70 @@ msgid "Send by email" msgstr "Verstuur via email" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "Verzenden vanuit" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "Verzenden naar" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "Klembord plakken" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "Adres plakken" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "Portemonnee overdracht" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "Scan QR-code" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Stuur Bitcoin sneller!" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Stuur Bitcoin sneller!" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "Sla veelgebruikte adressen op en verstuur ze Bitcoin met een druk op de knop" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "Uw eerste contact toevoegen" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "Uw Bitcoin portemonnee is leeg" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "Om aan de slag te gaan, koop Bitcoin Cash (BCH) of Bitcoin Core (BTC), of deel uw adres." + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "U kunt Bitcoin ontvangen van elke portemonnee of dienst." + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "Om aan de slag te gaan zult u een bitcoin portemonnee moeten aanmaken en wat bitcoin moeten verkrijgen." + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "Bitcoin kopen" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "Toon mijn adres" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "Verzend maximale hoeveelheid" @@ -3030,7 +3104,7 @@ msgstr "Om aan de slag te gaan, koop bitcoin of deel uw adres. U kunt bitcoin on #: www/views/tab-send.html:33 msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." -msgstr "Om aan de slag te gaan zult u een bitcoin portemonnee moeten aanmaken en wat bitcoin moeten verkrijgen." +msgstr "Om aan de slag te gaan, zult u een Bitcoin-portemonnee aan moeten maken en wat Bitcoin moeten verkrijgen." #: src/js/services/bitpayAccountService.js:73 msgid "To {{reason}} you must first add your BitPay account - {{email}}" @@ -3636,3 +3710,51 @@ msgstr "{{updatingTxHistoryProgress}} transacties gedownload" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "{{wallet.m}}-van-{{wallet.n}}" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "Shapeshift" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "Community" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "Bitcoin Cash Reddit" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "Bitcoin.com Twitter" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "Bitcoin.com verkennen" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "Bitcoin Cash spellen" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "Nieuws" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "Mining Pool" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "Hulpmiddelen" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "Bitcoin prijs grafieken" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "Gratis Bitcoin Cash" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "Uw bitcoin portemonnee is gereed!" + diff --git a/i18n/po/pl/template-pl.po b/i18n/po/pl/template-pl.po index ce075e00b..ef9888ca5 100644 --- a/i18n/po/pl/template-pl.po +++ b/i18n/po/pl/template-pl.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Polish\n" "Language: pl\n" -"PO-Revision-Date: 2018-07-04 03:58\n" +"PO-Revision-Date: 2018-07-27 08:44\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -79,7 +79,7 @@ msgstr "Numer konta" #: www/views/tab-home.html:61 msgid "Instant transactions with low fees" -msgstr "" +msgstr "Natychmiastowe transakcje z niskimi prowizjami" #: www/views/preferencesBitpayServices.html:23 msgid "Accounts" @@ -363,12 +363,12 @@ msgstr "Adres bitcoin" #: www/views/cashScan.html:4 msgid "Bitcoin Cash (BCH) Balances" -msgstr "" +msgstr "Salda Bitcoin Cash (BCH)" #: www/views/preferencesCash.html:3 #: www/views/tab-settings.html:47 msgid "Bitcoin Cash Support" -msgstr "" +msgstr "Wsparcie Bitcoin Cash" #: www/views/tab-home.html:98 #: www/views/tab-settings.html:115 @@ -384,7 +384,7 @@ msgstr "Polityka prowizji sieci bitcoin" #: www/views/tab-home.html:83 #: www/views/tab-settings.html:107 msgid "Bitcoin Core Wallets" -msgstr "" +msgstr "Portfele Bitcoin Core" #: src/js/services/incomingData.js:151 msgid "Bitcoin cash Payment" @@ -437,6 +437,7 @@ msgid "Buy & Sell Bitcoin" msgstr "Kup & sprzedaj bitcoiny" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "Kup bitcoiny" @@ -619,10 +620,14 @@ msgstr "Łączenie z Gildera..." msgid "Connection reset by peer" msgstr "Połączenie zostało zresetowane" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "Kontakty" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "Zapisane często używane adresy" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "Dalej" @@ -823,7 +828,7 @@ msgstr "Utwórz współdzielony portfel" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "Utwórz portfel bitcoin" @@ -977,7 +982,7 @@ msgstr "Puste adresy osiągnęły limit. Nowe adresy nie mogą być generowane." #: www/views/preferencesCash.html:17 msgid "Enable Bitcoin Cash wallet creation and operation within the App." -msgstr "" +msgstr "Włącz tworzenie i obsługę portfela Bitcoin Cash w aplikacji." #: www/views/tab-scan.html:19 msgid "Enable camera access in your device settings to get started." @@ -993,7 +998,7 @@ msgstr "Włącz powiadomienia" #: www/views/preferencesNotifications.html:33 msgid "Enable sound" -msgstr "" +msgstr "Włącz dźwięk" #: www/views/tab-scan.html:18 msgid "Enable the camera to get started." @@ -1001,7 +1006,7 @@ msgstr "Włącz kamerę aby rozpocząć." #: www/views/tab-settings.html:49 msgid "Enabled" -msgstr "" +msgstr "Włączono" #: src/js/services/walletService.js:1047 #: src/js/services/walletService.js:1062 @@ -1586,7 +1591,7 @@ msgstr "Nieprawidłowy adres" #: src/js/controllers/confirm.js:306 #: src/js/services/bwcError.js:44 msgid "Insufficient confirmed funds" -msgstr "" +msgstr "Niewystarczające potwierdzone środki" #: src/js/controllers/topup.js:165 #: src/js/controllers/topup.js:177 @@ -1931,7 +1936,7 @@ msgstr "Nie BIP44 portfel" #: www/views/cashScan.html:46 msgid "Non eligible BTC wallets" -msgstr "" +msgstr "Niewspierane portfele BTC" #: src/js/services/feeService.js:12 msgid "Normal" @@ -2004,7 +2009,7 @@ msgstr "Nadszedł czas, aby sprawdzić swoje otoczenie. Czy jesteś w pobliżu o #: src/js/services/popupService.js:72 #: www/views/modals/chooseFeeLevel.html:6 msgid "OK" -msgstr "" +msgstr "OK" #: www/views/modals/tx-status.html:12 #: www/views/modals/tx-status.html:24 @@ -2023,7 +2028,7 @@ msgstr "O nie!" #: src/js/controllers/buyMercadoLibre.js:306 msgid "Ok" -msgstr "" +msgstr "Ok" #: www/views/tab-home.html:39 msgid "On this screen you can see all your wallets, accounts, and assets." @@ -2053,7 +2058,7 @@ msgstr "Otwórz projekt GitHub" #: src/js/controllers/bitpayCard.js:123 #: src/js/controllers/tx-details.js:192 msgid "Open Explorer" -msgstr "" +msgstr "Otwórz Explorer" #: www/views/tab-scan.html:22 msgid "Open Settings" @@ -2069,7 +2074,7 @@ msgstr "Otwórz stronę internetową" #: src/js/controllers/preferencesCash.js:32 msgid "Open bitcoincash.org?" -msgstr "" +msgstr "Otworzyć bitcoincash.org?" #: src/js/controllers/cashScan.js:18 msgid "Open the recovery tool." @@ -2240,7 +2245,7 @@ msgstr "Proszę wybrać plik kopii zapasowej" #: www/views/bitpayCard.html:81 msgid "Pre-Auth Holds" -msgstr "" +msgstr "Wstrzymanie przedautoryzacyjne" #: www/views/tab-settings.html:40 msgid "Preferences" @@ -2353,7 +2358,7 @@ msgstr "Otrzymaj" #: www/views/customAmount.html:44 msgid "Receive in" -msgstr "" +msgstr "Otrzymaj w" #: www/views/includes/walletHistory.html:24 #: www/views/tx-details.html:18 @@ -2511,7 +2516,7 @@ msgstr "Proszę zeskanować linie papilarne" #: www/views/preferencesCash.html:23 msgid "Scan your wallets for Bitcoin Cash" -msgstr "" +msgstr "Skanuj portfele w poszukiwaniu Bitcoin Cash" #: src/js/services/onGoingProcess.js:30 msgid "Scanning Wallet funds..." @@ -2533,6 +2538,14 @@ msgstr "Szukaj transakcji" msgid "Search or enter bitcoin address" msgstr "Wyszukaj lub wpisz adres bitcoin" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "Schowek" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "Schowek jest pusty" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "Szukaj transakcji" @@ -2591,9 +2604,70 @@ msgid "Send by email" msgstr "Wyślij przez e-mail" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "Wyślij z" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "Wyślij do" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "Wklej ze schowka" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "Wklej adres" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "Transfer z portfela do portfela" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "Zeskanuj kod QR" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Przesyłaj Bitcoiny szybciej!" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Przesyłaj Bitcoiny szybciej!" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "Zapisz często używane adresy i wyślij im Bitcoin za pomocą jednego dotknięcia" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "Dodaj swój pierwszy kontakt" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "Twój portfel jest pusty" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "Aby zacząć, kup Bitcoin Cash (BCH) lub Bitcoin Core (BTC), albo udostępnij swój adres." + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "Bitcoiny można odbierać z dowolnego portfela lub usługi." + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "Aby rozpocząć należy utworzyć portfel i dostać trochę bitcoinów." + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "Kup Bitcoin teraz" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "Pokaż mój adres" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "Wyślij całą kwotę" @@ -2764,7 +2838,7 @@ msgstr "Przesuń, aby wysłać" #: www/views/cashScan.html:56 msgid "Some of your wallets are not eligible for Bitcoin Cash support. You can try to access BCH funds from these wallets using the" -msgstr "" +msgstr "Niektóre z twoich portfeli nie kwalifikują się do wsparcia Bitcoin Cash. Dostęp do środków BCH z tych portfeli można spróbować uzyskać, korzystając z" #: src/js/controllers/create.js:88 #: src/js/controllers/join.js:71 @@ -2785,7 +2859,7 @@ msgstr "Wymagane hasło wypłat" #: www/views/walletDetails.html:173 msgid "Spending this balance will need significant Bitcoin network fees" -msgstr "" +msgstr "Wydanie tego salda będzie wymagało znacznych opłat sieciowych Bitcoin" #: www/views/tab-send.html:28 msgid "Start sending bitcoin" @@ -2810,7 +2884,7 @@ msgstr "Super Ekonomiczna" #: www/views/preferencesCash.html:11 msgid "Support Bitcoin Cash" -msgstr "" +msgstr "Wsparcie Bitcoin Cash" #: www/views/paperWallet.html:7 msgid "Sweep" @@ -2860,7 +2934,7 @@ msgstr "Warunki użytkowania" #: www/views/tab-create-personal.html:118 #: www/views/tab-import-phrase.html:68 msgid "Testnet" -msgstr "" +msgstr "Testnet" #: www/views/includes/incomingDataMenu.html:61 msgid "Text" @@ -2999,7 +3073,7 @@ msgstr "Ta kluczowa fraza został utworzona przy użyciu hasła. Aby odzyskać p #: www/views/tx-details.html:91 msgid "This transaction amount is too small compared to current Bitcoin network fees. Spending these funds will need a Bitcoin network fee cost comparable to the funds itself." -msgstr "" +msgstr "Ta kwota transakcji jest zbyt mała w porównaniu z obecnymi opłatami sieci Bitcoin. Wydanie tych środków będzie wymagało opłaty sieciowej Bitcoin, która jest porównywalna do kosztów samych funduszy." #: www/views/tx-details.html:87 msgid "This transaction could take a long time to confirm or could be dropped due to the low fees set by the sender" @@ -3034,15 +3108,15 @@ msgstr "Aby rozpocząć należy utworzyć portfel i dostać trochę bitcoinów." #: src/js/services/bitpayAccountService.js:73 msgid "To {{reason}} you must first add your BitPay account - {{email}}" -msgstr "" +msgstr "Aby móc wykonać czynność {{reason}}, musisz przedtem dodać swoje konto BitPay – {{email}}" #: src/js/services/onGoingProcess.js:48 msgid "Top up in progress..." -msgstr "" +msgstr "Doładowanie w trakcie..." #: src/js/controllers/topup.js:206 msgid "Top up {{amountStr}} to debit card ({{cardLastNumber}})" -msgstr "" +msgstr "Doładuj o {{amountStr}} kartę debetową ({{cardLastNumber}})" #: www/views/buyAmazon.html:61 #: www/views/buyMercadoLibre.html:60 @@ -3061,7 +3135,7 @@ msgstr "Liczba współwłaścicieli portfela" #: www/views/addresses.html:81 msgid "Total wallet inputs" -msgstr "" +msgstr "Dane wejściowe portfela łącznie" #: src/js/services/fingerprintService.js:63 #: src/js/services/fingerprintService.js:68 @@ -3200,7 +3274,7 @@ msgstr "Zobacz zasady użytkowania" #: src/js/controllers/bitpayCard.js:122 #: src/js/controllers/tx-details.js:191 msgid "View Transaction on Explorer.Bitcoin.com" -msgstr "" +msgstr "Zobacz transakcję na Explorer.Bitcoin.com" #: src/js/controllers/tab-home.js:148 msgid "View Update" @@ -3212,7 +3286,7 @@ msgstr "Zobacz na blockchainie" #: www/views/mercadoLibre.html:26 msgid "Visit mercadolivre.com.br →" -msgstr "" +msgstr "Odwiedź mercadolivre.com.br →" #: www/views/walletDetails.html:182 msgid "WARNING: Key derivation is not working on this device/wallet. Actions cannot be performed on this wallet." @@ -3274,7 +3348,7 @@ msgstr "Informacje o portfelu" #: www/views/addresses.html:76 msgid "Wallet Inputs" -msgstr "" +msgstr "Dane wejściowe portfela" #: www/views/join.html:26 msgid "Wallet Invitation" @@ -3328,7 +3402,7 @@ msgstr "Portfel już istnieje" #: src/js/services/profileService.js:516 msgid "Wallet already in {{appName}}" -msgstr "" +msgstr "Portfel już w aplikacji {{appName}}" #: www/views/includes/walletActivity.html:6 msgid "Wallet created" @@ -3508,7 +3582,7 @@ msgstr "Nie należy ustawiać opłatę wyższą niż {{maxFeeRecommended}} satos #: www/views/modals/bitpay-card-confirmation.html:5 msgid "You will need to log back for fill in your BitPay Card." -msgstr "" +msgstr "Musisz zalogować się ponownie, aby wypełnić swoją kartę BitPay." #: www/views/preferencesNotifications.html:34 msgid "You'll receive email notifications about payments sent and received from your wallets." @@ -3580,7 +3654,7 @@ msgstr "[Balans ukryty]" #: www/views/walletDetails.html:141 #: www/views/walletDetails.html:61 msgid "[Scanning Funds]" -msgstr "" +msgstr "[Skanowanie środków]" #: src/js/controllers/bitpayCardIntro.js:11 msgid "add your BitPay Visa card(s)" @@ -3612,7 +3686,7 @@ msgstr "{{amountStr}} dla karty upominkowej Amazon.com" #: src/js/controllers/buyMercadoLibre.js:237 msgid "{{amountStr}} for Mercado Livre Brazil Gift Card" -msgstr "" +msgstr "{{amountStr}} na karcie podarunkowej Mercado Livre Brazil" #: www/views/preferencesBwsUrl.html:21 msgid "{{appName}} depends on Bitcore Wallet Service (BWS) for blockchain information, networking and Copayer synchronization. The default configuration points to https://bws.bitpay.com (BitPay's public BWS instance)." @@ -3624,7 +3698,7 @@ msgstr "{{fee}} zostanie potrącone jako prowizja sieci bitcoin." #: www/views/confirm.html:85 msgid "{{tx.txp[wallet.id].feeRatePerStr}} of the sending amount" -msgstr "" +msgstr "{{tx.txp[wallet.id].feeRatePerStr}} wysyłanej kwoty" #: www/views/walletDetails.html:218 msgid "{{updatingTxHistoryProgress}} transactions downloaded" @@ -3636,3 +3710,51 @@ msgstr "{{updatingTxHistoryProgress}} transakcje pobrane" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "{{wallet.m}}-z-{{wallet.n}}" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "Shapeshift" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "Społeczność" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "Reddit Bitcoin Cash" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "Twitter Bitcoin.com" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "Poznaj Bitcoin.com" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "Gry Bitcoin Cash" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "Aktualności" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "Pula kopalni" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "Narzędzia" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "Wykresy kursu Bitcoin" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "Darmowa Bitcoin Cash" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "Twoje portfele Bitcoin są gotowe!" + diff --git a/i18n/po/pt-BR/template-pt-BR.po b/i18n/po/pt-BR/template-pt-BR.po index c83259bf9..6879b52c4 100644 --- a/i18n/po/pt-BR/template-pt-BR.po +++ b/i18n/po/pt-BR/template-pt-BR.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Portuguese, Brazilian\n" "Language: pt\n" -"PO-Revision-Date: 2018-07-04 09:27\n" +"PO-Revision-Date: 2018-07-27 08:44\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -437,6 +437,7 @@ msgid "Buy & Sell Bitcoin" msgstr "Comprar & Vender Bitcoin" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "Comprar Bitcoin" @@ -619,10 +620,14 @@ msgstr "A conectar ao Glidera..." msgid "Connection reset by peer" msgstr "Ligação redefinida pelo mesmo nível" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "Contactos" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "Salvou os endereços usados com frequência" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "Continuar" @@ -823,7 +828,7 @@ msgstr "Criar carteira partilhada" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "Criar carteira bitcoin" @@ -2533,6 +2538,14 @@ msgstr "Procurar transações" msgid "Search or enter bitcoin address" msgstr "Procure ou digite o endereço bitcoin" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "Área de transferência" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "A sua área de transferência está vazia" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "Procurar transações" @@ -2591,9 +2604,70 @@ msgid "Send by email" msgstr "Enviar por E-mail" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "Enviar De" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "Enviar para" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "Colar na área de trabalho" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "Colar endereço" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "Transferência de carteira para carteira" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "Digitalizar o código QR" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Envie Bitcoin mais rápido!" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Envie Bitcoin mais rápido!" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "Salvar endereços usados com frequência e enviar Bitcoin com apenas um toque" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "Adicionar o seu primeiro contato" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "Sua carteira de Bitcoin está vazia" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "Para começar, compre Bitcoin Cash (BCH) ou Bitcoin Core (BTC) ou compartilhe o seu endereço." + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "Você pode receber bitcoin de qualquer carteira ou serviço." + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "Para começar, você precisa criar uma carteira de bitcoins e obter alguns bitcoins." + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "Compre Bitcoin agora" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "Mostrar meu endereço" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "Quantidade Máxima de envio" @@ -3030,7 +3104,7 @@ msgstr "Para começar, compre bitcoins ou compartilhe seu endereço. Você pode #: www/views/tab-send.html:33 msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." -msgstr "Para começar, você precisa criar uma carteira de bitcoins e obter alguns bitcoins." +msgstr "Para começar, você precisará criar uma carteira e obter algum bitcoin." #: src/js/services/bitpayAccountService.js:73 msgid "To {{reason}} you must first add your BitPay account - {{email}}" @@ -3636,3 +3710,51 @@ msgstr "Transações de {{updatingTxHistoryProgress}} transferidas" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "{{wallet.m}}-de-{{wallet.n}}" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "Shapeshift" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "Comunidade" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "Bitcoin Cash Reddit" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "Twitter Bitcoin.com" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "Explore Bitcoin.com" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "Jogos Bitcoin Cash" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "Notícias" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "Pool de mineração" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "Ferramentas" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "Gráficos de preço do Bitcoin" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "Bitcoin Cash grátis" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "As suas carteiras de Bitcoin estão prontas!" + diff --git a/i18n/po/ru/template-ru.po b/i18n/po/ru/template-ru.po index 074fa9937..a9d5d2970 100644 --- a/i18n/po/ru/template-ru.po +++ b/i18n/po/ru/template-ru.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Russian\n" "Language: ru\n" -"PO-Revision-Date: 2018-07-04 09:27\n" +"PO-Revision-Date: 2018-07-27 08:44\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -79,7 +79,7 @@ msgstr "Номер учётной записи" #: www/views/tab-home.html:61 msgid "Instant transactions with low fees" -msgstr "Мгновенные транзакции с низкой оплатой" +msgstr "Мгновенные транзакции с низкой комиссией" #: www/views/preferencesBitpayServices.html:23 msgid "Accounts" @@ -437,6 +437,7 @@ msgid "Buy & Sell Bitcoin" msgstr "Купить & продать биткойн" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "Купить биткойн" @@ -619,10 +620,14 @@ msgstr "Подключение к Glidera..." msgid "Connection reset by peer" msgstr "Соединение сброшено другой стороной" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "Контакты" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "Сохраненные часто используемые адреса" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "Продолжить" @@ -823,7 +828,7 @@ msgstr "Создать общий кошелёк" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "Создать биткойн-кошелёк" @@ -2533,6 +2538,14 @@ msgstr "Поиск транзакций" msgid "Search or enter bitcoin address" msgstr "Найти или ввести биткойн-адрес" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "Буфер обмена" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "Буфер обмена пуст" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "Поиск транзакций" @@ -2591,9 +2604,70 @@ msgid "Send by email" msgstr "Отправить на email" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "Отправить от" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "Получатель" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "Вставить из буфера обмена" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "Вставить адрес" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "Перевод с кошелька на кошелек" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "Сканировать QR код" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Отправляйте биткойны еще быстрее!" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "Отправляйте биткойны еще быстрее!" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "Сохраняйте часто используемые адреса кошельков и отправляйте на них биткойны одним нажатием" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "Добавить первый контакт" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "Ваш кошелёк пуст" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "Чтобы начать работу, купите Bitcoin Cash (BCH) или Bitcoin Core (BTC), или поделитесь вашим адресом." + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "Вы можете получать биткойны с любого кошелька или сервиса." + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "Чтобы начать работу, вам нужно создать кошелёк и получить биткойн." + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "Купить биткойны сейчас" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "Показать мой адрес" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "Отправить макс. сумму" @@ -3636,3 +3710,51 @@ msgstr "{{updatingTxHistoryProgress}} транзакций загружено" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "{{wallet.m}}-из-{{wallet.n}}" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "Shapeshift" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "Сообщество" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "Bitcoin Cash Reddit" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "Bitcoin.com Twitter" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "Обзор Bitcoin.com" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "Игры Bitcoin Cash" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "Новости" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "Пул для майнинга" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "Инструменты" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "Динамика курса биткойна" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "Bitcoin Cash бесплатно" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "Ваши кошельки готовы!" + diff --git a/i18n/po/sv-SE/template-sv-SE.po b/i18n/po/sv-SE/template-sv-SE.po index bc496d6d5..7ffdda946 100644 --- a/i18n/po/sv-SE/template-sv-SE.po +++ b/i18n/po/sv-SE/template-sv-SE.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Swedish\n" "Language: sv\n" -"PO-Revision-Date: 2018-07-04 03:58\n" +"PO-Revision-Date: 2018-07-27 08:44\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -437,6 +437,7 @@ msgid "Buy & Sell Bitcoin" msgstr "" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "" @@ -619,10 +620,14 @@ msgstr "" msgid "Connection reset by peer" msgstr "" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "" @@ -823,7 +828,7 @@ msgstr "" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "" @@ -2533,6 +2538,14 @@ msgstr "" msgid "Search or enter bitcoin address" msgstr "" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "" @@ -2591,9 +2604,70 @@ msgid "Send by email" msgstr "" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "" + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "" + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "" + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "" @@ -3636,3 +3710,51 @@ msgstr "" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "" + diff --git a/i18n/po/vi/template-vi.po b/i18n/po/vi/template-vi.po index d965b6ee8..ef9db2029 100644 --- a/i18n/po/vi/template-vi.po +++ b/i18n/po/vi/template-vi.po @@ -11,11 +11,11 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Vietnamese\n" "Language: vi\n" -"PO-Revision-Date: 2018-07-04 03:58\n" +"PO-Revision-Date: 2018-07-27 08:44\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" -msgstr "(Đáng tin cậy)" +msgstr "(Tin cậy)" #: www/views/includes/txp.html:23 #: www/views/includes/walletHistory.html:64 @@ -28,7 +28,7 @@ msgstr "* A payment proposal can be deleted if 1) you are the creator, and no ot #: www/views/tx-details.html:82 msgid "- {{btx.feeRateStr}} of the transaction" -msgstr "{{btx.feeRateStr}} của giao dịch" +msgstr "- {{btx.feeRateStr}} of the transaction" #: www/views/modals/txp-details.html:102 msgid "- {{tx.feeRateStr}} of the transaction" @@ -41,7 +41,9 @@ msgstr "Xếp hạng 5 sao giúp chúng tôi để {{appName}} đến tay nhiề #: www/views/mercadoLibre.html:18 #: www/views/mercadoLibre.html:40 msgid "Only redeemable on Mercado Livre (Brazil)" -msgstr "Only redeemable on Mercado Livre (Brazil)" +msgstr "Only redeemable on \n" +"Mercado Livre (Brazil) #\n\n" +"2" #: src/js/controllers/feedback/send.js:27 #: www/views/feedback/complete.html:21 @@ -54,7 +56,8 @@ 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 "A total of {{amountBelowFeeStr}} were excluded. These funds come from UTXOs smaller than the network fee provided." +msgstr "Tổng cộng {{amountBelowFeeStr}} đã bị loại trừ. Số tiền này đến từ UTXOs nhỏ hơn chi phí mạng cung cấp.#\n" +"1" #: src/js/controllers/preferencesAbout.js:6 #: www/views/tab-settings.html:156 @@ -79,7 +82,7 @@ msgstr "Account Number" #: www/views/tab-home.html:61 msgid "Instant transactions with low fees" -msgstr "" +msgstr "2" #: www/views/preferencesBitpayServices.html:23 msgid "Accounts" @@ -124,338 +127,339 @@ msgstr "Add account" #: www/views/tab-create-personal.html:62 #: www/views/tab-create-shared.html:91 msgid "Add an optional password to secure the recovery phrase" -msgstr "" +msgstr "Add a password option to allow a secure cluster" #: www/views/includes/incomingDataMenu.html:41 msgid "Add as a contact" -msgstr "" +msgstr "Thêm như một liên hệ" #: src/js/controllers/confirm.js:424 msgid "Add description" -msgstr "" +msgstr "Thêm mô tả" #: www/views/topup.html:6 msgid "Add funds" -msgstr "" +msgstr "Nạp tiền" #: src/js/services/bitpayAccountService.js:78 msgid "Add this BitPay account ({{email}})?" -msgstr "" +msgstr "Thêm BitPay ({{email}}) tài khoản này?" #: www/views/add.html:3 msgid "Add wallet" -msgstr "" +msgstr "Thêm ví" #: www/views/addressbook.view.html:26 #: www/views/customAmount.html:28 #: www/views/modals/paypro.html:24 msgid "Address" -msgstr "" +msgstr "Địa chỉ" #: www/views/addressbook.html:6 #: www/views/tab-settings.html:13 msgid "Address Book" -msgstr "" +msgstr "Sổ địa chỉ" #: www/views/preferencesInformation.html:41 msgid "Address Type" -msgstr "" +msgstr "Loại địa chỉ" #: www/views/addresses.html:64 msgid "Addresses With Balance" -msgstr "" +msgstr "Địa chỉ với số dư" #: www/views/tab-settings.html:149 msgid "Advanced" -msgstr "" +msgstr "Nâng cao" #: www/views/advancedSettings.html:3 msgid "Advanced Settings" -msgstr "" +msgstr "Cài đặt nâng cao" #: www/views/bitpayCard.html:62 msgid "All" -msgstr "" +msgstr "Tất cả" #: www/views/allAddresses.html:3 msgid "All Addresses" -msgstr "" +msgstr "Tất cả địa chỉ" #: www/views/modals/wallet-balance.html:18 msgid "All of your bitcoin wallet balance may not be available for immediate spending." -msgstr "" +msgstr "Tất cả số dư ví bitcoin của bạn có thể không có sẵn cho chi tiêu ngay lập tức." #: www/views/tab-receive.html:25 msgid "All signing devices must be added to this multisig wallet before bitcoin addresses can be created." -msgstr "" +msgstr "All contract device will be added to this multisig before the bitcoin address has been created." #: www/views/tab-scan.html:21 msgid "Allow Camera Access" -msgstr "" +msgstr "Allow camera access" #: www/views/onboarding/notifications.html:7 msgid "Allow notifications" -msgstr "" +msgstr "Allow notification" #: www/views/onboarding/disclaimer.html:14 msgid "Almost done! Let's review." -msgstr "" +msgstr "Most as completed! We see review." #: www/views/preferencesAltCurrency.html:4 #: www/views/tab-settings.html:79 msgid "Alternative Currency" -msgstr "" +msgstr "Money Currency instead" #: src/js/controllers/buyAmazon.js:98 msgid "Amazon.com is not available at this moment. Please try back later." -msgstr "" +msgstr "Amazon.com is not available at this time. Please try again again." #: www/views/amount.html:44 #: www/views/customAmount.html:34 #: www/views/includes/output.html:7 msgid "Amount" -msgstr "" +msgstr "Quantity" #: src/js/services/bwcError.js:110 msgid "Amount below minimum allowed" -msgstr "" +msgstr "Maximum amount of the minimum amount" #: src/js/controllers/confirm.js:216 msgid "Amount too big" -msgstr "" +msgstr "Amount too large" #: www/views/includes/walletHistory.html:31 msgid "Amount too low to spend" -msgstr "" +msgstr "Amount too low for standard" #: src/js/controllers/tab-home.js:147 msgid "An update to this app is available. For your security, please update to the latest version." -msgstr "" +msgstr "Hotel have a Update for this application. To preserve your password, please please update the latest version." #: www/views/backupWarning.html:14 msgid "Anyone with your backup phrase can access or spend your bitcoin." -msgstr "" +msgstr "Każdy, kto ma Twoją kluczową là một trong những điều tuyệt vời nhất." #: www/views/addresses.html:94 msgid "Approximate Bitcoin network fee to transfer wallet's balance (with normal priority)" -msgstr "" +msgstr "La taxa approssimativa della rete Bitcoin cho mỗi lần gửi tiền theo yêu cầu của bạn" #: www/views/backupWarning.html:10 msgid "Are you being watched?" -msgstr "" +msgstr "Chúng tôi vigilen?" #: src/js/controllers/preferencesExternal.js:15 msgid "Are you being watched? Anyone with your recovery phrase can access or spend your bitcoin." -msgstr "" +msgstr "You are tracking? Bất cứ ai có thể phục hồi từ bạn, bạn có thể truy cập hoặc bitcoin chi tiêu của bạn." #: src/js/controllers/copayers.js:56 msgid "Are you sure you want to cancel and delete this wallet?" -msgstr "" +msgstr "Are you sure you want to cancel and remove this video?" #: src/js/controllers/addressbookView.js:37 msgid "Are you sure you want to delete this contact?" -msgstr "" +msgstr "Bạn có chắc chắn muốn xóa địa chỉ liên hệ này không?" #: src/js/controllers/preferencesDelete.js:25 msgid "Are you sure you want to delete this wallet?" -msgstr "" +msgstr "Are you sure you want to delete this wallet?" #: src/js/controllers/modals/txpDetails.js:154 msgid "Are you sure you want to reject this transaction?" -msgstr "" +msgstr "Are you sure you want to reject this transaction?" #: src/js/controllers/modals/txpDetails.js:171 msgid "Are you sure you want to remove this transaction?" -msgstr "" +msgstr "Are you sure you want to remove this transaction?" #: src/js/controllers/onboarding/backupRequest.js:23 msgid "Are you sure you want to skip it?" -msgstr "" +msgstr "Are you sure you want to skip it?" #: www/views/modals/bitpay-card-confirmation.html:4 msgid "Are you sure you would like to log out of your BitPay Card account?" -msgstr "" +msgstr "Bạn có chắc chắn muốn đăng xuất khỏi tài khoản Thẻ BitPay của mình không?" #: src/js/controllers/preferencesBitpayCard.js:7 #: src/js/controllers/preferencesBitpayServices.js:20 msgid "Are you sure you would like to remove your BitPay Card ({{lastFourDigits}}) from this device?" -msgstr "" +msgstr "Are you sure you would like to remove your BitPay Card ({{lastFourDigits}}) from this device?" #: www/views/includes/walletInfo.html:10 msgid "Auditable" -msgstr "" +msgstr "Auditable" #: www/views/modals/wallet-balance.html:42 msgid "Available" -msgstr "" +msgstr "Available" #: www/views/includes/available-balance.html:3 msgid "Available Balance" -msgstr "" +msgstr "Available Balance" #: www/views/modals/chooseFeeLevel.html:24 #: www/views/preferencesFee.html:15 msgid "Average confirmation time" -msgstr "" +msgstr "Average confirmation time" #: www/views/join.html:143 #: www/views/tab-create-personal.html:113 #: www/views/tab-create-shared.html:142 #: www/views/tab-import-phrase.html:51 msgid "BIP32 path for address derivation" -msgstr "" +msgstr "Đường dẫn BIP32 cho dẫn xuất địa chỉ" #: www/views/cashScan.html:25 msgid "BTC wallets" -msgstr "" +msgstr "Ví BTC" #: www/views/preferences.html:34 msgid "Backup" -msgstr "" +msgstr "Backup" #: www/views/includes/backupNeededPopup.html:7 msgid "Backup Needed" -msgstr "" +msgstr "Backup Needed" #: src/js/controllers/lockSetup.js:87 msgid "Backup all livenet wallets before using this function" -msgstr "" +msgstr "Backup all livenet wallets before using this function" #: src/js/controllers/cashScan.js:64 #: www/views/includes/walletListSettings.html:12 #: www/views/preferences.html:36 msgid "Backup needed" -msgstr "" +msgstr "Cần sao lưu" #: www/views/includes/backupNeededPopup.html:9 msgid "Backup now" -msgstr "" +msgstr "Sao lưu ngay" #: www/views/onboarding/backupRequest.html:11 #: www/views/tab-export-file.html:89 msgid "Backup wallet" -msgstr "" +msgstr "Backup wallet" #: src/js/controllers/lockSetup.js:84 msgid "Backup your wallet before using this function" -msgstr "" +msgstr "Backup your wallet before using this function" #: src/js/services/profileService.js:446 msgid "Bad wallet invitation" -msgstr "" +msgstr "Bad wallet invitation" #: www/views/preferencesInformation.html:102 msgid "Balance By Address" -msgstr "" +msgstr "Balance By Address" #: www/views/includes/confirmBackupPopup.html:7 msgid "Be sure to store your recovery phrase in a secure place. If this app is deleted, your money cannot be recovered without it." -msgstr "" +msgstr "Hãy chắc chắn lưu trữ cụm từ khôi phục của bạn ở một nơi an toàn. Nếu ứng dụng này bị xóa, tiền của bạn không thể được phục hồi mà không có nó." #: www/views/preferencesBitpayServices.html:9 msgid "BitPay Visa® Cards" -msgstr "" +msgstr "BitPay Visa & reg; thẻ" #: www/views/addressbook.add.html:38 #: www/views/includes/incomingDataMenu.html:29 msgid "Bitcoin Address" -msgstr "" +msgstr "Bitcoin Address" #: www/views/cashScan.html:4 msgid "Bitcoin Cash (BCH) Balances" -msgstr "" +msgstr "Bitcoin Cash (BCH) Balances" #: www/views/preferencesCash.html:3 #: www/views/tab-settings.html:47 msgid "Bitcoin Cash Support" -msgstr "" +msgstr "Bitcoin Cash Support" #: www/views/tab-home.html:98 #: www/views/tab-settings.html:115 msgid "Bitcoin Cash Wallets" -msgstr "" +msgstr "Bitcoin Cash Wallets" #: www/views/modals/chooseFeeLevel.html:4 #: www/views/preferencesFee.html:4 #: www/views/tab-settings.html:90 msgid "Bitcoin Network Fee Policy" -msgstr "" +msgstr "Bitcoin Network Fee Policy" #: www/views/tab-home.html:83 #: www/views/tab-settings.html:107 msgid "Bitcoin Core Wallets" -msgstr "" +msgstr "Bitcoin Core Wallets" #: src/js/services/incomingData.js:151 msgid "Bitcoin cash Payment" -msgstr "" +msgstr "Bitcoin cash Payment" #: www/views/onboarding/tour.html:31 msgid "Bitcoin is a currency." -msgstr "" +msgstr "Bitcoin is a currency." #: www/views/onboarding/disclaimer.html:15 msgid "Bitcoin is different – it cannot be safely held with a bank or web service." -msgstr "" +msgstr "–." #: www/views/onboarding/tour.html:18 msgid "Bitcoin is secure,
digital money." -msgstr "" +msgstr "Bitcoin là an toàn,
tiền kỹ thuật số." #: www/views/preferencesFee.html:11 msgid "Bitcoin transactions include a fee collected by miners on the network." -msgstr "" +msgstr "Bitcoin transactions include a fee collected by miners on the network." #: www/views/buyAmazon.html:108 msgid "Bought {{amountUnitStr}}" -msgstr "" +msgstr "Bought {{amountUnitStr}}" #: www/views/modals/txp-details.html:36 msgid "Broadcast Payment" -msgstr "" +msgstr "Broadcast Payment" #: src/js/controllers/modals/txpDetails.js:64 #: src/js/controllers/tx-details.js:81 msgid "Broadcasted" -msgstr "" +msgstr "Broadcasted" #: src/js/services/onGoingProcess.js:11 msgid "Broadcasting transaction" -msgstr "" +msgstr "Broadcasting transaction" #: www/views/unsupported.html:6 msgid "Browser unsupported" -msgstr "" +msgstr "Browser unsupported" #: www/views/buyAmazon.html:5 #: www/views/buyMercadoLibre.html:6 msgid "Buy" -msgstr "" +msgstr "Buy" #: www/views/includes/buyAndSellCard.html:3 msgid "Buy & Sell Bitcoin" -msgstr "" +msgstr "Buy & Sell Bitcoin" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" -msgstr "" +msgstr "Buy Bitcoin" #: www/views/mercadoLibre.html:22 #: www/views/mercadoLibre.html:50 msgid "Buy a Gift Card" -msgstr "" +msgstr "Buy a Gift Card" #: src/js/controllers/buyAmazon.js:334 msgid "Buy from" -msgstr "" +msgstr "Buy from" #: src/js/services/onGoingProcess.js:40 msgid "Buying Bitcoin..." -msgstr "" +msgstr "Mua Bitcoin..." #: src/js/services/onGoingProcess.js:12 msgid "Calculating fee" -msgstr "" +msgstr "Phí tính toán" #: src/js/controllers/buyAmazon.js:313 #: src/js/controllers/buyMercadoLibre.js:307 @@ -474,65 +478,65 @@ msgstr "" #: www/views/modals/bitpay-card-confirmation.html:8 #: www/views/modals/confirmation.html:13 msgid "Cancel" -msgstr "" +msgstr "Hủy bỏ" #: www/views/copayers.html:36 msgid "Cancel invitation" -msgstr "" +msgstr "Hủy lời mời" #: src/js/controllers/onboarding/tour.js:52 msgid "Cannot Create Wallet" -msgstr "" +msgstr "Không thể tạo Wallet" #: src/js/services/profileService.js:442 msgid "Cannot join the same wallet more that once" -msgstr "" +msgstr "Cannot join the same wallet more that once" #: www/views/includes/bitpayCardsCard.html:2 msgid "Cards" -msgstr "" +msgstr "Cards" #: www/views/modals/paypro.html:30 msgid "Certified by" -msgstr "" +msgstr "Certified by" #: www/views/preferencesExternal.html:19 msgid "Check installation and retry." -msgstr "" +msgstr "Check installation and retry." #: www/views/tab-import-file.html:4 msgid "Choose a backup file from your computer" -msgstr "" +msgstr "Chọn tệp sao lưu từ máy tính của bạn" #: www/views/modals/wallets.html:9 msgid "Choose your destination wallet" -msgstr "" +msgstr "Choose your destination wallet" #: www/views/modals/wallets.html:10 msgid "Choose your source wallet" -msgstr "" +msgstr "Chọn ví nguồn của bạn" #: www/views/backup.html:61 msgid "Clear" -msgstr "" +msgstr "Clear" #: www/views/preferencesHistory.html:24 msgid "Clear cache" -msgstr "" +msgstr "Clear cache" #: src/js/controllers/confirm.js:373 #: src/js/controllers/modals/txpDetails.js:49 msgid "Click to accept" -msgstr "" +msgstr "Click to accept" #: src/js/controllers/confirm.js:367 msgid "Click to pay" -msgstr "" +msgstr "Click to pay" #: src/js/controllers/confirm.js:379 #: src/js/controllers/modals/txpDetails.js:42 msgid "Click to send" -msgstr "" +msgstr "Click to send" #: www/views/customAmount.html:4 #: www/views/modals/mercadolibre-card-details.html:3 @@ -542,28 +546,28 @@ msgstr "" #: www/views/modals/wallet-balance.html:3 #: www/views/modals/wallets.html:5 msgid "Close" -msgstr "" +msgstr "Close" #: www/views/includes/cash.html:2 #: www/views/preferencesInformation.html:17 msgid "Coin" -msgstr "" +msgstr "Coin" #: www/views/preferences.html:22 msgid "Color" -msgstr "" +msgstr "Màu" #: www/views/preferencesAbout.html:21 msgid "Commit hash" -msgstr "" +msgstr "Commit hash" #: www/views/preferences.html:49 msgid "Complete the backup process to use this option" -msgstr "" +msgstr "Complete the backup process to use this option" #: www/views/bitpayCard.html:93 msgid "Completed" -msgstr "" +msgstr "Completed" #: src/js/controllers/buyAmazon.js:311 #: src/js/controllers/buyMercadoLibre.js:305 @@ -575,95 +579,99 @@ msgstr "" #: www/views/confirm.html:4 #: www/views/onboarding/collectEmail.html:32 msgid "Confirm" -msgstr "" +msgstr "Confirm" #: www/views/modals/terms.html:26 #: www/views/onboarding/disclaimer.html:44 msgid "Confirm & Finish" -msgstr "" +msgstr "Confirm & Finish" #: www/views/buyAmazon.html:90 msgid "Confirm purchase" -msgstr "" +msgstr "Confirm purchase" #: www/views/modals/pin.html:10 msgid "Confirm your PIN" -msgstr "" +msgstr "Confirm your PIN" #: src/js/services/walletService.js:1033 msgid "Confirm your new spending password" -msgstr "" +msgstr "Confirm your new spending password" #: www/views/tx-details.html:98 msgid "Confirmations" -msgstr "" +msgstr "Confirmations" #: www/views/bitpayCard.html:68 #: www/views/modals/wallet-balance.html:61 msgid "Confirming" -msgstr "" +msgstr "Xác nhận" #: www/views/bitpayCardIntro.html:37 msgid "Connect my BitPay Card" -msgstr "" +msgstr "Kết nối thẻ BitPay của tôi" #: src/js/services/onGoingProcess.js:13 msgid "Connecting to Coinbase..." -msgstr "" +msgstr "Connecting to Coinbase..." #: src/js/services/onGoingProcess.js:14 msgid "Connecting to Glidera..." -msgstr "" +msgstr "Đang kết nối với Glidera ..." #: src/js/services/bwcError.js:53 msgid "Connection reset by peer" -msgstr "" +msgstr "Đặt lại kết nối theo ngang hàng" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" -msgstr "" +msgstr "Contacts" + +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "Save the regular usage" #: www/views/onboarding/notifications.html:9 msgid "Continue" -msgstr "" +msgstr "Continue" #: www/views/preferencesLanguage.html:26 msgid "Contribute Translations" -msgstr "" +msgstr "Contribute Translations" #: src/js/controllers/confirm.js:130 msgid "Copay only supports Bitcoin Cash using new version numbers addresses" -msgstr "" +msgstr "Copay chỉ hỗ trợ Bitcoin Cash bằng cách sử dụng số phiên bản mới" #: src/js/services/bwcError.js:62 msgid "Copayer already in this wallet" -msgstr "" +msgstr "Copayer already in this wallet" #: src/js/services/bwcError.js:77 msgid "Copayer already voted on this spend proposal" -msgstr "" +msgstr "Coper is this option" #: src/js/services/bwcError.js:107 msgid "Copayer data mismatch" -msgstr "" +msgstr "Dữ liệu Copayer không khớp" #: www/views/includes/walletActivity.html:2 msgid "Copayer joined" -msgstr "" +msgstr "Copayer joined" #: www/views/preferencesInformation.html:94 msgid "Copayer {{$index}}" -msgstr "" +msgstr "Copayer {{$index}}" #: src/js/controllers/copayers.js:79 #: src/js/controllers/export.js:193 #: www/views/includes/copyToClipboard.html:4 msgid "Copied to clipboard" -msgstr "" +msgstr "Sao chép vào clipboard" #: www/views/tab-export-file.html:94 msgid "Copy this text as it is to a safe place (notepad or email)" -msgstr "" +msgstr "Copy this text as it is to a safe place (notepad or email)" #: www/views/includes/incomingDataMenu.html:51 #: www/views/includes/incomingDataMenu.html:70 @@ -671,35 +679,35 @@ msgstr "" #: www/views/includes/logOptions.html:9 #: www/views/tab-export-file.html:78 msgid "Copy to clipboard" -msgstr "" +msgstr "Sao chép vào clipboard" #: src/js/controllers/buyMercadoLibre.js:102 msgid "Could not access Gift Card Service" -msgstr "" +msgstr "Could not access Gift Card Service" #: www/views/tab-import-phrase.html:2 msgid "Could not access the wallet at the server. Please check:" -msgstr "" +msgstr "Could not access the wallet at the server. Please check:" #: src/js/controllers/buyAmazon.js:102 msgid "Could not access to Amazon.com" -msgstr "" +msgstr "Could not access to Amazon.com" #: src/js/services/profileService.js:511 msgid "Could not access wallet" -msgstr "" +msgstr "Could not access wallet" #: src/js/controllers/confirm.js:210 msgid "Could not add message to imported wallet without shared encrypting key" -msgstr "" +msgstr "Could not add message to imported wallet without shared encrypting key" #: src/js/controllers/modals/txpDetails.js:199 msgid "Could not broadcast payment" -msgstr "" +msgstr "Could not broadcast payment" #: src/js/services/bwcError.js:41 msgid "Could not build transaction" -msgstr "" +msgstr "Cannot build transaction" #: src/js/services/walletService.js:854 msgid "Could not create address" @@ -823,7 +831,7 @@ msgstr "" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "" @@ -916,7 +924,7 @@ msgstr "" #: www/views/modals/mercadolibre-card-details.html:6 #: www/views/topup.html:45 msgid "Details" -msgstr "" +msgstr "Chi tiết" #: src/js/controllers/lockSetup.js:9 #: src/js/controllers/tab-settings.js:65 @@ -2533,6 +2541,14 @@ msgstr "" msgid "Search or enter bitcoin address" msgstr "" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "" @@ -2591,9 +2607,70 @@ msgid "Send by email" msgstr "" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "" + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "" + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "" + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "" @@ -3636,3 +3713,51 @@ msgstr "" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "" + diff --git a/i18n/po/zh-CN/template-zh-CN.po b/i18n/po/zh-CN/template-zh-CN.po index 8c274b798..eb8304e89 100644 --- a/i18n/po/zh-CN/template-zh-CN.po +++ b/i18n/po/zh-CN/template-zh-CN.po @@ -11,7 +11,7 @@ msgstr "" "Last-Translator: emilold\n" "Language-Team: Chinese Simplified\n" "Language: zh\n" -"PO-Revision-Date: 2018-07-04 03:57\n" +"PO-Revision-Date: 2018-07-27 08:43\n" #: www/views/modals/paypro.html:34 msgid "(Trusted)" @@ -79,7 +79,7 @@ msgstr "帐号" #: www/views/tab-home.html:61 msgid "Instant transactions with low fees" -msgstr "" +msgstr "以较低费用进行即时交易" #: www/views/preferencesBitpayServices.html:23 msgid "Accounts" @@ -363,12 +363,12 @@ msgstr "比特币地址" #: www/views/cashScan.html:4 msgid "Bitcoin Cash (BCH) Balances" -msgstr "" +msgstr "Bitcoin Cash (BCH) 余额" #: www/views/preferencesCash.html:3 #: www/views/tab-settings.html:47 msgid "Bitcoin Cash Support" -msgstr "" +msgstr "Bitcoin Cash 支持" #: www/views/tab-home.html:98 #: www/views/tab-settings.html:115 @@ -384,7 +384,7 @@ msgstr "比特币网络手续费策略" #: www/views/tab-home.html:83 #: www/views/tab-settings.html:107 msgid "Bitcoin Core Wallets" -msgstr "" +msgstr "Bitcoin Core 钱包" #: src/js/services/incomingData.js:151 msgid "Bitcoin cash Payment" @@ -437,6 +437,7 @@ msgid "Buy & Sell Bitcoin" msgstr "购买或出售比特币" #: www/views/tab-send.html:35 +#: src/js/services/buyAndSellService.js:26 msgid "Buy Bitcoin" msgstr "购买比特币" @@ -619,10 +620,14 @@ msgstr "正在连接 Glidera..." msgid "Connection reset by peer" msgstr "连接被对方重置" -#: www/views/tab-send.html:45 +#: www/views/tab-send.html:85 msgid "Contacts" msgstr "联系人" +#: www/views/tab-send.html:86 +msgid "Saved frequently used addresses" +msgstr "保存的常用地址" + #: www/views/onboarding/notifications.html:9 msgid "Continue" msgstr "继续" @@ -653,7 +658,7 @@ msgstr "Copayer 加入" #: www/views/preferencesInformation.html:94 msgid "Copayer {{$index}}" -msgstr "" +msgstr "Copayer {{$index}}" #: src/js/controllers/copayers.js:79 #: src/js/controllers/export.js:193 @@ -823,7 +828,7 @@ msgstr "创建自己的免费钱包" #: www/views/onboarding/tour.html:51 #: www/views/tab-home.html:75 -#: www/views/tab-send.html:36 +#: www/views/tab-send.html:75 msgid "Create bitcoin wallet" msgstr "创建比特币的钱包" @@ -977,7 +982,7 @@ msgstr "已达到空地址限制。无法生成新的地址。" #: www/views/preferencesCash.html:17 msgid "Enable Bitcoin Cash wallet creation and operation within the App." -msgstr "" +msgstr "在应用内启用 Bitcoin Cash 创建和操作。" #: www/views/tab-scan.html:19 msgid "Enable camera access in your device settings to get started." @@ -993,7 +998,7 @@ msgstr "启用推式通知" #: www/views/preferencesNotifications.html:33 msgid "Enable sound" -msgstr "" +msgstr "启用声音" #: www/views/tab-scan.html:18 msgid "Enable the camera to get started." @@ -1001,7 +1006,7 @@ msgstr "使该摄像机开始。" #: www/views/tab-settings.html:49 msgid "Enabled" -msgstr "" +msgstr "已启用" #: src/js/services/walletService.js:1047 #: src/js/services/walletService.js:1062 @@ -1586,7 +1591,7 @@ msgstr "网络地址不正确" #: src/js/controllers/confirm.js:306 #: src/js/services/bwcError.js:44 msgid "Insufficient confirmed funds" -msgstr "" +msgstr "确认的资金不足" #: src/js/controllers/topup.js:165 #: src/js/controllers/topup.js:177 @@ -2011,7 +2016,7 @@ msgstr "好的" #: www/views/modals/tx-status.html:36 #: www/views/modals/tx-status.html:46 msgid "OKAY" -msgstr "" +msgstr "确定" #: www/views/modals/terms.html:15 msgid "Official English Disclaimer" @@ -2053,7 +2058,7 @@ msgstr "打开 GitHub 项目" #: src/js/controllers/bitpayCard.js:123 #: src/js/controllers/tx-details.js:192 msgid "Open Explorer" -msgstr "" +msgstr "打开浏览器" #: www/views/tab-scan.html:22 msgid "Open Settings" @@ -2069,7 +2074,7 @@ msgstr "打开网站" #: src/js/controllers/preferencesCash.js:32 msgid "Open bitcoincash.org?" -msgstr "" +msgstr "打开 bitcoincash.org?" #: src/js/controllers/cashScan.js:18 msgid "Open the recovery tool." @@ -2511,7 +2516,7 @@ msgstr "请扫描你的指纹" #: www/views/preferencesCash.html:23 msgid "Scan your wallets for Bitcoin Cash" -msgstr "" +msgstr "扫描您的 Bitcoin Cash 钱包" #: src/js/services/onGoingProcess.js:30 msgid "Scanning Wallet funds..." @@ -2533,6 +2538,14 @@ msgstr "搜索交易" msgid "Search or enter bitcoin address" msgstr "搜索或输入比特币地址" +#: src/js/controllers/tab-send.js:28 +msgid "Clipboard" +msgstr "剪贴板" + +#: src/js/controllers/tab-send.js:29 +msgid "Your Clipboard is empty" +msgstr "您的剪贴板为空" + #: www/views/modals/search.html:16 msgid "Search transactions" msgstr "搜索交易" @@ -2591,9 +2604,70 @@ msgid "Send by email" msgstr "通过电邮发送" #: src/js/controllers/confirm.js:177 +#: src/js/controllers/tab-send.js:94 msgid "Send from" msgstr "从发送" +#: src/js/controllers/tab-send.js:77 +msgid "Send to" +msgstr "发送到" + +#: www/views/tab-send.html:20 +msgid "Paste Clipboard" +msgstr "粘贴剪贴板" + +#: www/views/tab-send.html:21 +msgid "Paste Address" +msgstr "粘贴地址" + +#: www/views/tab-send.html:27 +msgid "Wallet to Wallet Transfer" +msgstr "钱包转账" + +#: www/views/tab-send.html:35 +msgid "Scan QR Code" +msgstr "扫描二维码" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "更快地发送比特币!" + +#: www/views/tab-send.html:46 +msgid "Send Bitcoin faster!" +msgstr "更快地发送比特币!" + +#: www/views/tab-send.html:50 +msgid "Save frequently used addresses and send them Bitcoin in just one tap" +msgstr "保存常用地址,只需点击一下即可将比特币发送到这些地址" + +#: www/views/tab-send.html:55 +msgid "Add your first contact" +msgstr "添加您的第一个联系人" + +#: www/views/tab-send.html:65 +msgid "Your Bitcoin wallet is empty" +msgstr "您的比特币钱包为空" + +#: www/views/tab-send.html:69 +msgid "To get started, buy Bitcoin Cash (BCH) or Bitcoin Core (BTC), or share your address." +msgstr "首先,购买 Bitcoin Cash (BCH) 或 Bitcoin Core (BTC),或者共享您的地址。" + +#: www/views/tab-send.html:70 +msgid "You can receive bitcoin from any wallet or service." +msgstr "您可以从任何钱包或服务接收比特币。" + +#: www/views/tab-send.html:72 +msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." +msgstr "若要开始,您将需要创建一个比特币的钱包,并获得一些比特币。" + +#: www/views/tab-send.html:74 +msgid "Buy Bitcoin now" +msgstr "立即购买比特币" + +#: www/views/tab-send.html:76 +msgid "Show my address" +msgstr "显示我的地址" + #: www/views/includes/itemSelector.html:8 msgid "Send max amount" msgstr "发送最大数量" @@ -2810,7 +2884,7 @@ msgstr "超级经济" #: www/views/preferencesCash.html:11 msgid "Support Bitcoin Cash" -msgstr "" +msgstr "支持 Bitcoin Cash" #: www/views/paperWallet.html:7 msgid "Sweep" @@ -2860,7 +2934,7 @@ msgstr "使用条款" #: www/views/tab-create-personal.html:118 #: www/views/tab-import-phrase.html:68 msgid "Testnet" -msgstr "" +msgstr "测试网" #: www/views/includes/incomingDataMenu.html:61 msgid "Text" @@ -3030,7 +3104,7 @@ msgstr "若要开始,请买比特币或共享您的地址。你可以从任何 #: www/views/tab-send.html:33 msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." -msgstr "若要开始,您将需要创建一个比特币的钱包,并获得一些比特币。" +msgstr "首先,您需要创建一个比特币钱包,并获得一些比特币。" #: src/js/services/bitpayAccountService.js:73 msgid "To {{reason}} you must first add your BitPay account - {{email}}" @@ -3200,7 +3274,7 @@ msgstr "查看服务条款" #: src/js/controllers/bitpayCard.js:122 #: src/js/controllers/tx-details.js:191 msgid "View Transaction on Explorer.Bitcoin.com" -msgstr "" +msgstr "在 Explorer.Bitcoin.com 上查看交易" #: src/js/controllers/tab-home.js:148 msgid "View Update" @@ -3596,7 +3670,7 @@ msgstr "我" #: www/views/addressbook.add.html:32 msgid "name@example.com" -msgstr "" +msgstr "name@example.com" #: www/views/preferencesHistory.html:15 msgid "preparing..." @@ -3636,3 +3710,51 @@ msgstr "下载的 {{updatingTxHistoryProgress}} 交易" msgid "{{wallet.m}}-of-{{wallet.n}}" msgstr "{{wallet.m}}{{wallet.n}}" +#: src/js/services/shapeshiftService.js:8 +msgid "Shapeshift" +msgstr "Shapeshift" + +#: www/views/includes/community.html:3 +msgid "Community" +msgstr "社区" + +#: src/js/services/communityService.js:40 +msgid "Bitcoin Cash Reddit" +msgstr "Bitcoin Cash Reddit" + +#: src/js/services/communityService.js:47 +msgid "Bitcoin.com Twitter" +msgstr "Bitcoin.com Twitter" + +#: www/views/includes/nextSteps.html:3 +msgid "Explore Bitcoin.com" +msgstr "探索 Bitcoin.com" + +#: src/js/services/bitcoincomService.js:21 +msgid "Bitcoin Cash Games" +msgstr "Bitcoin Cash 游戏" + +#: src/js/services/bitcoincomService.js:28 +msgid "News" +msgstr "新闻" + +#: src/js/services/bitcoincomService.js:35 +msgid "Mining Pool" +msgstr "矿池" + +#: src/js/services/bitcoincomService.js:42 +msgid "Tools" +msgstr "工具" + +#: src/js/services/bitcoincomService.js:49 +msgid "Bitcoin Price Charts" +msgstr "比特币价格图表" + +#: src/js/services/bitcoincomService.js:56 +msgid "Free Bitcoin Cash" +msgstr "免费的 Bitcoin Cash" + +#: www/views/tab-home.html:30 +msgid "Your Bitcoin Wallets are ready!" +msgstr "您的比特币钱包已就绪!" + diff --git a/www/index.html b/www/index.html index 4c73317e3..ecc2d923c 100644 --- a/www/index.html +++ b/www/index.html @@ -11,9 +11,8 @@ - Bitcoin.com Wallet - Bitcoin.com Wallet - - + Bitcoin.com Wallet + @@ -31,7 +30,7 @@ - + From d122048a7b7d25a3934ef5557a778bfcff46317e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 27 Jul 2018 17:46:19 +0900 Subject: [PATCH 064/240] Increment version --- app-template/bitcoincom/appConfig.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app-template/bitcoincom/appConfig.json b/app-template/bitcoincom/appConfig.json index fefcb9a4f..238f38002 100644 --- a/app-template/bitcoincom/appConfig.json +++ b/app-template/bitcoincom/appConfig.json @@ -24,9 +24,9 @@ "windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c", "pushSenderId": "1036948132229", "description": "A Secure Bitcoin Wallet", - "version": "4.13.0", - "fullVersion": "4.13-rc1", - "androidVersion": "413000", + "version": "4.13.1", + "fullVersion": "4.13-rc2", + "androidVersion": "413100", "_extraCSS": "", "_enabledExtensions": { "coinbase": false, From 81e9f527ff05dbca5977c3bf7aff5c519cf6a6aa Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Sat, 28 Jul 2018 13:04:04 -0700 Subject: [PATCH 065/240] Route for send amount. --- src/js/routes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/routes.js b/src/js/routes.js index 5900b88da..5b0b59d77 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -292,7 +292,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr 'tab-send@tabs': { controller: 'amountController', controllerAs: 'vm', - templateUrl: 'views/amount.html' + templateUrl: 'views/amountNew.html' } } }) From 0ba1ea1f423435b7ce3e74a84750ca374dec2b03 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Sat, 28 Jul 2018 15:36:23 -0700 Subject: [PATCH 066/240] Use All Available Funds button. --- src/sass/variables.scss | 4 +++ src/sass/views/amountNew.scss | 60 +++++++++++++++++++++++------------ www/views/amountNew.html | 16 ++++++---- 3 files changed, 53 insertions(+), 27 deletions(-) diff --git a/src/sass/variables.scss b/src/sass/variables.scss index cb21c030a..c2ab16254 100644 --- a/src/sass/variables.scss +++ b/src/sass/variables.scss @@ -9,6 +9,7 @@ $v-font-family-light: "Roboto-Light", sans-serif- /* Colors */ $v-bitcoin-orange: #fab915 !default; +$v-off-black: #262424; $v-dark-gray: #445 !default; $v-mid-gray: #667 !default; $v-light-gray: #9b9bab !default; @@ -24,8 +25,11 @@ $v-text-accent-color: #647ce8 !default; $v-success-color: #13e5b6 !default; $v-warning-color: #ffa500 !default; +$v-warning-color-2: #b7664d; $v-error-color: #ef473a !default; +$v-background-under-card: #f2f2f2; + $v-wallet-color-map: ( 0: (color: #dd4b39, name: 'Cinnabar'), 1: (color: #f38f12, name: 'Carrot Orange'), diff --git a/src/sass/views/amountNew.scss b/src/sass/views/amountNew.scss index 5f27361b3..eb29d9ee4 100644 --- a/src/sass/views/amountNew.scss +++ b/src/sass/views/amountNew.scss @@ -249,7 +249,6 @@ min-height: 20px; .warning { - color: #b7664d; font-family: 'ProximaNova-Semibold'; font-size: 12px; padding: 0 6px 6px 6px; @@ -433,37 +432,59 @@ .keypad-container { position: relative; + font-size: 18px; + line-height: 2em; //flex: 0 1 196px; + @media (min-height: 667px) { + font-size: 24px; + } + + @media(max-height: 480px) { + font-size: 12px; + } + @media (min-height: 667px) { //flex: 0 1 224px; } + .sendmax { + background: $v-off-black; + + .button { + color: white; + background: black; + border: 1px solid $v-off-black; + border-radius: 0; + font-size: 0.8em; + line-height: 2em; + margin-bottom: 1.618em; + width: 100%; + + .available-funds-amount { + color: #C9C9C9; + } + + &:active { + background-color: $v-dark-gray; + } + } + } + .keypad { text-align: center; - font-size: 18px; font-weight: lighter; position: absolute; bottom: 0; width: 100%; color: $v-text-primary-color; - @media (min-height: 667px) { - font-size: 24px; - } + .row { padding: 0 !important; margin: 0 !important; } - - .col { - line-height: 38px; - - @media (min-height: 667px) { - line-height: 45px; - } - } .row { &:last-child { @@ -498,23 +519,22 @@ .digit{ cursor: pointer; background-color: #000; - border: 1px solid #262424; + border: 1px solid $v-off-black; transition: all 0.1s ease; - &:active { background-color: $v-dark-gray; } } - @media(max-height: 480px) { - font-size: 12px; - - } } } } - background: #f2f2f2; + + .warning { + color: $v-warning-color-2; + } + background: $v-background-under-card; ion-content { margin-bottom: constant(safe-area-inset-bottom); /* iOS 11.0 */ diff --git a/www/views/amountNew.html b/www/views/amountNew.html index 1b29f6e44..9449c361d 100644 --- a/www/views/amountNew.html +++ b/www/views/amountNew.html @@ -48,16 +48,18 @@ Available Funds: {{vm.availableFunds}}
- -
+
+ +
+
From 72a5b3cabd57396947d08fe329362ac59c3e690d Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Sat, 28 Jul 2018 17:18:52 -0700 Subject: [PATCH 067/240] Primary ("Next") button. --- src/sass/variables.scss | 1 + src/sass/views/amountNew.scss | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/sass/variables.scss b/src/sass/variables.scss index c2ab16254..67d5a044b 100644 --- a/src/sass/variables.scss +++ b/src/sass/variables.scss @@ -81,6 +81,7 @@ $v-button-primary-active-bg: darken($v-accent-color, 10% $v-button-primary-active-border: transparent !default; $v-button-primary-clear-bg: none !default; $v-button-primary-clear-color: $v-accent-color !default; +$v-button-primary-disabled-bg: $v-mid-gray; $v-button-primary-outline-bg: transparent !default; $v-button-primary-outline-border: $v-accent-color !default; $v-button-primary-outline-color: $v-accent-color !default; diff --git a/src/sass/views/amountNew.scss b/src/sass/views/amountNew.scss index eb29d9ee4..bbca0f5a2 100644 --- a/src/sass/views/amountNew.scss +++ b/src/sass/views/amountNew.scss @@ -529,6 +529,16 @@ } } + + .button-primary { + background-color: $v-primary-color; + border-radius: 0; + font-family: 'ProximaNova-Semibold'; + } + + .button-primary[disabled] { + background-color: $v-button-primary-disabled-bg; + } } .warning { From 9781f90f2b21095d2ab34468c3336cf663bad3b4 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Sat, 28 Jul 2018 19:20:12 -0700 Subject: [PATCH 068/240] Warning colour for insufficient funds. --- src/js/controllers/amount.js | 1 + src/sass/views/amountNew.scss | 6 +++++- www/views/amountNew.html | 8 ++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 1d4cb41f1..a4c8171a4 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -12,6 +12,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i vm.amountModel = { amount: 0 }; vm.availableFunds = '251.00 USD'; vm.fromWalletId = ''; + vm.fundsAreInsufficient = true; vm.globalResult = ''; vm.isRequestingSpecificAmount = false; vm.listComplete = false; diff --git a/src/sass/views/amountNew.scss b/src/sass/views/amountNew.scss index bbca0f5a2..9dad8b045 100644 --- a/src/sass/views/amountNew.scss +++ b/src/sass/views/amountNew.scss @@ -377,10 +377,14 @@ justify-content: space-between; margin: 0 14px; - .availableFunds { + .available-funds { color: #6F6F70; } + .warning { + color: $v-warning-color-2; + } + .extra, button.extra { display: flex; diff --git a/www/views/amountNew.html b/www/views/amountNew.html index 9449c361d..b23a34fbf 100644 --- a/www/views/amountNew.html +++ b/www/views/amountNew.html @@ -29,7 +29,7 @@
-
- -
- Available Funds: {{vm.availableFunds}} +
+ Available Funds:{{vm.availableFunds}}
From 7669ab247981245fbe611d59cb86bbd3372da2b4 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 30 Jul 2018 19:28:30 +1200 Subject: [PATCH 071/240] Transitioning from the Shapeshift screen. --- src/js/controllers/shapeshift.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/js/controllers/shapeshift.js b/src/js/controllers/shapeshift.js index a58fc20b6..5bc815d19 100644 --- a/src/js/controllers/shapeshift.js +++ b/src/js/controllers/shapeshift.js @@ -1,6 +1,9 @@ 'use strict'; angular.module('copayApp.controllers').controller('shapeshiftController', function($scope, $interval, profileService, walletService, popupService, lodash, $ionicNavBarDelegate) { + var vm = this; + + //vm.buyBitcion = buyBitcoin; var walletsBtc = []; var walletsBch = []; @@ -21,6 +24,12 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi $scope.singleToWallet = $scope.toWallets.length == 1; } + function buyBitcoin() { + console.log('buyBitcoin()'); + } + + $scope.buyBitcoin = buyBitcoin; + $scope.onFromWalletSelect = function(wallet) { $scope.fromWallet = wallet; showToWallets(); @@ -37,12 +46,24 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi } $scope.$on("$ionicView.beforeEnter", function(event, data) { + console.log('beforeEnter()'); walletsBtc = profileService.getWallets({coin: 'btc'}); walletsBch = profileService.getWallets({coin: 'bch'}); $scope.fromWallets = lodash.filter(walletsBtc.concat(walletsBch), function(w) { return w.status.balance.availableAmount > 0; }); - if ($scope.fromWallets.length == 0) return; + console.log('Checking wallets.'); + if ($scope.fromWallets.length == 0) { + // Need to go to new origin screen here, with parameters + var params = { + thirdParty: { + id: 'shapeshift' + } + }; + console.log('Asking for transition'); + $state.transitionTo('tabs.send', params); + return + } $scope.onFromWalletSelect($scope.fromWallets[0]); $scope.onToWalletSelect($scope.toWallets[0]); $scope.singleFromWallet = $scope.fromWallets.length == 1; From 3732d21728b92993771ab51aefc114b31a84d5bb Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 30 Jul 2018 09:37:14 +0200 Subject: [PATCH 072/240] send/receive iPhoneX fixes --- src/js/controllers/walletDetails.js | 14 +++++++------- src/sass/views/walletDetails.scss | 19 ++++++++++++------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index c824cbfda..f4f2d2488 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -327,16 +327,16 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun } scrollPos = scrollPos || 0; - var amountHeight = 230 - scrollPos; + var amountHeight = 210 - scrollPos; if (amountHeight < 80) { amountHeight = 80; } var contentMargin = amountHeight; - if (contentMargin > 230) { - contentMargin = 230; + if (contentMargin > 210) { + contentMargin = 210; } - var amountScale = (amountHeight / 230); + var amountScale = (amountHeight / 210); if (amountScale < 0.5) { amountScale = 0.5; } @@ -354,9 +354,9 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun top = TOP_BALANCE_BUTTON; } - var amountTop = ((amountScale - 0.85) / 0.85) * top; - if (amountTop < -10) { - amountTop = -10; + var amountTop = ((amountScale - 0.80) / 0.80) * top; //0.85 + if (amountTop < -2) { + amountTop = -2; } if (amountTop > top) { amountTop = top; diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index 0536a5735..8d6972f09 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -132,10 +132,15 @@ position: relative; height: 100%; height: calc(100% - env(safe-area-inset-bottom) * 2); + + &.status-bar { + margin-top: 20px; + margin-top: env(safe-area-inset-top); + } } .bar-header { border: 0; - background: none; + background: rgb(238, 182, 64); .title, .button { color: #fff; } @@ -143,13 +148,13 @@ background-color: transparent; } } - //.nav-bar-block, .bar { - //background-color: inherit !important; - //} + .nav-bar-block, .bar { + background-color: inherit !important; + } ion-content { &.collapsible { - margin-top: 210px; + margin-top: 230px; } padding-top: 0; @@ -189,7 +194,7 @@ .send-receive-buttons { max-width: 600px; - margin: 45px auto 0; + margin: 25px auto 0; >.col { padding: 5px 10px; margin-bottom: 0; @@ -211,7 +216,7 @@ width: 100%; text-align: center; color: #fff; - height: 210px; + height: 230px; padding-top: 40px; display: block; align-items: center; From 734153ec5b76cd4f8a5c130d4e534bbdcf62ba72 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 30 Jul 2018 15:17:15 +0200 Subject: [PATCH 073/240] more fixes on the send/receive on Android & iOS / removed some unused code --- src/js/controllers/walletDetails.js | 12 ++---------- src/sass/views/walletDetails.scss | 4 +++- www/views/walletDetails.html | 8 ++++---- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index f4f2d2488..ffbc070f5 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -12,13 +12,6 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.isAndroid = platformInfo.isAndroid; $scope.isIOS = platformInfo.isIOS; - $scope.currencySymbols = { - 'EUR': '€', - 'GBP': '£', - 'USD': '$', - 'YEN' : '' - }; - var channel = "firebase"; if (platformInfo.isNW) { channel = "ga"; @@ -354,7 +347,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun top = TOP_BALANCE_BUTTON; } - var amountTop = ((amountScale - 0.80) / 0.80) * top; //0.85 + var amountTop = ((amountScale - 0.80) / 0.80) * top; if (amountTop < -2) { amountTop = -2; } @@ -365,8 +358,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var t = amountTop; $scope.altAmountOpacity = (amountHeight - 100) / 80; - - $scope.buttonsOpacity = (amountHeight - 150)/80; + $scope.buttonsOpacity = (amountHeight - 140) / 70; $window.requestAnimationFrame(function() { $scope.amountHeight = amountHeight + 'px'; $scope.contentMargin = contentMargin + 'px'; diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index 8d6972f09..6b760bbc4 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -194,7 +194,9 @@ .send-receive-buttons { max-width: 600px; - margin: 25px auto 0; + position: absolute; + bottom: 20px; + >.col { padding: 5px 10px; margin-bottom: 0; diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index 85aace8fc..e29cd5c51 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -91,7 +91,7 @@
-
+
Receive @@ -214,17 +214,17 @@
-
+
Receive
-
+
Buy Bitcoin
-
+
Send
From ed291b1ac5f706315c5e074e1389cc9e6b3f1fbc Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 30 Jul 2018 15:25:52 +0200 Subject: [PATCH 074/240] removed currencySymbolService and it's references --- src/js/controllers/walletDetails.js | 7 +- src/js/services/currencySymbolService.js | 195 ----------------------- www/views/walletDetails.html | 16 +- 3 files changed, 9 insertions(+), 209 deletions(-) delete mode 100644 src/js/services/currencySymbolService.js diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index ffbc070f5..3e92b2a40 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -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, currencySymbolService, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService, appConfigService, rateService) { +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, appConfigService, rateService) { var HISTORY_SHOW_LIMIT = 10; var currentTxHistoryPage = 0; @@ -256,11 +256,6 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun return !tx.confirmations || tx.confirmations === 0; }; - $scope.currencySymbol = function(code) { - var symbol = currencySymbolService.getCurrencySymbol(code); - return symbol?symbol:""; - }; - $scope.showMore = function() { $timeout(function() { currentTxHistoryPage++; diff --git a/src/js/services/currencySymbolService.js b/src/js/services/currencySymbolService.js deleted file mode 100644 index 029cd9eb2..000000000 --- a/src/js/services/currencySymbolService.js +++ /dev/null @@ -1,195 +0,0 @@ -'use strict'; - -angular.module('copayApp.services').factory('currencySymbolService', function($log) { - var root = {}; - root.currencySymbols = { - 'AED': 'د.إ', - 'AFN': '؋', - 'ALL': 'L', - 'AMD': '֏', - 'ANG': 'ƒ', - 'AOA': 'Kz', - 'ARS': '$', - 'AUD': '$', - 'AWG': 'ƒ', - 'AZN': '₼', - 'BAM': 'KM', - 'BBD': '$', - 'BDT': '৳', - 'BGN': 'лв', - 'BHD': '.د.ب', - 'BIF': 'FBu', - 'BMD': '$', - 'BND': '$', - 'BOB': '$b', - 'BRL': 'R$', - 'BSD': '$', - 'BTC': '฿', - 'BTN': 'Nu.', - 'BWP': 'P', - 'BYR': 'Br', - 'BYN': 'Br', - 'BZD': 'BZ$', - 'CAD': '$', - 'CDF': 'FC', - 'CHF': 'CHF', - 'CLP': '$', - 'CNY': '¥', - 'COP': '$', - 'CRC': '₡', - 'CUC': '$', - 'CUP': '₱', - 'CVE': '$', - 'CZK': 'Kč', - 'DJF': 'Fdj', - 'DKK': 'kr', - 'DOP': 'RD$', - 'DZD': 'دج', - 'EEK': 'kr', - 'EGP': '£', - 'ERN': 'Nfk', - 'ETB': 'Br', - 'ETH': 'Ξ', - 'EUR': '€', - 'FJD': '$', - 'FKP': '£', - 'GBP': '£', - 'GEL': '₾', - 'GGP': '£', - 'GHC': '₵', - 'GHS': 'GH₵', - 'GIP': '£', - 'GMD': 'D', - 'GNF': 'FG', - 'GTQ': 'Q', - 'GYD': '$', - 'HKD': '$', - 'HNL': 'L', - 'HRK': 'kn', - 'HTG': 'G', - 'HUF': 'Ft', - 'IDR': 'Rp', - 'ILS': '₪', - 'IMP': '£', - 'INR': '₹', - 'IQD': 'ع.د', - 'IRR': '﷼', - 'ISK': 'kr', - 'JEP': '£', - 'JMD': 'J$', - 'JOD': 'JD', - 'JPY': '¥', - 'KES': 'KSh', - 'KGS': 'лв', - 'KHR': '៛', - 'KMF': 'CF', - 'KPW': '₩', - 'KRW': '₩', - 'KWD': 'KD', - 'KYD': '$', - 'KZT': 'лв', - 'LAK': '₭', - 'LBP': '£', - 'LKR': '₨', - 'LRD': '$', - 'LSL': 'M', - 'LTC': 'Ł', - 'LTL': 'Lt', - 'LVL': 'Ls', - 'LYD': 'LD', - 'MAD': 'MAD', - 'MDL': 'lei', - 'MGA': 'Ar', - 'MKD': 'ден', - 'MMK': 'K', - 'MNT': '₮', - 'MOP': 'MOP$', - 'MRO': 'UM', - 'MRU': 'UM', - 'MUR': '₨', - 'MVR': 'Rf', - 'MWK': 'MK', - 'MXN': '$', - 'MYR': 'RM', - 'MZN': 'MT', - 'NAD': '$', - 'NGN': '₦', - 'NIO': 'C$', - 'NOK': 'kr', - 'NPR': '₨', - 'NZD': '$', - 'OMR': '﷼', - 'PAB': 'B/.', - 'PEN': 'S/.', - 'PGK': 'K', - 'PHP': '₱', - 'PKR': '₨', - 'PLN': 'zł', - 'PYG': 'Gs', - 'QAR': '﷼', - 'RMB': '¥', - 'RON': 'lei', - 'RSD': 'Дин.', - 'RUB': '₽', - 'RWF': 'R₣', - 'SAR': '﷼', - 'SBD': '$', - 'SCR': '₨', - 'SDG': 'ج.س.', - 'SEK': 'kr', - 'SGD': '$', - 'SHP': '£', - 'SLL': 'Le', - 'SOS': 'S', - 'SRD': '$', - 'SSP': '£', - 'STD': 'Db', - 'STN': 'Db', - 'SVC': '$', - 'SYP': '£', - 'SZL': 'E', - 'THB': '฿', - 'TJS': 'SM', - 'TMT': 'T', - 'TND': 'د.ت', - 'TOP': 'T$', - 'TRL': '₤', - 'TRY': '₺', - 'TTD': 'TT$', - 'TVD': '$', - 'TWD': 'NT$', - 'TZS': 'TSh', - 'UAH': '₴', - 'UGX': 'USh', - 'USD': '$', - 'UYU': '$U', - 'UZS': 'лв', - 'VEF': 'Bs', - 'VND': '₫', - 'VUV': 'VT', - 'WST': 'WS$', - 'XAF': 'FCFA', - 'XBT': 'Ƀ', - 'XCD': '$', - 'XOF': 'CFA', - 'XPF': '₣', - 'YER': '﷼', - 'ZAR': 'R', - 'ZWD': 'Z$' - }; - - root.getCurrencySymbol = function(code) { - if (!code) return false; - - code = code.toUpperCase(); - - if (root.currencySymbols[code]) { - return root.currencySymbols[code]; - } - $log.debug("Currency symbol for "+code+" not found"); - return false; - }; - - return root; - -}); \ No newline at end of file diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index e29cd5c51..f9cf662cb 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -34,7 +34,7 @@ on-hold="hideToggle()" ng-style="{'transform': amountScale}" ng-class="{amount__balance: amountIsCollapsible}"> - {{currencySymbol(status.alternativeIsoCode)}} {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}} + {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}}
- {{currencySymbol(status.alternativeIsoCode)}} {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}} + {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}}
@@ -86,7 +86,7 @@   - {{currencySymbol(status.alternativeIsoCode)}} {{status.spendableBalanceAlternative}} {{status.alternativeIsoCode}} + {{status.spendableBalanceAlternative}} {{status.alternativeIsoCode}}
@@ -148,7 +148,7 @@ class="size-14 amount-alternative" ng-if="status.totalBalanceAlternative && wallet.network == 'livenet'" ng-style="{opacity: altAmountOpacity}"> - {{currencySymbol(status.alternativeIsoCode)}} {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}} + {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}}
@@ -158,7 +158,7 @@ on-hold="hideToggle()" ng-style="{'transform': amountScale}" ng-class="{amount__balance: amountIsCollapsible}"> - {{currencySymbol(status.alternativeIsoCode)}} {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}} + {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}}
  - {{currencySymbol(status.alternativeIsoCode)}} {{status.spendableBalanceAlternative}} {{status.alternativeIsoCode}} + {{status.spendableBalanceAlternative}} {{status.alternativeIsoCode}}
@@ -205,7 +205,7 @@
From 0fdd478ae4969e8258e53f70c6d7810dcfd3e0bc Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 31 Jul 2018 12:47:03 +1200 Subject: [PATCH 075/240] Fixed up bold fonts. --- src/sass/views/amountNew.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sass/views/amountNew.scss b/src/sass/views/amountNew.scss index 0e1d183b6..c0ce94c7a 100644 --- a/src/sass/views/amountNew.scss +++ b/src/sass/views/amountNew.scss @@ -273,7 +273,7 @@ .primary-amount { color: #333; - font-family: 'ProximaNova-Semibold'; + font-weight: bold; input, .unit, .primary-amount-display { font-size: 1.8em; @@ -537,11 +537,12 @@ .button-primary { background-color: $v-primary-color; border-radius: 0; - font-family: 'ProximaNova-Semibold'; + font-weight: bold; } .button-primary[disabled] { background-color: $v-button-primary-disabled-bg; + opacity: 1; } } From 71a31ce39954c1ec1d245f612ec0420196a5a649 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 31 Jul 2018 13:25:56 +1200 Subject: [PATCH 076/240] Available funds now accurately reflects the primary currency choice. --- src/js/controllers/amount.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 3cf93b4c9..070dc5e9b 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -671,21 +671,21 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i }; function updateAvailableFundsStringIfNeeded() { - console.log('updateAvailableFundsStringIfNeeded()'); if (vm.fromWalletId && availableSatoshis !== null) { - console.log('updating'); availableFundsInFiat = ''; vm.availableFunds = availableFundsInCrypto; var coin = availableUnits[altUnitIndex].isFiat ? availableUnits[unitIndex].id : availableUnits[altUnitIndex].id; txFormatService.formatAlternativeStr(coin, availableSatoshis, function formatCallback(formatted){ - console.log('txFormatService returned'); if (formatted) { availableFundsInFiat = formatted; - if (availableUnits[unitIndex].isFiat) { - vm.availableFunds = availableFundsInFiat; - } else { - vm.availableFunds = availableFundsInCrypto; - } + + $scope.$apply(function() { + if (availableUnits[unitIndex].isFiat) { + vm.availableFunds = availableFundsInFiat; + } else { + vm.availableFunds = availableFundsInCrypto; + } + }); } }); } From 2353109adfbacad28f9ab8f1e0c1e158dd4b75e4 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 31 Jul 2018 17:59:26 +1200 Subject: [PATCH 077/240] Bugfix for font for insufficient funds warning. --- src/sass/views/amountNew.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sass/views/amountNew.scss b/src/sass/views/amountNew.scss index c0ce94c7a..8c8e771f7 100644 --- a/src/sass/views/amountNew.scss +++ b/src/sass/views/amountNew.scss @@ -249,7 +249,7 @@ min-height: 20px; .warning { - font-family: 'ProximaNova-Semibold'; + font-weight: bold; font-size: 12px; padding: 0 6px 6px 6px; text-align: center; From 778cbfbab0a9877be520b05226f093a8edf12a66 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 31 Jul 2018 19:16:11 +1200 Subject: [PATCH 078/240] Should have been part of merge for amount directive. --- src/sass/main.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sass/main.scss b/src/sass/main.scss index 7b3e46291..516656449 100644 --- a/src/sass/main.scss +++ b/src/sass/main.scss @@ -9,4 +9,5 @@ @import "mixins/mixins"; @import "views/views"; @import "directives/directives"; +@import "components/components"; @import "shame"; From 02f7bcc2812f323fcf39363328e8f77c2d7a4087 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 31 Jul 2018 20:18:27 +1200 Subject: [PATCH 079/240] Not using amount directive. Removed safety area below All Available Funds button. --- src/js/controllers/amount.js | 49 ++++++++++++++++++----------------- src/sass/views/amountNew.scss | 1 - www/views/amountNew.html | 4 +-- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 070dc5e9b..c50a949a0 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -9,13 +9,14 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i vm.altCurrencyList = []; vm.alternativeAmount = ''; vm.alternativeUnit = ''; - vm.amountModel = { amount: 0 }; + vm.amount = '0'; vm.availableFunds = ''; vm.fromWalletId = ''; // Use insufficient for logic, as when the amount is invalid, funds being // either sufficent or insufficient doesn't make sense. vm.fundsAreInsufficient = false; vm.globalResult = ''; + vm.hello = 'hi'; vm.isRequestingSpecificAmount = false; vm.listComplete = false; vm.lastUsedPopularList = []; @@ -77,9 +78,9 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i } function onBeforeEnter(event, data) { - + initCurrencies(); - + vm.hello = 'greetings'; if (data.stateParams.shapeshiftOrderId && data.stateParams.shapeshiftOrderId.length > 0) { vm.minShapeshiftAmount = parseFloat(data.stateParams.minShapeshiftAmount); vm.maxShapeshiftAmount = parseFloat(data.stateParams.maxShapeshiftAmount); @@ -159,7 +160,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i // in SAT ALWAYS if ($stateParams.toAmount) { - vm.amountModel.amount = (($stateParams.toAmount) * satToUnit).toFixed(unitDecimals); + vm.amount = (($stateParams.toAmount) * satToUnit).toFixed(unitDecimals); } processAmount(); @@ -271,7 +272,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i } function paste(value) { - vm.amountModel.amount = value; + vm.amount = value; processAmount(); $timeout(function() { $scope.$apply(); @@ -299,7 +300,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i function changeUnit() { - vm.amountModel.amount = '0'; + vm.amount = '0'; if (fixedUnit) return; @@ -321,32 +322,32 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i }; function pushDigit(digit) { - if (vm.amountModel.amount && digit != '.') { - var amountSplitByComma = vm.amountModel.amount.split('.'); + if (vm.amount && digit != '.') { + var amountSplitByComma = vm.amount.split('.'); if (amountSplitByComma.length > 1 && amountSplitByComma[1].length >= LENGTH_AFTER_COMMA_EXPRESSION_LIMIT) return; if (amountSplitByComma.length == 1 && amountSplitByComma[0].length >= LENGTH_BEFORE_COMMA_EXPRESSION_LIMIT) return; } - if (vm.amountModel.amount && vm.amountModel.amount.length >= LENGTH_EXPRESSION_LIMIT) return; - if (vm.amountModel.amount.indexOf('.') > -1 && digit == '.') return; - if (vm.amountModel.amount == '0' && digit == '0') return; - if (availableUnits[unitIndex].isFiat && vm.amountModel.amount.indexOf('.') > -1 && vm.amountModel.amount[vm.amountModel.amount.indexOf('.') + 2]) return; + if (vm.amount && vm.amount.length >= LENGTH_EXPRESSION_LIMIT) return; + if (vm.amount.indexOf('.') > -1 && digit == '.') return; + if (vm.amount == '0' && digit == '0') return; + if (availableUnits[unitIndex].isFiat && vm.amount.indexOf('.') > -1 && vm.amount[vm.amount.indexOf('.') + 2]) return; - if (vm.amountModel.amount == '0' && digit != '.') { - vm.amountModel.amount = ''; + if (vm.amount == '0' && digit != '.') { + vm.amount = ''; } - if (vm.amountModel.amount == '' && digit == '.') { - vm.amountModel.amount = '0'; + if (vm.amount == '' && digit == '.') { + vm.amount = '0'; } - vm.amountModel.amount = (vm.amountModel.amount + digit).replace('..', '.'); + vm.amount = (vm.amount + digit).replace('..', '.'); processAmount(); }; function pushOperator(operator) { - if (!vm.amountModel.amount || vm.amountModel.amount.length == 0) return; - vm.amountModel.amount = pushOperator(vm.amountModel.amount); + if (!vm.amount || vm.amount.length == 0) return; + vm.amount = pushOperator(vm.amount); function pushOperator(val) { if (!isOperator(lodash.last(val))) { @@ -368,12 +369,12 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i }; function removeDigit() { - vm.amountModel.amount = (vm.amountModel.amount).toString().slice(0, -1); + vm.amount = (vm.amount).toString().slice(0, -1); processAmount(); } function resetAmount() { - vm.amountModel.amount = vm.alternativeAmount = vm.globalResult = ''; + vm.amount = vm.alternativeAmount = vm.globalResult = '0'; vm.allowSend = false; } @@ -393,11 +394,11 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i }; function processAmount() { - var formatedValue = format(vm.amountModel.amount); + var formatedValue = format(vm.amount); var result = evaluate(formatedValue); if (lodash.isNumber(result)) { - vm.globalResult = isExpression(vm.amountModel.amount) ? '= ' + processResult(result) : ''; + vm.globalResult = isExpression(vm.amount) ? '= ' + processResult(result) : ''; if (availableUnits[unitIndex].isFiat) { @@ -481,7 +482,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i function finish() { var unit = availableUnits[unitIndex]; - var _amount = evaluate(format(vm.amountModel.amount)); + var _amount = evaluate(format(vm.amount)); var coin = unit.id; if (unit.isFiat) { coin = availableUnits[altUnitIndex].id; diff --git a/src/sass/views/amountNew.scss b/src/sass/views/amountNew.scss index 8c8e771f7..24338fe99 100644 --- a/src/sass/views/amountNew.scss +++ b/src/sass/views/amountNew.scss @@ -462,7 +462,6 @@ border-radius: 0; font-size: 0.8em; line-height: 2em; - margin-bottom: 1.618em; width: 100%; .available-funds-amount { diff --git a/www/views/amountNew.html b/www/views/amountNew.html index b67757f5e..e0296219a 100644 --- a/www/views/amountNew.html +++ b/www/views/amountNew.html @@ -19,8 +19,8 @@
- {{ vm.amountModel.amount || 0 }}{{vm.unit}} + ng-class="{long: vm.amount.length > 5, 'very-long': vm.amount.length > 10}"> + {{vm.amount}} {{vm.unit}}
{{vm.globalResult}} {{vm.unit}}
From 0cba978d6539574e9a75098b6a94b2c596e36462 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 31 Jul 2018 20:25:21 +1200 Subject: [PATCH 080/240] Replaced old amount screen. --- src/js/routes.js | 4 +- src/sass/views/amount.scss | 158 +++++++--- src/sass/views/amountNew.scss | 557 ---------------------------------- src/sass/views/views.scss | 1 - www/views/amount.html | 63 ++-- www/views/amountNew.html | 102 ------- 6 files changed, 158 insertions(+), 727 deletions(-) delete mode 100644 src/sass/views/amountNew.scss delete mode 100644 www/views/amountNew.html diff --git a/src/js/routes.js b/src/js/routes.js index 5b0b59d77..286b27ab1 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -292,7 +292,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr 'tab-send@tabs': { controller: 'amountController', controllerAs: 'vm', - templateUrl: 'views/amountNew.html' + templateUrl: 'views/amount.html' } } }) @@ -701,7 +701,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr 'tab-receive@tabs': { controller: 'amountController', controllerAs: 'vm', - templateUrl: 'views/amountNew.html' + templateUrl: 'views/amount.html' } } }) diff --git a/src/sass/views/amount.scss b/src/sass/views/amount.scss index c712d85e5..daf6cf4fe 100644 --- a/src/sass/views/amount.scss +++ b/src/sass/views/amount.scss @@ -244,6 +244,18 @@ flex-direction: column; justify-content: center; + .send-amount-header-footer { + flex: 1 1 auto; + min-height: 20px; + + .warning { + font-weight: bold; + font-size: 12px; + padding: 0 6px 6px 6px; + text-align: center; + } + } + .send-amount-tool { flex: 0 1 auto; @@ -260,6 +272,8 @@ } .primary-amount { + color: #333; + font-weight: bold; input, .unit, .primary-amount-display { font-size: 1.8em; @@ -329,16 +343,15 @@ line-height: 1em; } - .unit { - font-weight: bold; - } - .primary-amount-display { margin-right: 5px; word-break: break-all; } } + .alternative-amount { + color: #6F6F70; + } .switch-currencies { position: absolute; right: 0; @@ -351,27 +364,56 @@ } } } + } + } - .send-amount-actions { - margin-top: 15px; + .send-amount-extras { + display: flex; + flex: 0 0 auto; + /* So that if only one item is present, it appears on the right. */ + flex-direction: row-reverse; + font-size: 12px; + align-items: center; + justify-content: space-between; + margin: 0 14px; + + .available-funds { + color: #6F6F70; + } + + .warning { + color: $v-warning-color-2; + } + + .extra, + button.extra { + /*display: flex;*/ + flex: 0 1 auto; + } + + button.extra { + background: none; + border: none; + color: #000; + font-family: 'ProximaNova'; + font-size: 14px; + line-height: normal; + min-height: auto; + min-width: auto; + padding: 0; + } + + .button .icon:before { + font-size: 14px; + line-height: normal; + } + + + .button { + span { display: flex; align-items: center; justify-content: center; - - .button { - flex: 1 1 auto; - line-height: 1.2em; - - + .button { - margin-left: 10px; - } - - span { - display: flex; - align-items: center; - justify-content: center; - } - } } } } @@ -394,37 +436,58 @@ .keypad-container { position: relative; + font-size: 18px; + line-height: 2em; //flex: 0 1 196px; + @media (min-height: 667px) { + font-size: 24px; + } + + @media(max-height: 480px) { + font-size: 12px; + } + @media (min-height: 667px) { //flex: 0 1 224px; } + .sendmax { + background: $v-off-black; + + .button { + color: white; + background: black; + border: 1px solid $v-off-black; + border-radius: 0; + font-size: 0.8em; + line-height: 2em; + width: 100%; + + .available-funds-amount { + color: #C9C9C9; + } + + &:active { + background-color: $v-dark-gray; + } + } + } + .keypad { text-align: center; - font-size: 18px; font-weight: lighter; position: absolute; bottom: 0; width: 100%; - color: $v-mid-gray; + color: $v-text-primary-color; + - @media (min-height: 667px) { - font-size: 24px; - } .row { padding: 0 !important; margin: 0 !important; } - - .col { - line-height: 38px; - - @media (min-height: 667px) { - line-height: 45px; - } - } .row { &:last-child { @@ -458,23 +521,34 @@ .digit{ cursor: pointer; - border-top: 1px solid $v-subtle-gray; - border-left: 1px solid $v-subtle-gray; + background-color: #000; + border: 1px solid $v-off-black; transition: all 0.1s ease; &:active { - background-color: $v-subtle-gray; + background-color: $v-dark-gray; } } - @media(max-height: 480px) { - font-size: 12px; - - } } } + + .button-primary { + background-color: $v-primary-color; + border-radius: 0; + font-weight: bold; + } + + .button-primary[disabled] { + background-color: $v-button-primary-disabled-bg; + opacity: 1; + } } - background: #494949; + + .warning { + color: $v-warning-color-2; + } + background: $v-background-under-card; ion-content { margin-bottom: constant(safe-area-inset-bottom); /* iOS 11.0 */ diff --git a/src/sass/views/amountNew.scss b/src/sass/views/amountNew.scss deleted file mode 100644 index 24338fe99..000000000 --- a/src/sass/views/amountNew.scss +++ /dev/null @@ -1,557 +0,0 @@ -#view-amount-new { - @extend .deflash-blue; - .recipient-label { - font-size: 14px; - padding-bottom: 0; - color: $v-mid-gray; - } - .item-no-bottom-border + .item { - border-top: 0; - } - .icon-bitpay-card { - background-image: url("../img/icon-bitpay.svg"); - } - .icon-amazon { - background-image: url("../img/icon-amazon.svg"); - } - @media(max-width: 480px) { - .bitcoin-address { - .icon { - left: 8px; - font-size: 24px; - } - .big-icon-svg { - left:5px; - & > .bg{ - width:30px; - height:30px; - box-shadow: none; - } - } - font-size: 13px; - padding-left: 48px; - } - } - @media(max-width: 320px) { - .bitcoin-address { - & > span:last-child { - margin-left: -2px; - } - } - } - .send-gravatar { - left: 11px; - position: absolute; - top: 10px; - } - .amount span input { - display: inline; - width: 120px; - } - .amount-pane-recipient { - position: absolute; - top: 95px; - bottom: 0; - width: 100%; - background-color: #fff; - padding: 0 16px; - - .amount-bar { - padding: 24px 0; - font-size: 18px; - @media(max-height: 480px) { - padding: 0px; - } - @media(max-width: 320px) { - padding: 0px; - } - .title { - float: left; - padding-top: 10px; - color: $v-dark-gray; - font-weight: bold; - @media(max-height: 480px) { - padding: 0px; - } - } - @media(max-height: 480px) { - padding-top: 3px; - } - } - .amount { - display: flex; - flex-direction: column; - justify-content: center; - flex-grow: 1; - position: absolute; - bottom: 254px; - top: 66px; - .light { - color: $v-light-gray; - } - @media(max-height: 480px) { - top: 45px; - } - @media(max-width: 320px) { - bottom: 276px; - top: 60px; - & > div { - display: inline-block; - } - & > div:first-child { - display: inherit; - } - } - } - } - .amount-pane-no-recipient { - position: absolute; - top: 0; - bottom: 0; - width: 100%; - background-color: #fff; - padding: 0 16px; - - .amount-bar { - padding: 24px 0; - font-size: 18px; - .title { - padding-top: 10px; - color: $v-dark-gray; - font-weight: bold; - .limits { - margin-top: 10px; - color: $v-light-gray; - font-size: 12px; - } - .select { - margin: 10px 1px; - } - } - } - .amount { - display: flex; - flex-direction: column; - justify-content: center; - flex-grow: 1; - position: absolute; - bottom: 254px; - top: 66px; - .light { - color: $v-light-gray; - } - } - } - .amount { - padding-top: 10px; - padding-bottom: 10px; - .icon-toggle { - font-size: 1.2em; - width: auto; - margin: 0.8em auto; - border: 1px solid $v-subtle-gray; - color: $v-dark-gray; - border-radius: 3px; - padding: 0 10px; - cursor: pointer; - @media(max-height: 280px) { - margin: 0.1em auto; - } - } - &__editable { - &--minimize { - font-size: 22px; - } - &--standard { - font-size: 42px; - @media(max-height: 480px) { - font-size: 26px; - padding-top: 10px; - } - } - &--placeholder { - color: $v-light-gray; - } - } - &__number { - color: $v-dark-gray; - } - &__currency-toggle { - border: 1px solid $v-subtle-gray; - color: $v-dark-gray; - border-radius: 3px; - padding: 0 10px; - cursor: pointer; - font-size: .6em; - position: relative; - top: -3px; - line-height: 1; - @media(max-width: 320px) { - line-height: 30px; - height: 30px; - } - } - &__currency-toggle-mobile { - border: 1px solid $v-subtle-gray; - color: $v-dark-gray; - border-radius: 3px; - cursor: pointer; - position: relative; - line-height: 1; - @media(max-width: 320px) { - line-height: 30px; - height: 30px; - } - } - &__results { - &--minimize { - font-size: 12px; - } - &--standard { - font-size: 18px; - padding: 10px 0; - } - &--placeholder { - color: $v-light-gray; - } - } - &__result { - color: $v-light-gray; - font-size: .9em; - //margin-bottom: -.9em; TODO matias - line-height: 1; - @media(max-height: 480px) { - margin-bottom: 0; - } - } - &__result-equiv { - color: $v-mid-gray; - font-size: 1.2em; - @media(max-height: 480px) { - margin-top: 0; - font-size: 16px; - } - } - } - - .scroll-content { - display: flex; - flex-direction: column; - - .send-amount { - flex: 1 1 auto; - display: flex; - flex-direction: column; - justify-content: center; - - .send-amount-header-footer { - flex: 1 1 auto; - min-height: 20px; - - .warning { - font-weight: bold; - font-size: 12px; - padding: 0 6px 6px 6px; - text-align: center; - } - } - - .send-amount-tool { - flex: 0 1 auto; - - .send-amount-tool-input { - text-align: center; - position: relative; - padding: 10px 30px; - - .text-selectable { - -webkit-user-select: text; - -moz-user-select: text; - -ms-user-select: text; - user-select: text; - } - - .primary-amount { - color: #333; - font-weight: bold; - input, .unit, .primary-amount-display { - font-size: 1.8em; - - @media (min-width: 375px) { - font-size: 2.1em; - } - - @media (min-width: 414px) { - font-size: 2.4em; - } - } - - &.long { - input, .unit, .primary-amount-display { - font-size: 1.6em; - - @media (min-width: 375px) { - font-size: 1.8em; - } - - @media (min-width: 414px) { - font-size: 2em; - } - } - } - - &.very-long { - input, .unit, .primary-amount-display { - font-size: 0.9em; - - @media (min-width: 375px) { - font-size: 1.3em; - } - - @media (min-width: 414px) { - font-size: 1.4em; - } - } - } - - - input { - border:0; - padding:0; - white-space:normal; - background:none; - line-height:1; - box-sizing:content-box; - display: inline-block; - vertical-align: middle; - margin: 0; - height: 1em; - margin-right: 5px; - font-family: 'ProximaNova'; - - @media (min-width: 375px) { - } - - @media (min-width: 414px) { - } - } - - .unit, - .primary-amount-display { - display: inline-block; - vertical-align: middle; - line-height: 1em; - } - - .primary-amount-display { - margin-right: 5px; - word-break: break-all; - } - } - - .alternative-amount { - color: #6F6F70; - } - .switch-currencies { - position: absolute; - right: 0; - top: 50%; - transform: translate(0, -50%); - padding: 15px; - - img { - width: 18px; - } - } - } - } - } - - .send-amount-extras { - display: flex; - flex: 0 0 auto; - /* So that if only one item is present, it appears on the right. */ - flex-direction: row-reverse; - font-size: 12px; - align-items: center; - justify-content: space-between; - margin: 0 14px; - - .available-funds { - color: #6F6F70; - } - - .warning { - color: $v-warning-color-2; - } - - .extra, - button.extra { - /*display: flex;*/ - flex: 0 1 auto; - } - - button.extra { - background: none; - border: none; - color: #000; - font-family: 'ProximaNova'; - font-size: 14px; - line-height: normal; - min-height: auto; - min-width: auto; - padding: 0; - } - - .button .icon:before { - font-size: 14px; - line-height: normal; - } - - - .button { - span { - display: flex; - align-items: center; - justify-content: center; - } - } - } - - .button { - &.no-margin { - margin: 0; - } - } - - .notification-warning { - display: block; - padding: .75rem 1.25rem; - color: #856404; - background-color: #fff3cd; - border: 1px solid #ffeeba; - line-height: 1.4em; - margin-bottom: 20px; - } - - .keypad-container { - position: relative; - font-size: 18px; - line-height: 2em; - //flex: 0 1 196px; - - @media (min-height: 667px) { - font-size: 24px; - } - - @media(max-height: 480px) { - font-size: 12px; - } - - @media (min-height: 667px) { - //flex: 0 1 224px; - } - - .sendmax { - background: $v-off-black; - - .button { - color: white; - background: black; - border: 1px solid $v-off-black; - border-radius: 0; - font-size: 0.8em; - line-height: 2em; - width: 100%; - - .available-funds-amount { - color: #C9C9C9; - } - - &:active { - background-color: $v-dark-gray; - } - } - } - - .keypad { - text-align: center; - font-weight: lighter; - position: absolute; - bottom: 0; - width: 100%; - color: $v-text-primary-color; - - - - .row { - padding: 0 !important; - margin: 0 !important; - } - - .row { - &:last-child { - .col { - padding-bottom: 10px; - } - } - } - - .operator { - background-color: $v-subtle-gray; - font-weight: normal; - cursor: pointer; - - &:active { - background-color: $v-light-gray; - } - } - - .operator-send { - font-weight: bolder; - color: #fff; - background-color: $positive; - font-size: 36px; - cursor: pointer; - - &:active { - background-color: #eaeaea; - } - } - - .digit{ - cursor: pointer; - background-color: #000; - border: 1px solid $v-off-black; - transition: all 0.1s ease; - - &:active { - background-color: $v-dark-gray; - } - } - - } - } - - .button-primary { - background-color: $v-primary-color; - border-radius: 0; - font-weight: bold; - } - - .button-primary[disabled] { - background-color: $v-button-primary-disabled-bg; - opacity: 1; - } - } - - .warning { - color: $v-warning-color-2; - } - background: $v-background-under-card; - - ion-content { - margin-bottom: constant(safe-area-inset-bottom); /* iOS 11.0 */ - margin-bottom: env(safe-area-inset-bottom); /* iOS 11.2 */ - } -} \ No newline at end of file diff --git a/src/sass/views/views.scss b/src/sass/views/views.scss index 387b68597..d4ed735ed 100644 --- a/src/sass/views/views.scss +++ b/src/sass/views/views.scss @@ -1,7 +1,6 @@ @import "tabs"; @import "add"; @import "amount"; -@import "amountNew"; @import "confirm"; @import "copayers"; @import "starting"; diff --git a/www/views/amount.html b/www/views/amount.html index 90187ef59..77c52f96c 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -1,24 +1,26 @@ - {{'Enter amount' | translate}} + {{'Enter Amount' | translate}} - +
-
-
- Minimum amount: {{vm.minShapeshiftAmount}}
- Maximum amount: {{vm.maxShapeshiftAmount}}
+
+
- {{ vm.amountModel.amount || 0 }}{{vm.unit}} + ng-class="{long: vm.amount.length > 5, 'very-long': vm.amount.length > 10}"> + {{vm.amount}} {{vm.unit}}
{{vm.globalResult}} {{vm.unit}}
@@ -26,26 +28,41 @@
-
- - + -
+
+
+ +
+ +
+ Available Funds:{{vm.availableFunds}} +
-
+
+ +
+ +
7
diff --git a/www/views/amountNew.html b/www/views/amountNew.html deleted file mode 100644 index e0296219a..000000000 --- a/www/views/amountNew.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - {{'Enter Amount' | translate}} - - - - - -
- -
- -
-
-
- {{vm.amount}} {{vm.unit}} -
- {{vm.globalResult}} {{vm.unit}} -
- {{vm.alternativeAmount || '0.00'}} {{vm.alternativeUnit}} -
-
-
- -
-
- -
- -
- Available Funds:{{vm.availableFunds}} -
-
-
- -
-
- -
- -
- -
-
7
-
8
-
9
-
- -
-
4
-
5
-
6
-
- -
-
1
-
2
-
3
-
- -
-
.
-
0
-
-
-
-
- - -
-
\ No newline at end of file From 281b969fc325f97e7ed287086960338fbc1ada07 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Tue, 31 Jul 2018 15:03:20 +0200 Subject: [PATCH 081/240] Shapeshift refactor / Empty case / CSS --- i18n/po/template.pot | 4 + src/js/controllers/shapeshift.js | 102 +++++---- ...troller.js => walletSelectorController.js} | 2 +- src/js/routes.js | 6 +- src/sass/mixins/layout.scss | 39 ++++ src/sass/views/shapeshift.scss | 14 ++ src/sass/views/tab-send.scss | 37 +--- src/sass/views/views.scss | 1 + www/views/shapeshift.html | 206 ++++++++++-------- 9 files changed, 230 insertions(+), 181 deletions(-) rename src/js/controllers/{sendFlowController.js => walletSelectorController.js} (94%) create mode 100644 src/sass/views/shapeshift.scss diff --git a/i18n/po/template.pot b/i18n/po/template.pot index df01cecec..10656ae42 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -3112,6 +3112,10 @@ msgstr "" msgid "Top up {{amountStr}} to debit card ({{cardLastNumber}})" msgstr "" +#: www/views/shapeshift.html:30 +msgid "Start ShapeShift" +msgstr "" + #: www/views/buyAmazon.html:61 #: www/views/buyMercadoLibre.html:60 #: www/views/modals/wallet-balance.html:23 diff --git a/src/js/controllers/shapeshift.js b/src/js/controllers/shapeshift.js index 5bc815d19..8e991c5b4 100644 --- a/src/js/controllers/shapeshift.js +++ b/src/js/controllers/shapeshift.js @@ -1,10 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('shapeshiftController', function($scope, $interval, profileService, walletService, popupService, lodash, $ionicNavBarDelegate) { - var vm = this; - - //vm.buyBitcion = buyBitcoin; - +angular.module('copayApp.controllers').controller('shapeshiftController', function($scope, $state, $interval, profileService, walletService, popupService, lodash, $ionicNavBarDelegate) { var walletsBtc = []; var walletsBch = []; @@ -19,59 +15,51 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi } function showToWallets() { - $scope.toWallets = $scope.fromWallet.coin == 'btc' ? walletsBch : walletsBtc; + $scope.toWallets = $scope.fromWallet.coin === 'btc' ? walletsBch : walletsBtc; $scope.onToWalletSelect($scope.toWallets[0]); - $scope.singleToWallet = $scope.toWallets.length == 1; + $scope.singleToWallet = $scope.toWallets.length === 1; } - function buyBitcoin() { - console.log('buyBitcoin()'); - } - - $scope.buyBitcoin = buyBitcoin; - - $scope.onFromWalletSelect = function(wallet) { - $scope.fromWallet = wallet; - showToWallets(); - generateAddress(wallet, function(addr) { - $scope.fromWalletAddress = addr; - }); - }; - - $scope.onToWalletSelect = function(wallet) { - $scope.toWallet = wallet; - generateAddress(wallet, function(addr) { - $scope.toWalletAddress = addr; - }); - } + // $scope.onFromWalletSelect = function(wallet) { + // $scope.fromWallet = wallet; + // showToWallets(); + // generateAddress(wallet, function(addr) { + // $scope.fromWalletAddress = addr; + // }); + // }; + // + // $scope.onToWalletSelect = function(wallet) { + // $scope.toWallet = wallet; + // generateAddress(wallet, function(addr) { + // $scope.toWalletAddress = addr; + // }); + // }; $scope.$on("$ionicView.beforeEnter", function(event, data) { - console.log('beforeEnter()'); walletsBtc = profileService.getWallets({coin: 'btc'}); walletsBch = profileService.getWallets({coin: 'bch'}); $scope.fromWallets = lodash.filter(walletsBtc.concat(walletsBch), function(w) { return w.status.balance.availableAmount > 0; }); - console.log('Checking wallets.'); - if ($scope.fromWallets.length == 0) { - // Need to go to new origin screen here, with parameters - var params = { - thirdParty: { - id: 'shapeshift' - } - }; - console.log('Asking for transition'); - $state.transitionTo('tabs.send', params); - return - } - $scope.onFromWalletSelect($scope.fromWallets[0]); - $scope.onToWalletSelect($scope.toWallets[0]); - $scope.singleFromWallet = $scope.fromWallets.length == 1; - $scope.singleToWallet = $scope.toWallets.length == 1; + + if ($scope.fromWallets.length === 0) { + // return + // } else { + // $scope.onFromWalletSelect($scope.fromWallets[0]); + } + + // $scope.onToWalletSelect($scope.toWallets[0]); + + $scope.singleFromWallet = $scope.fromWallets.length === 1; + // $scope.singleToWallet = $scope.toWallets.length == 1; $scope.fromWalletSelectorTitle = 'From'; $scope.toWalletSelectorTitle = 'To'; $scope.showFromWallets = false; $scope.showToWallets = false; + $scope.walletsWithFunds = profileService.getWallets({onlyComplete: true, hasFunds: true}); + console.log($scope.walletsWithFunds); + $scope.wallets = profileService.getWallets({onlyComplete: true}); + $scope.hasWallets = !lodash.isEmpty($scope.wallets); }); $scope.$on("$ionicView.enter", function(event, data) { @@ -80,9 +68,33 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi $scope.showFromWalletSelector = function() { $scope.showFromWallets = true; - } + }; $scope.showToWalletSelector = function() { $scope.showToWallets = true; + }; + + // This could probably be enhanced refactoring the routes abstract states + $scope.createWallet = function() { + $state.go('tabs.home').then(function() { + $state.go('tabs.add.create-personal'); + }); + }; + + $scope.buyBitcoin = function() { + $state.go('tabs.home').then(function() { + $state.go('tabs.buyandsell'); + }); + }; + + $scope.shapeshift = function() { + var params = { + thirdParty: { + id: 'shapeshift' + } + }; + $state.go('tabs.home').then(function() { + $state.transitionTo('tabs.send', params); + }); } }); diff --git a/src/js/controllers/sendFlowController.js b/src/js/controllers/walletSelectorController.js similarity index 94% rename from src/js/controllers/sendFlowController.js rename to src/js/controllers/walletSelectorController.js index a6965bd96..7f5519452 100644 --- a/src/js/controllers/sendFlowController.js +++ b/src/js/controllers/walletSelectorController.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('sendFlowController', function($scope, $rootScope, $state, $stateParams, $log, $ionicHistory, configService, gettextCatalog, profileService) { +angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $rootScope, $state, $stateParams, $log, $ionicHistory, configService, gettextCatalog, profileService) { $scope.$on("$ionicView.beforeEnter", function(event, data) { var config = configService.getSync().wallet.settings; diff --git a/src/js/routes.js b/src/js/routes.js index 0fa92750e..f7641b545 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -299,7 +299,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr url: '/wallet-to-wallet', views: { 'tab-send@tabs': { - controller: 'sendFlowController', + controller: 'walletSelectorController', templateUrl: 'views/wallet-origin-destination.html' } } @@ -308,7 +308,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr url: '/origin/:thirdParty/:amount/:toAddress/:toWalletId/:coin', views: { 'tab-send@tabs': { - controller: 'sendFlowController', + controller: 'walletSelectorController', templateUrl: 'views/wallet-origin-destination.html', } } @@ -317,7 +317,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr url: '/destination/:thirdParty/:amount/:fromWalletId', views: { 'tab-send@tabs': { - controller: 'sendFlowController', + controller: 'walletSelectorController', templateUrl: 'views/wallet-origin-destination.html', } } diff --git a/src/sass/mixins/layout.scss b/src/sass/mixins/layout.scss index b03d53800..e03a735fa 100644 --- a/src/sass/mixins/layout.scss +++ b/src/sass/mixins/layout.scss @@ -18,3 +18,42 @@ .absolute-center{ @include absolute-center(); } + +@mixin empty-case() { + padding-top: 5vh; + text-align: center; + .item { + border-style: none; + } + & > .title { + font-size: 20px; + color: $v-dark-gray; + margin: 20px 10px; + } + & > .subtitle { + font-size: 1rem; + line-height: 1.5em; + font-weight: 300; + color: #6F6F70; + margin: 20px 1em 2.5em; + } + .big-icon-svg { + .bg.green { + padding: 0 10px; + box-shadow: none; + } + } + .buttons { + margin-top: 18px; + .button { + font-weight: bold; + font-size: 19px; + } + } + .button-first-contact img { + height: 19px; + width: 19px; + margin-right: 6px; + vertical-align: sub; + } +} \ No newline at end of file diff --git a/src/sass/views/shapeshift.scss b/src/sass/views/shapeshift.scss new file mode 100644 index 000000000..8b4b941ab --- /dev/null +++ b/src/sass/views/shapeshift.scss @@ -0,0 +1,14 @@ +#shapeshift { + .empty-case { + @include empty-case(); + } + .button-shapeshift { + @extend %button-standard; + + @include button-style(#243F5D, #FFF, #606060, #FFF, #FFF); + @include button-clear(#FFF); + @include button-outline(#C1C1C1); + border: 0px; + @include button-shadow(); + } +} \ No newline at end of file diff --git a/src/sass/views/tab-send.scss b/src/sass/views/tab-send.scss index a4025156f..4fbe8e531 100644 --- a/src/sass/views/tab-send.scss +++ b/src/sass/views/tab-send.scss @@ -133,42 +133,7 @@ padding-left: 30px; } .sendTip { - padding-top: 5vh; - text-align: center; - .item { - border-style: none; - } - & > .title { - font-size: 20px; - color: $v-dark-gray; - margin: 20px 10px; - } - & > .subtitle { - font-size: 1rem; - line-height: 1.5em; - font-weight: 300; - color: #6F6F70; - margin: 20px 1em 2.5em; - } - .big-icon-svg { - .bg.green { - padding: 0 10px; - box-shadow: none; - } - } - .buttons { - margin-top: 18px; - .button { - font-weight: bold; - font-size: 19px; - } - } - .button-first-contact img { - height: 19px; - width: 19px; - margin-right: 6px; - vertical-align: sub; - } + @include empty-case(); } .item-heading { line-height: 16px; diff --git a/src/sass/views/views.scss b/src/sass/views/views.scss index e1a122dbb..700a45b62 100644 --- a/src/sass/views/views.scss +++ b/src/sass/views/views.scss @@ -14,6 +14,7 @@ @import "walletBalance"; @import "walletDetails"; @import "advancedSettings"; +@import "shapeshift"; @import "bitpayCard"; @import "bitpayCardIntro"; @import "buyandsell"; diff --git a/www/views/shapeshift.html b/www/views/shapeshift.html index 48664ebba..6a0a0e4e2 100644 --- a/www/views/shapeshift.html +++ b/www/views/shapeshift.html @@ -1,4 +1,4 @@ - + {{'Shapeshift'|translate}} @@ -10,107 +10,121 @@
-
-
- No available wallets to convert between. - - +
+
+ Your Bitcoin wallet is empty +
+
+
+

Before exchanging your BTC to BCH, you will need to add funds to your wallet.

+

You can receive Bitcoin from any wallet or service.

+
+
+

Using Shapeshift will allow you to exchange your BTC for BCH.

+

The process is fast and you will receive the exchanged amount in your wallet.

+
+
To get started, you'll need to create a bitcoin wallet and get some bitcoin.
+
+ + + +
- -
+ + + + + + + + + + + + + + - - -
- - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + - - - + + + + + + + + + From 42d77903e1dd02fe66d10bba16bc1dd2e433a14f Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Tue, 31 Jul 2018 17:21:56 +0200 Subject: [PATCH 082/240] Third Party Service integration (Shapeshift) (CSS + translations + wallet selector + routes) --- i18n/po/template.pot | 4 + src/js/controllers/shapeshift.js | 31 +--- .../controllers/walletSelectorController.js | 29 +++- src/js/routes.js | 8 +- src/js/services/incomingData.js | 33 ++-- src/sass/mixins/layout.scss | 11 ++ www/views/header-thirdparty.html | 3 + www/views/shapeshift.html | 148 +++++++++--------- ...n-destination.html => walletSelector.html} | 5 +- 9 files changed, 143 insertions(+), 129 deletions(-) create mode 100644 www/views/header-thirdparty.html rename www/views/{wallet-origin-destination.html => walletSelector.html} (90%) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index 10656ae42..4a53f4b7b 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -3116,6 +3116,10 @@ msgstr "" msgid "Start ShapeShift" msgstr "" +#: www/views/shapeshift.html:34 +msgid "This service is provided by the third-party ShapeShift, who will charge a small fee for the service. The fee will be shown before you start the transaction." +msgstr "" + #: www/views/buyAmazon.html:61 #: www/views/buyMercadoLibre.html:60 #: www/views/modals/wallet-balance.html:23 diff --git a/src/js/controllers/shapeshift.js b/src/js/controllers/shapeshift.js index 8e991c5b4..ade7afb5b 100644 --- a/src/js/controllers/shapeshift.js +++ b/src/js/controllers/shapeshift.js @@ -20,21 +20,6 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi $scope.singleToWallet = $scope.toWallets.length === 1; } - // $scope.onFromWalletSelect = function(wallet) { - // $scope.fromWallet = wallet; - // showToWallets(); - // generateAddress(wallet, function(addr) { - // $scope.fromWalletAddress = addr; - // }); - // }; - // - // $scope.onToWalletSelect = function(wallet) { - // $scope.toWallet = wallet; - // generateAddress(wallet, function(addr) { - // $scope.toWalletAddress = addr; - // }); - // }; - $scope.$on("$ionicView.beforeEnter", function(event, data) { walletsBtc = profileService.getWallets({coin: 'btc'}); walletsBch = profileService.getWallets({coin: 'bch'}); @@ -42,22 +27,12 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi return w.status.balance.availableAmount > 0; }); - if ($scope.fromWallets.length === 0) { - // return - // } else { - // $scope.onFromWalletSelect($scope.fromWallets[0]); - } - - // $scope.onToWalletSelect($scope.toWallets[0]); - $scope.singleFromWallet = $scope.fromWallets.length === 1; - // $scope.singleToWallet = $scope.toWallets.length == 1; $scope.fromWalletSelectorTitle = 'From'; $scope.toWalletSelectorTitle = 'To'; $scope.showFromWallets = false; $scope.showToWallets = false; $scope.walletsWithFunds = profileService.getWallets({onlyComplete: true, hasFunds: true}); - console.log($scope.walletsWithFunds); $scope.wallets = profileService.getWallets({onlyComplete: true}); $scope.hasWallets = !lodash.isEmpty($scope.wallets); }); @@ -89,12 +64,10 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi $scope.shapeshift = function() { var params = { - thirdParty: { - id: 'shapeshift' - } + thirdParty: JSON.stringify({id: 'shapeshift'}) }; $state.go('tabs.home').then(function() { - $state.transitionTo('tabs.send', params); + $state.transitionTo('tabs.send.origin', params); }); } }); diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index 7f5519452..4f0533084 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -1,16 +1,16 @@ 'use strict'; -angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $rootScope, $state, $stateParams, $log, $ionicHistory, configService, gettextCatalog, profileService) { +angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $rootScope, $state, $log, $ionicHistory, configService, gettextCatalog, profileService) { $scope.$on("$ionicView.beforeEnter", function(event, data) { var config = configService.getSync().wallet.settings; - $scope.sendFlowTitle = ""; + $scope.sendFlowTitle = ""; if ($state.current.name === 'tabs.send.wallet-to-wallet') { $scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer'); } - $scope.params = $stateParams; + $scope.params = $state.params; $scope.coin = false; // Wallets to show (for destination screen or contacts) $scope.type = data.stateParams && data.stateParams['fromWalletId'] ? 'destination' : 'origin'; // origin || destination @@ -21,14 +21,11 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu if ($scope.params.amount) { // There is an amount, so presume that it a payment request $scope.sendFlowTitle = gettextCatalog.getString('Payment request'); $scope.specificAmount = $scope.specificAlternativeAmount = ''; - $scope.requestAmount = (($stateParams.amount) * (1 / config.unitToSatoshi)).toFixed(config.unitDecimals); + $scope.requestAmount = (($state.params.amount) * (1 / config.unitToSatoshi)).toFixed(config.unitDecimals); $scope.isPaymentRequest = true; } if ($scope.params.thirdParty) { - // Third Party Service - if ($scope.params.thirdParty.id === 'shapeshift') { - - } + $scope.thirdParty = JSON.parse($scope.params.thirdParty); // Parse stringified JSON-object } }); @@ -48,6 +45,19 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to'); } + if ($scope.thirdParty) { + + // Third party services specific logic + + if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the + if ($scope.coin === 'bch') { + $scope.coin = 'btc'; + } else { + $scope.coin = 'bch'; + } + } + } + if (!$scope.coin || $scope.coin === 'bch') { // if no specific coin is set or coin is set to bch $scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: $scope.type==='origin'}); } @@ -57,6 +67,9 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu }); function getNextStep() { + if ($scope.thirdParty) { + $scope.params.thirdParty = JSON.stringify($scope.thirdParty) // re-stringify JSON-object + } if (!$scope.params.toWalletId && !$scope.params.toAddress) { // If we have no toAddress or fromWallet return 'tabs.send.destination'; } else if (!$scope.params.amount) { // If we have no amount diff --git a/src/js/routes.js b/src/js/routes.js index f7641b545..54f481783 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -300,7 +300,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr views: { 'tab-send@tabs': { controller: 'walletSelectorController', - templateUrl: 'views/wallet-origin-destination.html' + templateUrl: 'views/walletSelector.html' } } }) @@ -309,7 +309,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr views: { 'tab-send@tabs': { controller: 'walletSelectorController', - templateUrl: 'views/wallet-origin-destination.html', + templateUrl: 'views/walletSelector.html', } } }) @@ -318,7 +318,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr views: { 'tab-send@tabs': { controller: 'walletSelectorController', - templateUrl: 'views/wallet-origin-destination.html', + templateUrl: 'views/walletSelector.html', } } }) @@ -995,7 +995,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr /* Shapeshift */ .state('tabs.shapeshift', { - url: '/shapeshift', + url: '/shapeshift/:fromWalletId/:toWalletId', views: { 'tab-home@tabs': { controller: 'shapeshiftController', diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index e12cc2255..7a9e67fe7 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -8,7 +8,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat $rootScope.$broadcast('incomingDataMenu.showMenu', data); }; - root.redir = function(data, shapeshiftData) { + root.redir = function(data, serviceId, serviceData) { var originalAddress = null; var noPrefixInAddress = 0; @@ -75,7 +75,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat return true; } - function goSend(addr, amount, message, coin, shapeshiftData) { + function goSend(addr, amount, message, coin, serviceId, serviceData) { $state.go('tabs.send', {}, { 'reload': true, 'notify': $state.current.name == 'tabs.send' ? false : true @@ -97,11 +97,18 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat displayAddress: originalAddress ? originalAddress : addr, noPrefix: noPrefixInAddress }; - if (shapeshiftData) { - params['fromWalletId'] = shapeshiftData.fromWalletId; - params['minShapeshiftAmount'] = shapeshiftData.minAmount; - params['maxShapeshiftAmount'] = shapeshiftData.maxAmount; - params['shapeshiftOrderId'] = shapeshiftData.orderId; + if (serviceId) { + if (!params['thirdParty']) { + params['thirdParty'] = []; + } + params['thirdParty']['id'] = serviceId; + } + + if (serviceData) { + params['thirdParty']['data'] = serviceData; + // params['thirdParty']['minShapeshiftAmount'] = serviceData.minAmount; + // params['thirdParty']['maxShapeshiftAmount'] = serviceData.maxAmount; + // params['thirdParty']['shapeshiftOrderId'] = serviceData.orderId; $state.transitionTo('tabs.send.amount', params); } else { $state.transitionTo('tabs.send.origin', params); @@ -148,12 +155,12 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat if (parsed.r) { payproService.getPayProDetails(parsed.r, coin, function(err, details) { if (err) { - if (addr && amount) goSend(addr, amount, message, coin, shapeshiftData); + if (addr && amount) goSend(addr, amount, message, coin, serviceId, serviceData); else popupService.showAlert(gettextCatalog.getString('Error'), err); } else handlePayPro(details, coin); }); } else { - goSend(addr, amount, message, coin, shapeshiftData); + goSend(addr, amount, message, coin, serviceId, serviceData); } return true; // Cash URI @@ -171,14 +178,14 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat payproService.getPayProDetails(parsed.r, coin, function(err, details) { if (err) { if (addr && amount) - goSend(addr, amount, message, coin, shapeshiftData); + goSend(addr, amount, message, coin, serviceId, serviceData); else popupService.showAlert(gettextCatalog.getString('Error'), err); } handlePayPro(details, coin); }); } else { - goSend(addr, amount, message, coin, shapeshiftData); + goSend(addr, amount, message, coin, serviceId, serviceData); } return true; @@ -214,14 +221,14 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat payproService.getPayProDetails(parsed.r, coin, function(err, details) { if (err) { if (addr && amount) - goSend(addr, amount, message, coin, shapeshiftData); + goSend(addr, amount, message, coin, serviceId, serviceData); else popupService.showAlert(gettextCatalog.getString('Error'), err); } handlePayPro(details, coin); }); } else { - goSend(addr, amount, message, coin, shapeshiftData); + goSend(addr, amount, message, coin, serviceId, serviceData); } } ); diff --git a/src/sass/mixins/layout.scss b/src/sass/mixins/layout.scss index e03a735fa..269a50320 100644 --- a/src/sass/mixins/layout.scss +++ b/src/sass/mixins/layout.scss @@ -19,6 +19,17 @@ @include absolute-center(); } +.third-party-notice { + font-size: 12px; + margin: 0px 14px; + font-weight: 600; + color: #6F6F70; + + @media (min-width: 768px) { + text-align: center; + } +} + @mixin empty-case() { padding-top: 5vh; text-align: center; diff --git a/www/views/header-thirdparty.html b/www/views/header-thirdparty.html new file mode 100644 index 000000000..7a6750a22 --- /dev/null +++ b/www/views/header-thirdparty.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/www/views/shapeshift.html b/www/views/shapeshift.html index 6a0a0e4e2..9f5a98423 100644 --- a/www/views/shapeshift.html +++ b/www/views/shapeshift.html @@ -31,83 +31,85 @@
- - - - - - - - - - - - - +
This service is provided by the third-party ShapeShift, who will charge a small fee for the service. The fee will be shown before you start the transaction.
- - - - - - - - - - + +
+ + + +
+ + + + + + diff --git a/www/views/wallet-origin-destination.html b/www/views/walletSelector.html similarity index 90% rename from www/views/wallet-origin-destination.html rename to www/views/walletSelector.html index 4bdbde22b..a4cc4db81 100644 --- a/www/views/wallet-origin-destination.html +++ b/www/views/walletSelector.html @@ -4,6 +4,7 @@ +
Paying
$... USD
@@ -14,7 +15,7 @@ {{headerTitle}}
-
+
Bitcoin Cash (BCH)
Instant transactions with low fees
@@ -35,7 +36,7 @@ From a952edfe692ee0c82c873544d0a4a7e21a075315 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Tue, 31 Jul 2018 17:40:58 +0200 Subject: [PATCH 083/240] border-radius fix --- src/sass/buttons.scss | 1 + src/sass/views/includes/clickToAccept.scss | 1 + src/sass/views/includes/slideToAccept.scss | 3 +++ 3 files changed, 5 insertions(+) diff --git a/src/sass/buttons.scss b/src/sass/buttons.scss index a8512ae64..e343f8fd2 100644 --- a/src/sass/buttons.scss +++ b/src/sass/buttons.scss @@ -55,6 +55,7 @@ .button { border-radius: 6px; &.button-full { + border-radius: 0; display: block; } &-green { diff --git a/src/sass/views/includes/clickToAccept.scss b/src/sass/views/includes/clickToAccept.scss index a38455ac3..cbc7a2c75 100644 --- a/src/sass/views/includes/clickToAccept.scss +++ b/src/sass/views/includes/clickToAccept.scss @@ -8,6 +8,7 @@ click-to-accept { .click-to-accept { &__button.button.button-primary.button-standard { + border-radius: 0; height: 100%; max-width: 9999px; width: 100%; diff --git a/src/sass/views/includes/slideToAccept.scss b/src/sass/views/includes/slideToAccept.scss index 75502c6a8..24775116d 100644 --- a/src/sass/views/includes/slideToAccept.scss +++ b/src/sass/views/includes/slideToAccept.scss @@ -13,6 +13,9 @@ slide-to-accept { } .slide { + .button { + border-radius: 0; + } &__listener { height: 100%; width: 100%; From 18e00e5fcadbb6243b3c00bf87e57f5f0bb56240 Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Wed, 1 Aug 2018 16:59:13 +0800 Subject: [PATCH 084/240] Renames amount directive to formatted-amount, fixes formatting for crypto --- src/js/directives/amount.js | 92 ------------------ src/js/directives/formattedAmount.js | 96 +++++++++++++++++++ .../{amount.html => formatted-amount.html} | 0 3 files changed, 96 insertions(+), 92 deletions(-) delete mode 100644 src/js/directives/amount.js create mode 100644 src/js/directives/formattedAmount.js rename www/views/includes/{amount.html => formatted-amount.html} (100%) diff --git a/src/js/directives/amount.js b/src/js/directives/amount.js deleted file mode 100644 index 9622ca09d..000000000 --- a/src/js/directives/amount.js +++ /dev/null @@ -1,92 +0,0 @@ -'use strict'; - -/** - * @desc amount directive that can be used to display formatted financial values - * size-equal attribute is optional, defaults to false. - * @example fee = { - * value: 12.49382901, - * currency: 'BCH' - * } - * @example - * @example - */ -angular.module('bitcoincom.directives') - .directive('amount', [ - '$timeout', - function($timeout) { - return { - restrict: 'E', - scope: { - value: '=', - currency: '=', - sizeEqual: '=' - }, - templateUrl: 'views/includes/amount.html', - controller: ['$scope', function($scope) { - $scope.displaySizeEqual = typeof $scope.sizeEqual == 'undefined' ? false : true; - - var decimalPlaces = { - '0': ['BIF', 'CLP', 'DJF', 'GNF', 'ILS', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'], - '3': ['BHD', 'IQD', 'JOD', 'KWD', 'OMR', 'TND'], - '8': ['BCH', 'BTC'] - }; - - var numberWithCommas = function(x) { - return parseFloat(x).toLocaleString(); - }; - - var buildAmount = function(start, middle, end) { - $scope.start = start; - $scope.middle = middle; - $scope.end = end; - }; - - var getDecimalPlaces = function(currency) { - if (decimalPlaces['0'].indexOf($scope.currency.toUpperCase()) > -1) return '0'; - if (decimalPlaces['3'].indexOf($scope.currency.toUpperCase()) > -1) return '3'; - if (decimalPlaces['8'].indexOf($scope.currency.toUpperCase()) > -1) return '8'; - return '2'; - }; - - var formatNumbers = function(currency, value) { - switch (getDecimalPlaces(currency)) { - case '0': - var valueFormatted = numberWithCommas(Math.round(parseFloat(value))); - buildAmount(valueFormatted, '', ''); - break; - - case '2': - var valueProcessing = parseFloat(parseFloat(value).toFixed(2)); - var valueFormatted = numberWithCommas(valueProcessing); - buildAmount(valueFormatted, '', ''); - break; - - case '3': - var valueProcessing = parseFloat(parseFloat(value).toFixed(3)); - var valueFormatted = numberWithCommas(valueProcessing); - buildAmount(valueFormatted, '', ''); - break; - - case '8': - var valueFormatted = parseFloat(value).toFixed(8); - if (parseFloat(value) == 0) { - buildAmount('0', '', ''); - } else { - buildAmount(valueFormatted, '', ''); - var start = numberWithCommas(valueFormatted.slice(0, -5)); - var middle = valueFormatted.slice(-5, -2); - var end = valueFormatted.substr(valueFormatted.length - 2); - buildAmount(start, middle, end); - } - break; - } - } - - formatNumbers($scope.currency, $scope.value); - $scope.$watchGroup(['currency', 'value'], function() { - formatNumbers($scope.currency, $scope.value); - }); - }] - }; - } -]); \ No newline at end of file diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js new file mode 100644 index 000000000..ac74afd2b --- /dev/null +++ b/src/js/directives/formattedAmount.js @@ -0,0 +1,96 @@ +'use strict'; + +/** + * @desc amount directive that can be used to display formatted financial values + * size-equal attribute is optional, defaults to false. + * @example fee = { + * value: 12.49382901, + * currency: 'BCH' + * } + * @example + * @example + */ +angular.module('bitcoincom.directives') + .directive('formattedAmount', function(configService, uxLanguage) { + return { + restrict: 'E', + scope: { + value: '=', + currency: '=', + sizeEqual: '=' + }, + templateUrl: 'views/includes/formatted-amount.html', + controller: function($scope, $timeout) { + $scope.displaySizeEqual = !!$scope.sizeEqual; + + configService.whenAvailable(function(config) { + $timeout(function() { + var decimalPlaces = { + '0': ['BIF', 'CLP', 'DJF', 'GNF', 'ILS', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'], + '3': ['BHD', 'IQD', 'JOD', 'KWD', 'OMR', 'TND'], + '8': ['BCH', 'BTC'] + }; + var localizeNumbers = function(x, minimumFractionDigits = 0, useGrouping = true) { + return parseFloat(x).toLocaleString(uxLanguage.getCurrentLanguage(), { + minimumFractionDigits: minimumFractionDigits, + useGrouping: useGrouping + }); + }; + + var buildAmount = function(start, middle, end) { + $scope.start = start; + $scope.middle = middle; + $scope.end = end; + }; + + var getDecimalPlaces = function(currency) { + if (decimalPlaces['0'].indexOf($scope.currency.toUpperCase()) > -1) return '0'; + if (decimalPlaces['3'].indexOf($scope.currency.toUpperCase()) > -1) return '3'; + if (decimalPlaces['8'].indexOf($scope.currency.toUpperCase()) > -1) return '8'; + return '2'; + }; + + var formatNumbers = function(currency, value) { + switch (getDecimalPlaces(currency)) { + case '0': + var valueFormatted = localizeNumbers(Math.round(parseFloat(value))); + buildAmount(valueFormatted, '', ''); + break; + + case '3': + var valueProcessing = parseFloat(parseFloat(value).toFixed(3)); + var valueFormatted = localizeNumbers(valueProcessing); + buildAmount(valueFormatted, '', ''); + break; + + case '8': + var valueFormatted = parseFloat(value).toFixed(8); + if (parseFloat(value) == 0) { + buildAmount('0', '', ''); + } else { + var valueFormatted = localizeNumbers(valueFormatted, 8); + var start = valueFormatted.slice(0, -5); + var middle = valueFormatted.slice(-5, -2); + var end = valueFormatted.substr(valueFormatted.length - 2); + buildAmount(start, middle, end); + } + break; + + default: + var valueProcessing = parseFloat(parseFloat(value).toFixed(2)); + var valueFormatted = localizeNumbers(valueProcessing); + buildAmount(valueFormatted, '', ''); + break; + } + } + + formatNumbers($scope.currency, $scope.value); + $scope.$watchGroup(['currency', 'value'], function() { + formatNumbers($scope.currency, $scope.value); + }); + }); + }); + } + }; + } +); \ No newline at end of file diff --git a/www/views/includes/amount.html b/www/views/includes/formatted-amount.html similarity index 100% rename from www/views/includes/amount.html rename to www/views/includes/formatted-amount.html From 31d33ba65628e1cb14e10011b15a86e61617e7a2 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 1 Aug 2018 11:53:58 +0200 Subject: [PATCH 085/240] Shape shift track --- www/views/shapeshift.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/views/shapeshift.html b/www/views/shapeshift.html index 9f5a98423..2382f9897 100644 --- a/www/views/shapeshift.html +++ b/www/views/shapeshift.html @@ -27,7 +27,7 @@
- +
From 927c1e947847eba1ef284d060a307832b7d92528 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 2 Aug 2018 15:44:59 +1200 Subject: [PATCH 086/240] Request Specifc Amount integrated into new send flow. --- src/js/controllers/amount.js | 225 ++++++++++++----------------- src/js/controllers/customAmount.js | 9 +- src/js/controllers/tab-receive.js | 3 +- src/js/routes.js | 4 +- 4 files changed, 99 insertions(+), 142 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index c50a949a0..4d8c8671b 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -2,7 +2,7 @@ angular.module('copayApp.controllers').controller('amountController', amountController); -function amountController(configService, $filter, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $stateParams, $timeout, txFormatService, platformInfo, popupService, profileService, walletService, $window) { +function amountController(configService, $filter, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, txFormatService, platformInfo, popupService, profileService, walletService, $window) { var vm = this; vm.allowSend = false; @@ -11,12 +11,10 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i vm.alternativeUnit = ''; vm.amount = '0'; vm.availableFunds = ''; - vm.fromWalletId = ''; // Use insufficient for logic, as when the amount is invalid, funds being // either sufficent or insufficient doesn't make sense. vm.fundsAreInsufficient = false; vm.globalResult = ''; - vm.hello = 'hi'; vm.isRequestingSpecificAmount = false; vm.listComplete = false; vm.lastUsedPopularList = []; @@ -44,33 +42,25 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i var LENGTH_BEFORE_COMMA_EXPRESSION_LIMIT = 8; var LENGTH_AFTER_COMMA_EXPRESSION_LIMIT = 8; - var _id; var altCurrencyModal = null; var altUnitIndex = 0; var availableFundsInCrypto = ''; var availableFundsInFiat = ''; var availableSatoshis = null; var availableUnits = []; - var displayAddress = null; var fiatCode; - var fixedUnit; var hasMaxAmount = true; var isNW = platformInfo.isNW; var isAndroid = platformInfo.isAndroid; var isIos = platformInfo.isIOS; var lastUsedAltCurrencyList = []; - var nextStep = null; - var unitToSatoshi; - var recipientType = null; + var passthroughParams = {}; var satToUnit; var showMenu = false; var showWarningMessage = false; - var toAddress = ''; - var toColor = null; - var toEmail = null; - var toName = null; var unitDecimals; var unitIndex = 0; + var unitToSatoshi; var useSendMax = false; function onLeave() { @@ -87,24 +77,11 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i vm.shapeshiftOrderId = data.stateParams.shapeshiftOrderId; } - // To get the wallet from with the new flow - vm.fromWalletId = data.stateParams.fromWalletId; + passthroughParams = data.stateParams; - if (data.stateParams.noPrefix) { - showWarningMessage = data.stateParams.noPrefix != 0; - if (showWarningMessage) { - var message = 'Address doesn\'t contain currency information, please make sure you are sending the correct currency.'; - popupService.showAlert('', message, function() {}, 'Ok'); - } - } - - vm.isRequestingSpecificAmount = !!data.stateParams.id; + vm.isRequestingSpecificAmount = !data.stateParams.fromWalletId; var config = configService.getSync().wallet.settings; - // Go to... - _id = data.stateParams.id; // Optional (BitPay Card ID or Wallet ID) - nextStep = data.stateParams.nextStep; - setAvailableUnits(); updateUnitUI(); @@ -112,19 +89,8 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i hasMaxAmount = false; } - showMenu = $ionicHistory.backView() && ($ionicHistory.backView().stateName == 'tabs.send' || $ionicHistory.backView().stateName == 'tabs.bitpayCard'); - recipientType = data.stateParams.recipientType || null; - toAddress = data.stateParams.toAddress; - displayAddress = data.stateParams.displayAddress; - toName = data.stateParams.toName; - toEmail = data.stateParams.toEmail; - toColor = data.stateParams.toColor; - - if (!nextStep && !data.stateParams.toAddress) { - $log.error('Bad params at amount') - throw ('bad params'); - } - + showMenu = $ionicHistory.backView() && ($ionicHistory.backView().stateName == 'tabs.send'); + var reNr = /^[1234567890\.]$/; var reOp = /^[\*\+\-\/]$/; @@ -158,11 +124,6 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i resetAmount(); - // in SAT ALWAYS - if ($stateParams.toAmount) { - vm.amount = (($stateParams.toAmount) * satToUnit).toFixed(unitDecimals); - } - processAmount(); $timeout(function() { @@ -174,11 +135,16 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i var configCache = configService.getSync(); availableUnits = []; - var hasBCHWallets = profileService.getWallets({ - coin: 'bch' - }).length; + var coinFromWallet = ''; + if (passthroughParams.fromWalletId) { + var fromWallet = profileService.getWallet(passthroughParams.fromWalletId); + coinFromWallet = fromWallet.coin; + } else { + var toWallet = profileService.getWallet(passthroughParams.toWalletId); + coinFromWallet = toWallet.coin; + } - if (hasBCHWallets) { + if (coinFromWallet === 'bch') { availableUnits.push({ name: 'Bitcoin Cash', id: 'bch', @@ -186,11 +152,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i }); }; - var hasBTCWallets = profileService.getWallets({ - coin: 'btc' - }).length; - - if (hasBTCWallets) { + if (coinFromWallet === 'btc') { availableUnits.push({ name: 'Bitcoin', id: 'btc', @@ -200,27 +162,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i unitIndex = 0; - if (data.stateParams.coin) { - var coins = data.stateParams.coin.split(','); - var newAvailableUnits = []; - - lodash.each(coins, function(c) { - var coin = lodash.find(availableUnits, { - id: c - }); - if (!coin) { - $log.warn('Could not find desired coin:' + data.stateParams.coin) - } else { - newAvailableUnits.push(coin); - } - }); - - if (newAvailableUnits.length > 0) { - availableUnits = newAvailableUnits; - } - } - - + // currency have preference var fiatName; if (data.stateParams.currency) { @@ -241,18 +183,14 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i isFiat: true, }); - if (data.stateParams.fixedUnit) { - fixedUnit = true; - } - unitIndex = lodash.findIndex(availableUnits, { isFiat: true }); altUnitIndex = 0; - if (vm.fromWalletId) { - var fromWallet = profileService.getWallet(vm.fromWalletId); + if (passthroughParams.fromWalletId) { + var fromWallet = profileService.getWallet(passthroughParams.fromWalletId); updateAvailableFundsFromWallet(fromWallet); } }; @@ -302,8 +240,6 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i vm.amount = '0'; - if (fixedUnit) return; - if (!(availableUnits[unitIndex].isFiat && availableUnits.length > 2 && altUnitIndex == 0)) { unitIndex++; if (unitIndex >= availableUnits.length) unitIndex = 0; @@ -405,7 +341,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i var a = fromFiat(result); if (a) { var amountInSatoshis = a * unitToSatoshi; - vm.fundsAreInsufficient = !!vm.fromWalletId + vm.fundsAreInsufficient = !!passthroughParams.fromWalletId && availableSatoshis !== null && availableSatoshis < amountInSatoshis; @@ -425,7 +361,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i vm.allowSend = false; } } else { - vm.fundsAreInsufficient = vm.fromWalletId + vm.fundsAreInsufficient = passthroughParams.fromWalletId && availableSatoshis !== null && availableSatoshis < result * unitToSatoshi; @@ -482,67 +418,88 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i function finish() { var unit = availableUnits[unitIndex]; - var _amount = evaluate(format(vm.amount)); - var coin = unit.id; + var uiAmount = evaluate(format(vm.amount)); + + var satoshis = 0; if (unit.isFiat) { - coin = availableUnits[altUnitIndex].id; + satoshis = (fromFiat(uiAmount) * unitToSatoshi).toFixed(0); + } else { + satoshis = (uiAmount * unitToSatoshi).toFixed(0); } - if (nextStep) { - $state.transitionTo(nextStep, { - id: _id, - amount: useSendMax ? null : _amount, - currency: unit.id.toUpperCase(), - coin: coin, - useSendMax: useSendMax, - fromWalletId: vm.fromWalletId - }); - } else { - var amount = _amount; + var confirmData = { + amount: useSendMax ? undefined : satoshis, + fromWalletId: passthroughParams.fromWalletId, + sendMax: useSendMax, + thirdParty: passthroughParams.thirdParty, + toAddr: passthroughParams.toAddress, + toWalletId: passthroughParams.toWalletId + }; + console.log('confirmData:', confirmData); + + if (!confirmData.fromWalletId) { + $state.transitionTo('tabs.paymentRequest.confirm', confirmData); + } else { + + + var coin = unit.id; if (unit.isFiat) { - amount = (fromFiat(amount) * unitToSatoshi).toFixed(0); - } else { - amount = (amount * unitToSatoshi).toFixed(0); + coin = availableUnits[altUnitIndex].id; } - var confirmData = { - recipientType: recipientType, - toAmount: amount, - toAddress: toAddress, - displayAddress: displayAddress || toAddress, - toName: toName, - toEmail: toEmail, - toColor: toColor, - coin: coin, - useSendMax: useSendMax, - fromWalletId: vm.fromWalletId - }; + if (nextStep) { + $state.transitionTo(nextStep, { + id: _id, + amount: useSendMax ? null : _amount, + currency: unit.id.toUpperCase(), + coin: coin, + useSendMax: useSendMax, + fromWalletId: passthroughParams.fromWalletId + }); + } else { + var amount = _amount; - if (vm.shapeshiftOrderId) { - var shapeshiftOrderUrl = 'https://www.shapeshift.io/#/status/'; - shapeshiftOrderUrl += vm.shapeshiftOrderId; - confirmData.description = shapeshiftOrderUrl; - confirmData.fromWalletId = vm.fromWalletId; + if (unit.isFiat) { + amount = (fromFiat(amount) * unitToSatoshi).toFixed(0); + } else { + amount = (amount * unitToSatoshi).toFixed(0); + } - if (confirmData.useSendMax) { - var wallet = lodash.find(profileService.getWallets({ coin: coin }), - function(w) { - return w.id == vm.fromWalletId; - }); + var confirmData = { + amount: useSendMax ? undefined : amount, + fromWalletId: passthroughParams.fromWalletId, + sendMax: useSendMax, + thirdParty: passthroughParams.thirdParty, + toAddr: passthroughParams.toAddress, + toWalletId: passthroughParams.toWalletId + }; - var balance = parseFloat(wallet.cachedBalance.substring(0, wallet.cachedBalance.length-4)); - if (balance < vm.minShapeshiftAmount * 1.04) { - confirmData.useSendMax = false; - confirmData.toAmount = vm.minShapeshiftAmount * unitToSatoshi; - } else if (balance > vm.maxShapeshiftAmount) { - confirmData.useSendMax = false; - confirmData.toAmount = vm.maxShapeshiftAmount * unitToSatoshi * 0.99; + if (vm.shapeshiftOrderId) { + var shapeshiftOrderUrl = 'https://www.shapeshift.io/#/status/'; + shapeshiftOrderUrl += vm.shapeshiftOrderId; + confirmData.description = shapeshiftOrderUrl; + + if (confirmData.useSendMax) { + var wallet = lodash.find(profileService.getWallets({ coin: coin }), + function(w) { + return w.id == vm.fromWalletId; + }); + + var balance = parseFloat(wallet.cachedBalance.substring(0, wallet.cachedBalance.length-4)); + if (balance < vm.minShapeshiftAmount * 1.04) { + confirmData.useSendMax = false; + confirmData.amount = vm.minShapeshiftAmount * unitToSatoshi; + } else if (balance > vm.maxShapeshiftAmount) { + confirmData.useSendMax = false; + confirmData.amount = vm.maxShapeshiftAmount * unitToSatoshi * 0.99; + } } } - } - $state.transitionTo('tabs.send.confirm', confirmData); + + $state.transitionTo('tabs.send.confirm', confirmData); + } } useSendMax = null; } diff --git a/src/js/controllers/customAmount.js b/src/js/controllers/customAmount.js index f6b96c22c..d74ebca30 100644 --- a/src/js/controllers/customAmount.js +++ b/src/js/controllers/customAmount.js @@ -17,7 +17,7 @@ angular.module('copayApp.controllers').controller('customAmountController', func } $scope.$on("$ionicView.beforeEnter", function(event, data) { - var walletId = data.stateParams.id; + var walletId = data.stateParams.toWalletId; if (!walletId) { showErrorAndBack('Error', 'No wallet selected'); @@ -53,11 +53,12 @@ angular.module('copayApp.controllers').controller('customAmountController', func $scope.address = bchAddresses[$scope.bchAddressType]; } - $scope.coin = data.stateParams.coin; + $scope.coin = $scope.wallet.coin; + var satoshis = parseInt(data.stateParams.amount, 10); var parsedAmount = txFormatService.parseAmount( $scope.wallet.coin, - data.stateParams.amount, - data.stateParams.currency); + satoshis, + 'sat'); // Amount in USD or BTC var amount = parsedAmount.amount; diff --git a/src/js/controllers/tab-receive.js b/src/js/controllers/tab-receive.js index 8f25412ec..3c48818b6 100644 --- a/src/js/controllers/tab-receive.js +++ b/src/js/controllers/tab-receive.js @@ -19,8 +19,7 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi $scope.requestSpecificAmount = function() { $state.go('tabs.paymentRequest.amount', { - id: $scope.wallet.credentials.walletId, - coin: $scope.wallet.coin + toWalletId: $scope.wallet.credentials.walletId }); }; diff --git a/src/js/routes.js b/src/js/routes.js index 234344d5f..e5430b5f5 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -723,7 +723,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr }) .state('tabs.paymentRequest.amount', { - url: '/amount/:coin', + url: '/amount/:toWalletId', views: { 'tab-receive@tabs': { controller: 'amountController', @@ -733,7 +733,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }) .state('tabs.paymentRequest.confirm', { - url: '/confirm/:amount/:currency/:coin', + url: '/confirm/:amount/:toWalletId', views: { 'tab-receive@tabs': { controller: 'customAmountController', From dc41aa604426866cb5a471dc4d170f52123a2309 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 2 Aug 2018 16:40:00 +1200 Subject: [PATCH 087/240] Warning messages for ShapeShift amounts. --- src/js/controllers/amount.js | 194 +++++++++++++---------------------- www/views/amount.html | 8 +- 2 files changed, 78 insertions(+), 124 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 4d8c8671b..309949f97 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -2,7 +2,7 @@ angular.module('copayApp.controllers').controller('amountController', amountController); -function amountController(configService, $filter, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, txFormatService, platformInfo, popupService, profileService, walletService, $window) { +function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, txFormatService, platformInfo, profileService, walletService, $window) { var vm = this; vm.allowSend = false; @@ -18,8 +18,8 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i vm.isRequestingSpecificAmount = false; vm.listComplete = false; vm.lastUsedPopularList = []; - vm.maxShapeshiftAmount = 0; - vm.minShapeshiftAmount = 0; + vm.maxAmount = 0; + vm.minAmount = 0; vm.shapeshiftOrderId = ''; vm.unit = ''; @@ -34,6 +34,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i vm.removeDigit = removeDigit; vm.save = save; vm.sendMax = sendMax; + vm.errorMessage = ''; $scope.$on('$ionicView.beforeEnter', onBeforeEnter); $scope.$on('$ionicView.leave', onLeave); @@ -49,15 +50,12 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i var availableSatoshis = null; var availableUnits = []; var fiatCode; - var hasMaxAmount = true; var isNW = platformInfo.isNW; var isAndroid = platformInfo.isAndroid; var isIos = platformInfo.isIOS; var lastUsedAltCurrencyList = []; var passthroughParams = {}; var satToUnit; - var showMenu = false; - var showWarningMessage = false; var unitDecimals; var unitIndex = 0; var unitToSatoshi; @@ -70,26 +68,19 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i function onBeforeEnter(event, data) { initCurrencies(); - vm.hello = 'greetings'; - if (data.stateParams.shapeshiftOrderId && data.stateParams.shapeshiftOrderId.length > 0) { - vm.minShapeshiftAmount = parseFloat(data.stateParams.minShapeshiftAmount); - vm.maxShapeshiftAmount = parseFloat(data.stateParams.maxShapeshiftAmount); - vm.shapeshiftOrderId = data.stateParams.shapeshiftOrderId; - } + + vm.minAmount = parseFloat(data.stateParams.minShapeshiftAmount); + vm.maxAmount = parseFloat(data.stateParams.maxShapeshiftAmount); + vm.shapeshiftOrderId = data.stateParams.shapeshiftOrderId; passthroughParams = data.stateParams; + console.log('stateParams:', data.stateParams); vm.isRequestingSpecificAmount = !data.stateParams.fromWalletId; var config = configService.getSync().wallet.settings; setAvailableUnits(); updateUnitUI(); - - if ($ionicHistory.backView().stateName == 'tabs.receive') { - hasMaxAmount = false; - } - - showMenu = $ionicHistory.backView() && ($ionicHistory.backView().stateName == 'tabs.send'); var reNr = /^[1234567890\.]$/; var reOp = /^[\*\+\-\/]$/; @@ -333,6 +324,8 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i var formatedValue = format(vm.amount); var result = evaluate(formatedValue); + var amountInSatoshis = 0; + if (lodash.isNumber(result)) { vm.globalResult = isExpression(vm.amount) ? '= ' + processResult(result) : ''; @@ -340,7 +333,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i var a = fromFiat(result); if (a) { - var amountInSatoshis = a * unitToSatoshi; + amountInSatoshis = a * unitToSatoshi; vm.fundsAreInsufficient = !!passthroughParams.fromWalletId && availableSatoshis !== null && availableSatoshis < amountInSatoshis; @@ -349,7 +342,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i vm.allowSend = lodash.isNumber(a) && a > 0 && (!vm.shapeshiftOrderId - || (a >= vm.minShapeshiftAmount && a <= vm.maxShapeshiftAmount)) + || (a >= vm.minAmount && a <= vm.maxAmount)) && !vm.fundsAreInsufficient; } else { if (result) { @@ -361,6 +354,7 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i vm.allowSend = false; } } else { + amountInSatoshis = result; vm.fundsAreInsufficient = passthroughParams.fromWalletId && availableSatoshis !== null && availableSatoshis < result * unitToSatoshi; @@ -369,13 +363,27 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i vm.allowSend = lodash.isNumber(result) && result > 0 && (!vm.shapeshiftOrderId - || (result >= vm.minShapeshiftAmount && result <= vm.maxShapeshiftAmount)) + || (result >= vm.minAmount && result <= vm.maxAmount)) && !vm.fundsAreInsufficient; } } else { vm.fundsAreInsufficient = false; } + + if (vm.fundsAreInsufficient) { + vm.errorMessage = gettextCatalog.getString('Not enough available funds'); + } else if (amountInSatoshis && vm.shapeshiftOrderId) { + if (amountInSatoshis < (vm.minAmount * unitToSatoshi)) { + vm.errorMessage = gettextCatalog.getString('Amount is below minimum'); + } else if (amountInSatoshis > (vm.maxAmount * unitToSatoshi)) { + vm.errorMessage = gettextCatalog.getString('Amount is above maximum'); + } else { + vm.errorMessage = ''; + } + } else { + vm.errorMessage = ''; + } }; function processResult(val) { @@ -415,109 +423,55 @@ function amountController(configService, $filter, $ionicHistory, $ionicModal, $i }; function finish() { + var unit = availableUnits[unitIndex]; + var uiAmount = evaluate(format(vm.amount)); - function finish() { - var unit = availableUnits[unitIndex]; - var uiAmount = evaluate(format(vm.amount)); - - var satoshis = 0; - if (unit.isFiat) { - satoshis = (fromFiat(uiAmount) * unitToSatoshi).toFixed(0); - } else { - satoshis = (uiAmount * unitToSatoshi).toFixed(0); - } - - var confirmData = { - amount: useSendMax ? undefined : satoshis, - fromWalletId: passthroughParams.fromWalletId, - sendMax: useSendMax, - thirdParty: passthroughParams.thirdParty, - toAddr: passthroughParams.toAddress, - toWalletId: passthroughParams.toWalletId - }; - - console.log('confirmData:', confirmData); - - if (!confirmData.fromWalletId) { - $state.transitionTo('tabs.paymentRequest.confirm', confirmData); - } else { - - - var coin = unit.id; - if (unit.isFiat) { - coin = availableUnits[altUnitIndex].id; - } - - if (nextStep) { - $state.transitionTo(nextStep, { - id: _id, - amount: useSendMax ? null : _amount, - currency: unit.id.toUpperCase(), - coin: coin, - useSendMax: useSendMax, - fromWalletId: passthroughParams.fromWalletId - }); - } else { - var amount = _amount; - - if (unit.isFiat) { - amount = (fromFiat(amount) * unitToSatoshi).toFixed(0); - } else { - amount = (amount * unitToSatoshi).toFixed(0); - } - - var confirmData = { - amount: useSendMax ? undefined : amount, - fromWalletId: passthroughParams.fromWalletId, - sendMax: useSendMax, - thirdParty: passthroughParams.thirdParty, - toAddr: passthroughParams.toAddress, - toWalletId: passthroughParams.toWalletId - }; - - if (vm.shapeshiftOrderId) { - var shapeshiftOrderUrl = 'https://www.shapeshift.io/#/status/'; - shapeshiftOrderUrl += vm.shapeshiftOrderId; - confirmData.description = shapeshiftOrderUrl; - - if (confirmData.useSendMax) { - var wallet = lodash.find(profileService.getWallets({ coin: coin }), - function(w) { - return w.id == vm.fromWalletId; - }); - - var balance = parseFloat(wallet.cachedBalance.substring(0, wallet.cachedBalance.length-4)); - if (balance < vm.minShapeshiftAmount * 1.04) { - confirmData.useSendMax = false; - confirmData.amount = vm.minShapeshiftAmount * unitToSatoshi; - } else if (balance > vm.maxShapeshiftAmount) { - confirmData.useSendMax = false; - confirmData.amount = vm.maxShapeshiftAmount * unitToSatoshi * 0.99; - } - } - } - - - $state.transitionTo('tabs.send.confirm', confirmData); - } - } - useSendMax = null; - } - - if (showWarningMessage) { - var u = vm.unit == 'BCH' || vm.unit == 'BTC' ? vm.unit : vm.alternativeUnit; - var message = 'Are you sure you want to send ' + u.toUpperCase() + '?'; - popupService.showConfirm(message, '', 'Yes', 'No', function(res) { - if (!res) { - useSendMax = null; - return; - }; - finish(); - }); + var satoshis = 0; + if (unit.isFiat) { + satoshis = (fromFiat(uiAmount) * unitToSatoshi).toFixed(0); } else { - finish(); + satoshis = (uiAmount * unitToSatoshi).toFixed(0); } + var confirmData = { + amount: useSendMax ? undefined : satoshis, + fromWalletId: passthroughParams.fromWalletId, + sendMax: useSendMax, + thirdParty: passthroughParams.thirdParty, + toAddr: passthroughParams.toAddress, + toWalletId: passthroughParams.toWalletId + }; + + console.log('confirmData:', confirmData); + + if (!confirmData.fromWalletId) { + $state.transitionTo('tabs.paymentRequest.confirm', confirmData); + } else { + + if (vm.shapeshiftOrderId) { + var shapeshiftOrderUrl = 'https://www.shapeshift.io/#/status/'; + shapeshiftOrderUrl += vm.shapeshiftOrderId; + confirmData.description = shapeshiftOrderUrl; + + if (confirmData.sendMax) { + var wallet = lodash.find(profileService.getWallets({ coin: coin }), + function(w) { + return w.id == vm.fromWalletId; + }); + + var balance = parseFloat(wallet.cachedBalance.substring(0, wallet.cachedBalance.length-4)); + if (balance < vm.minAmount * 1.04) { + confirmData.sendMax = false; + confirmData.amount = vm.minAmount * unitToSatoshi; + } else if (balance > vm.maxAmount) { + confirmData.sendMax = false; + confirmData.amount = vm.maxAmount * unitToSatoshi * 0.99; + } + } + } + + $state.transitionTo('tabs.send.confirm', confirmData); + } }; diff --git a/www/views/amount.html b/www/views/amount.html index 77c52f96c..1757bf25a 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -12,8 +12,8 @@
@@ -29,8 +29,8 @@
From 1590a295da6d9380044a6722865ec6f9c34e68f4 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 2 Aug 2018 17:10:58 +1200 Subject: [PATCH 088/240] Bugfix for ShapeShift warnings. --- src/js/controllers/amount.js | 30 +++++++++++++++++------------- src/js/routes.js | 2 +- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 309949f97..269ff1634 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -69,13 +69,13 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, initCurrencies(); - vm.minAmount = parseFloat(data.stateParams.minShapeshiftAmount); - vm.maxAmount = parseFloat(data.stateParams.maxShapeshiftAmount); - vm.shapeshiftOrderId = data.stateParams.shapeshiftOrderId; - passthroughParams = data.stateParams; console.log('stateParams:', data.stateParams); + vm.minAmount = parseFloat(data.stateParams.minAmount); + vm.maxAmount = parseFloat(data.stateParams.maxAmount); + vm.shapeshiftOrderId = data.stateParams.thirdPartyOrderId; + vm.isRequestingSpecificAmount = !data.stateParams.fromWalletId; var config = configService.getSync().wallet.settings; @@ -324,7 +324,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, var formatedValue = format(vm.amount); var result = evaluate(formatedValue); - var amountInSatoshis = 0; + var amountInCrypto = 0; if (lodash.isNumber(result)) { vm.globalResult = isExpression(vm.amount) ? '= ' + processResult(result) : ''; @@ -333,7 +333,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, var a = fromFiat(result); if (a) { - amountInSatoshis = a * unitToSatoshi; + amountInCrypto = a; + var amountInSatoshis = a * unitToSatoshi; vm.fundsAreInsufficient = !!passthroughParams.fromWalletId && availableSatoshis !== null && availableSatoshis < amountInSatoshis; @@ -354,7 +355,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.allowSend = false; } } else { - amountInSatoshis = result; + amountInCrypto = result; vm.fundsAreInsufficient = passthroughParams.fromWalletId && availableSatoshis !== null && availableSatoshis < result * unitToSatoshi; @@ -373,11 +374,14 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, if (vm.fundsAreInsufficient) { vm.errorMessage = gettextCatalog.getString('Not enough available funds'); - } else if (amountInSatoshis && vm.shapeshiftOrderId) { - if (amountInSatoshis < (vm.minAmount * unitToSatoshi)) { + + } else if (amountInCrypto && vm.shapeshiftOrderId) { + if (amountInCrypto < vm.minAmount) { vm.errorMessage = gettextCatalog.getString('Amount is below minimum'); - } else if (amountInSatoshis > (vm.maxAmount * unitToSatoshi)) { + + } else if (amountInCrypto > vm.maxAmount) { vm.errorMessage = gettextCatalog.getString('Amount is above maximum'); + } else { vm.errorMessage = ''; } @@ -437,7 +441,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, amount: useSendMax ? undefined : satoshis, fromWalletId: passthroughParams.fromWalletId, sendMax: useSendMax, - thirdParty: passthroughParams.thirdParty, + thirdPartyOrderId: passthroughParams.thirdPartyOrderId, toAddr: passthroughParams.toAddress, toWalletId: passthroughParams.toWalletId }; @@ -456,7 +460,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, if (confirmData.sendMax) { var wallet = lodash.find(profileService.getWallets({ coin: coin }), function(w) { - return w.id == vm.fromWalletId; + return w.id == passthroughParams.fromWalletId; }); var balance = parseFloat(wallet.cachedBalance.substring(0, wallet.cachedBalance.length-4)); @@ -583,7 +587,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, }; function updateAvailableFundsStringIfNeeded() { - if (vm.fromWalletId && availableSatoshis !== null) { + if (passthroughParams.fromWalletId && availableSatoshis !== null) { availableFundsInFiat = ''; vm.availableFunds = availableFundsInCrypto; var coin = availableUnits[altUnitIndex].isFiat ? availableUnits[unitIndex].id : availableUnits[altUnitIndex].id; diff --git a/src/js/routes.js b/src/js/routes.js index e5430b5f5..7bb35a89f 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -287,7 +287,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr */ .state('tabs.send.amount', { - url: '/amount/:thirdParty/:fromWalletId/:toWalletId/:toAddress', + url: '/amount/:thirdPartyId/:thirdPartyOrderId/:fromWalletId/:maxAmount/:minAmount/:toWalletId/:toAddress', views: { 'tab-send@tabs': { controller: 'amountController', From 01dd774faea3f2927379a89f8c2d55e37914c08c Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 2 Aug 2018 15:15:23 +0200 Subject: [PATCH 089/240] thirdParty updates + shapeshift screens + merged with review transaction --- src/js/controllers/amount.js | 46 +++++++-- src/js/routes.js | 2 +- src/js/services/incomingData.js | 2 +- src/js/services/shapeshiftService.js | 141 ++++++++++++++++++++++++++- src/sass/views/shapeshift.scss | 4 + www/img/shapeshift_swap.png | Bin 0 -> 209589 bytes www/views/amount.html | 8 +- www/views/shapeshift.html | 4 +- 8 files changed, 187 insertions(+), 20 deletions(-) create mode 100644 www/img/shapeshift_swap.png diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index b623aa592..88d4901e9 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -2,7 +2,7 @@ angular.module('copayApp.controllers').controller('amountController', amountController); -function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, txFormatService, platformInfo, profileService, walletService, $window) { +function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, shapeshiftService, txFormatService, platformInfo, profileService, walletService, $window) { var vm = this; vm.allowSend = false; @@ -21,6 +21,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.maxAmount = 0; vm.minAmount = 0; vm.shapeshiftOrderId = ''; + vm.thirdParty = false; vm.unit = ''; vm.changeUnit = changeUnit; @@ -72,11 +73,39 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, passthroughParams = data.stateParams; console.log('stateParams:', data.stateParams); + vm.fromWalletId = data.stateParams.fromWalletId; + vm.toWalletId = data.stateParams.toWalletId; vm.minAmount = parseFloat(data.stateParams.minAmount); vm.maxAmount = parseFloat(data.stateParams.maxAmount); - vm.shapeshiftOrderId = data.stateParams.thirdPartyOrderId; + + if (passthroughParams.thirdParty) { + vm.thirdParty = JSON.parse(passthroughParams.thirdParty); // Parse stringified JSON-object + if (vm.thirdParty) { + if (vm.thirdParty.id === 'shapeshift') { + if (!vm.thirdParty.data) { + vm.thirdParty.data = {}; + } + vm.thirdParty.data['fromWalletId'] = vm.fromWalletId; + + vm.fromWallet = profileService.getWallet(vm.fromWalletId); + vm.toWallet = profileService.getWallet(vm.toWalletId); + + shapeshiftService.getMarketData(vm.fromWallet.coin, vm.toWallet.coin, function(data) { + console.log(data); + vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum); + vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit); + }); + + // if (vm.thirdParty.data['shapeshiftOrderId'] && data.stateParams.shapeshiftOrderId.length > 0) { + // vm.shapeshiftOrderId = vm.thirdParty.data['shapeshiftOrderId']; + // } + } + } + } + // vm.shapeshiftOrderId = data.stateParams.thirdPartyOrderId; vm.isRequestingSpecificAmount = !data.stateParams.fromWalletId; + var config = configService.getSync().wallet.settings; setAvailableUnits(); @@ -335,8 +364,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, if (a) { amountInCrypto = a; var amountInSatoshis = a * unitToSatoshi; - vm.fundsAreInsufficient = !!passthroughParams.fromWalletId - && availableSatoshis !== null + vm.fundsAreInsufficient = !!passthroughParams.fromWalletId + && availableSatoshis !== null && availableSatoshis < amountInSatoshis; vm.alternativeAmount = txFormatService.formatAmount(amountInSatoshis, true); @@ -356,8 +385,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } } else { amountInCrypto = result; - vm.fundsAreInsufficient = passthroughParams.fromWalletId - && availableSatoshis !== null + vm.fundsAreInsufficient = passthroughParams.fromWalletId + && availableSatoshis !== null && availableSatoshis < result * unitToSatoshi; vm.alternativeAmount = $filter('formatFiatAmount')(toFiat(result)); @@ -441,11 +470,14 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, amount: useSendMax ? undefined : satoshis, fromWalletId: passthroughParams.fromWalletId, sendMax: useSendMax, - thirdPartyOrderId: passthroughParams.thirdPartyOrderId, toAddr: passthroughParams.toAddress, toWalletId: passthroughParams.toWalletId }; + if (vm.thirdParty) { + confirmData['thirdParty'] = JSON.stringify(this.thirdParty); + } + console.log('confirmData:', confirmData); if (!confirmData.fromWalletId) { diff --git a/src/js/routes.js b/src/js/routes.js index 286042266..d9c900e34 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -287,7 +287,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr */ .state('tabs.send.amount', { - url: '/amount/:thirdPartyId/:thirdPartyOrderId/:fromWalletId/:maxAmount/:minAmount/:toWalletId/:toAddress', + url: '/amount/:thirdParty/:fromWalletId/:maxAmount/:minAmount/:toWalletId/:toAddress', views: { 'tab-send@tabs': { controller: 'amountController', diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index 7a9e67fe7..fb8d8868a 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -86,7 +86,6 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat $state.transitionTo('tabs.send.origin', { amount: amount, toAddress: addr, - displayAddress: originalAddress ? originalAddress : addr, description: message, coin: coin }); @@ -109,6 +108,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat // params['thirdParty']['minShapeshiftAmount'] = serviceData.minAmount; // params['thirdParty']['maxShapeshiftAmount'] = serviceData.maxAmount; // params['thirdParty']['shapeshiftOrderId'] = serviceData.orderId; + params['thirdParty'] = JSON.stringify(params['thirdParty']); $state.transitionTo('tabs.send.amount', params); } else { $state.transitionTo('tabs.send.origin', params); diff --git a/src/js/services/shapeshiftService.js b/src/js/services/shapeshiftService.js index 8a9b8fcaa..41af14002 100644 --- a/src/js/services/shapeshiftService.js +++ b/src/js/services/shapeshiftService.js @@ -1,7 +1,143 @@ 'use strict'; -angular.module('copayApp.services').factory('shapeshiftService', function($http, $log, lodash, moment, storageService, configService, platformInfo, servicesService) { +angular.module('copayApp.services').factory('shapeshiftService', function($http, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, platformInfo, servicesService) { var root = {}; - var credentials = {}; + root.ShiftState = 'Shift'; + root.withdrawalAddress = '' + root.returnAddress = '' + root.amount = ''; + root.marketData = {} + this.withdrawalAddress = function(address) { + root.withdrawalAddress = address; + }; + this.returnAddress = function(address) { + root.returnAddress = address; + }; + this.amount = function(amount) { + root.amount = amount; + }; + this.fromWalletId = function(id) { + root.fromWalletId = id; + }; + this.toWalletId = function(id) { + root.toWalletId = id; + }; + + root.getMarketDataIn = function(coin) { + if(coin === root.coinOut) return root.getMarketData(root.coinOut, root.coinIn); + return root.getMarketData(coin, root.coinOut); + }; + root.getMarketDataOut = function(coin) { + if(coin === root.coinIn) return root.getMarketData(root.coinOut, root.coinIn); + return root.getMarketData(root.coinIn, coin); + }; + root.getMarketData = function(coinIn, coinOut, cb) { + root.coinIn = coinIn; + root.coinOut= coinOut; + if(root.coinIn === undefined || root.coinOut === undefined) return; + shapeshiftApiService + .marketInfo(root.coinIn, root.coinOut) + .then(function(marketData){ + root.marketData = marketData; + root.rateString = root.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase(); + if (cb) { + cb(marketData); + } + }); + }; + + /*shapeshiftApiService.coins().then(function(coins){ + root.coins = coins; + root.coinIn = coins['BTC'].symbol; + root.coinOut = coins['BCH'].symbol; + root.getMarketData(root.coinIn, root.coinOut); + });*/ + + root.coins = { + 'BTC': { name: 'Bitcoin', symbol: 'BTC' }, + 'BCH': { name: 'Bitcoin Cash', symbol: 'BCH' } + }; + + function checkForError(data){ + if(data.error) return true; + return false; + } + + root.shiftIt = function(){ + ongoingProcess.set('connectingShapeshift', true); + var validate=shapeshiftApiService.ValidateAddress(root.withdrawalAddress, root.coinOut); + validate.then(function(valid){ + //console.log(root.withdrawalAddress) + //console.log(valid) + var tx = ShapeShift(); + tx.then(function(txData){ + if(txData['fixedTxData']){ + txData = txData.fixedTxData; + if(checkForError(txData)) return; + //console.log(txData) + var coinPair=txData.pair.split('_'); + txData.depositType = coinPair[0].toUpperCase(); + txData.withdrawalType = coinPair[1].toUpperCase(); + var coin = root.coins[txData.depositType].name.toLowerCase(); + //console.log(coin) + txData.depositQR = coin + ":" + txData.deposit + "?amount=" + txData.depositAmount + root.txFixedPending = true; + } else if(txData['normalTxData']){ + txData = txData.normalTxData; + if(checkForError(txData)) return; + var coin = root.coins[txData.depositType.toUpperCase()].name.toLowerCase(); + txData.depositQR = coin + ":" + txData.deposit; + } else if(txData['cancelTxData']){ + if(checkForError(txData.cancelTxData)) return; + if(root.txFixedPending) { + $interval.cancel(root.txInterval); + root.txFixedPending = false; + } + root.ShiftState = 'Shift'; + return; + } + root.depositInfo = txData; + //console.log(root.marketData); + //console.log(root.depositInfo); + var sendAddress = txData.depositQR; + if (sendAddress && sendAddress.indexOf('bitcoin cash') >= 0) + sendAddress = sendAddress.replace('bitcoin cash', 'bitcoincash'); + + var shapeshiftData = { + fromWalletId: root.fromWalletId, + minAmount: root.marketData.minimum, + maxAmount: root.marketData.maxLimit, + orderId: root.depositInfo.orderId + }; + + if (incomingData.redir(sendAddress, shapeshiftData)) { + ongoingProcess.set('connectingShapeshift', false); + return; + } + + /*root.ShiftState = 'Cancel'; + root.GetStatus(); + root.txInterval=$interval(root.GetStatus, 8000);*/ + }); + }) + }; + + function ShapeShift() { + if(root.ShiftState === 'Cancel') return shapeshiftApiService.CancelTx(root); + if(parseFloat(root.amount) > 0) return shapeshiftApiService.FixedAmountTx(root); + return shapeshiftApiService.NormalTx(root); + } + + root.GetStatus = function(){ + var address = root.depositInfo.deposit + shapeshiftApiService.GetStatusOfDepositToAddress(address).then(function(data){ + root.DepositStatus = data; + if(root.DepositStatus.status === 'complete'){ + $interval.cancel(root.txInterval); + root.depositInfo = null; + root.ShiftState = 'Shift' + } + }); + }; var servicesItem = { name: 'shapeshift', @@ -13,7 +149,6 @@ angular.module('copayApp.services').factory('shapeshiftService', function($http, var register = function() { servicesService.register(servicesItem); }; - register(); return root; }); diff --git a/src/sass/views/shapeshift.scss b/src/sass/views/shapeshift.scss index 8b4b941ab..1054fece2 100644 --- a/src/sass/views/shapeshift.scss +++ b/src/sass/views/shapeshift.scss @@ -1,4 +1,8 @@ #shapeshift { + .swap-image { + width: 70%; + max-width: 400px; + } .empty-case { @include empty-case(); } diff --git a/www/img/shapeshift_swap.png b/www/img/shapeshift_swap.png new file mode 100644 index 0000000000000000000000000000000000000000..83905b75141199bfdc0831c1a3cc309317da07dc GIT binary patch literal 209589 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

*N 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

#Jiv_ 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�a5T*f=2P13V~g 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 literal 0 HcmV?d00001 diff --git a/www/views/amount.html b/www/views/amount.html index 1757bf25a..e5df7dc0f 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -6,15 +6,11 @@ - +

-
diff --git a/www/views/shapeshift.html b/www/views/shapeshift.html index 2382f9897..81e502631 100644 --- a/www/views/shapeshift.html +++ b/www/views/shapeshift.html @@ -11,8 +11,8 @@
-
- Your Bitcoin wallet is empty +
+
From aacc80ea218b678d6c26e9a591b44d118bf48e79 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 2 Aug 2018 15:42:35 +0200 Subject: [PATCH 090/240] shapeshift flow --- src/js/controllers/amount.js | 3 +-- src/js/controllers/review.controller.js | 17 ++++++++++++++++- src/js/routes.js | 2 +- src/sass/views/amount.scss | 3 +++ src/sass/views/shapeshift.scss | 3 +++ www/views/amount.html | 6 +++--- www/views/review.html | 3 ++- www/views/shapeshift.html | 4 +--- .../shapeshift-header.html} | 0 www/views/walletSelector.html | 2 +- 10 files changed, 31 insertions(+), 12 deletions(-) rename www/views/{header-thirdparty.html => thirdparty/shapeshift-header.html} (100%) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 88d4901e9..576fb4500 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -506,8 +506,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } } - $state.transitionTo('tabs.send.review', confirmData); - } + $state.transitionTo('tabs.send.review', confirmData); $scope.useSendMax = null; } }; diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index b81645488..edf7787d5 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -6,7 +6,7 @@ angular function reviewController(addressbookService, configService, profileService, $log, $scope, txFormatService) { var vm = this; - + vm.destination = { address: '', balanceAmount: '', @@ -32,6 +32,7 @@ function reviewController(addressbookService, configService, profileService, $lo vm.primaryCurrency = ''; vm.secondaryAmount = ''; vm.secondaryCurrency = ''; + vm.thirdParty = false; var config = null; var coin = ''; @@ -57,6 +58,20 @@ function reviewController(addressbookService, configService, profileService, $lo vm.origin.name = originWallet.name; coin = originWallet.coin; + if (data.stateParams.thirdParty) { + vm.thirdParty = JSON.parse(data.stateParams.thirdParty); // Parse stringified JSON-object + if (vm.thirdParty) { + if (vm.thirdParty.id === 'shapeshift') { + if (!vm.thirdParty.data) { + vm.thirdParty.data = {}; + } + vm.thirdParty.data['fromWalletId'] = vm.fromWalletId; + vm.fromWallet = profileService.getWallet(vm.fromWalletId); + vm.toWallet = profileService.getWallet(vm.toWalletId); + } + } + } + configService.get(function onConfig(err, configCache) { if (err) { $log.err('Error getting config.', err); diff --git a/src/js/routes.js b/src/js/routes.js index d9c900e34..905683dcb 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -345,7 +345,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }) .state('tabs.send.review', { - url: '/review/:amount/:fromWalletId/:sendMax/:toAddr/:toWalletId', + url: '/review/:thirdParty/:amount/:fromWalletId/:sendMax/:toAddr/:toWalletId', views: { 'tab-send@tabs': { controller: 'reviewController', diff --git a/src/sass/views/amount.scss b/src/sass/views/amount.scss index daf6cf4fe..ca32c6ac4 100644 --- a/src/sass/views/amount.scss +++ b/src/sass/views/amount.scss @@ -254,6 +254,9 @@ padding: 0 6px 6px 6px; text-align: center; } + &__max { + float: right; + } } .send-amount-tool { diff --git a/src/sass/views/shapeshift.scss b/src/sass/views/shapeshift.scss index 1054fece2..158babb16 100644 --- a/src/sass/views/shapeshift.scss +++ b/src/sass/views/shapeshift.scss @@ -15,4 +15,7 @@ border: 0px; @include button-shadow(); } +} +.header.shapeshift { + background: #243F5D; } \ No newline at end of file diff --git a/www/views/amount.html b/www/views/amount.html index e5df7dc0f..48637ec1b 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -6,17 +6,17 @@ -
+
- {{vm.amount}} {{vm.unit}} + {{vm.amount || '0'}} {{vm.unit}}
{{vm.globalResult}} {{vm.unit}}
diff --git a/www/views/review.html b/www/views/review.html index 36bb67410..b9c190ab5 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -9,7 +9,8 @@ -
+
+

You are sending

{{vm.primaryAmount}} {{vm.primaryCurrency}}

diff --git a/www/views/shapeshift.html b/www/views/shapeshift.html index 81e502631..764ef8851 100644 --- a/www/views/shapeshift.html +++ b/www/views/shapeshift.html @@ -7,9 +7,7 @@ -
- -
+
diff --git a/www/views/header-thirdparty.html b/www/views/thirdparty/shapeshift-header.html similarity index 100% rename from www/views/header-thirdparty.html rename to www/views/thirdparty/shapeshift-header.html diff --git a/www/views/walletSelector.html b/www/views/walletSelector.html index a4cc4db81..49a7ba208 100644 --- a/www/views/walletSelector.html +++ b/www/views/walletSelector.html @@ -4,7 +4,7 @@ -
+
Paying
$... USD
From f49e8725e8543f852f612b069b561d23bbe4c833 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 2 Aug 2018 16:05:12 +0200 Subject: [PATCH 091/240] Translations additions + some (s)css + review transaction changes --- i18n/po/template.pot | 19 ++++++++++++++++++- src/js/controllers/review.controller.js | 4 +++- src/sass/views/review.scss | 4 ++++ src/sass/views/shapeshift.scss | 6 ++++-- www/views/review.html | 16 +++------------- www/views/shapeshift.html | 5 ++--- 6 files changed, 34 insertions(+), 20 deletions(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index 2e7cedcbe..ecf44efe9 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -2651,6 +2651,7 @@ msgid "You can receive bitcoin from any wallet or service." msgstr "" #: www/views/tab-send.html:72 +#: www/views/shapeshift.html:23 msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." msgstr "" @@ -3116,6 +3117,18 @@ msgstr "" msgid "Start ShapeShift" msgstr "" +#: www/views/shapeshift.html:30 +msgid "Exchange your BTC to BCH in minutes." +msgstr "" + +#: www/views/shapeshift.html:30 +msgid "To start the process you need to add funds to your wallet." +msgstr "" + +#: www/views/shapeshift.html:30 +msgid "he process is fast and you will receive the exchanged amount in your wallet." +msgstr "" + #: www/views/shapeshift.html:34 msgid "This service is provided by the third-party ShapeShift, who will charge a small fee for the service. The fee will be shown before you start the transaction." msgstr "" @@ -3720,10 +3733,14 @@ msgstr "" msgid "Review Transaction" msgstr "" -#: www/views/review.html:14 +#: src/js/controllers/review.controller.js:36 msgid "You are sending" msgstr "" +#: src/js/controllers/review.controller.js:66 +msgid "You are shifting" +msgstr "" + #: www/views/review.html:22 msgid "From:" msgstr "" diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index edf7787d5..473ac4452 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -4,7 +4,7 @@ angular .module('copayApp.controllers') .controller('reviewController', reviewController); -function reviewController(addressbookService, configService, profileService, $log, $scope, txFormatService) { +function reviewController(addressbookService, configService, gettextCatalog, profileService, $log, $scope, txFormatService) { var vm = this; vm.destination = { @@ -33,6 +33,7 @@ function reviewController(addressbookService, configService, profileService, $lo vm.secondaryAmount = ''; vm.secondaryCurrency = ''; vm.thirdParty = false; + vm.sendingTitle = gettextCatalog.getString('You are sending'); var config = null; var coin = ''; @@ -62,6 +63,7 @@ function reviewController(addressbookService, configService, profileService, $lo vm.thirdParty = JSON.parse(data.stateParams.thirdParty); // Parse stringified JSON-object if (vm.thirdParty) { if (vm.thirdParty.id === 'shapeshift') { + vm.sendingTitle = gettextCatalog.getString('You are shifting'); if (!vm.thirdParty.data) { vm.thirdParty.data = {}; } diff --git a/src/sass/views/review.scss b/src/sass/views/review.scss index 67733fe22..22470a7b2 100644 --- a/src/sass/views/review.scss +++ b/src/sass/views/review.scss @@ -10,4 +10,8 @@ position: absolute; bottom: 92px; } + + .shapeshift-banner { + box-shadow: none; + } } \ No newline at end of file diff --git a/src/sass/views/shapeshift.scss b/src/sass/views/shapeshift.scss index 158babb16..ee4cd0b0f 100644 --- a/src/sass/views/shapeshift.scss +++ b/src/sass/views/shapeshift.scss @@ -1,7 +1,8 @@ #shapeshift { .swap-image { - width: 70%; + width: auto; max-width: 400px; + max-height: 25vh; } .empty-case { @include empty-case(); @@ -17,5 +18,6 @@ } } .header.shapeshift { - background: #243F5D; + background: url(../img/shapeshiftbg.jpg) center center no-repeat #28394d; + opacity: 0.99; } \ No newline at end of file diff --git a/www/views/review.html b/www/views/review.html index b9c190ab5..e398e68a1 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -12,7 +12,7 @@
-

You are sending

+

{{vm.sendingTitle}}

{{vm.primaryAmount}} {{vm.primaryCurrency}}

{{vm.secondaryAmount}} {{vm.secondaryCurrency}}

@@ -84,13 +84,13 @@ {{buttonText}} @@ -104,14 +104,4 @@ Proposal Created Transaction Created - - - - diff --git a/www/views/shapeshift.html b/www/views/shapeshift.html index 764ef8851..61ad0952e 100644 --- a/www/views/shapeshift.html +++ b/www/views/shapeshift.html @@ -13,12 +13,11 @@
+

Exchange your BTC to BCH in minutes.

-

Before exchanging your BTC to BCH, you will need to add funds to your wallet.

-

You can receive Bitcoin from any wallet or service.

+

To start the process you need to add funds to your wallet.

-

Using Shapeshift will allow you to exchange your BTC for BCH.

The process is fast and you will receive the exchanged amount in your wallet.

To get started, you'll need to create a bitcoin wallet and get some bitcoin.
From 66c9cefb0c539ed1817b3ecb7674082a5eb9f924 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 3 Aug 2018 01:15:54 +0900 Subject: [PATCH 092/240] Fix the tx history --- src/js/services/walletService.js | 42 +++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js index 774fa0906..7ea98600f 100644 --- a/src/js/services/walletService.js +++ b/src/js/services/walletService.js @@ -343,21 +343,23 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim if (err) return cb(err); if (!txsFromServer.length) - return cb(); + return cb(null, []); - var res = lodash.takeWhile(txsFromServer, function(tx) { + /*var res = lodash.takeWhile(txsFromServer, function(tx) { return tx.txid != endingTxid; - }); + });*/ - return cb(null, res, res.length >= limit); + return cb(null, txsFromServer); }); }; var removeAndMarkSoftConfirmedTx = function(txs) { return lodash.filter(txs, function(tx) { - if (tx.confirmations >= root.SOFT_CONFIRMATION_LIMIT) - return tx; - tx.recent = true; + var isConfirm = (tx.confirmations >= root.SOFT_CONFIRMATION_LIMIT); + if (!isConfirm) { + tx.recent = true; + } + return isConfirm; }); } @@ -437,12 +439,14 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim var endingTxid = confirmedTxs[0] ? confirmedTxs[0].txid : null; var endingTs = confirmedTxs[0] ? confirmedTxs[0].time : null; + $log.debug('Confirmed TXs. Got:' + confirmedTxs.length + '/' + txsFromLocal.length); + // First update progressFn(txsFromLocal, 0); wallet.completeHistory = txsFromLocal; function getNewTxs(newTxs, skip, next) { - getTxsFromServer(wallet, skip, endingTxid, requestLimit, function(err, res, shouldContinue) { + getTxsFromServer(wallet, skip, endingTxid, requestLimit, function(err, res) { if (err) { $log.warn(bwcError.msg(err, 'Server Error')); //TODO if (err instanceof errors.CONNECTION_ERROR || (err.message && err.message.match(/5../))) { @@ -454,7 +458,27 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim return next(err); } - newTxs = newTxs.concat(processNewTxs(wallet, lodash.compact(res))); + // Check if new txs are founds, if yes, lets investigate in the 50 next + // To be sure we are not missing txs by order (maybe a new tx is after the "endingTxid" + + var newDiscoveredTxs = res.filter(function (x) { + return confirmedTxs.filter(function (confX) { + return confX.txid == x.txid; + }).length == 0; + }); + + $log.debug('Discovering TXs. Got:' + newDiscoveredTxs.length); + + /*var newDiscoveredTxs = lodash.uniq(lodash.union(res, newTxs), function(x) { + return x.txid; + });*/ + + var shouldContinue = newDiscoveredTxs.length > 0; + + // If no new tx, no need to check + if (shouldContinue) { + newTxs = newTxs.concat(processNewTxs(wallet, lodash.compact(newDiscoveredTxs))); + } progressFn(newTxs.concat(txsFromLocal), newTxs.length); From 9c8ec7d3a21f890d73ac18dc84a82e76c99266fe Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 3 Aug 2018 01:17:01 +0900 Subject: [PATCH 093/240] Fix tx history --- src/js/services/walletService.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js index 7ea98600f..cb04ffbe3 100644 --- a/src/js/services/walletService.js +++ b/src/js/services/walletService.js @@ -345,10 +345,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim if (!txsFromServer.length) return cb(null, []); - /*var res = lodash.takeWhile(txsFromServer, function(tx) { - return tx.txid != endingTxid; - });*/ - return cb(null, txsFromServer); }); }; @@ -459,8 +455,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim } // Check if new txs are founds, if yes, lets investigate in the 50 next - // To be sure we are not missing txs by order (maybe a new tx is after the "endingTxid" - + // To be sure we are not missing txs by sorting (maybe a new tx is after the "endingTxid" var newDiscoveredTxs = res.filter(function (x) { return confirmedTxs.filter(function (confX) { return confX.txid == x.txid; @@ -469,10 +464,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim $log.debug('Discovering TXs. Got:' + newDiscoveredTxs.length); - /*var newDiscoveredTxs = lodash.uniq(lodash.union(res, newTxs), function(x) { - return x.txid; - });*/ - var shouldContinue = newDiscoveredTxs.length > 0; // If no new tx, no need to check From eef84b25f80063b547b078406247e4e10a350fee Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 2 Aug 2018 18:30:35 +0200 Subject: [PATCH 094/240] update to txp creation. merged and refactored many features from confirm.js --- src/js/controllers/confirm.js | 3 +- src/js/controllers/review.controller.js | 425 +++++++++++++++++++++- src/js/directives/shapeshiftCoinTrader.js | 2 +- src/js/services/shapeshiftService.js | 2 +- www/views/review.html | 16 +- 5 files changed, 428 insertions(+), 20 deletions(-) diff --git a/src/js/controllers/confirm.js b/src/js/controllers/confirm.js index 762bfe42b..07256c0b2 100644 --- a/src/js/controllers/confirm.js +++ b/src/js/controllers/confirm.js @@ -140,7 +140,7 @@ angular.module('copayApp.controllers').controller('confirmController', function( return cb(); }); - } + }; $scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.fromWallet = profileService.getWallet(data.stateParams.fromWalletId); // Wallet to send from @@ -463,7 +463,6 @@ angular.module('copayApp.controllers').controller('confirmController', function( } }; - $scope.toggleAddress = function() { $scope.showAddress = !$scope.showAddress; }; diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 473ac4452..f0a0ec2ae 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -4,7 +4,7 @@ angular .module('copayApp.controllers') .controller('reviewController', reviewController); -function reviewController(addressbookService, configService, gettextCatalog, profileService, $log, $scope, txFormatService) { +function reviewController($log, $scope, $ionicLoading, $ionicModal, $timeout, addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, configService, feeService, gettextCatalog, lodash, ongoingProcess, platformInfo, profileService, walletService, txFormatService) { var vm = this; vm.destination = { @@ -20,6 +20,8 @@ function reviewController(addressbookService, configService, gettextCatalog, pro }; vm.feeCrypto = ''; vm.feeFiat = ''; + vm.fiatCurrency = ''; + vm.feeLessThanACent = false; vm.origin = { balanceAmount: '', balanceCurrency: '', @@ -28,21 +30,30 @@ function reviewController(addressbookService, configService, gettextCatalog, pro currencyColor: '', name: '', }; + vm.isCordova = platformInfo.isCordova; vm.primaryAmount = ''; vm.primaryCurrency = ''; + vm.usingMerchantFee = false; vm.secondaryAmount = ''; vm.secondaryCurrency = ''; vm.thirdParty = false; vm.sendingTitle = gettextCatalog.getString('You are sending'); + vm.buttonText = ''; var config = null; var coin = ''; + var countDown = null; + var usingCustomFee = false; + var usingMerchantFee = false; + var destinationWalletId = ''; var originWalletId = ''; + var originWallet; var priceDisplayIsFiat = true; var satoshis = null; var toAddress = ''; - var destinationWalletId = ''; + var tx = {}; + var FEE_TOO_HIGH_LIMIT_PERCENTAGE = 15; $scope.$on("$ionicView.beforeEnter", onBeforeEnter); @@ -53,7 +64,7 @@ function reviewController(addressbookService, configService, gettextCatalog, pro satoshis = parseInt(data.stateParams.amount, 10); toAddress = data.stateParams.toAddr; - var originWallet = profileService.getWallet(originWalletId); + originWallet = profileService.getWallet(originWalletId); vm.origin.currency = originWallet.coin.toUpperCase(); vm.origin.color = originWallet.color; vm.origin.name = originWallet.name; @@ -68,8 +79,6 @@ function reviewController(addressbookService, configService, gettextCatalog, pro vm.thirdParty.data = {}; } vm.thirdParty.data['fromWalletId'] = vm.fromWalletId; - vm.fromWallet = profileService.getWallet(vm.fromWalletId); - vm.toWallet = profileService.getWallet(vm.toWalletId); } } } @@ -86,15 +95,186 @@ function reviewController(addressbookService, configService, gettextCatalog, pro getOriginWalletBalance(originWallet); handleDestinationAsAddress(toAddress, coin); handleDestinationAsWallet(data.stateParams.toWalletId); + createVanityTransaction(data); }); - } + } + vm.chooseFeeLevel = function(tx, wallet) { + + if (wallet.coin == 'bch') return; + if (usingMerchantFee) return; + + var scope = $rootScope.$new(true); + scope.network = tx.network; + scope.feeLevel = tx.feeLevel; + scope.noSave = true; + scope.coin = originWallet.coin; + + if (usingCustomFee) { + scope.customFeePerKB = tx.feeRate; + scope.feePerSatByte = tx.feeRate / 1000; + } + + $ionicModal.fromTemplateUrl('views/modals/chooseFeeLevel.html', { + scope: scope, + backdropClickToClose: false, + hardwareBackButtonClose: false + }).then(function(modal) { + scope.chooseFeeLevelModal = modal; + scope.openModal(); + }); + scope.openModal = function() { + scope.chooseFeeLevelModal.show(); + }; + + scope.hideModal = function(newFeeLevel, customFeePerKB) { + scope.chooseFeeLevelModal.hide(); + $log.debug('New fee level choosen:' + newFeeLevel + ' was:' + tx.feeLevel); + + usingCustomFee = newFeeLevel == 'custom' ? true : false; + + if (tx.feeLevel == newFeeLevel && !usingCustomFee) return; + + tx.feeLevel = newFeeLevel; + if (usingCustomFee) tx.feeRate = parseInt(customFeePerKB); + + updateTx(tx, originWallet, { + clearCache: true, + dryRun: true + }, function() {}); + }; + }; + + function createVanityTransaction(data) { + var configFeeLevel = config.wallet.settings.feeLevel ? config.wallet.settings.feeLevel : 'normal'; + + // Grab stateParams + tx = { + amount: parseInt(data.stateParams.amount), + sendMax: data.stateParams.useSendMax == 'true' ? true : false, + fromWalletId: data.stateParams.fromWalletId, + toAddress: data.stateParams.toAddress, + feeLevel: configFeeLevel, + spendUnconfirmed: config.wallet.spendUnconfirmed, + + // Vanity tx info (not in the real tx) + recipientType: vm.destination.kind || null, + toName: vm.destination.name || null, + toEmail: vm.destination.email || null, + toColor: vm.destination.color || null, + network: false, + coin: originWallet.coin, + txp: {}, + }; + + if (data.stateParams.requiredFeeRate) { + vm.usingMerchantFee = true; + tx.feeRate = parseInt(data.stateParams.requiredFeeRate); + } + + if (tx.coin && tx.coin === 'bch') { + tx.feeLevel = 'normal'; + } + + var B = data.stateParams.coin === 'bch' ? bitcoreCash : bitcore; + var networkName; + try { + if (vm.destination.kind === 'wallet') { // There is a wallet-to-wallet transfer + $ionicLoading.show(); + var toWallet = profileService.getWallet(data.stateParams.toWalletId); + + // We need an address to send to, so we ask the walletService to create a new address for the toWallet. + walletService.getAddress(toWallet, true, function (err, addr) { + $ionicLoading.hide(); + tx.toAddress = addr; + networkName = (new B.Address(tx.toAddress)).network.name; + tx.network = networkName; + setupTx(tx); + }); + } else { // This is a Wallet-to-address transfer + networkName = (new B.Address(tx.toAddress)).network.name; + tx.network = networkName; + setupTx(tx); + } + } catch (e) { + var message = gettextCatalog.getString('Invalid address'); + popupService.showAlert(null, message, function () { + $ionicHistory.nextViewOptions({ + disableAnimate: true, + historyRoot: true + }); + $state.go('tabs.send').then(function () { + $ionicHistory.clearHistory(); + }); + }); + return; + } + } function getOriginWalletBalance(originWallet) { var balanceText = getWalletBalanceDisplayText(originWallet); vm.origin.balanceAmount = balanceText.amount; vm.origin.balanceCurrency = balanceText.currency; } + 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.amount > 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.amount, + 'message': tx.description + }]; + + if (tx.sendMaxInfo) { + txp.inputs = tx.sendMaxInfo.inputs; + txp.fee = tx.sendMaxInfo.fee; + } else { + if (usingCustomFee || usingMerchantFee) { + txp.feePerKb = tx.feeRate; + } 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 getWalletBalanceDisplayText(wallet) { var balanceCryptoAmount = ''; var balanceCryptoCurrencyCode = ''; @@ -157,7 +337,8 @@ function reviewController(addressbookService, configService, gettextCatalog, pro function handleDestinationAsContact(contact) { vm.destination.kind = 'contact'; vm.destination.name = contact.name; - vm.destination.color = contact.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; + vm.destination.email = contact.email; + vm.destination.color = contact.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; vm.destination.currency = contact.coin.toUpperCase(); vm.destination.currencyColor = vm.destination.color; } @@ -228,4 +409,234 @@ function reviewController(addressbookService, configService, gettextCatalog, pro }); } + function setButtonText(isMultisig, isPayPro) { + if (isPayPro) { + if (vm.isCordova) { + vm.buttonText = gettextCatalog.getString('Slide to pay'); + } else { + vm.buttonText = gettextCatalog.getString('Click to pay'); + } + } else if (isMultisig) { + if (vm.isCordova) { + vm.buttonText = gettextCatalog.getString('Slide to accept'); + } else { + vm.buttonText = gettextCatalog.getString('Click to accept'); + } + } else { + if (vm.isCordova) { + vm.buttonText = gettextCatalog.getString('Slide to send'); + } else { + vm.buttonText = gettextCatalog.getString('Click to send'); + } + } + } + + function setupTx(tx) { + if (tx.coin === 'bch') { + tx.displayAddress = bitcoinCashJsService.readAddress(tx.toAddress).cashaddr; + } else { + tx.displayAddress = entry.address; + } + + addressbookService.get(tx.coin+tx.toAddress, function(err, addr) { // Check if the recipient is a contact + if (!err && addr) { + tx.toName = addr.name; + tx.toEmail = addr.email; + tx.recipientType = 'contact'; + } + }); + + // Other Scope vars + vm.showAddress = false; + + + setButtonText(originWallet.credentials.m > 1, !!tx.paypro); + + if (tx.paypro) + _paymentTimeControl(tx.paypro.expires); + + updateTx(tx, originWallet, { + dryRun: true + }, function(err) { + $timeout(function() { + $scope.$apply(); + }, 10); + + }); + + // setWalletSelector(tx.coin, tx.network, tx.amount, function(err) { + // if (err) { + // return exitWithError('Could not update wallets'); + // } + // + // if (vm.wallets.length > 1) { + // vm.showWalletSelector(); + // } else if (vm.wallets.length) { + // setWallet(vm.wallets[0], tx); + // } + // }); + } + function updateTx(tx, wallet, opts, cb) { + ongoingProcess.set('calculatingFee', true); + + if (opts.clearCache) { + tx.txp = {}; + } + + // $scope.tx = tx; + + // function updateAmount() { + // if (!tx.amount) return; + // + // // Amount + // tx.amountStr = txFormatService.formatAmountStr(originWallet.coin, tx.amount); + // tx.amountValueStr = tx.amountStr.split(' ')[0]; + // tx.amountUnitStr = tx.amountStr.split(' ')[1]; + // txFormatService.formatAlternativeStr(wallet.coin, tx.amount, function(v) { + // var parts = v.split(' '); + // tx.alternativeAmountStr = v; + // tx.alternativeAmountValueStr = parts[0]; + // tx.alternativeAmountUnitStr = (parts.length > 0) ? parts[1] : ''; + // }); + // } + // + // updateAmount(); + // refresh(); + + // End of quick refresh, before wallet is selected. + if (!wallet) { + ongoingProcess.set('calculatingFee', false); + return cb(); + } + + var feeServiceLevel = usingMerchantFee && originWallet.coin == 'btc' ? 'urgent' : tx.feeLevel; + feeService.getFeeRate(originWallet.coin, tx.network, feeServiceLevel, function(err, feeRate) { + if (err) { + ongoingProcess.set('calculatingFee', false); + return cb(err); + } + + var msg; + if (usingCustomFee) { + msg = gettextCatalog.getString('Custom'); + tx.feeLevelName = msg; + } else if (usingMerchantFee) { + $log.info('Using Merchant Fee:' + tx.feeRate + ' vs. Urgent level:' + feeRate); + msg = gettextCatalog.getString('Suggested by Merchant'); + tx.feeLevelName = msg; + } else { + tx.feeLevelName = feeService.feeOpts[tx.feeLevel]; + tx.feeRate = feeRate; + } + + getSendMaxInfo(lodash.clone(tx), wallet, function(err, sendMaxInfo) { + if (err) { + ongoingProcess.set('calculatingFee', false); + 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) { + ongoingProcess.set('calculatingFee', false); + setNoWallet(gettextCatalog.getString('Insufficient confirmed funds')); + popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee')); + return cb('no_funds'); + } + + tx.sendMaxInfo = sendMaxInfo; + tx.amount = tx.sendMaxInfo.amount; + updateAmount(); + ongoingProcess.set('calculatingFee', false); + $timeout(function() { + showSendMaxWarning(wallet, sendMaxInfo); + }, 200); + } + + // txp already generated for this wallet? + if (tx.txp[wallet.id]) { + ongoingProcess.set('calculatingFee', false); + updateSendAmounts(); + return cb(); + } + + getTxp(lodash.clone(tx), wallet, opts.dryRun, function(err, txp) { + ongoingProcess.set('calculatingFee', false); + if (err) { + if (err.message == 'Insufficient funds') { + setNoWallet(gettextCatalog.getString('Insufficient funds')); + popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee')); + return cb('no_funds'); + } else + return cb(err); + } + + txp.feeStr = txFormatService.formatAmountStr(wallet.coin, txp.fee); + txFormatService.formatAlternativeStr(wallet.coin, txp.fee, function(v) { + // txp.alternativeFeeStr = v; + // if (txp.alternativeFeeStr.substring(0, 4) == '0.00') + // txp.alternativeFeeStr = '< ' + txp.alternativeFeeStr; + vm.feeFiat = v; + vm.fiatCurrency = config.wallet.settings.alternativeIsoCode; + if (v.substring(0, 1) === "<") { + vm.feeLessThanACent = true; + } + + console.log("fiat", vm.feeFiat); + + }); + + var per = (txp.fee / (txp.amount + txp.fee) * 100); + var perString = per.toFixed(2); + txp.feeRatePerStr = (perString == '0.00' ? '< ' : '') + perString + '%'; + txp.feeToHigh = per > FEE_TOO_HIGH_LIMIT_PERCENTAGE; + vm.feeCrypto = txp.fee; + console.log("crypto", vm.feeCrypto); + + + tx.txp[wallet.id] = txp; + $log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx); + updateSendAmounts(); + + return cb(); + }); + }); + }); + } + + function _paymentTimeControl(expirationTime) { + $scope.paymentExpired = false; + setExpirationTime(); + + countDown = $interval(function() { + setExpirationTime(); + }, 1000); + + function setExpirationTime() { + var now = Math.floor(Date.now() / 1000); + + if (now > expirationTime) { + setExpiredValues(); + return; + } + + var totalSecs = expirationTime - now; + var m = Math.floor(totalSecs / 60); + var s = totalSecs % 60; + $scope.remainingTimeStr = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2); + }; + + function setExpiredValues() { + $scope.paymentExpired = true; + $scope.remainingTimeStr = gettextCatalog.getString('Expired'); + if (countDown) $interval.cancel(countDown); + $timeout(function() { + $scope.$apply(); + }); + }; + }; + } diff --git a/src/js/directives/shapeshiftCoinTrader.js b/src/js/directives/shapeshiftCoinTrader.js index d5c62f431..60cc66bdf 100644 --- a/src/js/directives/shapeshiftCoinTrader.js +++ b/src/js/directives/shapeshiftCoinTrader.js @@ -111,7 +111,7 @@ angular.module('copayApp.directives').directive('shapeshiftCoinTrader', function orderId: $scope.depositInfo.orderId }; - if (incomingData.redir(sendAddress, shapeshiftData)) { + if (incomingData.redir(sendAddress, 'shapeshift', shapeshiftData)) { ongoingProcess.set('connectingShapeshift', false); return; } diff --git a/src/js/services/shapeshiftService.js b/src/js/services/shapeshiftService.js index 41af14002..131df0cd0 100644 --- a/src/js/services/shapeshiftService.js +++ b/src/js/services/shapeshiftService.js @@ -109,7 +109,7 @@ angular.module('copayApp.services').factory('shapeshiftService', function($http, orderId: root.depositInfo.orderId }; - if (incomingData.redir(sendAddress, shapeshiftData)) { + if (incomingData.redir(sendAddress, 'shapeshift', shapeshiftData)) { ongoingProcess.set('connectingShapeshift', false); return; } diff --git a/www/views/review.html b/www/views/review.html index e398e68a1..a26143d2c 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -13,7 +13,7 @@

{{vm.sendingTitle}}

-

{{vm.primaryAmount}} {{vm.primaryCurrency}}

+

{{vm.primaryAmount}} {{vm.primaryCurrency}}

{{vm.secondaryAmount}} {{vm.secondaryCurrency}}

@@ -73,24 +73,22 @@
-
Fee: Less than 1 cent
-
- +
Fee: Less than 1 cent
+
Fee:
+
+
{{buttonText}} From f75866839523806056403e01be65f65584aa2550 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 3 Aug 2018 08:48:24 +1200 Subject: [PATCH 095/240] Title on wallet-to-wallet destination screen. --- src/js/controllers/walletSelectorController.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index 4f0533084..f73bd4830 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -5,9 +5,17 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu $scope.$on("$ionicView.beforeEnter", function(event, data) { var config = configService.getSync().wallet.settings; - $scope.sendFlowTitle = ""; - if ($state.current.name === 'tabs.send.wallet-to-wallet') { - $scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer'); + switch($state.current.name) { + case 'tabs.send.wallet-to-wallet': + $scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer'); + break; + case 'tabs.send.destination': + if (data.stateParams.fromWalletId) { + $scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer'); + } + break; + default: + // nop } $scope.params = $state.params; From 6626663a313d6d90c6066f8481eb04777492a7b8 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 3 Aug 2018 10:41:21 +1200 Subject: [PATCH 096/240] Displays error when transaction amount too low. Reinstated warning colour for transactions with high fees. --- src/js/controllers/review.controller.js | 18 +++++++++++++++--- www/views/review.html | 6 +++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index f0a0ec2ae..85950a8aa 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -4,7 +4,7 @@ angular .module('copayApp.controllers') .controller('reviewController', reviewController); -function reviewController($log, $scope, $ionicLoading, $ionicModal, $timeout, addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, configService, feeService, gettextCatalog, lodash, ongoingProcess, platformInfo, profileService, walletService, txFormatService) { +function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, configService, feeService, gettextCatalog, $ionicLoading, $ionicModal, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, $timeout, txFormatService, walletService) { var vm = this; vm.destination = { @@ -21,6 +21,7 @@ function reviewController($log, $scope, $ionicLoading, $ionicModal, $timeout, ad vm.feeCrypto = ''; vm.feeFiat = ''; vm.fiatCurrency = ''; + vm.feeIsHigh = false; vm.feeLessThanACent = false; vm.origin = { balanceAmount: '', @@ -52,6 +53,7 @@ function reviewController($log, $scope, $ionicLoading, $ionicModal, $timeout, ad var satoshis = null; var toAddress = ''; var tx = {}; + var unitFromSat = 0; var FEE_TOO_HIGH_LIMIT_PERCENTAGE = 15; @@ -89,7 +91,8 @@ function reviewController($log, $scope, $ionicLoading, $ionicModal, $timeout, ad } else { config = configCache; priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; - vm.origin.currencyColor = originWallet.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; + vm.origin.currencyColor = originWallet.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; + unitFromSat = 1 / config.wallet.settings.unitToSatoshi; } updateSendAmounts(); getOriginWalletBalance(originWallet); @@ -431,6 +434,14 @@ function reviewController($log, $scope, $ionicLoading, $ionicModal, $timeout, ad } } + function setSendError(msg) { + $scope.sendStatus = ''; + $timeout(function() { + $scope.$apply(); + }); + popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg)); + }; + function setupTx(tx) { if (tx.coin === 'bch') { tx.displayAddress = bitcoinCashJsService.readAddress(tx.toAddress).cashaddr; @@ -593,7 +604,8 @@ function reviewController($log, $scope, $ionicLoading, $ionicModal, $timeout, ad var perString = per.toFixed(2); txp.feeRatePerStr = (perString == '0.00' ? '< ' : '') + perString + '%'; txp.feeToHigh = per > FEE_TOO_HIGH_LIMIT_PERCENTAGE; - vm.feeCrypto = txp.fee; + vm.feeCrypto = (unitFromSat * txp.fee).toFixed(8); + vm.feeIsHigh = txp.feeToHigh; console.log("crypto", vm.feeCrypto); diff --git a/www/views/review.html b/www/views/review.html index a26143d2c..c1f37baf9 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -73,10 +73,10 @@
-
Fee: Less than 1 cent
-
Fee:
+
Fee: Less than 1 cent
+
Fee: {{vm.feeFiat}} {{vm.feeCurrency}}
- + {{vm.feeCrypto}} {{vm.origin.currency}}
From 9e5bd663a7c7146729de82d8d5772c2d361b3954 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 3 Aug 2018 10:37:49 +0900 Subject: [PATCH 097/240] Update appConfig.json --- app-template/bitcoincom/appConfig.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app-template/bitcoincom/appConfig.json b/app-template/bitcoincom/appConfig.json index 238f38002..084a586ce 100644 --- a/app-template/bitcoincom/appConfig.json +++ b/app-template/bitcoincom/appConfig.json @@ -24,9 +24,9 @@ "windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c", "pushSenderId": "1036948132229", "description": "A Secure Bitcoin Wallet", - "version": "4.13.1", - "fullVersion": "4.13-rc2", - "androidVersion": "413100", + "version": "4.13.2", + "fullVersion": "4.13-rc3", + "androidVersion": "413200", "_extraCSS": "", "_enabledExtensions": { "coinbase": false, From e649da69ef124605199f3d69085a739fb176f53f Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 3 Aug 2018 17:11:47 +1200 Subject: [PATCH 098/240] In WalletService, createAddress now terminated properly, preventing double callback. --- src/js/services/walletService.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js index 774fa0906..a69b505c1 100644 --- a/src/js/services/walletService.js +++ b/src/js/services/walletService.js @@ -884,7 +884,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim var createAddress = function(wallet, cb) { $log.debug('Creating address for wallet:', wallet.id); - wallet.createAddress({}, function(err, addr) { + wallet.createAddress({}, function onWalletCreatedAddress(err, addr) { if (err) { var prefix = gettextCatalog.getString('Could not create address'); if (err instanceof errors.CONNECTION_ERROR || (err.message && err.message.match(/5../))) { @@ -902,6 +902,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim if (err) return cb(err); return cb(null, addr[0].address); }); + return; } return bwcError.cb(err, prefix, cb); } From f96610a66b7016e1e65b65e87ea44ff670ae6acd Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Fri, 3 Aug 2018 13:34:09 +0800 Subject: [PATCH 099/240] Removes border and background from Paste Address button for send tab page --- src/sass/views/tab-send.scss | 2 + www/css/main.css | 1524 ++++++++++++++++++++++------------ 2 files changed, 982 insertions(+), 544 deletions(-) diff --git a/src/sass/views/tab-send.scss b/src/sass/views/tab-send.scss index 4fbe8e531..82b6f8d02 100644 --- a/src/sass/views/tab-send.scss +++ b/src/sass/views/tab-send.scss @@ -88,6 +88,8 @@ &.contains-address { .address { display: inline; + border: none; + background-color: transparent; } .non-address { display: none; diff --git a/www/css/main.css b/www/css/main.css index b4e67edac..e39d96cf3 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -9970,7 +9970,7 @@ ion-nav-bar.hide { .card { margin: 20px 14px; } -ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm:before, ion-view#copayers-invitation:before, ion-view#tab-home:before, ion-view#tab-receive:before, ion-view#tab-send:before, ion-view.settings:before, ion-view#bitpayCard:before, ion-view#bitpayCard-intro:before, ion-view#view-address-book:before, ion-view#addresses:before, ion-view#send-feedback:before, ion-view#choose-fee-level:before, ion-view#txp-details:before, ion-view#coinbase:before, ion-view#glidera:before, ion-view#amazon:before, ion-view#mercadolibre:before, ion-view#custom-amount:before { +ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm:before, ion-view#copayers-invitation:before, ion-view#tab-home:before, ion-view#tab-receive:before, ion-view#tab-send:before, ion-view.settings:before, ion-view#bitpayCard:before, ion-view#bitpayCard-intro:before, ion-view#view-address-book:before, ion-view#addresses:before, ion-view#choose-fee-level:before, ion-view#txp-details:before, ion-view#coinbase:before, ion-view#glidera:before, ion-view#amazon:before, ion-view#mercadolibre:before, ion-view#custom-amount:before { content: " "; display: block; position: absolute; @@ -9980,7 +9980,7 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm height: 44px; background-color: #fab915; } -.platform-ios.platform-cordova:not(.fullscreen) ion-view.deflash-blue:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#view-amount:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#view-confirm:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#copayers-invitation:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#tab-home:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#tab-receive:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#tab-send:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view.settings:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#bitpayCard:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#bitpayCard-intro:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#view-address-book:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#addresses:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#send-feedback:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#choose-fee-level:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#txp-details:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#coinbase:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#glidera:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#amazon:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#mercadolibre:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#custom-amount:before { +.platform-ios.platform-cordova:not(.fullscreen) ion-view.deflash-blue:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#view-amount:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#view-confirm:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#copayers-invitation:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#tab-home:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#tab-receive:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#tab-send:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view.settings:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#bitpayCard:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#bitpayCard-intro:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#view-address-book:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#addresses:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#choose-fee-level:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#txp-details:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#coinbase:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#glidera:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#amazon:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#mercadolibre:before, .platform-ios.platform-cordova:not(.fullscreen) ion-view#custom-amount:before { height: 64px; } .just-a-hint, .icon.bp-arrow-right, .icon.bp-arrow-down, .icon.bp-arrow-up { @@ -10037,6 +10037,11 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm .big-icon-svg.theme-circle > .bg.icon-faucet { background-image: url("../img/icon-faucet.svg"); background-size: 70%; } + .big-icon-svg.theme-circle > .bg.icon-wallet { + background-color: #FAB915; + background-image: url("../img/icon-wallet.svg"); + border: none; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3) inset; } .big-icon-svg.theme-circle-services > .bg { border: 1px solid #191919; } .big-icon-svg.theme-circle-community > .bg { @@ -10071,11 +10076,13 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm .loading .spinner svg { margin-top: 0; } -.button.button-primary.button-standard, .button.button-secondary.button-standard, .button.button-light.button-standard, .button.button-assertive.button-standard, +.button.button-primary.button-standard, .button.button-secondary.button-standard, .button.button-light.button-standard, .button.button-white.button-standard, .button.button-green.button-standard, .button.button-assertive.button-standard, .onboarding .button.button-primary.button-standard, .onboarding .button.button-secondary.button-standard, .onboarding .button.button-light.button-standard, -.onboarding .button.button-assertive.button-standard { +.onboarding .button.button-white.button-standard, +.onboarding .button.button-green.button-standard, +.onboarding .button.button-assertive.button-standard, #shapeshift .button-shapeshift { width: 85%; max-width: 300px; margin-left: auto; @@ -10118,10 +10125,12 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm box-shadow: none; color: #fff; } -.button.button-primary.button-standard + .button-standard, .button.button-secondary.button-standard + .button-standard, .button.button-light.button-standard + .button-standard, .button.button-assertive.button-standard + .button-standard, +.button.button-primary.button-standard + .button-standard, .button.button-secondary.button-standard + .button-standard, .button.button-light.button-standard + .button-standard, .button.button-white.button-standard + .button-standard, .button.button-green.button-standard + .button-standard, .button.button-assertive.button-standard + .button-standard, .onboarding .button.button-primary.button-standard + .button-standard, .onboarding .button.button-secondary.button-standard + .button-standard, .onboarding .button.button-light.button-standard + .button-standard, +.onboarding .button.button-white.button-standard + .button-standard, +.onboarding .button.button-green.button-standard + .button-standard, .onboarding .button.button-assertive.button-standard + .button-standard { margin-top: 1rem; } @@ -10183,8 +10192,67 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm font-size: 0.7em !important; display: inline !important; } -.button.button-full { - display: block; } +.button { + border-radius: 6px; } + .button.button-full { + display: block; } + .button-green { + border-color: #FFF; + background-color: #719561; + color: #FFF; + border: 0px; + box-shadow: 0 2px 11px 0 #C1C1C1; } + .button-green:hover { + color: #FFF; + text-decoration: none; } + .button-green.active, .button-green.activated { + border-color: #FFF; + background-color: #606060; } + .button-green.button-clear { + border-color: transparent; + background: none; + box-shadow: none; + color: #FFF; } + .button-green.button-icon { + border-color: transparent; + background: none; } + .button-green.button-outline { + border-color: #C1C1C1; + background: transparent; + color: #C1C1C1; } + .button-green.button-outline.active, .button-green.button-outline.activated { + background-color: #C1C1C1; + box-shadow: none; + color: #fff; } + .button-white { + border-color: #C1C1C1; + background-color: #FFF; + color: #606060; + box-shadow: 0 2px 11px 0 #C1C1C1; } + .button-white:hover { + color: #606060; + text-decoration: none; } + .button-white.active, .button-white.activated { + border-color: #FFF; + background-color: #C1C1C1; } + .button-white.button-clear { + border-color: transparent; + background: none; + box-shadow: none; + color: #FFF; } + .button-white.button-icon { + border-color: transparent; + background: none; } + .button-white.button-outline { + border-color: #C1C1C1; + background: transparent; + color: #C1C1C1; } + .button-white.button-outline.active, .button-white.button-outline.activated { + background-color: #C1C1C1; + box-shadow: none; + color: #fff; } + .button-white.activated { + color: #FFF; } .button-clear { background: none !important; } @@ -10197,6 +10265,22 @@ textarea.d-block { display: block; width: 100%; } +qrcode { + position: relative; } + qrcode.qr-overlay::before { + content: ""; + background-size: 100% 100%; + display: block; + left: 88px; + margin-top: 88px; + width: 44px; + height: 44px; + position: absolute; } + qrcode.qr-overlay--bch::before { + background-image: url("../img/qr-overlay-bch.png"); } + qrcode.qr-overlay--btc::before { + background-image: url("../img/qr-overlay-btc.png"); } + .center-block { float: none; margin: 0 auto; } @@ -10208,6 +10292,15 @@ textarea.d-block { top: 50%; left: 50%; } +.third-party-notice { + font-size: 12px; + margin: 0px 14px; + font-weight: 600; + color: #6F6F70; } + @media (min-width: 768px) { + .third-party-notice { + text-align: center; } } + .tabs .tab-item .icon { background-repeat: no-repeat; background-position: center; @@ -10237,6 +10330,10 @@ textarea.d-block { font-weight: 700; } #tab-home .card > .item-heading .icon, #tab-home .list > .item-heading .icon, #tab-send .card > .item-heading .icon, #tab-send .list > .item-heading .icon { color: #667; } + #tab-home .card > .item-heading .subtitle, #tab-home .list > .item-heading .subtitle, #tab-send .card > .item-heading .subtitle, #tab-send .list > .item-heading .subtitle { + color: #667; + font-size: 12px; + font-weight: 300; } #view-add .item { margin-bottom: 10px; @@ -10260,349 +10357,397 @@ textarea.d-block { #view-add .bg.join { padding: 10px; } -#view-amount .recipient-label { - font-size: 14px; - padding-bottom: 0; - color: #667; } - -#view-amount .item-no-bottom-border + .item { - border-top: 0; } - -#view-amount .icon-bitpay-card { - background-image: url("../img/icon-bitpay.svg"); } - -#view-amount .icon-amazon { - background-image: url("../img/icon-amazon.svg"); } - -@media (max-width: 480px) { - #view-amount .bitcoin-address { - font-size: 13px; - padding-left: 48px; } - #view-amount .bitcoin-address .icon { - left: 8px; - font-size: 24px; } - #view-amount .bitcoin-address .big-icon-svg { - left: 5px; } - #view-amount .bitcoin-address .big-icon-svg > .bg { - width: 30px; - height: 30px; - box-shadow: none; } } - -@media (max-width: 320px) { - #view-amount .bitcoin-address > span:last-child { - margin-left: -2px; } } - -#view-amount .send-gravatar { - left: 11px; - position: absolute; - top: 10px; } - -#view-amount .amount span input { - display: inline; - width: 120px; } - -#view-amount .amount-pane-recipient { - position: absolute; - top: 95px; - bottom: 0; - width: 100%; - background-color: #fff; - padding: 0 16px; } - #view-amount .amount-pane-recipient .amount-bar { - padding: 24px 0; - font-size: 18px; } - @media (max-height: 480px) { - #view-amount .amount-pane-recipient .amount-bar { - padding: 0px; } } - @media (max-width: 320px) { - #view-amount .amount-pane-recipient .amount-bar { - padding: 0px; } } - #view-amount .amount-pane-recipient .amount-bar .title { - float: left; - padding-top: 10px; - color: #445; - font-weight: bold; } +#view-amount { + background: #f2f2f2; } + #view-amount .recipient-label { + font-size: 14px; + padding-bottom: 0; + color: #667; } + #view-amount .item-no-bottom-border + .item { + border-top: 0; } + #view-amount .icon-bitpay-card { + background-image: url("../img/icon-bitpay.svg"); } + #view-amount .icon-amazon { + background-image: url("../img/icon-amazon.svg"); } + @media (max-width: 480px) { + #view-amount .bitcoin-address { + font-size: 13px; + padding-left: 48px; } + #view-amount .bitcoin-address .icon { + left: 8px; + font-size: 24px; } + #view-amount .bitcoin-address .big-icon-svg { + left: 5px; } + #view-amount .bitcoin-address .big-icon-svg > .bg { + width: 30px; + height: 30px; + box-shadow: none; } } + @media (max-width: 320px) { + #view-amount .bitcoin-address > span:last-child { + margin-left: -2px; } } + #view-amount .send-gravatar { + left: 11px; + position: absolute; + top: 10px; } + #view-amount .amount span input { + display: inline; + width: 120px; } + #view-amount .amount-pane-recipient { + position: absolute; + top: 95px; + bottom: 0; + width: 100%; + background-color: #fff; + padding: 0 16px; } + #view-amount .amount-pane-recipient .amount-bar { + padding: 24px 0; + font-size: 18px; } @media (max-height: 480px) { - #view-amount .amount-pane-recipient .amount-bar .title { + #view-amount .amount-pane-recipient .amount-bar { padding: 0px; } } - @media (max-height: 480px) { - #view-amount .amount-pane-recipient .amount-bar { - padding-top: 3px; } } - #view-amount .amount-pane-recipient .amount { - display: flex; - flex-direction: column; - justify-content: center; - flex-grow: 1; + @media (max-width: 320px) { + #view-amount .amount-pane-recipient .amount-bar { + padding: 0px; } } + #view-amount .amount-pane-recipient .amount-bar .title { + float: left; + padding-top: 10px; + color: #445; + font-weight: bold; } + @media (max-height: 480px) { + #view-amount .amount-pane-recipient .amount-bar .title { + padding: 0px; } } + @media (max-height: 480px) { + #view-amount .amount-pane-recipient .amount-bar { + padding-top: 3px; } } + #view-amount .amount-pane-recipient .amount { + display: flex; + flex-direction: column; + justify-content: center; + flex-grow: 1; + position: absolute; + bottom: 254px; + top: 66px; } + #view-amount .amount-pane-recipient .amount .light { + color: #9b9bab; } + @media (max-height: 480px) { + #view-amount .amount-pane-recipient .amount { + top: 45px; } } + @media (max-width: 320px) { + #view-amount .amount-pane-recipient .amount { + bottom: 276px; + top: 60px; } + #view-amount .amount-pane-recipient .amount > div { + display: inline-block; } + #view-amount .amount-pane-recipient .amount > div:first-child { + display: inherit; } } + #view-amount .amount-pane-no-recipient { position: absolute; - bottom: 254px; - top: 66px; } - #view-amount .amount-pane-recipient .amount .light { - color: #9b9bab; } - @media (max-height: 480px) { - #view-amount .amount-pane-recipient .amount { - top: 45px; } } - @media (max-width: 320px) { - #view-amount .amount-pane-recipient .amount { - bottom: 276px; - top: 60px; } - #view-amount .amount-pane-recipient .amount > div { - display: inline-block; } - #view-amount .amount-pane-recipient .amount > div:first-child { - display: inherit; } } - -#view-amount .amount-pane-no-recipient { - position: absolute; - top: 0; - bottom: 0; - width: 100%; - background-color: #fff; - padding: 0 16px; } - #view-amount .amount-pane-no-recipient .amount-bar { - padding: 24px 0; - font-size: 18px; } - #view-amount .amount-pane-no-recipient .amount-bar .title { - padding-top: 10px; + top: 0; + bottom: 0; + width: 100%; + background-color: #fff; + padding: 0 16px; } + #view-amount .amount-pane-no-recipient .amount-bar { + padding: 24px 0; + font-size: 18px; } + #view-amount .amount-pane-no-recipient .amount-bar .title { + padding-top: 10px; + color: #445; + font-weight: bold; } + #view-amount .amount-pane-no-recipient .amount-bar .title .limits { + margin-top: 10px; + color: #9b9bab; + font-size: 12px; } + #view-amount .amount-pane-no-recipient .amount-bar .title .select { + margin: 10px 1px; } + #view-amount .amount-pane-no-recipient .amount { + display: flex; + flex-direction: column; + justify-content: center; + flex-grow: 1; + position: absolute; + bottom: 254px; + top: 66px; } + #view-amount .amount-pane-no-recipient .amount .light { + color: #9b9bab; } + #view-amount .amount { + padding-top: 10px; + padding-bottom: 10px; } + #view-amount .amount .icon-toggle { + font-size: 1.2em; + width: auto; + margin: 0.8em auto; + border: 1px solid #f2f2f2; color: #445; - font-weight: bold; } - #view-amount .amount-pane-no-recipient .amount-bar .title .limits { - margin-top: 10px; - color: #9b9bab; - font-size: 12px; } - #view-amount .amount-pane-no-recipient .amount-bar .title .select { - margin: 10px 1px; } - #view-amount .amount-pane-no-recipient .amount { - display: flex; - flex-direction: column; - justify-content: center; - flex-grow: 1; - position: absolute; - bottom: 254px; - top: 66px; } - #view-amount .amount-pane-no-recipient .amount .light { + border-radius: 3px; + padding: 0 10px; + cursor: pointer; } + @media (max-height: 280px) { + #view-amount .amount .icon-toggle { + margin: 0.1em auto; } } + #view-amount .amount__editable--minimize { + font-size: 22px; } + #view-amount .amount__editable--standard { + font-size: 42px; } + @media (max-height: 480px) { + #view-amount .amount__editable--standard { + font-size: 26px; + padding-top: 10px; } } + #view-amount .amount__editable--placeholder { color: #9b9bab; } - -#view-amount .amount { - padding-top: 10px; - padding-bottom: 10px; } - #view-amount .amount .icon-toggle { - font-size: 1.2em; - width: auto; - margin: 0.8em auto; - border: 1px solid #f2f2f2; - color: #445; - border-radius: 3px; - padding: 0 10px; - cursor: pointer; } - @media (max-height: 280px) { - #view-amount .amount .icon-toggle { - margin: 0.1em auto; } } - #view-amount .amount__editable--minimize { - font-size: 22px; } - #view-amount .amount__editable--standard { - font-size: 42px; } - @media (max-height: 480px) { - #view-amount .amount__editable--standard { - font-size: 26px; - padding-top: 10px; } } - #view-amount .amount__editable--placeholder { - color: #9b9bab; } - #view-amount .amount__number { - color: #445; } - #view-amount .amount__currency-toggle { - border: 1px solid #f2f2f2; - color: #445; - border-radius: 3px; - padding: 0 10px; - cursor: pointer; - font-size: .6em; - position: relative; - top: -3px; - line-height: 1; } - @media (max-width: 320px) { - #view-amount .amount__currency-toggle { - line-height: 30px; - height: 30px; } } - #view-amount .amount__currency-toggle-mobile { - border: 1px solid #f2f2f2; - color: #445; - border-radius: 3px; - cursor: pointer; - position: relative; - line-height: 1; } - @media (max-width: 320px) { - #view-amount .amount__currency-toggle-mobile { - line-height: 30px; - height: 30px; } } - #view-amount .amount__results--minimize { - font-size: 12px; } - #view-amount .amount__results--standard { - font-size: 18px; - padding: 10px 0; } - #view-amount .amount__results--placeholder { - color: #9b9bab; } - #view-amount .amount__result { - color: #9b9bab; - font-size: .9em; - line-height: 1; } - @media (max-height: 480px) { - #view-amount .amount__result { - margin-bottom: 0; } } - #view-amount .amount__result-equiv { - color: #667; - font-size: 1.2em; } - @media (max-height: 480px) { - #view-amount .amount__result-equiv { - margin-top: 0; - font-size: 16px; } } - -#view-amount .scroll-content { - display: flex; - flex-direction: column; } - #view-amount .scroll-content .send-amount { - flex: 1 1 auto; + #view-amount .amount__number { + color: #445; } + #view-amount .amount__currency-toggle { + border: 1px solid #f2f2f2; + color: #445; + border-radius: 3px; + padding: 0 10px; + cursor: pointer; + font-size: .6em; + position: relative; + top: -3px; + line-height: 1; } + @media (max-width: 320px) { + #view-amount .amount__currency-toggle { + line-height: 30px; + height: 30px; } } + #view-amount .amount__currency-toggle-mobile { + border: 1px solid #f2f2f2; + color: #445; + border-radius: 3px; + cursor: pointer; + position: relative; + line-height: 1; } + @media (max-width: 320px) { + #view-amount .amount__currency-toggle-mobile { + line-height: 30px; + height: 30px; } } + #view-amount .amount__results--minimize { + font-size: 12px; } + #view-amount .amount__results--standard { + font-size: 18px; + padding: 10px 0; } + #view-amount .amount__results--placeholder { + color: #9b9bab; } + #view-amount .amount__result { + color: #9b9bab; + font-size: .9em; + line-height: 1; } + @media (max-height: 480px) { + #view-amount .amount__result { + margin-bottom: 0; } } + #view-amount .amount__result-equiv { + color: #667; + font-size: 1.2em; } + @media (max-height: 480px) { + #view-amount .amount__result-equiv { + margin-top: 0; + font-size: 16px; } } + #view-amount .scroll-content { display: flex; - flex-direction: column; - justify-content: center; } - #view-amount .scroll-content .send-amount .send-amount-tool { - flex: 0 1 auto; } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input { - text-align: center; - position: relative; - padding: 10px 30px; } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .text-selectable { - -webkit-user-select: text; - -moz-user-select: text; - -ms-user-select: text; - user-select: text; } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .primary-amount-display { - font-size: 1.8em; } - @media (min-width: 375px) { + flex-direction: column; } + #view-amount .scroll-content .send-amount { + flex: 1 1 auto; + display: flex; + flex-direction: column; + justify-content: center; } + #view-amount .scroll-content .send-amount .send-amount-header-footer { + flex: 1 1 auto; + min-height: 20px; } + #view-amount .scroll-content .send-amount .send-amount-header-footer .warning { + font-weight: bold; + font-size: 12px; + padding: 0 6px 6px 6px; + text-align: center; } + #view-amount .scroll-content .send-amount .send-amount-header-footer__max { + float: right; } + #view-amount .scroll-content .send-amount .send-amount-tool { + flex: 0 1 auto; } + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input { + text-align: center; + position: relative; + padding: 10px 30px; } + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .text-selectable { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; } + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount { + color: #333; + font-weight: bold; } #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .primary-amount-display { - font-size: 2.1em; } } - @media (min-width: 414px) { - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .primary-amount-display { - font-size: 2.4em; } } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .primary-amount-display { - font-size: 1.6em; } - @media (min-width: 375px) { + font-size: 1.8em; } + @media (min-width: 375px) { + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .primary-amount-display { + font-size: 2.1em; } } + @media (min-width: 414px) { + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .primary-amount-display { + font-size: 2.4em; } } #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .primary-amount-display { - font-size: 1.8em; } } - @media (min-width: 414px) { - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .primary-amount-display { - font-size: 2em; } } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .primary-amount-display { - font-size: 0.9em; } - @media (min-width: 375px) { + font-size: 1.6em; } + @media (min-width: 375px) { + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .primary-amount-display { + font-size: 1.8em; } } + @media (min-width: 414px) { + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.long .primary-amount-display { + font-size: 2em; } } #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .primary-amount-display { - font-size: 1.3em; } } - @media (min-width: 414px) { - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .primary-amount-display { - font-size: 1.4em; } } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount input { - border: 0; - padding: 0; - white-space: normal; - background: none; - line-height: 1; - box-sizing: content-box; - display: inline-block; - vertical-align: middle; - margin: 0; - height: 1em; - margin-right: 5px; - font-family: 'ProximaNova'; } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .unit, - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .primary-amount-display { - display: inline-block; - vertical-align: middle; - line-height: 1em; } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .unit { - font-weight: bold; } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .primary-amount-display { - margin-right: 5px; - word-break: break-all; } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .switch-currencies { - position: absolute; - right: 0; - top: 50%; - transform: translate(0, -50%); - padding: 15px; } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .switch-currencies img { - width: 18px; } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-actions { - margin-top: 15px; + font-size: 0.9em; } + @media (min-width: 375px) { + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .primary-amount-display { + font-size: 1.3em; } } + @media (min-width: 414px) { + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long input, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .unit, #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount.very-long .primary-amount-display { + font-size: 1.4em; } } + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount input { + border: 0; + padding: 0; + white-space: normal; + background: none; + line-height: 1; + box-sizing: content-box; + display: inline-block; + vertical-align: middle; + margin: 0; + height: 1em; + margin-right: 5px; + font-family: 'ProximaNova'; } + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .unit, + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .primary-amount-display { + display: inline-block; + vertical-align: middle; + line-height: 1em; } + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .primary-amount .primary-amount-display { + margin-right: 5px; + word-break: break-all; } + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .alternative-amount { + color: #6F6F70; } + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .switch-currencies { + position: absolute; + right: 0; + top: 50%; + transform: translate(0, -50%); + padding: 15px; } + #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-tool-input .switch-currencies img { + width: 18px; } + #view-amount .scroll-content .send-amount-extras { + display: flex; + flex: 0 0 auto; + /* So that if only one item is present, it appears on the right. */ + flex-direction: row-reverse; + font-size: 12px; + align-items: center; + justify-content: space-between; + margin: 0 14px; } + #view-amount .scroll-content .send-amount-extras .available-funds { + color: #6F6F70; } + #view-amount .scroll-content .send-amount-extras .warning { + color: #b7664d; } + #view-amount .scroll-content .send-amount-extras .extra, + #view-amount .scroll-content .send-amount-extras button.extra { + /*display: flex;*/ + flex: 0 1 auto; } + #view-amount .scroll-content .send-amount-extras button.extra { + background: none; + border: none; + color: #000; + font-family: 'ProximaNova'; + font-size: 14px; + line-height: normal; + min-height: auto; + min-width: auto; + padding: 0; } + #view-amount .scroll-content .send-amount-extras .button .icon:before { + font-size: 14px; + line-height: normal; } + #view-amount .scroll-content .send-amount-extras .button span { display: flex; align-items: center; justify-content: center; } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-actions .button { - flex: 1 1 auto; - line-height: 1.2em; } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-actions .button + .button { - margin-left: 10px; } - #view-amount .scroll-content .send-amount .send-amount-tool .send-amount-actions .button span { - display: flex; - align-items: center; - justify-content: center; } - #view-amount .scroll-content .button.no-margin { - margin: 0; } - #view-amount .scroll-content .notification-warning { - display: block; - padding: .75rem 1.25rem; - color: #856404; - background-color: #fff3cd; - border: 1px solid #ffeeba; - line-height: 1.4em; - margin-bottom: 20px; } - #view-amount .scroll-content .keypad-container { - position: relative; } - #view-amount .scroll-content .keypad-container .keypad { - text-align: center; + #view-amount .scroll-content .button.no-margin { + margin: 0; } + #view-amount .scroll-content .notification-warning { + display: block; + padding: .75rem 1.25rem; + color: #856404; + background-color: #fff3cd; + border: 1px solid #ffeeba; + line-height: 1.4em; + margin-bottom: 20px; } + #view-amount .scroll-content .keypad-container { + position: relative; font-size: 18px; - font-weight: lighter; - position: absolute; - bottom: 0; - width: 100%; - color: #667; } + line-height: 2em; } @media (min-height: 667px) { - #view-amount .scroll-content .keypad-container .keypad { + #view-amount .scroll-content .keypad-container { font-size: 24px; } } - #view-amount .scroll-content .keypad-container .keypad .row { - padding: 0 !important; - margin: 0 !important; } - #view-amount .scroll-content .keypad-container .keypad .col { - line-height: 38px; } - @media (min-height: 667px) { - #view-amount .scroll-content .keypad-container .keypad .col { - line-height: 45px; } } - #view-amount .scroll-content .keypad-container .keypad .row:last-child .col { - padding-bottom: 10px; } - #view-amount .scroll-content .keypad-container .keypad .operator { - background-color: #f2f2f2; - font-weight: normal; - cursor: pointer; } - #view-amount .scroll-content .keypad-container .keypad .operator:active { - background-color: #9b9bab; } - #view-amount .scroll-content .keypad-container .keypad .operator-send { - font-weight: bolder; - color: #fff; - background-color: #494949; - font-size: 36px; - cursor: pointer; } - #view-amount .scroll-content .keypad-container .keypad .operator-send:active { - background-color: #eaeaea; } - #view-amount .scroll-content .keypad-container .keypad .digit { - cursor: pointer; - border-top: 1px solid #f2f2f2; - border-left: 1px solid #f2f2f2; - transition: all 0.1s ease; } - #view-amount .scroll-content .keypad-container .keypad .digit:active { - background-color: #f2f2f2; } @media (max-height: 480px) { - #view-amount .scroll-content .keypad-container .keypad { + #view-amount .scroll-content .keypad-container { font-size: 12px; } } + #view-amount .scroll-content .keypad-container .sendmax { + background: #262424; } + #view-amount .scroll-content .keypad-container .sendmax .button { + color: white; + background: black; + border: 1px solid #262424; + border-radius: 0; + font-size: 0.8em; + line-height: 2em; + width: 100%; } + #view-amount .scroll-content .keypad-container .sendmax .button .available-funds-amount { + color: #C9C9C9; } + #view-amount .scroll-content .keypad-container .sendmax .button:active { + background-color: #445; } + #view-amount .scroll-content .keypad-container .keypad { + text-align: center; + font-weight: lighter; + position: absolute; + bottom: 0; + width: 100%; + color: #fff; } + #view-amount .scroll-content .keypad-container .keypad .row { + padding: 0 !important; + margin: 0 !important; } + #view-amount .scroll-content .keypad-container .keypad .row:last-child .col { + padding-bottom: 10px; } + #view-amount .scroll-content .keypad-container .keypad .operator { + background-color: #f2f2f2; + font-weight: normal; + cursor: pointer; } + #view-amount .scroll-content .keypad-container .keypad .operator:active { + background-color: #9b9bab; } + #view-amount .scroll-content .keypad-container .keypad .operator-send { + font-weight: bolder; + color: #fff; + background-color: #494949; + font-size: 36px; + cursor: pointer; } + #view-amount .scroll-content .keypad-container .keypad .operator-send:active { + background-color: #eaeaea; } + #view-amount .scroll-content .keypad-container .keypad .digit { + cursor: pointer; + background-color: #000; + border: 1px solid #262424; + transition: all 0.1s ease; } + #view-amount .scroll-content .keypad-container .keypad .digit:active { + background-color: #445; } + #view-amount .scroll-content .button-primary { + background-color: #fab915; + border-radius: 0; + font-weight: bold; } + #view-amount .scroll-content .button-primary[disabled] { + background-color: #667; + opacity: 1; } + #view-amount .warning { + color: #b7664d; } + #view-amount ion-content { + margin-bottom: constant(safe-area-inset-bottom); + /* iOS 11.0 */ + margin-bottom: env(safe-area-inset-bottom); + /* iOS 11.2 */ } #view-confirm { - background-color: #ffffff; } + background-color: #494949; } #view-confirm .item-note { float: none; } #view-confirm .item-note .fee-rate { @@ -10622,6 +10767,13 @@ textarea.d-block { margin-top: -3px; } #view-confirm .toggle { cursor: pointer; } + #view-confirm ion-content { + background-color: #ffffff; } + #view-confirm slide-to-accept, #view-confirm slide-to-accept-success { + margin-bottom: constant(safe-area-inset-bottom); + /* iOS 11.0 */ + margin-bottom: env(safe-area-inset-bottom); + /* iOS 11.2 */ } #copayers-invitation .button-share { color: #fff; @@ -10728,6 +10880,8 @@ textarea.d-block { #tab-home .card-banner { padding: 0; } + #tab-home .card-banner svg { + margin: 40px auto 40px; } #tab-home .card-banner__img { width: 100%; display: block; } @@ -10951,123 +11105,156 @@ textarea.d-block { #cordova-plugin-qrscanner-still, #cordova-plugin-qrscanner-video-preview { background-color: #fab915 !important; } -#tab-send .input input { - width: 100%; - height: auto; } +#tab-send-header { + height: 300px; + width: 100%; } -#tab-send .input.item { - height: 55px; } +#tab-send-contacts { + height: calc(100vh - 300px - 50px - 44px); + /* screen size - button container - bottom-tab-menu - header top */ + overflow: scroll; } + #tab-send-contacts.ios { + height: calc(100vh - 300px - 50px - 44px - 18px); } -#tab-send .input i.left { - padding-left: 15px; } +#tab-send .input { + width: 100%; } + #tab-send .input input { + width: 100%; + height: 57px; + background: #FFF; + border: 1px #D9D9D9 solid; } + #tab-send .input input::placeholder { + color: #DCDCDC; } + #tab-send .input i.left { + padding-left: 15px; } + #tab-send .input i.qr { + cursor: pointer; + cursor: hand; + padding-right: 5px; } -#tab-send .input i.qr { - cursor: pointer; - cursor: hand; - padding-right: 5px; } - -#tab-send .qr-scan-icon { - cursor: pointer; - cursor: hand; - border-left: 1px solid #e4e4e4; - padding-left: 10px; } - -#tab-send .qr-icon { - line-height: 20px; } - -#tab-send .zero-state-cta { - padding-bottom: 3vh; - left: 0; } - -#tab-send .send-heading { - font-size: 14px; - font-weight: bold; - padding: 0 0 16px 0; - border: none; } - -#tab-send .send-header-wrapper { - padding: 10px; - background-color: white; - box-shadow: 0px 5px 10px 0px #cccccc; } - -#tab-send .search-wrapper { +#tab-send .send-wrapper { + padding: 18px 9px 9px 9px; background-color: #f2f2f2; border-radius: 3px; border: none; } - #tab-send .search-wrapper .svg#Bitcoin_Symbol { - width: 14px; } - #tab-send .search-wrapper .svg#Bitcoin_Symbol .st0 { - fill: #cccccc; } - #tab-send .search-wrapper.focus { - background: none; } - #tab-send .search-wrapper.focus .svg#Bitcoin_Symbol { - display: none; } - #tab-send .search-wrapper.focus .search-input { - padding-left: 30px; } - #tab-send .search-wrapper.focus .search-input:focus::-webkit-input-placeholder { - opacity: 0; } - -#tab-send .abs-v-center { - position: absolute; - top: 50%; - transform: translateY(-50%); } + #tab-send .send-wrapper:after { + display: block; + position: relative; + height: 1px; + background: #DEDEDE; + bottom: 0; + content: ''; + margin: 10px 6px 0px; } + #tab-send .send-wrapper.focus .search-input { + padding-left: 30px; } + #tab-send .send-wrapper.focus .search-input:focus::-webkit-input-placeholder { + opacity: 0; } + #tab-send .send-wrapper .buttons { + margin: auto; + margin-top: 18px; } + #tab-send .send-wrapper .buttons .button { + height: 60px; + line-height: 16px; + margin-right: 0px; + width: 95%; + max-width: none; + padding: 2px; } + #tab-send .send-wrapper .buttons .button-clipboard-paste { + margin-left: 0; } + #tab-send .send-wrapper .buttons .button-clipboard-paste .address { + display: none; } + #tab-send .send-wrapper .buttons .button-clipboard-paste .icon { + background: url(../img/icon-clipboard-paste.svg); + width: 15px; + height: 19px; + display: inline-block; + margin-bottom: 4px; } + #tab-send .send-wrapper .buttons .button-clipboard-paste.contains-address, #tab-send .send-wrapper .buttons .button-clipboard-paste.contains-content { + background: #FAB915; + color: #FFF !important; + border: 0; + box-shadow: 0 2px 11px 0 #C1C1C1; } + #tab-send .send-wrapper .buttons .button-clipboard-paste.contains-address .address, #tab-send .send-wrapper .buttons .button-clipboard-paste.contains-content .address { + display: none; } + #tab-send .send-wrapper .buttons .button-clipboard-paste.contains-address .icon, #tab-send .send-wrapper .buttons .button-clipboard-paste.contains-content .icon { + background: url(../img/icon-clipboard-paste-white.svg); } + #tab-send .send-wrapper .buttons .button-clipboard-paste.contains-address.contains-address .address, #tab-send .send-wrapper .buttons .button-clipboard-paste.contains-content.contains-address .address { + display: inline; + border: none; + background-color: transparent; } + #tab-send .send-wrapper .buttons .button-clipboard-paste.contains-address.contains-address .non-address, #tab-send .send-wrapper .buttons .button-clipboard-paste.contains-content.contains-address .non-address { + display: none; } + #tab-send .send-wrapper .buttons .button span { + font-size: 14px; } + #tab-send .send-wrapper .buttons .button img { + height: 16px; + width: auto; + margin: 2px 0 4px; } + #tab-send .send-wrapper .buttons .button-qr { + font-weight: bold; + max-width: none; + width: 100%; + height: 95px; + margin-top: 20px; } + #tab-send .send-wrapper .buttons .button-qr img { + vertical-align: middle; + margin-right: 12px; + width: 43px; + height: 43px; } + #tab-send .send-wrapper .buttons .button-qr span { + font-size: 19px; } #tab-send .search-input { background-color: transparent; padding-left: 30px; } -#tab-send .separator-left { - border-left: 1px solid #d9d9df; - padding-left: 10px; - height: 70%; } - -#tab-send .bitcoin-address { - border-top: none; - padding-bottom: .5rem; } - @media (max-width: 480px) { - #tab-send .bitcoin-address input { - font-size: 14px; } } - #tab-send .bitcoin-address .icon { - line-height: 31px; - padding-top: 2px; - padding-bottom: 1px; } - -#tab-send .show-more { - text-align: center; - padding: 20px; - font-size: 16px; - color: #387ef5; - font-weight: bold; } - #tab-send .sendTip { + padding-top: 5vh; text-align: center; } - #tab-send .sendTip > .item-heading { - margin-top: 10px; - background: 0 none; } - #tab-send .sendTip img { - content: url("../img/app/tab-icons/ico-send-selected.svg"); } #tab-send .sendTip .item { border-style: none; } #tab-send .sendTip > .title { font-size: 20px; - font-weight: bold; color: #445; margin: 20px 10px; } #tab-send .sendTip > .subtitle { font-size: 1rem; line-height: 1.5em; font-weight: 300; - color: #445; + color: #6F6F70; margin: 20px 1em 2.5em; } #tab-send .sendTip .big-icon-svg .bg.green { padding: 0 10px; box-shadow: none; } + #tab-send .sendTip .buttons { + margin-top: 18px; } + #tab-send .sendTip .buttons .button { + font-weight: bold; + font-size: 19px; } + #tab-send .sendTip .button-first-contact img { + height: 19px; + width: 19px; + margin-right: 6px; + vertical-align: sub; } + +#tab-send .item-heading { + line-height: 16px; + font-size: 14px; + font-weight: bold; } + #tab-send .item-heading .subtitle { + color: #B5B2B2; + font-size: 12px; + font-weight: 300; } #tab-send .list .item { + font-weight: 600; color: #444; - border-top: none; - padding-top: 1.5rem; - padding-bottom: 1.5rem; } + padding-top: 0.6rem; + padding-bottom: 0.6rem; } + #tab-send .list .item p { + font-weight: normal; } + #tab-send .list .item.item-icon-left { + padding-left: 64px; } #tab-send .list .item .big-icon-svg { left: 5px; } #tab-send .list .item .big-icon-svg > .bg { @@ -11077,7 +11264,7 @@ textarea.d-block { #tab-send .list .item:before { display: block; position: absolute; - width: 80%; + width: 100%; height: 1px; background: rgba(221, 221, 221, 0.3); top: 0; @@ -11096,6 +11283,83 @@ textarea.d-block { #tab-send .scroll { height: 100%; } +#tab-send .card.contacts { + margin: 4px 4px 16px 4px; + border-radius: 6px; + box-shadow: 0px 2px 1px 0 #C1C1C1; } + #tab-send .card.contacts .gravatar { + border-radius: 30px; + height: 40px; + width: 40px; } + +@media only screen and (min-device-width: 320px) and (max-device-width: 568px) { + #tab-send .send-wrapper .buttons .button-qr { + height: 60px; } + #tab-send .send-wrapper .buttons .button-qr span { + font-size: 16px; } + #tab-send #tab-send-header { + height: 270px; } + #tab-send #tab-send-contacts { + height: calc(100vh - 270px - 50px - 44px); + /* screen size - button container - bottom-tab-menu - header top */ } + #tab-send #tab-send-contacts.ios { + height: calc(100vh - 270px - 50px - 44px - 18px); } } + +#wallet-origin-destination .header--request { + padding: 30px 24px; + width: 100%; + height: 139px; + background-color: #fff; } + #wallet-origin-destination .header--request__title { + width: 46px; + height: 20px; + font-size: 16px; + font-weight: 600; + letter-spacing: -0.4px; + color: #000000; } + #wallet-origin-destination .header--request__amount { + font-size: 29px; + font-weight: 600; + letter-spacing: -0.7px; + color: #000000; + margin: 11px 0 2px; } + #wallet-origin-destination .header--request__amount-alt { + opacity: 0.45; + font-size: 16px; + font-weight: 600; + letter-spacing: -0.4px; + color: #000000; } + +#wallet-origin-destination .wallets-header { + margin: 20px 14px 0px; } + #wallet-origin-destination .wallets-header .title { + font-size: 16px; + font-weight: bold; + color: #445; + margin-bottom: -12px; } + +#wallet-origin-destination .card { + font-size: 12px; + margin: 20px 14px 0px; } + #wallet-origin-destination .card .item-heading { + font-weight: 600; } + #wallet-origin-destination .card .item-heading .subtitle { + font-size: 12px; } + #wallet-origin-destination .card-insufficient .wallet { + opacity: 0.4; } + #wallet-origin-destination .card-insufficient .item-heading { + font-size: 12px; } + #wallet-origin-destination .card-insufficient .item-heading > div { + display: inline-block; + vertical-align: text-bottom; } + #wallet-origin-destination .card-insufficient__dot { + display: inline-block; + width: 16px; + height: 16px; + background-color: #ec5959; + border-radius: 8px; + margin: 2px 6px 2px 2px; } + .settings .icon-bitpay { background-image: url("../img/icon-bitpay.svg"); } @@ -11604,7 +11868,8 @@ textarea.d-block { fill: white; } #walletDetails .bp-content { position: relative; - height: 100%; } + height: 100%; + height: calc(100% - env(safe-area-inset-bottom) * 2); } #walletDetails .bp-content.status-bar { margin-top: 20px; } #walletDetails .bar-header { @@ -11618,7 +11883,8 @@ textarea.d-block { background-color: inherit !important; } #walletDetails ion-content { padding-top: 0; - top: 0; } + top: 0; + margin-bottom: 16px; } #walletDetails ion-content.collapsible { margin-top: 210px; } #walletDetails ion-content .scroll { @@ -11763,6 +12029,73 @@ a.item { color: #667; font-size: 0.9em; } +#shapeshift .swap-image { + width: auto; + max-width: 400px; + max-height: 25vh; } + +#shapeshift .empty-case { + padding-top: 5vh; + text-align: center; } + #shapeshift .empty-case .item { + border-style: none; } + #shapeshift .empty-case > .title { + font-size: 20px; + color: #445; + margin: 20px 10px; } + #shapeshift .empty-case > .subtitle { + font-size: 1rem; + line-height: 1.5em; + font-weight: 300; + color: #6F6F70; + margin: 20px 1em 2.5em; } + #shapeshift .empty-case .big-icon-svg .bg.green { + padding: 0 10px; + box-shadow: none; } + #shapeshift .empty-case .buttons { + margin-top: 18px; } + #shapeshift .empty-case .buttons .button { + font-weight: bold; + font-size: 19px; } + #shapeshift .empty-case .button-first-contact img { + height: 19px; + width: 19px; + margin-right: 6px; + vertical-align: sub; } + +#shapeshift .button-shapeshift { + border-color: #FFF; + background-color: #243F5D; + color: #FFF; + border: 0px; + box-shadow: 0 2px 11px 0 #C1C1C1; } + #shapeshift .button-shapeshift:hover { + color: #FFF; + text-decoration: none; } + #shapeshift .button-shapeshift.active, #shapeshift .button-shapeshift.activated { + border-color: #FFF; + background-color: #606060; } + #shapeshift .button-shapeshift.button-clear { + border-color: transparent; + background: none; + box-shadow: none; + color: #FFF; } + #shapeshift .button-shapeshift.button-icon { + border-color: transparent; + background: none; } + #shapeshift .button-shapeshift.button-outline { + border-color: #C1C1C1; + background: transparent; + color: #C1C1C1; } + #shapeshift .button-shapeshift.button-outline.active, #shapeshift .button-shapeshift.button-outline.activated { + background-color: #C1C1C1; + box-shadow: none; + color: #fff; } + +.header.shapeshift { + background: url(../img/shapeshiftbg.jpg) center center no-repeat #28394d; + opacity: 0.99; } + #bitpayCard { background: white; } #bitpayCard .status-label { @@ -12211,7 +12544,6 @@ a.item { position: relative; height: 70px; border-color: #fab915; - background-color: #fab915; padding-top: 20px; margin-bottom: 50px; text-align: center; } @@ -13013,74 +13345,13 @@ a.item { .onboarding-illustration-backup-warning { background-image: url(../img/app/backup-warning.svg); } -#rate-card .item-heading { - font-weight: 700; } - -#rate-card .row { - border: none; } - -#rate-card .item-icon-right { - margin: 0; } - -#rate-card .feedback-flow-button { - margin-bottom: 20px; } - -#rate-card .icon-svg > img { - height: 1.8rem; - margin-bottom: 5px; } - -#send-feedback { - background-color: #ffffff; } - #send-feedback .row { - border: none; } - #send-feedback .skip { - color: rgba(255, 255, 255, 0.3); } - #send-feedback .feedback-heading { - padding-top: 20px; } - #send-feedback .feedback-title { - padding-left: 10px; - font-size: 20px; - font-weight: bold; - color: #445; } - #send-feedback .rating { - text-align: right; - padding-right: 15px; } - #send-feedback .comment { - padding: 0 20px 20px; - font-size: 1rem; - line-height: 1.5em; - font-weight: 300; - color: #445; } - #send-feedback .user-feedback { - border-top: 1px solid #f2f2f2; - border-bottom: 1px solid #f2f2f2; - padding: 20px; - width: 100%; - margin-bottom: 20px; - -webkit-appearance: none; } - #send-feedback .send-feedback-star { - height: 1rem; - margin-left: 5px; } - #send-feedback .form-fade-in { - opacity: 0; - animation-name: fadeIn; - animation-duration: .5s; - animation-fill-mode: forwards; - animation-timing-function: ease-in; } - -@keyframes fadeIn { - from { - opacity: 0; } - to { - opacity: 1; } } - -#complete { +#share-app { background-color: #fff; } - #complete .complete-layout { + #share-app .share-app-layout { display: flex; flex-direction: column; height: 100%; } - #complete .complete-layout__expand { + #share-app .share-app-layout__expand { display: flex; flex-direction: column; flex-grow: 1; @@ -13089,36 +13360,27 @@ a.item { text-align: center; opacity: 0; transition: opacity .3s; } - #complete .complete-layout__expand.fade-in { + #share-app .share-app-layout__expand.fade-in { opacity: 1; } - #complete .share-the-love-illustration { + #share-app .share-the-love-illustration { width: 5rem; margin: 1rem; } - #complete .send-feedback-illustration { - height: 16rem; - margin: 1rem; } - #complete .feedback-title { - font-size: 20px; - font-weight: bold; - color: #445; - margin: 20px 10px; - text-align: center; } - #complete .subtitle { + #share-app .subtitle { padding: 10px 30px 20px; text-align: center; color: #667; } - #complete .icon-svg > img { + #share-app .icon-svg > img { height: 16rem; width: 16rem; margin: 10px; } - #complete .socialsharing-icon { + #share-app .socialsharing-icon { display: inline-block; width: 60px; } - #complete .addressbook-icon-svg { + #share-app .addressbook-icon-svg { display: inline-block; width: 50px; height: 50px; } - #complete .share-buttons { + #share-app .share-buttons { padding: 50px 10px 30px; background-color: #f2f2f2; text-align: center; @@ -13130,7 +13392,7 @@ a.item { animation-fill-mode: forwards; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); animation-delay: .2s; } - #complete .share-buttons__action { + #share-app .share-buttons__action { display: inline-block; color: #667; font-size: .9rem; @@ -13148,38 +13410,6 @@ a.item { transform: translateY(0); opacity: 1; } } -#rate-app { - background-color: #ffffff; - text-align: center; } - #rate-app .skip-rating { - color: #445; - position: absolute; - top: 5px; - right: 10px; - padding: 15px; } - #rate-app .icon-svg > img { - width: 80px; - height: 80px; - margin-top: 15px; } - #rate-app .feedback-title { - font-size: 20px; - font-weight: bold; - color: #445; - margin: 80px 50px 10px; - text-align: center; } - #rate-app .share-the-love-illustration { - width: 5rem; - margin: 1rem; } - #rate-app .subtitle { - padding: 10px 30px 20px 40px; - color: #667; } - #rate-app .rate-buttons { - bottom: 0; - width: 100%; - position: absolute; - background-color: #f2f2f2; - padding: 30px 0 15px; } - action-sheet .bp-action-sheet__sheet { background: #fff; width: calc(100% + 1px); @@ -13800,7 +14030,11 @@ slide-to-accept-success { transform: translateY(5rem); opacity: 0; transition: transform 400ms ease, opacity 400ms ease; - transition-delay: 250ms; } + transition-delay: 250ms; + margin-bottom: constant(safe-area-inset-bottom); + /* iOS 11.0 */ + margin-bottom: env(safe-area-inset-bottom); + /* iOS 11.2 */ } slide-to-accept-success .slide-success__footer.reveal { -webkit-transform: translateY(0); transform: translateY(0); @@ -13858,16 +14092,16 @@ slide-to-accept-success { line-height: 30px; } #txp-details .head .amount-label .amount, #view-confirm .head .amount-label .amount { - font-size: 38px; - margin-bottom: .5rem; } - #txp-details .head .amount-label .amount > .unit, - #view-confirm .head .amount-label .amount > .unit { - font-family: "Roboto-Light"; } + font-size: 16px; + color: #9B9B9B; + font-family: "Roboto-Light"; } #txp-details .head .amount-label .alternative, #view-confirm .head .amount-label .alternative { - font-size: 16px; - font-family: "Roboto-Light"; - color: #9B9B9B; } + font-size: 38px; + margin-bottom: .5rem; } + #txp-details .head .amount-label .alternative > .unit, + #view-confirm .head .amount-label .alternative > .unit { + font-family: "Roboto-Light"; } #txp-details .item, #view-confirm .item { border-color: #EFEFEF; } @@ -14155,6 +14389,10 @@ wallet-selector .subheader { font-weight: bold; padding-bottom: 10px; border-bottom: 1px solid #EFEFEF; } + wallet-selector .subheader .subtitle { + color: #667; + font-size: 12px; + font-weight: 300; } wallet-selector .subheader .wallet-coin-logo { vertical-align: middle; margin-right: 5px; } @@ -15014,10 +15252,208 @@ log-options #check-bar .checkbox-icon { #cash-scan a { cursor: pointer; } +#view-review { + background-color: #494949; } + #view-review slide-to-accept, #view-review slide-to-accept-success { + margin-bottom: constant(safe-area-inset-bottom); + /* iOS 11.0 */ + margin-bottom: env(safe-area-inset-bottom); + /* iOS 11.2 */ } + #view-review .fee-summary { + position: absolute; + bottom: 92px; } + #view-review .shapeshift-banner { + box-shadow: none; } + .gravatar { border-radius: 3px; display: inline-block; } +.elastic { + width: 100%; + font-size: 14px; } + +/* +* Extends Ionic v1 item +*/ +.item.item-compact { + padding: 11px 13px; } + +.item.item-gutterless { + padding: 0; } + +.item .item-content.item-content-avatar { + min-height: 69px; + padding: 13px 11px 13px 68px; } + .item .item-content.item-content-avatar > img:first-child, + .item .item-content.item-content-avatar > i:first-child { + position: absolute; + max-width: 40px; + max-height: 40px; + width: 100%; + height: 100%; + border-radius: 50%; + left: 13px; + top: 50%; + padding: 0; + transform: translate(0, -50%); } + +.item .item-content.item-content-compact { + min-height: 0; + padding: 13px 11px; } + +.item .item-content .highlight { + color: #FAB915; } + +.item .item-content + .item-content { + padding-top: 0; } + +/* +* Extends Ionic v1 ion-content +*/ +ion-content.bg-neutral { + background-color: #F2F2F2; } + +ion-content.padded-bottom-cta { + bottom: 92px; } + +ion-content.padded-bottom-cta-with-summary { + bottom: 134px; } + +.card.card-gutter-compact { + margin: 10px 12px; } + +.header { + padding: 29px 12px 61px; + background-color: #FAB915; + color: #FFFFFF; } + .header .title { + font-size: 18px; + font-weight: 400; + line-height: 1em; + color: #FFFFFF; + text-align: center; } + .header .title + .content { + margin-top: 23px; } + .header .content { + text-align: center; } + .header .content p { + margin: 0; + line-height: 1em; + font-size: 18px; } + .header .content p.large { + font-size: 29px; + font-weight: 600; } + .header .content p + p { + margin-top: 8px; } + +.content-frame.negative-top { + margin-top: -40px; } + .content-frame.negative-top .card:first-child { + margin-top: 0; } + +.address { + background-color: #F8F8F8; + border: 0.5px solid #EDEBEB; + border-radius: 3px; + padding: 9px; + text-align: center; + font-size: 14px; + overflow: hidden; + text-overflow: ellipsis; } + .address.expanded { + white-space: pre-wrap; + word-break: break-all; } + .address .prefix { + color: #000000; } + .address .mid { + color: #919191; } + .address .suffix { + color: #000000; } + +.action-minor { + margin: 20px 14px; + font-size: 14px; } + .action-minor.mt-negative { + margin-top: 0; } + .action-minor.text-right { + text-align: right; } + .action-minor > .action-icon { + width: 15px; + height: 15px; + vertical-align: middle; + margin-right: 3px; } + .action-minor > .action-text { + vertical-align: middle; + color: #444444; } + +.expand-content-frame { + position: relative; } + .expand-content-frame .expand-content-trigger { + position: absolute; + top: 0; + transition: opacity 0.3s ease; + right: 0; } + .expand-content-frame .expand-content-trigger.expand-content-revealed { + opacity: 0; } + .expand-content-frame .expand-content { + opacity: 0; + transform-origin: 100% 0%; + transform: scale(0, 0); + transition: opacity 0.3s ease, transform 0.3s ease; } + .expand-content-frame .expand-content.expand-content-revealed { + opacity: 1; + transform: scale(1, 1); } + +.fee-summary { + position: relative; + display: flex; + justify-content: space-between; + width: 100%; + padding: 5px 12px 15px; + box-sizing: border-box; + background-color: #F2F2F2; } + .fee-summary:before { + content: ''; + position: absolute; + left: 0; + top: -15px; + width: 100%; + height: 15px; + background: linear-gradient(to bottom, rgba(242, 242, 242, 0) 0%, #f2f2f2 100%); } + .fee-summary .fee-fiat.positive { + color: #70955F; } + .fee-summary .fee-fiat.negative { + color: #C24633; } + .fee-summary .fee-crypto { + color: #A7A7A7; } + +.amount .start, +.amount .middle, +.amount .end, +.amount .currency { + display: inline-block; } + +.amount .start { + font-size: 1em; } + +.amount .middle { + font-size: 0.7857em; + margin-left: 5px; } + +.amount .end { + font-size: 0.7857em; + margin-left: 5px; } + +.amount.size-equal .middle, +.amount.size-equal .end { + font-size: 1em; } + +.amount .currency { + font-size: 1em; + margin-left: 5px; + text-transform: uppercase; } + /* This is for rules that don't yet have a home. * Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/ */ From 3ca95d6d407304f6a4789cbe38b3ad4a882d6236 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 3 Aug 2018 17:56:46 +1200 Subject: [PATCH 100/240] Wallet to wallet transaction sent successfully. The UI for it is still incomplete. --- src/js/controllers/review.controller.js | 109 ++++++++++++++++++++---- www/views/review.html | 2 +- 2 files changed, 93 insertions(+), 18 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 85950a8aa..e487c6bfc 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -7,6 +7,7 @@ angular function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, configService, feeService, gettextCatalog, $ionicLoading, $ionicModal, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, $timeout, txFormatService, walletService) { var vm = this; + vm.buttonText = ''; vm.destination = { address: '', balanceAmount: '', @@ -32,15 +33,16 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit name: '', }; vm.isCordova = platformInfo.isCordova; + vm.notReadyMessage = ''; vm.primaryAmount = ''; vm.primaryCurrency = ''; vm.usingMerchantFee = false; + vm.readyToSend = false; vm.secondaryAmount = ''; vm.secondaryCurrency = ''; - vm.thirdParty = false; vm.sendingTitle = gettextCatalog.getString('You are sending'); - vm.buttonText = ''; - + vm.thirdParty = false; + var config = null; var coin = ''; var countDown = null; @@ -102,6 +104,65 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit }); } + vm.approve = function(onSendStatusChange) { + + if (!tx || !originWallet) return; + + if ($scope.paymentExpired) { + popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.')); + $scope.sendStatus = ''; + $timeout(function() { + $scope.$apply(); + }); + return; + } + + ongoingProcess.set('creatingTx', true, onSendStatusChange); + getTxp(lodash.clone(tx), originWallet, false, function(err, txp) { + ongoingProcess.set('creatingTx', false, onSendStatusChange); + if (err) return; + + // confirm txs for more that 20usd, if not spending/touchid is enabled + function confirmTx(cb) { + if (walletService.isEncrypted(originWallet)) + return cb(); + + var amountUsd = parseFloat(txFormatService.formatToUSD(originWallet.coin, txp.amount)); + return cb(); + }; + + function publishAndSign() { + if (!originWallet.canSign() && !originWallet.isPrivKeyExternal()) { + $log.info('No signing proposal: No private key'); + + return walletService.onlyPublish(originWallet, txp, function(err) { + if (err) setSendError(err); + }, onSendStatusChange); + } + + walletService.publishAndSign(originWallet, txp, function(err, txp) { + if (err) return setSendError(err); + if (config.confirmedTxsNotifications && config.confirmedTxsNotifications.enabled) { + txConfirmNotification.subscribe(originWallet, { + txid: txp.txid + }); + } + }, onSendStatusChange); + }; + + confirmTx(function(nok) { + if (nok) { + $scope.sendStatus = ''; + $timeout(function() { + $scope.$apply(); + }); + return; + } + publishAndSign(); + }); + }); + }; + vm.chooseFeeLevel = function(tx, wallet) { if (wallet.coin == 'bch') return; @@ -149,12 +210,13 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit }; function createVanityTransaction(data) { + console.log('createVanityTransaction()'); var configFeeLevel = config.wallet.settings.feeLevel ? config.wallet.settings.feeLevel : 'normal'; // Grab stateParams tx = { amount: parseInt(data.stateParams.amount), - sendMax: data.stateParams.useSendMax == 'true' ? true : false, + sendMax: data.stateParams.sendMax === 'true' ? true : false, fromWalletId: data.stateParams.fromWalletId, toAddress: data.stateParams.toAddress, feeLevel: configFeeLevel, @@ -182,24 +244,29 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit var B = data.stateParams.coin === 'bch' ? bitcoreCash : bitcore; var networkName; try { - if (vm.destination.kind === 'wallet') { // There is a wallet-to-wallet transfer + if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer $ionicLoading.show(); var toWallet = profileService.getWallet(data.stateParams.toWalletId); // We need an address to send to, so we ask the walletService to create a new address for the toWallet. - walletService.getAddress(toWallet, true, function (err, addr) { + console.log('Getting address for wallet...'); + walletService.getAddress(toWallet, true, function onWalletAddress(err, addr) { + console.log('getAddress cb called', err); $ionicLoading.hide(); tx.toAddress = addr; networkName = (new B.Address(tx.toAddress)).network.name; tx.network = networkName; + console.log('calling setupTx() for wallet.'); setupTx(tx); }); } else { // This is a Wallet-to-address transfer networkName = (new B.Address(tx.toAddress)).network.name; tx.network = networkName; + console.log('calling setupTx() for address.'); setupTx(tx); } } catch (e) { + console.error('Error setting up tx', e); var message = gettextCatalog.getString('Invalid address'); popupService.showAlert(null, message, function () { $ionicHistory.nextViewOptions({ @@ -326,10 +393,8 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit // Check if the recipient is a contact addressbookService.get(originCoin + address, function(err, contact) { if (!err && contact) { - console.log('destination is contact'); handleDestinationAsContact(contact); } else { - console.log('destination is address'); vm.destination.address = address; vm.destination.kind = 'address'; } @@ -352,7 +417,6 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit return; } - console.log('destination is wallet'); var destinationWallet = profileService.getWallet(destinationWalletId); vm.destination.coin = destinationWallet.coin; vm.destination.color = destinationWallet.color; @@ -434,8 +498,19 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } } + function setNotReady(msg, criticalError) { + vn.readyToSend = false; + vm.notReadyMessage = msg; + $scope.criticalError = criticalError; + $log.warn('Not ready to make the payment:' + msg); + $timeout(function() { + $scope.$apply(); + }); + }; + function setSendError(msg) { $scope.sendStatus = ''; + vm.readyToSend = false; $timeout(function() { $scope.$apply(); }); @@ -514,12 +589,6 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit // updateAmount(); // refresh(); - // End of quick refresh, before wallet is selected. - if (!wallet) { - ongoingProcess.set('calculatingFee', false); - return cb(); - } - var feeServiceLevel = usingMerchantFee && originWallet.coin == 'btc' ? 'urgent' : tx.feeLevel; feeService.getFeeRate(originWallet.coin, tx.network, feeServiceLevel, function(err, feeRate) { if (err) { @@ -553,7 +622,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit if (tx.sendMax && sendMaxInfo.amount == 0) { ongoingProcess.set('calculatingFee', false); - setNoWallet(gettextCatalog.getString('Insufficient confirmed funds')); + setNotReady(gettextCatalog.getString('Insufficient confirmed funds')); popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee')); return cb('no_funds'); } @@ -570,15 +639,18 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit // txp already generated for this wallet? if (tx.txp[wallet.id]) { ongoingProcess.set('calculatingFee', false); + vm.readyToSend = true; updateSendAmounts(); + $scope.$apply(); return cb(); } + console.log('calling getTxp() from getSendMaxInfo cb.'); getTxp(lodash.clone(tx), wallet, opts.dryRun, function(err, txp) { ongoingProcess.set('calculatingFee', false); if (err) { if (err.message == 'Insufficient funds') { - setNoWallet(gettextCatalog.getString('Insufficient funds')); + setNotReady(gettextCatalog.getString('Insufficient funds')); popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee')); return cb('no_funds'); } else @@ -611,7 +683,10 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit tx.txp[wallet.id] = txp; $log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx); + vm.readyToSend = true; updateSendAmounts(); + console.log('readyToSend:', vm.readyToSend); + $scope.$apply(); return cb(); }); diff --git a/www/views/review.html b/www/views/review.html index c1f37baf9..0b20bd7fc 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -73,7 +73,7 @@
-
Fee: Less than 1 cent
+
Fee: Less than 1 cent
Fee: {{vm.feeFiat}} {{vm.feeCurrency}}
{{vm.feeCrypto}} {{vm.origin.currency}} From 5973b30551fe3c50820c1b2ca720813163590263 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 3 Aug 2018 19:14:33 +1200 Subject: [PATCH 101/240] Sends wallet to wallet transaction with sound. --- src/js/controllers/review.controller.js | 117 +++++++++++++++++------- www/views/review.html | 34 +++---- 2 files changed, 99 insertions(+), 52 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index e487c6bfc..833c8a330 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -4,7 +4,7 @@ angular .module('copayApp.controllers') .controller('reviewController', reviewController); -function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, configService, feeService, gettextCatalog, $ionicLoading, $ionicModal, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, $timeout, txFormatService, walletService) { +function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, configService, feeService, gettextCatalog, $ionicLoading, $ionicModal, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { var vm = this; vm.buttonText = ''; @@ -24,16 +24,15 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.fiatCurrency = ''; vm.feeIsHigh = false; vm.feeLessThanACent = false; + vm.isCordova = platformInfo.isCordova; + vm.notReadyMessage = ''; vm.origin = { balanceAmount: '', balanceCurrency: '', - color: '', currency: '', currencyColor: '', - name: '', }; - vm.isCordova = platformInfo.isCordova; - vm.notReadyMessage = ''; + vm.originWallet = null; vm.primaryAmount = ''; vm.primaryCurrency = ''; vm.usingMerchantFee = false; @@ -41,7 +40,9 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.secondaryAmount = ''; vm.secondaryCurrency = ''; vm.sendingTitle = gettextCatalog.getString('You are sending'); + vm.sendStatus = ''; vm.thirdParty = false; + vm.wallet = null; var config = null; var coin = ''; @@ -50,7 +51,6 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit var usingMerchantFee = false; var destinationWalletId = ''; var originWalletId = ''; - var originWallet; var priceDisplayIsFiat = true; var satoshis = null; var toAddress = ''; @@ -68,11 +68,9 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit satoshis = parseInt(data.stateParams.amount, 10); toAddress = data.stateParams.toAddr; - originWallet = profileService.getWallet(originWalletId); - vm.origin.currency = originWallet.coin.toUpperCase(); - vm.origin.color = originWallet.color; - vm.origin.name = originWallet.name; - coin = originWallet.coin; + vm.originWallet = profileService.getWallet(originWalletId); + vm.origin.currency = vm.originWallet.coin.toUpperCase(); + coin = vm.originWallet.coin; if (data.stateParams.thirdParty) { vm.thirdParty = JSON.parse(data.stateParams.thirdParty); // Parse stringified JSON-object @@ -93,20 +91,20 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } else { config = configCache; priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; - vm.origin.currencyColor = originWallet.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; + vm.origin.currencyColor = vm.originWallet.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; unitFromSat = 1 / config.wallet.settings.unitToSatoshi; } updateSendAmounts(); - getOriginWalletBalance(originWallet); + getOriginWalletBalance(vm.originWallet); handleDestinationAsAddress(toAddress, coin); handleDestinationAsWallet(data.stateParams.toWalletId); createVanityTransaction(data); }); } - vm.approve = function(onSendStatusChange) { + vm.approve = function() { - if (!tx || !originWallet) return; + if (!tx || !vm.originWallet) return; if ($scope.paymentExpired) { popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.')); @@ -117,42 +115,42 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit return; } - ongoingProcess.set('creatingTx', true, onSendStatusChange); - getTxp(lodash.clone(tx), originWallet, false, function(err, txp) { - ongoingProcess.set('creatingTx', false, onSendStatusChange); + ongoingProcess.set('creatingTx', true, statusChangeHandler); + getTxp(lodash.clone(tx), vm.originWallet, false, function(err, txp) { + ongoingProcess.set('creatingTx', false, statusChangeHandler); if (err) return; // confirm txs for more that 20usd, if not spending/touchid is enabled function confirmTx(cb) { - if (walletService.isEncrypted(originWallet)) + if (walletService.isEncrypted(vm.originWallet)) return cb(); - var amountUsd = parseFloat(txFormatService.formatToUSD(originWallet.coin, txp.amount)); + var amountUsd = parseFloat(txFormatService.formatToUSD(vm.originWallet.coin, txp.amount)); return cb(); }; function publishAndSign() { - if (!originWallet.canSign() && !originWallet.isPrivKeyExternal()) { + if (!vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) { $log.info('No signing proposal: No private key'); - return walletService.onlyPublish(originWallet, txp, function(err) { + return walletService.onlyPublish(vm.originWallet, txp, function(err) { if (err) setSendError(err); - }, onSendStatusChange); + }, statusChangeHandler); } - walletService.publishAndSign(originWallet, txp, function(err, txp) { + walletService.publishAndSign(vm.originWallet, txp, function(err, txp) { if (err) return setSendError(err); if (config.confirmedTxsNotifications && config.confirmedTxsNotifications.enabled) { - txConfirmNotification.subscribe(originWallet, { + txConfirmNotification.subscribe(vm.originWallet, { txid: txp.txid }); } - }, onSendStatusChange); + }, statusChangeHandler); }; confirmTx(function(nok) { if (nok) { - $scope.sendStatus = ''; + vm.sendStatus = ''; $timeout(function() { $scope.$apply(); }); @@ -172,7 +170,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit scope.network = tx.network; scope.feeLevel = tx.feeLevel; scope.noSave = true; - scope.coin = originWallet.coin; + scope.coin = vm.originWallet.coin; if (usingCustomFee) { scope.customFeePerKB = tx.feeRate; @@ -202,7 +200,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit tx.feeLevel = newFeeLevel; if (usingCustomFee) tx.feeRate = parseInt(customFeePerKB); - updateTx(tx, originWallet, { + updateTx(tx, vm.originWallet, { clearCache: true, dryRun: true }, function() {}); @@ -228,7 +226,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit toEmail: vm.destination.email || null, toColor: vm.destination.color || null, network: false, - coin: originWallet.coin, + coin: vm.originWallet.coin, txp: {}, }; @@ -281,7 +279,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } } function getOriginWalletBalance(originWallet) { - var balanceText = getWalletBalanceDisplayText(originWallet); + var balanceText = getWalletBalanceDisplayText(vm.originWallet); vm.origin.balanceAmount = balanceText.amount; vm.origin.balanceCurrency = balanceText.currency; } @@ -476,6 +474,18 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit }); } + vm.onSuccessConfirm = function() { + vm.sendStatus = ''; + $ionicHistory.nextViewOptions({ + disableAnimate: true, + historyRoot: true + }); + $state.go('tabs.send').then(function() { + $ionicHistory.clearHistory(); + $state.transitionTo('tabs.home'); + }); + }; + function setButtonText(isMultisig, isPayPro) { if (isPayPro) { if (vm.isCordova) { @@ -536,12 +546,12 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.showAddress = false; - setButtonText(originWallet.credentials.m > 1, !!tx.paypro); + setButtonText(vm.originWallet.credentials.m > 1, !!tx.paypro); if (tx.paypro) _paymentTimeControl(tx.paypro.expires); - updateTx(tx, originWallet, { + updateTx(tx, vm.originWallet, { dryRun: true }, function(err) { $timeout(function() { @@ -562,6 +572,43 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit // } // }); } + + function statusChangeHandler(processName, showName, isOn) { + $log.debug('statusChangeHandler: ', processName, showName, isOn); + if ( + ( + processName === 'broadcastingTx' || + ((processName === 'signingTx') && vm.originWallet.m > 1) || + (processName == 'sendingTx' && !vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) + ) && !isOn) { + $scope.sendStatus = 'success'; + + if ($state.current.name === "tabs.send.review") { // 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'); + } + + var channel = "firebase"; + if (platformInfo.isNW) { + channel = "ga"; + } + // When displaying Fiat, if the formatting fails, the crypto will be the primary amount. + var amount = priceDisplayIsFiat ? vm.secondaryAmount || vm.primaryAmount : vm.primaryAmount; + var log = new window.BitAnalytics.LogEvent("transfer_success", [{ + "coin": vm.originWallet.coin, + "type": "outgoing", + "amount": amount, + "fees": vm.feeCrypto + }], [channel, "adjust"]); + window.BitAnalytics.LogEventHandlers.postEvent(log); + + $timeout(function() { + $scope.$digest(); + }, 100); + } else if (showName) { + $scope.sendStatus = showName; + } + }; + function updateTx(tx, wallet, opts, cb) { ongoingProcess.set('calculatingFee', true); @@ -589,8 +636,8 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit // updateAmount(); // refresh(); - var feeServiceLevel = usingMerchantFee && originWallet.coin == 'btc' ? 'urgent' : tx.feeLevel; - feeService.getFeeRate(originWallet.coin, tx.network, feeServiceLevel, function(err, feeRate) { + var feeServiceLevel = usingMerchantFee && vm.originWallet.coin == 'btc' ? 'urgent' : tx.feeLevel; + feeService.getFeeRate(vm.originWallet.coin, tx.network, feeServiceLevel, function(err, feeRate) { if (err) { ongoingProcess.set('calculatingFee', false); return cb(err); diff --git a/www/views/review.html b/www/views/review.html index 0b20bd7fc..4b1b47a98 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -25,10 +25,10 @@
-

{{vm.origin.name}} ({{vm.origin.currency}})

+

{{vm.originWallet.name}} ({{vm.origin.currency}})

{{vm.origin.balanceAmount}} {{vm.origin.balanceCurrency}}

@@ -73,7 +73,7 @@
-
Fee: Less than 1 cent
+
Fee: Less than 1 cent
Fee: {{vm.feeFiat}} {{vm.feeCurrency}}
{{vm.feeCrypto}} {{vm.origin.currency}} @@ -81,25 +81,25 @@
- {{buttonText}} + ng-click="vm.approve()" + ng-if="!vm.isCordova" + click-send-status="vm.sendStatus" + is-disabled="!vm.readyToSend"> + {{vm.buttonText}} - {{buttonText}} + slide-on-confirm="vm.approve()" + slide-send-status="vm.sendStatus" + is-disabled="!vm.readyToSend"> + {{vm.buttonText}} - Payment Sent - Proposal Created - Transaction Created + Payment Sent + Proposal Created + Transaction Created From 671b46da41867e08de840abc643cfcf411f0fa92 Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Fri, 3 Aug 2018 15:32:54 +0800 Subject: [PATCH 102/240] Changes address component name to address-frame --- .../components/{address.scss => address-frame.scss} | 2 +- src/sass/components/components.scss | 2 +- www/css/main.css | 10 +++++----- www/views/review.html | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) rename src/sass/components/{address.scss => address-frame.scss} (95%) diff --git a/src/sass/components/address.scss b/src/sass/components/address-frame.scss similarity index 95% rename from src/sass/components/address.scss rename to src/sass/components/address-frame.scss index 2848deb82..b06ce8bea 100644 --- a/src/sass/components/address.scss +++ b/src/sass/components/address-frame.scss @@ -1,4 +1,4 @@ -.address { +.address-frame { background-color: #F8F8F8; border: 0.5px solid #EDEBEB; border-radius: 3px; diff --git a/src/sass/components/components.scss b/src/sass/components/components.scss index eae56e786..0af55e5be 100644 --- a/src/sass/components/components.scss +++ b/src/sass/components/components.scss @@ -4,7 +4,7 @@ @import "header"; @import "content-frame"; -@import "address"; +@import "address-frame"; @import "action-minor"; @import "expand-content"; @import "fee-summary"; diff --git a/www/css/main.css b/www/css/main.css index e39d96cf3..df39fda50 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -15352,7 +15352,7 @@ ion-content.padded-bottom-cta-with-summary { .content-frame.negative-top .card:first-child { margin-top: 0; } -.address { +.address-frame { background-color: #F8F8F8; border: 0.5px solid #EDEBEB; border-radius: 3px; @@ -15361,14 +15361,14 @@ ion-content.padded-bottom-cta-with-summary { font-size: 14px; overflow: hidden; text-overflow: ellipsis; } - .address.expanded { + .address-frame.expanded { white-space: pre-wrap; word-break: break-all; } - .address .prefix { + .address-frame .prefix { color: #000000; } - .address .mid { + .address-frame .mid { color: #919191; } - .address .suffix { + .address-frame .suffix { color: #000000; } .action-minor { diff --git a/www/views/review.html b/www/views/review.html index 4b1b47a98..2e7823f69 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -48,7 +48,7 @@

{{vm.destination.balanceAmount}} {{vm.destination.balanceCurrency}}

-
qz9cqq5pryv9hnqwa8q8mccmynk9uf4vlu5nxerpzmc
+
qz9cqq5pryv9hnqwa8q8mccmynk9uf4vlu5nxerpzmc
From 2f62092fd8994941dc3252aaad48e45aeea33aab Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 3 Aug 2018 19:59:25 +1200 Subject: [PATCH 103/240] Payment sent message is displayed. --- src/js/controllers/review.controller.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 833c8a330..9285436f5 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -4,7 +4,7 @@ angular .module('copayApp.controllers') .controller('reviewController', reviewController); -function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, configService, feeService, gettextCatalog, $ionicLoading, $ionicModal, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { +function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, configService, feeService, gettextCatalog, $ionicHistory, $ionicLoading, $ionicModal, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { var vm = this; vm.buttonText = ''; @@ -581,7 +581,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit ((processName === 'signingTx') && vm.originWallet.m > 1) || (processName == 'sendingTx' && !vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) ) && !isOn) { - $scope.sendStatus = 'success'; + vm.sendStatus = 'success'; if ($state.current.name === "tabs.send.review") { // 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'); @@ -605,7 +605,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit $scope.$digest(); }, 100); } else if (showName) { - $scope.sendStatus = showName; + vm.sendStatus = showName; } }; From 8148ad66b4c8647af82c93a2d4faff07d835bf4b Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 3 Aug 2018 20:11:14 +1200 Subject: [PATCH 104/240] Payments to contacts now work. --- src/js/controllers/review.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 9285436f5..b3addc64f 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -216,7 +216,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit amount: parseInt(data.stateParams.amount), sendMax: data.stateParams.sendMax === 'true' ? true : false, fromWalletId: data.stateParams.fromWalletId, - toAddress: data.stateParams.toAddress, + toAddress: data.stateParams.toAddr, feeLevel: configFeeLevel, spendUnconfirmed: config.wallet.spendUnconfirmed, From cebe9507f169d42451c5a16391e0e260dfb8b325 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Fri, 3 Aug 2018 13:13:29 +0200 Subject: [PATCH 105/240] Make the "to"-address field work in new style + BTC transactions fix --- src/js/controllers/review.controller.js | 2 +- src/js/services/incomingData.js | 2 +- www/views/review.html | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index b3addc64f..7d8efbfac 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -531,7 +531,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit if (tx.coin === 'bch') { tx.displayAddress = bitcoinCashJsService.readAddress(tx.toAddress).cashaddr; } else { - tx.displayAddress = entry.address; + tx.displayAddress = tx.toAddress; } addressbookService.get(tx.coin+tx.toAddress, function(err, addr) { // Check if the recipient is a contact diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index fb8d8868a..55a453dab 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -386,7 +386,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat 'notify': $state.current.name == 'tabs.send' ? false : true }); $timeout(function() { - $state.transitionTo('tabs.send.amount', { + $state.transitionTo('tabs.send.origin', { toAddress: toAddress, coin: coin, noPrefix: 1 diff --git a/www/views/review.html b/www/views/review.html index 2e7823f69..2f8a4ee3a 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -48,7 +48,9 @@

{{vm.destination.balanceAmount}} {{vm.destination.balanceCurrency}}

-
qz9cqq5pryv9hnqwa8q8mccmynk9uf4vlu5nxerpzmc
+
+ {{vm.destination.address.substring(0,5)}}{{vm.destination.address.substring(5,vm.destination.address.length-4)}}{{vm.destination.address.substring(vm.destination.address.length-4)}} +
From d7fabc36422ae698fa56fd86163e1c053376e5fd Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Fri, 3 Aug 2018 14:28:56 +0200 Subject: [PATCH 106/240] header + default colors --- src/js/controllers/review.controller.js | 12 +++++++----- src/sass/components/header.scss | 5 ++++- src/sass/variables.scss | 1 + www/views/review.html | 4 ++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 7d8efbfac..82ef2aa30 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -45,6 +45,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.wallet = null; var config = null; + var defaults = {}; var coin = ''; var countDown = null; var usingCustomFee = false; @@ -63,7 +64,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit function onBeforeEnter(event, data) { - + defaults = configService.getDefaults(); originWalletId = data.stateParams.fromWalletId; satoshis = parseInt(data.stateParams.amount, 10); toAddress = data.stateParams.toAddr; @@ -91,7 +92,8 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } else { config = configCache; priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; - vm.origin.currencyColor = vm.originWallet.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; + vm.origin.currencyColor = (vm.originWallet.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor); + console.log("coin", vm.originWallet.coin, vm.origin.currencyColor, config.bitcoinWalletColor, vm.originWallet.coin === 'btc'); unitFromSat = 1 / config.wallet.settings.unitToSatoshi; } updateSendAmounts(); @@ -404,7 +406,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.destination.kind = 'contact'; vm.destination.name = contact.name; vm.destination.email = contact.email; - vm.destination.color = contact.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; + vm.destination.color = contact.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor; vm.destination.currency = contact.coin.toUpperCase(); vm.destination.currencyColor = vm.destination.color; } @@ -422,8 +424,8 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.destination.kind = 'wallet'; vm.destination.name = destinationWallet.name; - if (config) { - vm.destination.currencyColor = vm.destination.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; + if (defaults) { + vm.destination.currencyColor = vm.destination.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor; } var balanceText = getWalletBalanceDisplayText(destinationWallet); diff --git a/src/sass/components/header.scss b/src/sass/components/header.scss index fad1f1812..d44c93b60 100644 --- a/src/sass/components/header.scss +++ b/src/sass/components/header.scss @@ -1,6 +1,9 @@ .header { padding: 29px 12px 61px; - background-color: #FAB915; + background-color: $v-bitcoin-orange; + &.btc { + background-color: $v-bitcoin-core; + } color: #FFFFFF; .title { diff --git a/src/sass/variables.scss b/src/sass/variables.scss index 67d5a044b..49ee6ae89 100644 --- a/src/sass/variables.scss +++ b/src/sass/variables.scss @@ -8,6 +8,7 @@ $v-font-family-light: "Roboto-Light", sans-serif- /* Colors */ $v-bitcoin-orange: #fab915 !default; +$v-bitcoin-core: #535353 !default; $v-off-black: #262424; $v-dark-gray: #445 !default; diff --git a/www/views/review.html b/www/views/review.html index 2f8a4ee3a..631d1bec4 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -1,5 +1,5 @@ - + {{'Review Transaction' | translate}} @@ -10,7 +10,7 @@
-
+

{{vm.sendingTitle}}

{{vm.primaryAmount}} {{vm.primaryCurrency}}

From fb4bc1563c6b9cc378a184aa4bf5221bd55f26eb Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 6 Aug 2018 12:04:14 +1200 Subject: [PATCH 107/240] Starting to integrate BIP70 payment protocol into new send flow. --- i18n/po/template.pot | 6 +- .../controllers/walletSelectorController.js | 35 ++++++--- src/js/services/incomingData.js | 22 ++++-- src/js/services/paypro.service.js | 76 +++++++++++++++++++ src/js/services/payproService.js | 69 ----------------- 5 files changed, 120 insertions(+), 88 deletions(-) create mode 100644 src/js/services/paypro.service.js delete mode 100644 src/js/services/payproService.js diff --git a/i18n/po/template.pot b/i18n/po/template.pot index ecf44efe9..358145a1c 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -2179,7 +2179,7 @@ msgid "Payment details" msgstr "" #: www/views/modals/paypro.html:6 -msgid "Payment request" +msgid "Payment Request" msgstr "" #: www/views/mercadoLibreCards.html:22 @@ -3760,4 +3760,8 @@ msgstr "" #: www/views/review.html:69 msgid "Less than 1 cent" +msgstr "" + +#: src/js/services/incomingData.js:129 +msgid "This invoice is no longer accepting payments" msgstr "" \ No newline at end of file diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index f73bd4830..667bff3cb 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -26,8 +26,8 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu $scope.coin = $scope.params.coin; // Contacts have a coin embedded } - if ($scope.params.amount) { // There is an amount, so presume that it a payment request - $scope.sendFlowTitle = gettextCatalog.getString('Payment request'); + if ($scope.params.amount) { // There is an amount, so presume that it is a payment request + $scope.sendFlowTitle = gettextCatalog.getString('Payment Request'); $scope.specificAmount = $scope.specificAlternativeAmount = ''; $scope.requestAmount = (($state.params.amount) * (1 / config.unitToSatoshi)).toFixed(config.unitDecimals); $scope.isPaymentRequest = true; @@ -54,16 +54,9 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu } if ($scope.thirdParty) { - // Third party services specific logic - - if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the - if ($scope.coin === 'bch') { - $scope.coin = 'btc'; - } else { - $scope.coin = 'bch'; - } - } + handleThirdPartyIfBip70PaymentProtocol(); + handleThirdPartyIfShapeshift(); } if (!$scope.coin || $scope.coin === 'bch') { // if no specific coin is set or coin is set to bch @@ -87,6 +80,26 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu } } + function handleThirdPartyIfBip70PaymentProtocol() { + if ($scope.thirdParty.id === 'bip70PaymentProtocol') { + $scope.coin = $scope.thirdParty.coin; + $scope.requestAmount = $scope.thirdParty.details. + console.log('paypro details:', $scope.thirdParty.details); + } + } + + function handleThirdPartyIfShapeshift() { + if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the + if ($scope.coin === 'bch') { + $scope.coin = 'btc'; + } else { + $scope.coin = 'bch'; + } + } + } + + + $scope.useWallet = function(wallet) { if ($scope.type === 'origin') { // we're on the origin screen, set wallet to send from $scope.params['fromWalletId'] = wallet.id; diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index 55a453dab..72b1a70b5 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -121,9 +121,14 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat var coin = data.indexOf('bitcoincash') >= 0 ? 'bch' : 'btc'; data = decodeURIComponent(data.replace(/bitcoin(cash)?:\?r=/, '')); if (coin == 'bch') { - payproService.getPayProDetailsViaHttp(data, function(err, details) { + payproService.getPayProDetailsViaHttp(data, function onGetPayProDetailsViaHttp(err, details) { if (err) { - popupService.showAlert(gettextCatalog.getString('Error'), err) + var message = err.toString(); + if (typeof err.data === 'string') { + // i.e. 'This invoice is no longer accepting payments' + message = gettextCatalog.getString(err.data); + } + popupService.showAlert(gettextCatalog.getString('Error'), message) } else { handlePayPro(createBchPayProObject(details), coin); } @@ -415,12 +420,15 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat } function handlePayPro(payProDetails, coin) { + var thirdPartyData = { + id: 'bip70PaymentProtocol', + coin: coin, + details: payProDetails + }; var stateParams = { amount: payProDetails.amount, - toAddress: payProDetails.toAddress, - description: payProDetails.memo, - paypro: payProDetails, - coin: coin, + toAddr: payProDetails.toAddress, + thirdParty: JSON.stringify(thirdPartyData) }; // fee @@ -434,7 +442,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat 'notify': $state.current.name == 'tabs.send' ? false : true }).then(function() { $timeout(function() { - $state.transitionTo('tabs.send.confirm', stateParams); + $state.transitionTo('tabs.send.origin', stateParams); }); }); } diff --git a/src/js/services/paypro.service.js b/src/js/services/paypro.service.js new file mode 100644 index 000000000..4cdc78e7c --- /dev/null +++ b/src/js/services/paypro.service.js @@ -0,0 +1,76 @@ +'use strict'; + +angular + .module('copayApp.services') + .factory('payproService', payproService); + +function payproService(gettextCatalog, $http, $log, ongoingProcess, platformInfo, profileService) { + + var service = { + getPayProDetails: getPayProDetails, + getPayProDetailsViaHttp: getPayProDetailsViaHttp, + broadcastBchTx: broadcastBchTx + }; + + return service; + + function getPayProDetails(uri, coin, cb, disableLoader) { + if (!cb) cb = function() {}; + + var wallet = profileService.getWallets({ + onlyComplete: true, + coin: coin + })[0]; + + if (!wallet) return cb(); + + if (platformInfo.isChromeApp) { + return cb(gettextCatalog.getString('Payment Protocol not supported on Chrome App')); + } + + $log.debug('Fetch PayPro Request...', uri); + + if (!disableLoader) ongoingProcess.set('fetchingPayPro', true); + + wallet.fetchPayPro({ + payProUrl: uri, + }, function(err, paypro) { + if (!disableLoader) ongoingProcess.set('fetchingPayPro', false); + if (err) return cb(gettextCatalog.getString('Could Not Fetch Payment: Check if it is still valid')); + else if (!paypro.verified) { + $log.warn('Failed to verify payment protocol signatures'); + return cb(gettextCatalog.getString('Payment Protocol Invalid')); + } + return cb(null, paypro); + }); + } + + function getPayProDetailsViaHttp(uri, cb) { + var config = { + headers: {'Accept': 'application/payment-request'} + }; + $http.get(uri, config).then(function onGetPayProDetailsSuccess(response) { + return cb(null, response.data); + }, function onGetPayProDetailsError(error) { + return cb(error, null); + }); + } + + function broadcastBchTx(signedTxp, cb) { + var config = { + headers: {'Content-Type': 'application/payment'} + }; + + var data = { + currency: 'BCH', + transactions: [signedTxp.raw] + }; + + $http.post(signedTxp.payProUrl, data, config).then(function(response) { + signedTxp.response = response.data; + return cb(null, signedTxp); + }, function(error) { + return cb(error.data, null); + }); + } +} diff --git a/src/js/services/payproService.js b/src/js/services/payproService.js deleted file mode 100644 index f0814cc0f..000000000 --- a/src/js/services/payproService.js +++ /dev/null @@ -1,69 +0,0 @@ -'use strict'; - -angular.module('copayApp.services').factory('payproService', - function(profileService, platformInfo, gettextCatalog, ongoingProcess, $log, $http) { - - var ret = {}; - - ret.getPayProDetails = function(uri, coin, cb, disableLoader) { - if (!cb) cb = function() {}; - - var wallet = profileService.getWallets({ - onlyComplete: true, - coin: coin - })[0]; - - if (!wallet) return cb(); - - if (platformInfo.isChromeApp) { - return cb(gettextCatalog.getString('Payment Protocol not supported on Chrome App')); - } - - $log.debug('Fetch PayPro Request...', uri); - - if (!disableLoader) ongoingProcess.set('fetchingPayPro', true); - - wallet.fetchPayPro({ - payProUrl: uri, - }, function(err, paypro) { - if (!disableLoader) ongoingProcess.set('fetchingPayPro', false); - if (err) return cb(gettextCatalog.getString('Could Not Fetch Payment: Check if it is still valid')); - else if (!paypro.verified) { - $log.warn('Failed to verify payment protocol signatures'); - return cb(gettextCatalog.getString('Payment Protocol Invalid')); - } - return cb(null, paypro); - }); - }; - - ret.getPayProDetailsViaHttp = function(uri, cb) { - var config = { - headers: {'Accept': 'application/payment-request'} - }; - $http.get(uri, config).then(function(response) { - return cb(null, response.data); - }, function(error) { - return cb(error, null); - }); - } - - ret.broadcastBchTx = function(signedTxp, cb) { - var config = { - headers: {'Content-Type': 'application/payment'} - }; - - var data = { - currency: 'BCH', - transactions: [signedTxp.raw] - }; - - $http.post(signedTxp.payProUrl, data, config).then(function(response) { - signedTxp.response = response.data; - return cb(null, signedTxp); - }, function(error) { - return cb(error.data, null); - }); - } - - return ret; - }); From 4203a449d98827efdb8f12e156708d1536f27d5e Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 6 Aug 2018 13:39:03 +1200 Subject: [PATCH 108/240] Displaying requested amount in origin screen. --- .../controllers/walletSelectorController.js | 46 +++++++++++++++++-- src/js/services/paypro.service.js | 2 + www/views/walletSelector.html | 4 +- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index 667bff3cb..541794b22 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -1,9 +1,17 @@ 'use strict'; -angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $rootScope, $state, $log, $ionicHistory, configService, gettextCatalog, profileService) { +angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $rootScope, $state, $log, $ionicHistory, configService, gettextCatalog, profileService, txFormatService) { + + var priceDisplayAsFiat = false; + var requestedSatoshis = 0; + var unitDecimals = 0; + var unitsFromSatoshis = 0; $scope.$on("$ionicView.beforeEnter", function(event, data) { var config = configService.getSync().wallet.settings; + priceDisplayAsFiat = config.priceDisplay === 'fiat'; + unitDecimals = config.unitDecimals; + unitsFromSatoshis = 1 / config.unitToSatoshi; switch($state.current.name) { case 'tabs.send.wallet-to-wallet': @@ -29,7 +37,8 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu if ($scope.params.amount) { // There is an amount, so presume that it is a payment request $scope.sendFlowTitle = gettextCatalog.getString('Payment Request'); $scope.specificAmount = $scope.specificAlternativeAmount = ''; - $scope.requestAmount = (($state.params.amount) * (1 / config.unitToSatoshi)).toFixed(config.unitDecimals); + //requestedAmountCrypto = (($state.params.amount) * (1 / config.unitToSatoshi)).toFixed(config.unitDecimals); + requestedSatoshis = $state.params.amount; $scope.isPaymentRequest = true; } if ($scope.params.thirdParty) { @@ -65,8 +74,39 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu if (!$scope.coin || $scope.coin === 'btc') { // if no specific coin is set or coin is set btc $scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: $scope.type === 'origin'}); } + + formatRequestedAmount(); }); + function formatRequestedAmount() { + if (requestedSatoshis) { + var cryptoAmount = (unitsFromSatoshis * requestedSatoshis).toFixed(unitDecimals); + var cryptoCoin = $scope.coin.toUpperCase(); + + txFormatService.formatAlternativeStr($scope.coin, requestedSatoshis, function onFormatAlternativeStr(formatted){ + if (formatted) { + var fiatParts = formatted.split(' '); + var fiatAmount = fiatParts[0]; + var fiatCurrrency = fiatParts.length > 1 ? fiatParts[1] : ''; + + if (priceDisplayAsFiat) { + $scope.requestAmount = fiatAmount; + $scope.requestCurrency = fiatCurrrency; + + $scope.requestAmountSecondary = cryptoAmount; + $scope.requestCurrencySecondary = cryptoCoin; + } else { + $scope.requestAmount = cryptoAmount; + $scope.requestCurrency = cryptoCoin; + + $scope.requestAmountSecondary = fiatAmount; + $scope.requestCurrencySecondary = fiatCurrrency; + } + } + }); + } + } + function getNextStep() { if ($scope.thirdParty) { $scope.params.thirdParty = JSON.stringify($scope.thirdParty) // re-stringify JSON-object @@ -83,7 +123,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu function handleThirdPartyIfBip70PaymentProtocol() { if ($scope.thirdParty.id === 'bip70PaymentProtocol') { $scope.coin = $scope.thirdParty.coin; - $scope.requestAmount = $scope.thirdParty.details. + $scope.requestAmount = unitsFromSatoshis * $scope.thirdParty.details.amount; console.log('paypro details:', $scope.thirdParty.details); } } diff --git a/src/js/services/paypro.service.js b/src/js/services/paypro.service.js index 4cdc78e7c..8f8db5f5b 100644 --- a/src/js/services/paypro.service.js +++ b/src/js/services/paypro.service.js @@ -1,5 +1,7 @@ 'use strict'; +// For BIP70 Payment Protocol + angular .module('copayApp.services') .factory('payproService', payproService); diff --git a/www/views/walletSelector.html b/www/views/walletSelector.html index 49a7ba208..3eecb1204 100644 --- a/www/views/walletSelector.html +++ b/www/views/walletSelector.html @@ -7,8 +7,8 @@
Paying
-
$... USD
-
{{requestAmount}} {{coin.toUpperCase()}}
+
{{requestAmount}} {{requestCurrency}}
+
{{requestAmountSecondary}} {{requestCurrencySecondary}}
From b0e46f18a1acda441d32b1e99f3bbfd73aec96a9 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 6 Aug 2018 16:45:00 +1200 Subject: [PATCH 109/240] The Origin screen can now display payment requests properly. --- .../controllers/walletSelectorController.js | 88 ++++++++++++++----- src/js/services/incomingData.js | 2 +- www/views/walletSelector.html | 4 +- 3 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index 541794b22..1d52a715a 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -2,6 +2,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $rootScope, $state, $log, $ionicHistory, configService, gettextCatalog, profileService, txFormatService) { + var fromWalletId = ''; var priceDisplayAsFiat = false; var requestedSatoshis = 0; var unitDecimals = 0; @@ -29,6 +30,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu $scope.params = $state.params; $scope.coin = false; // Wallets to show (for destination screen or contacts) $scope.type = data.stateParams && data.stateParams['fromWalletId'] ? 'destination' : 'origin'; // origin || destination + fromWalletId = data.stateParams && data.stateParams.fromWalletId; if ($scope.params.coin) { $scope.coin = $scope.params.coin; // Contacts have a coin embedded @@ -51,30 +53,13 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; }); - $scope.walletsEmpty = []; // empty wallets for origin screen - - if ($scope.type === 'origin') { - $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); - $scope.walletsEmpty = profileService.getWallets({coin: $scope.coin, hasNoFunds: true}); - } else if ($scope.type === 'destination') { - $scope.fromWallet = profileService.getWallet(data.stateParams.fromWalletId); - $scope.coin = $scope.fromWallet.coin; // Only show wallets with the select origin wallet coin - $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to'); - } - if ($scope.thirdParty) { // Third party services specific logic handleThirdPartyIfBip70PaymentProtocol(); handleThirdPartyIfShapeshift(); } - if (!$scope.coin || $scope.coin === 'bch') { // if no specific coin is set or coin is set to bch - $scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: $scope.type==='origin'}); - } - if (!$scope.coin || $scope.coin === 'btc') { // if no specific coin is set or coin is set btc - $scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: $scope.type === 'origin'}); - } - + prepareWalletLists(); formatRequestedAmount(); }); @@ -116,14 +101,17 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu } else if (!$scope.params.amount) { // If we have no amount return 'tabs.send.amount'; } else { // If we do have them - return 'tabs.send.confirm'; + return 'tabs.send.review'; } } function handleThirdPartyIfBip70PaymentProtocol() { if ($scope.thirdParty.id === 'bip70PaymentProtocol') { + requestedSatoshis = $scope.thirdParty.details.amount; $scope.coin = $scope.thirdParty.coin; - $scope.requestAmount = unitsFromSatoshis * $scope.thirdParty.details.amount; + $scope.requestAmount = unitsFromSatoshis * requestedSatoshis; + $scope.params.amount = requestedSatoshis; + $scope.params.toAddr = $scope.thirdParty.details.toAddress; console.log('paypro details:', $scope.thirdParty.details); } } @@ -138,6 +126,66 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu } } + function prepareWalletLists() { + var walletsAll = []; + var walletsSufficientFunds = []; + $scope.walletsInsufficientFunds = []; // For origin screen + + if ($scope.type === 'origin') { + $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send from'); + + if ($scope.params.amount) { + + walletsAll = profileService.getWallets({coin: $scope.coin}); + + walletsAll.forEach(function forWallet(wallet){ + if (wallet.status.availableBalanceSat > $scope.params.amount) { + walletsSufficientFunds.push(wallet); + } else { + $scope.walletsInsufficientFunds.push(wallet); + } + }); + + if ($scope.coin === 'btc') { + $scope.walletsBtc = walletsSufficientFunds; + } else { + $scope.walletsBch = walletsSufficientFunds; + } + + } else if ($scope.coin) { + walletsAll = profileService.getWallets({coin: $scope.coin}); + walletsAll.forEach(function forWallet(wallet){ + if (wallet.status.availableBalanceSat > 0) { + walletsSufficientFunds.push(wallet); + } else { + $scope.walletsInsufficientFunds.push(wallet); + } + }); + + if ($scope.coin === 'btc') { + $scope.walletsBtc = walletsSufficientFunds; + } else { + $scope.walletsBch = walletsSufficientFunds; + } + } else { + $scope.walletsBch = profileService.getWallets({coin: 'bch', hasFunds: true}); + $scope.walletsBtc = profileService.getWallets({coin: 'btc', hasFunds: true}); + $scope.walletsInsufficientFunds = profileService.getWallets({coin: $scope.coin, hasNoFunds: true}); + } + + } else if ($scope.type === 'destination') { + $scope.fromWallet = profileService.getWallet(fromWalletId); + $scope.coin = $scope.fromWallet.coin; // Only show wallets with the select origin wallet coin + $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to'); + + if ($scope.coin === 'btc') { // if no specific coin is set or coin is set btc + $scope.walletsBtc = profileService.getWallets({coin: $scope.coin}); + } else { + $scope.walletsBch = profileService.getWallets({coin: $scope.coin}); + } + } + } + $scope.useWallet = function(wallet) { diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index 72b1a70b5..fa5abd56b 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -427,7 +427,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat }; var stateParams = { amount: payProDetails.amount, - toAddr: payProDetails.toAddress, + toAddress: payProDetails.toAddress, thirdParty: JSON.stringify(thirdPartyData) }; diff --git a/www/views/walletSelector.html b/www/views/walletSelector.html index 3eecb1204..c863f4ea7 100644 --- a/www/views/walletSelector.html +++ b/www/views/walletSelector.html @@ -42,12 +42,12 @@
-
+
Insufficient funds
- From f1f8f6e0f51026b0c8b624c9eca9f25ffd8d1f71 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 6 Aug 2018 20:31:06 +1200 Subject: [PATCH 110/240] Tidied up third party data from BIP70 Payment Protocol. --- src/js/controllers/amount.js | 2 +- src/js/controllers/review.controller.js | 4 ++-- src/js/controllers/walletSelectorController.js | 16 +++++----------- src/js/routes.js | 2 +- src/js/services/incomingData.js | 15 +++++++++++++-- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 576fb4500..db1bb52fe 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -470,7 +470,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, amount: useSendMax ? undefined : satoshis, fromWalletId: passthroughParams.fromWalletId, sendMax: useSendMax, - toAddr: passthroughParams.toAddress, + toAddress: passthroughParams.toAddress, toWalletId: passthroughParams.toWalletId }; diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 82ef2aa30..5c05ea6fa 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -67,7 +67,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit defaults = configService.getDefaults(); originWalletId = data.stateParams.fromWalletId; satoshis = parseInt(data.stateParams.amount, 10); - toAddress = data.stateParams.toAddr; + toAddress = data.stateParams.toAddress; vm.originWallet = profileService.getWallet(originWalletId); vm.origin.currency = vm.originWallet.coin.toUpperCase(); @@ -218,7 +218,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit amount: parseInt(data.stateParams.amount), sendMax: data.stateParams.sendMax === 'true' ? true : false, fromWalletId: data.stateParams.fromWalletId, - toAddress: data.stateParams.toAddr, + toAddress: data.stateParams.toAddress, feeLevel: configFeeLevel, spendUnconfirmed: config.wallet.spendUnconfirmed, diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index 1d52a715a..df733e11f 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -4,7 +4,6 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu var fromWalletId = ''; var priceDisplayAsFiat = false; - var requestedSatoshis = 0; var unitDecimals = 0; var unitsFromSatoshis = 0; @@ -39,8 +38,6 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu if ($scope.params.amount) { // There is an amount, so presume that it is a payment request $scope.sendFlowTitle = gettextCatalog.getString('Payment Request'); $scope.specificAmount = $scope.specificAlternativeAmount = ''; - //requestedAmountCrypto = (($state.params.amount) * (1 / config.unitToSatoshi)).toFixed(config.unitDecimals); - requestedSatoshis = $state.params.amount; $scope.isPaymentRequest = true; } if ($scope.params.thirdParty) { @@ -64,11 +61,11 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu }); function formatRequestedAmount() { - if (requestedSatoshis) { - var cryptoAmount = (unitsFromSatoshis * requestedSatoshis).toFixed(unitDecimals); + if ($scope.params.amount) { + var cryptoAmount = (unitsFromSatoshis * $scope.params.amount).toFixed(unitDecimals); var cryptoCoin = $scope.coin.toUpperCase(); - txFormatService.formatAlternativeStr($scope.coin, requestedSatoshis, function onFormatAlternativeStr(formatted){ + txFormatService.formatAlternativeStr($scope.coin, $scope.params.amount, function onFormatAlternativeStr(formatted){ if (formatted) { var fiatParts = formatted.split(' '); var fiatAmount = fiatParts[0]; @@ -107,12 +104,9 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu function handleThirdPartyIfBip70PaymentProtocol() { if ($scope.thirdParty.id === 'bip70PaymentProtocol') { - requestedSatoshis = $scope.thirdParty.details.amount; $scope.coin = $scope.thirdParty.coin; - $scope.requestAmount = unitsFromSatoshis * requestedSatoshis; - $scope.params.amount = requestedSatoshis; - $scope.params.toAddr = $scope.thirdParty.details.toAddress; - console.log('paypro details:', $scope.thirdParty.details); + $scope.requestAmount = unitsFromSatoshis * $scope.params.amount; + console.log('paypro details:', $scope.thirdParty); } } diff --git a/src/js/routes.js b/src/js/routes.js index 905683dcb..8a8adc964 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -345,7 +345,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }) .state('tabs.send.review', { - url: '/review/:thirdParty/:amount/:fromWalletId/:sendMax/:toAddr/:toWalletId', + url: '/review/:thirdParty/:amount/:fromWalletId/:sendMax/:toAddress/:toWalletId', views: { 'tab-send@tabs': { controller: 'reviewController', diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index fa5abd56b..3b4b024c4 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -421,9 +421,20 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat function handlePayPro(payProDetails, coin) { var thirdPartyData = { - id: 'bip70PaymentProtocol', + caName: payProDetails.caName, + caTrusted: payProDetails.caTrusted, coin: coin, - details: payProDetails + domain: payProDetails.domain, + expires: payProDetails.expires, + id: 'bip70PaymentProtocol', + memo: payProDetails.memo, + merchant_data: payProDetails.merchant_data, + network: payProDetails.network, + requiredFeeRate: payProDetails.requiredFeeRate, + selfSigned: payProDetails.selfSigned, + time: payProDetails.time, + url: payProDetails.url, + verified: payProDetails.verified }; var stateParams = { amount: payProDetails.amount, From 731cfebc8a336e91e107570e2bbf031b68f22375 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 6 Aug 2018 20:39:59 +1200 Subject: [PATCH 111/240] Fixed destination coin for Shapeshift. --- src/js/controllers/walletSelectorController.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index df733e11f..e603032bd 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -168,8 +168,10 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu } } else if ($scope.type === 'destination') { - $scope.fromWallet = profileService.getWallet(fromWalletId); - $scope.coin = $scope.fromWallet.coin; // Only show wallets with the select origin wallet coin + if (!$scope.coin) { // Allow for the coin to be set by a third party + $scope.fromWallet = profileService.getWallet(fromWalletId); + $scope.coin = $scope.fromWallet.coin; // Only show wallets with the select origin wallet coin + } $scope.headerTitle = gettextCatalog.getString('Choose a wallet to send to'); if ($scope.coin === 'btc') { // if no specific coin is set or coin is set btc From ec354bd340414dda1f7271dfe0492a2946cc7a6d Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 6 Aug 2018 11:25:29 +0200 Subject: [PATCH 112/240] header css --- src/js/controllers/review.controller.js | 4 ++++ src/js/services/shapeshiftService.js | 19 +++++++++++++------ src/sass/views/shapeshift.scss | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 5c05ea6fa..c2e6be3de 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -105,6 +105,10 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } vm.approve = function() { + if (vm.thirdParty.id === 'shapeshift') { + shapeshiftService.shiftIt(); + return; + } if (!tx || !vm.originWallet) return; diff --git a/src/js/services/shapeshiftService.js b/src/js/services/shapeshiftService.js index 131df0cd0..317e72394 100644 --- a/src/js/services/shapeshiftService.js +++ b/src/js/services/shapeshiftService.js @@ -1,26 +1,32 @@ 'use strict'; -angular.module('copayApp.services').factory('shapeshiftService', function($http, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, platformInfo, servicesService) { +angular.module('copayApp.services').factory('shapeshiftService', function($http, $interval, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, incomingData, platformInfo, servicesService) { var root = {}; root.ShiftState = 'Shift'; root.withdrawalAddress = '' root.returnAddress = '' root.amount = ''; root.marketData = {} - this.withdrawalAddress = function(address) { + root.withdrawalAddress = function(address) { root.withdrawalAddress = address; }; - this.returnAddress = function(address) { + root.returnAddress = function(address) { root.returnAddress = address; }; - this.amount = function(amount) { + root.amount = function(amount) { root.amount = amount; }; - this.fromWalletId = function(id) { + root.fromWalletId = function(id) { root.fromWalletId = id; }; - this.toWalletId = function(id) { + root.toWalletId = function(id) { root.toWalletId = id; }; + root.coinIn = function(coinIn) { + root.coinIn = coinIn.toUpperCase(); + }; + root.coinOut = function(coinOut) { + root.coinOut = coinOut.toUpperCase(); + }; root.getMarketDataIn = function(coin) { if(coin === root.coinOut) return root.getMarketData(root.coinOut, root.coinIn); @@ -104,6 +110,7 @@ angular.module('copayApp.services').factory('shapeshiftService', function($http, var shapeshiftData = { fromWalletId: root.fromWalletId, + toWalletId: root.toWalletId, minAmount: root.marketData.minimum, maxAmount: root.marketData.maxLimit, orderId: root.depositInfo.orderId diff --git a/src/sass/views/shapeshift.scss b/src/sass/views/shapeshift.scss index ee4cd0b0f..5b63c0354 100644 --- a/src/sass/views/shapeshift.scss +++ b/src/sass/views/shapeshift.scss @@ -18,6 +18,6 @@ } } .header.shapeshift { - background: url(../img/shapeshiftbg.jpg) center center no-repeat #28394d; + background: url(../img/shapeshiftbg.jpg) center center repeat #28394d; opacity: 0.99; } \ No newline at end of file From cea77e910f2342a973f919a83278d72c8026b526 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 6 Aug 2018 21:46:03 +1200 Subject: [PATCH 113/240] One step closer to getting send max working. --- src/js/controllers/review.controller.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 5c05ea6fa..4fcb447e2 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -594,7 +594,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit channel = "ga"; } // When displaying Fiat, if the formatting fails, the crypto will be the primary amount. - var amount = priceDisplayIsFiat ? vm.secondaryAmount || vm.primaryAmount : vm.primaryAmount; + var amount = unitFromSat * satoshis; var log = new window.BitAnalytics.LogEvent("transfer_success", [{ "coin": vm.originWallet.coin, "type": "outgoing", @@ -678,7 +678,8 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit tx.sendMaxInfo = sendMaxInfo; tx.amount = tx.sendMaxInfo.amount; - updateAmount(); + satoshis = tx.amount; + updateSendAmounts(); ongoingProcess.set('calculatingFee', false); $timeout(function() { showSendMaxWarning(wallet, sendMaxInfo); From cc213956d08793bca81b7e8282d381515738c31b Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Mon, 6 Aug 2018 22:12:33 +0900 Subject: [PATCH 114/240] header bitpay on the wallet selector, incoming data fix, clean a bit --- app-template/bitcoincom/css/bitcoin.com.css | 23 ++++++++++++++++++- .../controllers/walletSelectorController.js | 7 +----- src/js/services/incomingData.js | 7 +++--- src/sass/views/review.scss | 2 +- www/css/bitcoin.com.css | 16 ++++++++++++- www/css/main.css | 6 +++-- www/img/bitpay_banner.svg | 13 +++++++++++ www/views/thirdparty/bitpay-header.html | 3 +++ www/views/walletSelector.html | 1 + 9 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 www/img/bitpay_banner.svg create mode 100644 www/views/thirdparty/bitpay-header.html diff --git a/app-template/bitcoincom/css/bitcoin.com.css b/app-template/bitcoincom/css/bitcoin.com.css index e9b316761..19113dd2c 100644 --- a/app-template/bitcoincom/css/bitcoin.com.css +++ b/app-template/bitcoincom/css/bitcoin.com.css @@ -264,9 +264,30 @@ div.onboarding-topic { height: 5em; } +.shapeshift-banner { + background: url(../img/shapeshiftbg.jpg) center center no-repeat #28394d; + padding: 10px; + box-shadow: 0px 5px 10px 0px #cccccc; + height: 5em; +} + +.bitpay-banner { + background: center center no-repeat #1A3A8B; + padding: 10px; + box-shadow: 0px 5px 10px 0px #cccccc; + height: 5em; +} + +.bitpay-logo { + display: block; + max-height: 100%; + width: 100%; + height: 4em; +} + .shapeshift-logo { display: block; float: left; max-height: 100%; - max-width: 100%; + max-width: 100%; } diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index 1d52a715a..fd5b4f1cd 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -106,12 +106,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu } function handleThirdPartyIfBip70PaymentProtocol() { - if ($scope.thirdParty.id === 'bip70PaymentProtocol') { - requestedSatoshis = $scope.thirdParty.details.amount; - $scope.coin = $scope.thirdParty.coin; - $scope.requestAmount = unitsFromSatoshis * requestedSatoshis; - $scope.params.amount = requestedSatoshis; - $scope.params.toAddr = $scope.thirdParty.details.toAddress; + if ($scope.thirdParty.id === 'bitpay') { console.log('paypro details:', $scope.thirdParty.details); } } diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index fa5abd56b..e76627fcf 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -405,6 +405,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat return { amount: payProData.outputs[0].amount, caTrusted: true, + name: 'bitpay', domain: 'bitpay.com', expires: Math.floor(new Date(payProData.expires).getTime() / 1000), memo: payProData.memo, @@ -421,13 +422,13 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat function handlePayPro(payProDetails, coin) { var thirdPartyData = { - id: 'bip70PaymentProtocol', - coin: coin, + id: payProDetails.name, details: payProDetails }; var stateParams = { amount: payProDetails.amount, - toAddress: payProDetails.toAddress, + toAddr: payProDetails.toAddress, + coin: coin, thirdParty: JSON.stringify(thirdPartyData) }; diff --git a/src/sass/views/review.scss b/src/sass/views/review.scss index 22470a7b2..8bda83890 100644 --- a/src/sass/views/review.scss +++ b/src/sass/views/review.scss @@ -11,7 +11,7 @@ bottom: 92px; } - .shapeshift-banner { + .shapeshift-banner, .bitpay-banner { box-shadow: none; } } \ No newline at end of file diff --git a/www/css/bitcoin.com.css b/www/css/bitcoin.com.css index 9b69005c4..59e503ac7 100644 --- a/www/css/bitcoin.com.css +++ b/www/css/bitcoin.com.css @@ -314,9 +314,23 @@ div.slide-success__background.fill-screen { height: 5em; } +.bitpay-banner { + background: #1A3A8B; + padding: 10px; + box-shadow: 0px 5px 10px 0px #cccccc; + height: 5em; +} + +.bitpay-logo { + display: block; + max-height: 100%; + width: 100%; + height: 4em; +} + .shapeshift-logo { display: block; float: left; max-height: 100%; - max-width: 100%; + max-width: 100%; } diff --git a/www/css/main.css b/www/css/main.css index df39fda50..c2e4325f9 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -15262,7 +15262,7 @@ log-options #check-bar .checkbox-icon { #view-review .fee-summary { position: absolute; bottom: 92px; } - #view-review .shapeshift-banner { + #view-review .shapeshift-banner, #view-review .bitpay-banner { box-shadow: none; } .gravatar { @@ -15325,8 +15325,10 @@ ion-content.padded-bottom-cta-with-summary { .header { padding: 29px 12px 61px; - background-color: #FAB915; + background-color: #fab915; color: #FFFFFF; } + .header.btc { + background-color: #535353; } .header .title { font-size: 18px; font-weight: 400; diff --git a/www/img/bitpay_banner.svg b/www/img/bitpay_banner.svg new file mode 100644 index 000000000..cf5829899 --- /dev/null +++ b/www/img/bitpay_banner.svg @@ -0,0 +1,13 @@ + + + + Artboard + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/www/views/thirdparty/bitpay-header.html b/www/views/thirdparty/bitpay-header.html new file mode 100644 index 000000000..a5ffcb3a5 --- /dev/null +++ b/www/views/thirdparty/bitpay-header.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/www/views/walletSelector.html b/www/views/walletSelector.html index c863f4ea7..15b4935fa 100644 --- a/www/views/walletSelector.html +++ b/www/views/walletSelector.html @@ -5,6 +5,7 @@
+
Paying
{{requestAmount}} {{requestCurrency}}
From bb3b00ae8d54b89103346bf2ff35715c27a8c88b Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Mon, 6 Aug 2018 22:20:51 +0900 Subject: [PATCH 115/240] Fix incomingData toAddress, mistake --- src/js/services/incomingData.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index f541deb58..cfa6f0a03 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -438,10 +438,10 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat url: payProDetails.url, verified: payProDetails.verified }; - + var stateParams = { amount: payProDetails.amount, - toAddr: payProDetails.toAddress, + toAddress: payProDetails.toAddress, coin: coin, thirdParty: JSON.stringify(thirdPartyData) }; From 3652419b0dc681dfc6b6f404c1c5e01e29ae89cb Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Mon, 6 Aug 2018 22:21:40 +0900 Subject: [PATCH 116/240] Fix double id --- src/js/services/incomingData.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index cfa6f0a03..ffa3e95f1 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -428,7 +428,6 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat coin: coin, domain: payProDetails.domain, expires: payProDetails.expires, - id: 'bip70PaymentProtocol', memo: payProDetails.memo, merchant_data: payProDetails.merchant_data, network: payProDetails.network, From 470868ade9baf9cca2f9a9ef65d8258e1cdb6e0e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Mon, 6 Aug 2018 23:19:46 +0900 Subject: [PATCH 117/240] Review screen, cleaning in the same time --- src/js/controllers/review.controller.js | 11 +++++++++-- src/js/services/incomingData.js | 3 ++- www/css/main.css | 2 +- www/views/review.html | 19 ++++++++++++------- www/views/walletSelector.html | 2 +- 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 1a52fb56a..3143fabcf 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -43,6 +43,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.sendStatus = ''; vm.thirdParty = false; vm.wallet = null; + vm.memoExpanded = false; var config = null; var defaults = {}; @@ -83,6 +84,12 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } vm.thirdParty.data['fromWalletId'] = vm.fromWalletId; } + if (vm.thirdParty.id === 'bip70') { + if (vm.thirdParty.memo) { + vm.memo = 'Payment request for BitPay invoice.\n' + toAddress + ' for merchant'; + vm.memoExpanded = true; + } + } } } @@ -321,7 +328,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit txp.outputs = [{ 'toAddress': tx.toAddress, 'amount': tx.amount, - 'message': tx.description + 'message': vm.memo }]; if (tx.sendMaxInfo) { @@ -333,7 +340,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } else txp.feeLevel = tx.feeLevel; } - txp.message = tx.description; + txp.message = vm.memo; if (tx.paypro) { txp.payProUrl = tx.paypro.url; diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index ffa3e95f1..be391edc3 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -422,7 +422,8 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat function handlePayPro(payProDetails, coin) { var thirdPartyData = { - id: payProDetails.name, + id: 'bip70', + name: payProDetails.name, caName: payProDetails.caName, caTrusted: payProDetails.caTrusted, coin: coin, diff --git a/www/css/main.css b/www/css/main.css index c2e4325f9..87333d1a6 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -12093,7 +12093,7 @@ a.item { color: #fff; } .header.shapeshift { - background: url(../img/shapeshiftbg.jpg) center center no-repeat #28394d; + background: url(../img/shapeshiftbg.jpg) center center repeat #28394d; opacity: 0.99; } #bitpayCard { diff --git a/www/views/review.html b/www/views/review.html index 631d1bec4..0ad5e2148 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -7,8 +7,7 @@ - +
@@ -47,7 +46,13 @@

{{vm.destination.name}} ({{vm.destination.currency}})

{{vm.destination.balanceAmount}} {{vm.destination.balanceCurrency}}

-
+
+ +

BitPay

+

Payment expired in XX:XX

+
+
{{vm.destination.address.substring(0,5)}}{{vm.destination.address.substring(5,vm.destination.address.length-4)}}{{vm.destination.address.substring(vm.destination.address.length-4)}}
@@ -56,17 +61,17 @@
+ ng-class="{ 'expand-content-revealed': vm.memoExpanded }" + ng-click="vm.memoExpanded = !vm.memoExpanded"> Add personal note
+ ng-class="{ 'expand-content-revealed': vm.memoExpanded }">
Personal Note:
- +
diff --git a/www/views/walletSelector.html b/www/views/walletSelector.html index 15b4935fa..eeb7591a8 100644 --- a/www/views/walletSelector.html +++ b/www/views/walletSelector.html @@ -5,7 +5,7 @@
-
+
Paying
{{requestAmount}} {{requestCurrency}}
From caafec4625bb6bca860cd41c082bd98738f48f3f Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 6 Aug 2018 18:06:10 +0200 Subject: [PATCH 118/240] shapeshift controller --- src/js/controllers/amount.js | 25 +--- src/js/controllers/review.controller.js | 19 ++- .../controllers/walletSelectorController.js | 1 + src/js/services/shapeshiftService.js | 131 ++++++++++-------- www/views/review.html | 3 +- 5 files changed, 89 insertions(+), 90 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index db1bb52fe..c8b7722f9 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -217,7 +217,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, }; function goBack() { - if (vm.shapeshiftOrderId) { + if (vm.thirdParty && vm.thirdParty.id === 'shapeshift') { $state.go('tabs.send').then(function() { $ionicHistory.clearHistory(); $state.go('tabs.home').then(function() { @@ -483,29 +483,6 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, if (!confirmData.fromWalletId) { $state.transitionTo('tabs.paymentRequest.confirm', confirmData); } else { - - if (vm.shapeshiftOrderId) { - var shapeshiftOrderUrl = 'https://www.shapeshift.io/#/status/'; - shapeshiftOrderUrl += vm.shapeshiftOrderId; - confirmData.description = shapeshiftOrderUrl; - - if (confirmData.sendMax) { - var wallet = lodash.find(profileService.getWallets({ coin: coin }), - function(w) { - return w.id == passthroughParams.fromWalletId; - }); - - var balance = parseFloat(wallet.cachedBalance.substring(0, wallet.cachedBalance.length-4)); - if (balance < vm.minAmount * 1.04) { - confirmData.sendMax = false; - confirmData.amount = vm.minAmount * unitToSatoshi; - } else if (balance > vm.maxAmount) { - confirmData.sendMax = false; - confirmData.amount = vm.maxAmount * unitToSatoshi * 0.99; - } - } - } - $state.transitionTo('tabs.send.review', confirmData); $scope.useSendMax = null; } diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 3143fabcf..13f6e6cb1 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -4,7 +4,7 @@ angular .module('copayApp.controllers') .controller('reviewController', reviewController); -function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, configService, feeService, gettextCatalog, $ionicHistory, $ionicLoading, $ionicModal, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { +function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, configService, feeService, gettextCatalog, $ionicHistory, $ionicLoading, $ionicModal, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, shapeshiftService, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { var vm = this; vm.buttonText = ''; @@ -82,7 +82,20 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit if (!vm.thirdParty.data) { vm.thirdParty.data = {}; } - vm.thirdParty.data['fromWalletId'] = vm.fromWalletId; + + var toWallet = profileService.getWallet(data.stateParams.toWalletId); + $ionicLoading.show(); + walletService.getAddress(vm.originWallet, false, function onWalletAddress(err, returnAddr) { + walletService.getAddress(toWallet, false, function onWalletAddress(err, withdrawalAddr) { + $ionicLoading.hide(); + shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, function(shapeshiftData) { + vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/'+shapeshiftData.orderId; + toAddress = shapeshiftData.toAddress; + vm.destination.address = toAddress; + vm.destination.kind = 'shapeshift'; + }); + }); + }); } if (vm.thirdParty.id === 'bip70') { if (vm.thirdParty.memo) { @@ -115,8 +128,6 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit if (vm.thirdParty.id === 'shapeshift') { shapeshiftService.shiftIt(); return; - } - if (!tx || !vm.originWallet) return; if ($scope.paymentExpired) { diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index b251da987..824d0e6ca 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -103,6 +103,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu function handleThirdPartyIfShapeshift() { if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the + $scope.coin = profileService.getWallet(fromWalletId).coin; if ($scope.coin === 'bch') { $scope.coin = 'btc'; } else { diff --git a/src/js/services/shapeshiftService.js b/src/js/services/shapeshiftService.js index 317e72394..b3e307667 100644 --- a/src/js/services/shapeshiftService.js +++ b/src/js/services/shapeshiftService.js @@ -1,48 +1,44 @@ 'use strict'; -angular.module('copayApp.services').factory('shapeshiftService', function($http, $interval, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, incomingData, platformInfo, servicesService) { +angular.module('copayApp.services').factory('shapeshiftService', function ($http, $interval, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, incomingData, platformInfo, servicesService) { var root = {}; root.ShiftState = 'Shift'; - root.withdrawalAddress = '' - root.returnAddress = '' + root.coinIn = ''; + root.coinOut = ''; + root.withdrawalAddress = ''; + root.returnAddress = ''; root.amount = ''; - root.marketData = {} - root.withdrawalAddress = function(address) { + root.marketData = {}; + root.withdrawalAddress = function (address) { root.withdrawalAddress = address; }; - root.returnAddress = function(address) { + root.returnAddress = function (address) { root.returnAddress = address; }; - root.amount = function(amount) { + root.amount = function (amount) { root.amount = amount; }; - root.fromWalletId = function(id) { - root.fromWalletId = id; - }; - root.toWalletId = function(id) { - root.toWalletId = id; - }; - root.coinIn = function(coinIn) { + root.coinIn = function (coinIn) { root.coinIn = coinIn.toUpperCase(); }; - root.coinOut = function(coinOut) { + root.coinOut = function (coinOut) { root.coinOut = coinOut.toUpperCase(); }; - root.getMarketDataIn = function(coin) { - if(coin === root.coinOut) return root.getMarketData(root.coinOut, root.coinIn); + root.getMarketDataIn = function (coin) { + if (coin === root.coinOut) return root.getMarketData(root.coinOut, root.coinIn); return root.getMarketData(coin, root.coinOut); }; - root.getMarketDataOut = function(coin) { - if(coin === root.coinIn) return root.getMarketData(root.coinOut, root.coinIn); + root.getMarketDataOut = function (coin) { + if (coin === root.coinIn) return root.getMarketData(root.coinOut, root.coinIn); return root.getMarketData(root.coinIn, coin); }; - root.getMarketData = function(coinIn, coinOut, cb) { + root.getMarketData = function (coinIn, coinOut, cb) { root.coinIn = coinIn; - root.coinOut= coinOut; - if(root.coinIn === undefined || root.coinOut === undefined) return; + root.coinOut = coinOut; + if (root.coinIn === undefined || root.coinOut === undefined) return; shapeshiftApiService .marketInfo(root.coinIn, root.coinOut) - .then(function(marketData){ + .then(function (marketData) { root.marketData = marketData; root.rateString = root.marketData.rate.toString() + ' ' + coinOut.toUpperCase() + '/' + coinIn.toUpperCase(); if (cb) { @@ -59,43 +55,50 @@ angular.module('copayApp.services').factory('shapeshiftService', function($http, });*/ root.coins = { - 'BTC': { name: 'Bitcoin', symbol: 'BTC' }, - 'BCH': { name: 'Bitcoin Cash', symbol: 'BCH' } + 'BTC': {name: 'Bitcoin', symbol: 'BTC'}, + 'BCH': {name: 'Bitcoin Cash', symbol: 'BCH'} }; - function checkForError(data){ - if(data.error) return true; + function checkForError(data) { + if (data.error) return true; return false; } - root.shiftIt = function(){ + root.shiftIt = function (coinIn, coinOut, withdrawalAddress, returnAddress, cb) { ongoingProcess.set('connectingShapeshift', true); - var validate=shapeshiftApiService.ValidateAddress(root.withdrawalAddress, root.coinOut); - validate.then(function(valid){ - //console.log(root.withdrawalAddress) - //console.log(valid) + root.withdrawalAddress(withdrawalAddress); + root.returnAddress(returnAddress); + var validate = shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut); + validate.then(function (valid) { var tx = ShapeShift(); - tx.then(function(txData){ - if(txData['fixedTxData']){ + var coin; + console.log("Starting"); + tx.then(function (txData) { + console.log("Got txData", txData); + if (txData['fixedTxData']) { txData = txData.fixedTxData; - if(checkForError(txData)) return; + if (checkForError(txData)) return; //console.log(txData) - var coinPair=txData.pair.split('_'); + var coinPair = txData.pair.split('_'); txData.depositType = coinPair[0].toUpperCase(); txData.withdrawalType = coinPair[1].toUpperCase(); - var coin = root.coins[txData.depositType].name.toLowerCase(); - //console.log(coin) - txData.depositQR = coin + ":" + txData.deposit + "?amount=" + txData.depositAmount + coin = root.coins[txData.depositType].name.toLowerCase(); + + txData.depositQR = coin + ":" + txData.deposit + "?amount=" + txData.depositAmount; + root.txFixedPending = true; - } else if(txData['normalTxData']){ + + } else if (txData['normalTxData']) { + txData = txData.normalTxData; - if(checkForError(txData)) return; - var coin = root.coins[txData.depositType.toUpperCase()].name.toLowerCase(); + if (checkForError(txData)) return; + coin = root.coins[txData.depositType.toUpperCase()].name.toLowerCase(); txData.depositQR = coin + ":" + txData.deposit; - } else if(txData['cancelTxData']){ - if(checkForError(txData.cancelTxData)) return; - if(root.txFixedPending) { - $interval.cancel(root.txInterval); + + } else if (txData['cancelTxData']) { + + if (checkForError(txData.cancelTxData)) return; + if (root.txFixedPending) { root.txFixedPending = false; } root.ShiftState = 'Shift'; @@ -108,37 +111,43 @@ angular.module('copayApp.services').factory('shapeshiftService', function($http, if (sendAddress && sendAddress.indexOf('bitcoin cash') >= 0) sendAddress = sendAddress.replace('bitcoin cash', 'bitcoincash'); + ongoingProcess.set('connectingShapeshift', false); + + root.ShiftState = 'Cancel'; + root.GetStatus(); + root.txInterval=$interval(root.GetStatus, 8000); + var shapeshiftData = { - fromWalletId: root.fromWalletId, + coinIn: coinIn, + coinOut: coinOut, toWalletId: root.toWalletId, minAmount: root.marketData.minimum, maxAmount: root.marketData.maxLimit, - orderId: root.depositInfo.orderId + orderId: root.depositInfo.orderId, + toAddress: txData.deposit }; - - if (incomingData.redir(sendAddress, 'shapeshift', shapeshiftData)) { + // + // if (incomingData.redir(sendAddress, 'shapeshift', shapeshiftData)) { ongoingProcess.set('connectingShapeshift', false); - return; - } + // return; + // } + cb(shapeshiftData); - /*root.ShiftState = 'Cancel'; - root.GetStatus(); - root.txInterval=$interval(root.GetStatus, 8000);*/ }); }) }; function ShapeShift() { - if(root.ShiftState === 'Cancel') return shapeshiftApiService.CancelTx(root); - if(parseFloat(root.amount) > 0) return shapeshiftApiService.FixedAmountTx(root); + if (root.ShiftState === 'Cancel') return shapeshiftApiService.CancelTx(root); + if (parseFloat(root.amount) > 0) return shapeshiftApiService.FixedAmountTx(root); return shapeshiftApiService.NormalTx(root); } - root.GetStatus = function(){ + root.GetStatus = function () { var address = root.depositInfo.deposit - shapeshiftApiService.GetStatusOfDepositToAddress(address).then(function(data){ + shapeshiftApiService.GetStatusOfDepositToAddress(address).then(function (data) { root.DepositStatus = data; - if(root.DepositStatus.status === 'complete'){ + if (root.DepositStatus.status === 'complete') { $interval.cancel(root.txInterval); root.depositInfo = null; root.ShiftState = 'Shift' @@ -153,7 +162,7 @@ angular.module('copayApp.services').factory('shapeshiftService', function($http, sref: 'tabs.shapeshift', }; - var register = function() { + var register = function () { servicesService.register(servicesItem); }; register(); diff --git a/www/views/review.html b/www/views/review.html index 0ad5e2148..7317d7b68 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -32,13 +32,14 @@
+
To:
- +
From 5b6f48e6a2d8bb34a8d94fd197386f1ef3f04d24 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 6 Aug 2018 18:11:37 +0200 Subject: [PATCH 119/240] Check min max on shapeshift flow --- src/js/controllers/amount.js | 14 +++----------- src/js/controllers/review.controller.js | 3 +-- src/js/controllers/walletSelectorController.js | 1 + 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index c8b7722f9..c53d49061 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -20,7 +20,6 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.lastUsedPopularList = []; vm.maxAmount = 0; vm.minAmount = 0; - vm.shapeshiftOrderId = ''; vm.thirdParty = false; vm.unit = ''; @@ -95,14 +94,9 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum); vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit); }); - - // if (vm.thirdParty.data['shapeshiftOrderId'] && data.stateParams.shapeshiftOrderId.length > 0) { - // vm.shapeshiftOrderId = vm.thirdParty.data['shapeshiftOrderId']; - // } } } } - // vm.shapeshiftOrderId = data.stateParams.thirdPartyOrderId; vm.isRequestingSpecificAmount = !data.stateParams.fromWalletId; @@ -371,8 +365,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.alternativeAmount = txFormatService.formatAmount(amountInSatoshis, true); vm.allowSend = lodash.isNumber(a) && a > 0 - && (!vm.shapeshiftOrderId - || (a >= vm.minAmount && a <= vm.maxAmount)) + && (a >= vm.minAmount && a <= vm.maxAmount) && !vm.fundsAreInsufficient; } else { if (result) { @@ -392,8 +385,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.alternativeAmount = $filter('formatFiatAmount')(toFiat(result)); vm.allowSend = lodash.isNumber(result) && result > 0 - && (!vm.shapeshiftOrderId - || (result >= vm.minAmount && result <= vm.maxAmount)) + && (result >= vm.minAmount && result <= vm.maxAmount) && !vm.fundsAreInsufficient; } @@ -404,7 +396,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, if (vm.fundsAreInsufficient) { vm.errorMessage = gettextCatalog.getString('Not enough available funds'); - } else if (amountInCrypto && vm.shapeshiftOrderId) { + } else if (amountInCrypto && vm.thirdParty && vm.thirdParty.id === 'shapeshift') { if (amountInCrypto < vm.minAmount) { vm.errorMessage = gettextCatalog.getString('Amount is below minimum'); diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 13f6e6cb1..19bab5a2e 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -125,8 +125,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } vm.approve = function() { - if (vm.thirdParty.id === 'shapeshift') { - shapeshiftService.shiftIt(); + if (vm.thirdParty.id === 'shapeshift') return; if (!tx || !vm.originWallet) return; diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index 824d0e6ca..a4ff3ab3e 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -102,6 +102,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu } function handleThirdPartyIfShapeshift() { + console.log($scope.thirdParty, $scope.coin); if ($scope.thirdParty.id === 'shapeshift' && $scope.type === 'destination') { // Shapeshift wants to know the $scope.coin = profileService.getWallet(fromWalletId).coin; if ($scope.coin === 'bch') { From d183077a203d36a1482631fc167bb4092ca77a4b Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 7 Aug 2018 09:39:48 +1200 Subject: [PATCH 120/240] Countdown timer for BIP70 invoices. --- src/js/controllers/review.controller.js | 154 ++++++++++++++---------- src/js/services/incomingData.js | 2 +- www/views/review.html | 2 +- 3 files changed, 94 insertions(+), 64 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 19bab5a2e..5785ee831 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -4,7 +4,7 @@ angular .module('copayApp.controllers') .controller('reviewController', reviewController); -function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, configService, feeService, gettextCatalog, $ionicHistory, $ionicLoading, $ionicModal, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, shapeshiftService, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { +function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, configService, feeService, gettextCatalog, $interval, $ionicHistory, $ionicLoading, $ionicModal, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, shapeshiftService, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { var vm = this; vm.buttonText = ''; @@ -33,19 +33,27 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit currencyColor: '', }; vm.originWallet = null; + vm.paymentExpired = false; vm.primaryAmount = ''; vm.primaryCurrency = ''; vm.usingMerchantFee = false; vm.readyToSend = false; + vm.remainingTimeStr = ''; vm.secondaryAmount = ''; vm.secondaryCurrency = ''; vm.sendingTitle = gettextCatalog.getString('You are sending'); vm.sendStatus = ''; + vm.showAddress = true; vm.thirdParty = false; vm.wallet = null; vm.memoExpanded = false; + + // Functions + vm.onSuccessConfirm = onSuccessConfirm; + var config = null; + var countDown = null; var defaults = {}; var coin = ''; var countDown = null; @@ -57,6 +65,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit var satoshis = null; var toAddress = ''; var tx = {}; + var txPayproData = null; var unitFromSat = 0; var FEE_TOO_HIGH_LIMIT_PERCENTAGE = 15; @@ -77,32 +86,8 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit if (data.stateParams.thirdParty) { vm.thirdParty = JSON.parse(data.stateParams.thirdParty); // Parse stringified JSON-object if (vm.thirdParty) { - if (vm.thirdParty.id === 'shapeshift') { - vm.sendingTitle = gettextCatalog.getString('You are shifting'); - if (!vm.thirdParty.data) { - vm.thirdParty.data = {}; - } - - var toWallet = profileService.getWallet(data.stateParams.toWalletId); - $ionicLoading.show(); - walletService.getAddress(vm.originWallet, false, function onWalletAddress(err, returnAddr) { - walletService.getAddress(toWallet, false, function onWalletAddress(err, withdrawalAddr) { - $ionicLoading.hide(); - shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, function(shapeshiftData) { - vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/'+shapeshiftData.orderId; - toAddress = shapeshiftData.toAddress; - vm.destination.address = toAddress; - vm.destination.kind = 'shapeshift'; - }); - }); - }); - } - if (vm.thirdParty.id === 'bip70') { - if (vm.thirdParty.memo) { - vm.memo = 'Payment request for BitPay invoice.\n' + toAddress + ' for merchant'; - vm.memoExpanded = true; - } - } + handleThirdPartyInitIfBip70(); + handleThirdPartyInitIfShapeshift(); } } @@ -240,6 +225,8 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit sendMax: data.stateParams.sendMax === 'true' ? true : false, fromWalletId: data.stateParams.fromWalletId, toAddress: data.stateParams.toAddress, + paypro: txPayproData, + feeLevel: configFeeLevel, spendUnconfirmed: config.wallet.spendUnconfirmed, @@ -253,6 +240,8 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit txp: {}, }; + + if (data.stateParams.requiredFeeRate) { vm.usingMerchantFee = true; tx.feeRate = parseInt(data.stateParams.requiredFeeRate); @@ -454,6 +443,80 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.destination.balanceCurrency = balanceText.currency; } + function handleThirdPartyInitIfBip70() { + if (vm.thirdParty.id === 'bip70') { + if (vm.thirdParty.memo) { + // Why not the whole memo? + vm.memo = 'Payment request for BitPay invoice.\n' + toAddress + ' for merchant'; + vm.memoExpanded = true; + } + txPayproData = { + caTrusted: vm.thirdParty.caTrusted, + domain: vm.thirdParty.domain, + expires: vm.thirdParty.expires, + toAddress: toAddress, + url: vm.thirdParty.url, + verified: vm.thirdParty.verified, + }; + } + } + + function handleThirdPartyInitIfShapeshift() { + if (vm.thirdParty.id === 'shapeshift') { + vm.sendingTitle = gettextCatalog.getString('You are shifting'); + if (!vm.thirdParty.data) { + vm.thirdParty.data = {}; + } + + var toWallet = profileService.getWallet(data.stateParams.toWalletId); + $ionicLoading.show(); + walletService.getAddress(vm.originWallet, false, function onWalletAddress(err, returnAddr) { + walletService.getAddress(toWallet, false, function onWalletAddress(err, withdrawalAddr) { + $ionicLoading.hide(); + shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, function(shapeshiftData) { + vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; + toAddress = shapeshiftData.toAddress; + vm.destination.address = toAddress; + vm.destination.kind = 'shapeshift'; + }); + }); + }); + } + } + + function startExpirationTimer(expirationTime) { + vm.paymentExpired = false; + setExpirationTime(); + + countDown = $interval(function() { + setExpirationTime(); + }, 1000); + + function setExpirationTime() { + console.log('setExpirationTime()'); + var now = Math.floor(Date.now() / 1000); + + if (now > expirationTime) { + setExpiredValues(); + return; + } + + var totalSecs = expirationTime - now; + var m = Math.floor(totalSecs / 60); + var s = totalSecs % 60; + vm.remainingTimeStr = m + ":" + ('0' + s).slice(-2); + }; + + function setExpiredValues() { + vm.paymentExpired = true; + vm.remainingTimeStr = gettextCatalog.getString('Expired'); + if (countDown) $interval.cancel(countDown); + $timeout(function() { + $scope.$apply(); + }); + }; + }; + function updateSendAmounts() { if (typeof satoshis !== 'number') { return; @@ -497,7 +560,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit }); } - vm.onSuccessConfirm = function() { + function onSuccessConfirm() { vm.sendStatus = ''; $ionicHistory.nextViewOptions({ disableAnimate: true, @@ -565,14 +628,13 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } }); - // Other Scope vars vm.showAddress = false; setButtonText(vm.originWallet.credentials.m > 1, !!tx.paypro); if (tx.paypro) - _paymentTimeControl(tx.paypro.expires); + startExpirationTimer(tx.paypro.expires); updateTx(tx, vm.originWallet, { dryRun: true @@ -765,36 +827,4 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit }); } - function _paymentTimeControl(expirationTime) { - $scope.paymentExpired = false; - setExpirationTime(); - - countDown = $interval(function() { - setExpirationTime(); - }, 1000); - - function setExpirationTime() { - var now = Math.floor(Date.now() / 1000); - - if (now > expirationTime) { - setExpiredValues(); - return; - } - - var totalSecs = expirationTime - now; - var m = Math.floor(totalSecs / 60); - var s = totalSecs % 60; - $scope.remainingTimeStr = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2); - }; - - function setExpiredValues() { - $scope.paymentExpired = true; - $scope.remainingTimeStr = gettextCatalog.getString('Expired'); - if (countDown) $interval.cancel(countDown); - $timeout(function() { - $scope.$apply(); - }); - }; - }; - } diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index be391edc3..703056f03 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -134,7 +134,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat } }); } else { - payproService.getPayProDetails(data, coin, function(err, details) { + payproService.getPayProDetails(data, coin, function onGetPayProDetails(err, details) { if (err) { popupService.showAlert(gettextCatalog.getString('Error'), err); } else { diff --git a/www/views/review.html b/www/views/review.html index 7317d7b68..c401b8c34 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -51,7 +51,7 @@ ng-if="vm.thirdParty && vm.thirdParty.id === 'bip70' && vm.thirdParty.name === 'bitpay'">

BitPay

-

Payment expired in XX:XX

+

Payment expires: {{vm.remainingTimeStr}}

From e29f97eddf4564322d912ab0a964049394aec84c Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 7 Aug 2018 11:07:19 +1200 Subject: [PATCH 121/240] Payment request expiration. --- src/js/controllers/review.controller.js | 1 + www/views/review.html | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 5785ee831..fe1eac2cd 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -510,6 +510,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit function setExpiredValues() { vm.paymentExpired = true; vm.remainingTimeStr = gettextCatalog.getString('Expired'); + vm.readyToSend = false; if (countDown) $interval.cancel(countDown); $timeout(function() { $scope.$apply(); diff --git a/www/views/review.html b/www/views/review.html index c401b8c34..c6631fd19 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -51,7 +51,8 @@ ng-if="vm.thirdParty && vm.thirdParty.id === 'bip70' && vm.thirdParty.name === 'bitpay'">

BitPay

-

Payment expires: {{vm.remainingTimeStr}}

+

Payment expires: {{vm.remainingTimeStr}}

+

Payment request has expired

From 09f627ea5118b0dfdd2b18601e73958fb02f8a52 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 7 Aug 2018 11:27:05 +1200 Subject: [PATCH 122/240] Suggested by merchant. --- src/sass/components/fee-summary.scss | 26 ++++++++++++++++---------- src/sass/views/review.scss | 4 ++++ www/views/review.html | 11 +++++++---- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/sass/components/fee-summary.scss b/src/sass/components/fee-summary.scss index 404643a82..47fe2f6d1 100644 --- a/src/sass/components/fee-summary.scss +++ b/src/sass/components/fee-summary.scss @@ -1,7 +1,7 @@ .fee-summary { position: relative; display: flex; - justify-content: space-between; + flex-direction: column; width: 100%; padding: 5px 12px 15px; box-sizing: border-box; @@ -17,17 +17,23 @@ background: linear-gradient(to bottom, rgba(242,242,242,0) 0%,rgba(242,242,242,1) 100%); } - .fee-fiat { - &.positive { - color: #70955F; + .amount { + display: flex; + flex-direction: row; + justify-content: space-between; + + .fee-fiat { + &.positive { + color: #70955F; + } + + &.negative { + color: #C24633; + } } - &.negative { - color: #C24633; + .fee-crypto { + color: #A7A7A7; } } - - .fee-crypto { - color: #A7A7A7; - } } \ No newline at end of file diff --git a/src/sass/views/review.scss b/src/sass/views/review.scss index 8bda83890..44b634697 100644 --- a/src/sass/views/review.scss +++ b/src/sass/views/review.scss @@ -14,4 +14,8 @@ .shapeshift-banner, .bitpay-banner { box-shadow: none; } + + .warning { + color: $v-warning-color-2; + } } \ No newline at end of file diff --git a/www/views/review.html b/www/views/review.html index c6631fd19..4eb4763d7 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -82,10 +82,13 @@
-
Fee: Less than 1 cent
-
Fee: {{vm.feeFiat}} {{vm.feeCurrency}}
-
- {{vm.feeCrypto}} {{vm.origin.currency}} +
Suggested by merchant:
+
+
Fee: Less than 1 cent
+
Fee: {{vm.feeFiat}} {{vm.feeCurrency}}
+
+ {{vm.feeCrypto}} {{vm.origin.currency}} +
From 7d1fff424ac68fa75fd5e6f5d90eb9fba0316596 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 7 Aug 2018 11:34:35 +1200 Subject: [PATCH 123/240] Streamlined display of memo. --- src/js/controllers/review.controller.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index fe1eac2cd..49c4d51ab 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -445,11 +445,9 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit function handleThirdPartyInitIfBip70() { if (vm.thirdParty.id === 'bip70') { - if (vm.thirdParty.memo) { - // Why not the whole memo? - vm.memo = 'Payment request for BitPay invoice.\n' + toAddress + ' for merchant'; - vm.memoExpanded = true; - } + vm.memo = vm.thirdParty.memo; + vm.memoExpanded = !!vm.memo; + txPayproData = { caTrusted: vm.thirdParty.caTrusted, domain: vm.thirdParty.domain, From 38852d32d4b300d82e8251b5a28a0f26cdc8b6f6 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 7 Aug 2018 11:59:36 +1200 Subject: [PATCH 124/240] Sending BIP70 payments. --- src/js/controllers/amount.js | 6 ++++-- src/js/controllers/review.controller.js | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index c53d49061..e6913a2cb 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -365,7 +365,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.alternativeAmount = txFormatService.formatAmount(amountInSatoshis, true); vm.allowSend = lodash.isNumber(a) && a > 0 - && (a >= vm.minAmount && a <= vm.maxAmount) + && (!vm.minAmount || a >= vm.minAmount) + && (!vm.maxAmount || a <= vm.maxAmount) && !vm.fundsAreInsufficient; } else { if (result) { @@ -385,7 +386,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.alternativeAmount = $filter('formatFiatAmount')(toFiat(result)); vm.allowSend = lodash.isNumber(result) && result > 0 - && (result >= vm.minAmount && result <= vm.maxAmount) + && (!vm.minAmount || result >= vm.minAmount) + && (!vm.maxAmount || result <= vm.maxAmount) && !vm.fundsAreInsufficient; } diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 49c4d51ab..73af91188 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -110,13 +110,14 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } vm.approve = function() { + if (vm.thirdParty.id === 'shapeshift') return; if (!tx || !vm.originWallet) return; - if ($scope.paymentExpired) { + if (vm.paymentExpired) { popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.')); - $scope.sendStatus = ''; + vm.sendStatus = ''; $timeout(function() { $scope.$apply(); }); @@ -445,6 +446,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit function handleThirdPartyInitIfBip70() { if (vm.thirdParty.id === 'bip70') { + vm.sendingTitle = gettextCatalog.getString('You are paying'); vm.memo = vm.thirdParty.memo; vm.memoExpanded = !!vm.memo; From 6dfbbb15d44b8e612ba6427eee4af7ffb116095f Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 7 Aug 2018 12:16:59 +1200 Subject: [PATCH 125/240] Added showSendMaxWarning(). --- src/js/controllers/review.controller.js | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 73af91188..7c8bd784d 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -659,6 +659,46 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit // }); } + function showSendMaxWarning(wallet, sendMaxInfo) { + var feeAlternative = '', + msg = ''; + + 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(wallet.coin, sendMaxInfo.amountBelowFee) + })); + } + + 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(vm.originWallet.coin, sendMaxInfo.amountAboveMaxSize) + })); + } + return warningMsg.join('\n'); + }; + + feeAlternative = txFormatService.formatAlternativeStr(vm.originWallet.coin, sendMaxInfo.fee); + if (feeAlternative) { + msg = gettextCatalog.getString("{{feeAlternative}} will be deducted for bitcoin networking fees ({{fee}}).", { + fee: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.fee), + feeAlternative: feeAlternative + }); + } else { + msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees).", { + fee: txFormatService.formatAmountStr(vm.originWallet.coin, sendMaxInfo.fee) + }); + } + + var warningMsg = verifyExcludedUtxos(); + + if (!lodash.isEmpty(warningMsg)) + msg += '\n' + warningMsg; + + popupService.showAlert(null, msg, function() {}); + }; + function statusChangeHandler(processName, showName, isOn) { $log.debug('statusChangeHandler: ', processName, showName, isOn); if ( From 6c46ec2bcab7315df92dbefd417bd13682e3ee09 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 7 Aug 2018 14:23:53 +1200 Subject: [PATCH 126/240] Fix for some changes which broke Shapeshift flow. --- src/js/controllers/review.controller.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 7c8bd784d..ce453cd8a 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -78,6 +78,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit originWalletId = data.stateParams.fromWalletId; satoshis = parseInt(data.stateParams.amount, 10); toAddress = data.stateParams.toAddress; + destinationWalletId = data.stateParams.toWalletId; vm.originWallet = profileService.getWallet(originWalletId); vm.origin.currency = vm.originWallet.coin.toUpperCase(); @@ -257,7 +258,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit try { if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer $ionicLoading.show(); - var toWallet = profileService.getWallet(data.stateParams.toWalletId); + var toWallet = profileService.getWallet(destinationWalletId); // We need an address to send to, so we ask the walletService to create a new address for the toWallet. console.log('Getting address for wallet...'); @@ -468,7 +469,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.thirdParty.data = {}; } - var toWallet = profileService.getWallet(data.stateParams.toWalletId); + var toWallet = profileService.getWallet(destinationWalletId); $ionicLoading.show(); walletService.getAddress(vm.originWallet, false, function onWalletAddress(err, returnAddr) { walletService.getAddress(toWallet, false, function onWalletAddress(err, withdrawalAddr) { From db7eee984bfe8304c309c32600171e61b18d5997 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 7 Aug 2018 14:48:43 +1200 Subject: [PATCH 127/240] Error handling for Shapeshift, and UI for destination wallet. --- src/js/controllers/review.controller.js | 21 ++++++++++++++++++--- www/views/review.html | 2 +- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index ce453cd8a..6ae2a51c0 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -470,11 +470,26 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } var toWallet = profileService.getWallet(destinationWalletId); + vm.destination.name = toWallet.name; + vm.destination.color = toWallet.color; + vm.destination.currency = toWallet.coin.toUpperCase(); + $ionicLoading.show(); - walletService.getAddress(vm.originWallet, false, function onWalletAddress(err, returnAddr) { - walletService.getAddress(toWallet, false, function onWalletAddress(err, withdrawalAddr) { + walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) { + if (err) { $ionicLoading.hide(); - shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, function(shapeshiftData) { + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString()); + return; + } + walletService.getAddress(toWallet, false, function onWithdrawalWalletAddress(err, withdrawalAddr) { + if (err) { + $ionicLoading.hide(); + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString()); + return; + } + + $ionicLoading.hide(); + shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, function onShiftIt(shapeshiftData) { vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; toAddress = shapeshiftData.toAddress; vm.destination.address = toAddress; diff --git a/www/views/review.html b/www/views/review.html index 4eb4763d7..9edd672ec 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -37,7 +37,7 @@
To:
+ ng-if="vm.destination.kind === 'contact' || vm.destination.kind === 'wallet' || vm.destination.kind == 'shapeshift'">
Date: Tue, 7 Aug 2018 13:56:56 +0900 Subject: [PATCH 128/240] incomingData, bip70 & bitpay --- src/js/services/incomingData.js | 116 +++++++++++++++----------------- 1 file changed, 54 insertions(+), 62 deletions(-) diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index 703056f03..a41fcb5f4 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -82,37 +82,33 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat }); // Timeout is required to enable the "Back" button $timeout(function() { + var params = {}; + if (amount) { - $state.transitionTo('tabs.send.origin', { - amount: amount, - toAddress: addr, - description: message, - coin: coin - }); - } else { - var params = { - toAddress: addr, - coin: coin, - displayAddress: originalAddress ? originalAddress : addr, - noPrefix: noPrefixInAddress - }; - if (serviceId) { - if (!params['thirdParty']) { - params['thirdParty'] = []; - } - params['thirdParty']['id'] = serviceId; - } + params.amount = amount; + } - if (serviceData) { - params['thirdParty']['data'] = serviceData; - // params['thirdParty']['minShapeshiftAmount'] = serviceData.minAmount; - // params['thirdParty']['maxShapeshiftAmount'] = serviceData.maxAmount; - // params['thirdParty']['shapeshiftOrderId'] = serviceData.orderId; - params['thirdParty'] = JSON.stringify(params['thirdParty']); - $state.transitionTo('tabs.send.amount', params); - } else { - $state.transitionTo('tabs.send.origin', params); - } + if (addr) { + params.toAddress = addr; + params.displayAddress = originalAddress ? originalAddress : addr; + } + + if (coin) { + params.coin = coin; + } + + if (noPrefixInAddress) { + params.noPrefixInAddress = noPrefixInAddress; + } + + if (serviceId) { + params.thirdParty = []; + params.thirdParty.id = serviceId; + params.thirdParty.data = serviceData; + params.thirdParty = JSON.stringify(params.thirdParty); + $state.transitionTo('tabs.send.amount', params); + } else { + $state.transitionTo('tabs.send.origin', params); } }, 100); } @@ -130,7 +126,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat } popupService.showAlert(gettextCatalog.getString('Error'), message) } else { - handlePayPro(createBchPayProObject(details), coin); + handlePayPro(details, coin); } }); } else { @@ -399,14 +395,30 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat }, 100); } - function createBchPayProObject(payProData) { - var displayAddr = payProData.outputs[0].address; - var toAddr = bitcoinCashJsService.readAddress('bitcoincash:' + displayAddr).legacy; - return { - amount: payProData.outputs[0].amount, + function handlePayPro(payProData, coin) { + + var toAddr = payProData.toAddress; + var amount = payProData.amount; + var paymentUrl = payProData.url; + + if (coin === 'bch') { + var displayAddr = payProData.outputs[0].address; + toAddr = bitcoinCashJsService.readAddress('bitcoincash:' + displayAddr).legacy; + amount = payProData.outputs[0].amount; + paymentUrl = payProData.paymentUrl; + } + + var name = payProData.domain; + if (paymentUrl.indexOf('https://bitpay.com') > -1) { + name = 'bitpay'; + } + + var thirdPartyData = { + id: 'bip70', + amount: amount, caTrusted: true, - name: 'bitpay', - domain: 'bitpay.com', + name: name, + domain: payProData.domain, expires: Math.floor(new Date(payProData.expires).getTime() / 1000), memo: payProData.memo, network: 'livenet', @@ -415,40 +427,20 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat time: Math.ceil(new Date(payProData.time).getTime() / 1000), displayAddress: displayAddr, toAddress: toAddr, - url: payProData.paymentUrl, + url: paymentUrl, verified: true }; - } - - function handlePayPro(payProDetails, coin) { - var thirdPartyData = { - id: 'bip70', - name: payProDetails.name, - caName: payProDetails.caName, - caTrusted: payProDetails.caTrusted, - coin: coin, - domain: payProDetails.domain, - expires: payProDetails.expires, - memo: payProDetails.memo, - merchant_data: payProDetails.merchant_data, - network: payProDetails.network, - requiredFeeRate: payProDetails.requiredFeeRate, - selfSigned: payProDetails.selfSigned, - time: payProDetails.time, - url: payProDetails.url, - verified: payProDetails.verified - }; var stateParams = { - amount: payProDetails.amount, - toAddress: payProDetails.toAddress, + amount: thirdPartyData.amount, + toAddress: thirdPartyData.toAddress, coin: coin, thirdParty: JSON.stringify(thirdPartyData) }; // fee - if (payProDetails.requiredFeeRate) { - stateParams.requiredFeeRate = payProDetails.requiredFeeRate * 1024; + if (thirdPartyData.requiredFeeRate) { + stateParams.requiredFeeRate = thirdPartyData.requiredFeeRate * 1024; } scannerService.pausePreview(); From a9711c257537bbbf7278ca96905f13773898efca Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 7 Aug 2018 14:02:53 +0900 Subject: [PATCH 129/240] Fix incomingData, expires different depending on btc or bch (bitpay...) --- src/js/services/incomingData.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index a41fcb5f4..e42661115 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -400,12 +400,14 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat var toAddr = payProData.toAddress; var amount = payProData.amount; var paymentUrl = payProData.url; + var expires = payProData.expires if (coin === 'bch') { var displayAddr = payProData.outputs[0].address; toAddr = bitcoinCashJsService.readAddress('bitcoincash:' + displayAddr).legacy; amount = payProData.outputs[0].amount; paymentUrl = payProData.paymentUrl; + expires = Math.floor(new Date(expires).getTime() / 1000) } var name = payProData.domain; @@ -419,7 +421,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat caTrusted: true, name: name, domain: payProData.domain, - expires: Math.floor(new Date(payProData.expires).getTime() / 1000), + expires: expires, memo: payProData.memo, network: 'livenet', requiredFeeRate: payProData.requiredFeeRate, From 886204d8b02ae067ea8c1e60a0e8e8a5a2c99de1 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 7 Aug 2018 14:34:02 +0900 Subject: [PATCH 130/240] incomingData time for bch --- src/js/services/incomingData.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index e42661115..2b9f5db25 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -400,7 +400,8 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat var toAddr = payProData.toAddress; var amount = payProData.amount; var paymentUrl = payProData.url; - var expires = payProData.expires + var expires = payProData.expires; + var time = payProData.time; if (coin === 'bch') { var displayAddr = payProData.outputs[0].address; @@ -408,6 +409,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat amount = payProData.outputs[0].amount; paymentUrl = payProData.paymentUrl; expires = Math.floor(new Date(expires).getTime() / 1000) + time = Math.ceil(new Date(time).getTime() / 1000) } var name = payProData.domain; @@ -426,7 +428,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat network: 'livenet', requiredFeeRate: payProData.requiredFeeRate, selfSigned: 0, - time: Math.ceil(new Date(payProData.time).getTime() / 1000), + time: time, displayAddress: displayAddr, toAddress: toAddr, url: paymentUrl, From 59d1704673266051a3f0c3cdae61e8c0ac0c8fbb Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 7 Aug 2018 17:34:28 +1200 Subject: [PATCH 131/240] Fix for display of fee when not for a BIP70 invoice. --- src/sass/components/fee-summary.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sass/components/fee-summary.scss b/src/sass/components/fee-summary.scss index 47fe2f6d1..e09af4be3 100644 --- a/src/sass/components/fee-summary.scss +++ b/src/sass/components/fee-summary.scss @@ -21,6 +21,7 @@ display: flex; flex-direction: row; justify-content: space-between; + width: 100%; .fee-fiat { &.positive { From 91487cf74c9f10d0a0796e9620d0f480c6bc7fd8 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 7 Aug 2018 15:02:29 +0900 Subject: [PATCH 132/240] shapeshift add the toAddress in the tx. Sending works. --- src/js/controllers/review.controller.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 6ae2a51c0..eb3fde53e 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -111,9 +111,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } vm.approve = function() { - - if (vm.thirdParty.id === 'shapeshift') - return; + if (!tx || !vm.originWallet) return; if (vm.paymentExpired) { @@ -491,7 +489,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit $ionicLoading.hide(); shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, function onShiftIt(shapeshiftData) { vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; - toAddress = shapeshiftData.toAddress; + tx.toAddress = shapeshiftData.toAddress; vm.destination.address = toAddress; vm.destination.kind = 'shapeshift'; }); From d26a96500b73c6ebf81cba9fd38e279adc795d34 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 7 Aug 2018 15:30:28 +0900 Subject: [PATCH 133/240] eGifter incomingData --- src/js/services/incomingData.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index 1a2601fda..946144dce 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -397,6 +397,8 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat function handlePayPro(payProData, coin) { + console.log(payProData); + var toAddr = payProData.toAddress; var amount = payProData.amount; var paymentUrl = payProData.url; @@ -413,7 +415,10 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat } var name = payProData.domain; - if (paymentUrl.indexOf('https://bitpay.com') > -1) { + + if (payProData.memo.indexOf('eGifter') > -1) { + name = 'eGifter' + } else if (paymentUrl.indexOf('https://bitpay.com') > -1) { name = 'BitPay'; } From 1aa31b69352e49ba1207f9d68e346b1508028600 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 7 Aug 2018 16:36:33 +0900 Subject: [PATCH 134/240] egifter template, waiting the logo in svg. --- app-template/bitcoincom/css/bitcoin.com.css | 29 +++++++++++++-------- src/sass/views/review.scss | 2 +- www/css/bitcoin.com.css | 24 +++++++++++++---- www/css/main.css | 23 ++++++++++------ www/views/thirdparty/egifter-header.html | 3 +++ www/views/walletSelector.html | 3 ++- 6 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 www/views/thirdparty/egifter-header.html diff --git a/app-template/bitcoincom/css/bitcoin.com.css b/app-template/bitcoincom/css/bitcoin.com.css index 19113dd2c..793276e25 100644 --- a/app-template/bitcoincom/css/bitcoin.com.css +++ b/app-template/bitcoincom/css/bitcoin.com.css @@ -264,15 +264,15 @@ div.onboarding-topic { height: 5em; } -.shapeshift-banner { - background: url(../img/shapeshiftbg.jpg) center center no-repeat #28394d; - padding: 10px; - box-shadow: 0px 5px 10px 0px #cccccc; - height: 5em; +.shapeshift-logo { + display: block; + float: left; + max-height: 100%; + max-width: 100%; } .bitpay-banner { - background: center center no-repeat #1A3A8B; + background: #1A3A8B; padding: 10px; box-shadow: 0px 5px 10px 0px #cccccc; height: 5em; @@ -285,9 +285,16 @@ div.onboarding-topic { height: 4em; } -.shapeshift-logo { - display: block; - float: left; - max-height: 100%; - max-width: 100%; +.egifter-banner { + background: #1A3A8B; + padding: 10px; + box-shadow: 0px 5px 10px 0px #cccccc; + height: 5em; +} + +.egifter-logo { + display: block; + max-height: 100%; + width: 100%; + height: 4em; } diff --git a/src/sass/views/review.scss b/src/sass/views/review.scss index 44b634697..79bca1896 100644 --- a/src/sass/views/review.scss +++ b/src/sass/views/review.scss @@ -11,7 +11,7 @@ bottom: 92px; } - .shapeshift-banner, .bitpay-banner { + .shapeshift-banner, .bitpay-banner, .egifter-banner { box-shadow: none; } diff --git a/www/css/bitcoin.com.css b/www/css/bitcoin.com.css index 59e503ac7..0cb170440 100644 --- a/www/css/bitcoin.com.css +++ b/www/css/bitcoin.com.css @@ -314,6 +314,13 @@ div.slide-success__background.fill-screen { height: 5em; } +.shapeshift-logo { + display: block; + float: left; + max-height: 100%; + max-width: 100%; +} + .bitpay-banner { background: #1A3A8B; padding: 10px; @@ -328,9 +335,16 @@ div.slide-success__background.fill-screen { height: 4em; } -.shapeshift-logo { - display: block; - float: left; - max-height: 100%; - max-width: 100%; +.egifter-banner { + background: #1A3A8B; + padding: 10px; + box-shadow: 0px 5px 10px 0px #cccccc; + height: 5em; +} + +.egifter-logo { + display: block; + max-height: 100%; + width: 100%; + height: 4em; } diff --git a/www/css/main.css b/www/css/main.css index 87333d1a6..350d5bbce 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -15262,8 +15262,10 @@ log-options #check-bar .checkbox-icon { #view-review .fee-summary { position: absolute; bottom: 92px; } - #view-review .shapeshift-banner, #view-review .bitpay-banner { + #view-review .shapeshift-banner, #view-review .bitpay-banner, #view-review .egifter-banner { box-shadow: none; } + #view-review .warning { + color: #b7664d; } .gravatar { border-radius: 3px; @@ -15410,7 +15412,7 @@ ion-content.padded-bottom-cta-with-summary { .fee-summary { position: relative; display: flex; - justify-content: space-between; + flex-direction: column; width: 100%; padding: 5px 12px 15px; box-sizing: border-box; @@ -15423,12 +15425,17 @@ ion-content.padded-bottom-cta-with-summary { width: 100%; height: 15px; background: linear-gradient(to bottom, rgba(242, 242, 242, 0) 0%, #f2f2f2 100%); } - .fee-summary .fee-fiat.positive { - color: #70955F; } - .fee-summary .fee-fiat.negative { - color: #C24633; } - .fee-summary .fee-crypto { - color: #A7A7A7; } + .fee-summary .amount { + display: flex; + flex-direction: row; + justify-content: space-between; + width: 100%; } + .fee-summary .amount .fee-fiat.positive { + color: #70955F; } + .fee-summary .amount .fee-fiat.negative { + color: #C24633; } + .fee-summary .amount .fee-crypto { + color: #A7A7A7; } .amount .start, .amount .middle, diff --git a/www/views/thirdparty/egifter-header.html b/www/views/thirdparty/egifter-header.html new file mode 100644 index 000000000..97d38603f --- /dev/null +++ b/www/views/thirdparty/egifter-header.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/www/views/walletSelector.html b/www/views/walletSelector.html index eeb7591a8..b375ddb8c 100644 --- a/www/views/walletSelector.html +++ b/www/views/walletSelector.html @@ -5,7 +5,8 @@
-
+
+
Paying
{{requestAmount}} {{requestCurrency}}
From 37614f08118cecc86dd5ff29c1b25b843da7d65a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 7 Aug 2018 16:45:58 +0900 Subject: [PATCH 135/240] Change to svg for egifter --- www/views/thirdparty/egifter-header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/views/thirdparty/egifter-header.html b/www/views/thirdparty/egifter-header.html index 97d38603f..a260e0726 100644 --- a/www/views/thirdparty/egifter-header.html +++ b/www/views/thirdparty/egifter-header.html @@ -1,3 +1,3 @@
- +
\ No newline at end of file From 83b1977a6f93922b9ee67730c1559fae6ed67b02 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Tue, 7 Aug 2018 09:47:37 +0200 Subject: [PATCH 136/240] Shapeshift controller useless functions removed --- src/js/services/shapeshiftService.js | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/js/services/shapeshiftService.js b/src/js/services/shapeshiftService.js index b3e307667..329cadbb3 100644 --- a/src/js/services/shapeshiftService.js +++ b/src/js/services/shapeshiftService.js @@ -8,21 +8,6 @@ angular.module('copayApp.services').factory('shapeshiftService', function ($http root.returnAddress = ''; root.amount = ''; root.marketData = {}; - root.withdrawalAddress = function (address) { - root.withdrawalAddress = address; - }; - root.returnAddress = function (address) { - root.returnAddress = address; - }; - root.amount = function (amount) { - root.amount = amount; - }; - root.coinIn = function (coinIn) { - root.coinIn = coinIn.toUpperCase(); - }; - root.coinOut = function (coinOut) { - root.coinOut = coinOut.toUpperCase(); - }; root.getMarketDataIn = function (coin) { if (coin === root.coinOut) return root.getMarketData(root.coinOut, root.coinIn); @@ -66,8 +51,10 @@ angular.module('copayApp.services').factory('shapeshiftService', function ($http root.shiftIt = function (coinIn, coinOut, withdrawalAddress, returnAddress, cb) { ongoingProcess.set('connectingShapeshift', true); - root.withdrawalAddress(withdrawalAddress); - root.returnAddress(returnAddress); + root.withdrawalAddress = withdrawalAddress; + root.returnAddress = returnAddress; + root.coinIn = coinIn; + root.coinOut = coinOut; var validate = shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut); validate.then(function (valid) { var tx = ShapeShift(); From 485039223fa72ce6bde1f70a18cbfe405c135588 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 7 Aug 2018 17:37:23 +0900 Subject: [PATCH 137/240] egifter template --- app-template/bitcoincom/css/bitcoin.com.css | 6 +++--- www/css/bitcoin.com.css | 8 ++++---- www/img/egifter_banner.png | Bin 0 -> 9837 bytes www/img/icon-egifter.png | Bin 0 -> 8025 bytes www/views/review.html | 6 ++++-- www/views/thirdparty/egifter-header.html | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 www/img/egifter_banner.png create mode 100644 www/img/icon-egifter.png diff --git a/app-template/bitcoincom/css/bitcoin.com.css b/app-template/bitcoincom/css/bitcoin.com.css index 793276e25..cb20ff48d 100644 --- a/app-template/bitcoincom/css/bitcoin.com.css +++ b/app-template/bitcoincom/css/bitcoin.com.css @@ -289,12 +289,12 @@ div.onboarding-topic { background: #1A3A8B; padding: 10px; box-shadow: 0px 5px 10px 0px #cccccc; - height: 5em; + height: 5em; + text-align: center; } .egifter-logo { - display: block; max-height: 100%; - width: 100%; + max-width: 100%; height: 4em; } diff --git a/www/css/bitcoin.com.css b/www/css/bitcoin.com.css index 0cb170440..f0d7eab30 100644 --- a/www/css/bitcoin.com.css +++ b/www/css/bitcoin.com.css @@ -336,15 +336,15 @@ div.slide-success__background.fill-screen { } .egifter-banner { - background: #1A3A8B; + background: #066EAA; padding: 10px; box-shadow: 0px 5px 10px 0px #cccccc; - height: 5em; + height: 5em; + text-align: center; } .egifter-logo { - display: block; max-height: 100%; - width: 100%; + max-width: 100%; height: 4em; } diff --git a/www/img/egifter_banner.png b/www/img/egifter_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..cb2e7c11e4a793788a9420258fae00c48b5c0f91 GIT binary patch literal 9837 zcmYLPcRbtQ*GJQowkl}VrWCb05PQ}tYE?z-po+vOirURrhpo$~+0s&C6MI(ey%N<5 zwFzRy9#5>_^ABF1d)#yG8TX#^J|{#^N1gg2^F=Z;GHQ(nDv!v>D4?XjcPW9SpCzvI zXwol$oszZ^8CgNpr9G?jWMsTIHB^)yzaU>sT8#f&mZj!3-MT-zD%J4#0cxbAMv?AA zY~aIaTPh2NA+9BzY3$(@=>x)O8c(9-A-nL}=S$?2iewa^mt+7qrQ-;!So|>$reF|)cSdcD^>N2Lyj-p{@e>7FxRo4?iZst3v4e^`f>hI{PpVIxOyS!phs8Aubjxi zsLkmZ1WwbRea%u(AJ-|!6@>FEymBlb=a@FLF!zq!xZ!B8W5@HlJAN8 zVOP}{(q#t>Vv;y7e|&B-3J8T24yNkDuK7hB&*4;il>>X@NFk&n{@??p*Js7DdQJb} zwf|)9@}Y8eJ(9)uL^Vi3B#0WL!BXTUgxknHUnh0KiHza^aDVSgj2Hd;U2{$sOI`j} zhZiR9Y$@M~^H`rH+DgN7_|yFbsaOYLXvYQ7_*e!xELYrD!hApeFI{OYaN*Qe5W21E zzVZwLM%#Qw-SLTIr~yAEwyH*w}5%=gDN><-=ggdMJzXgLxa$ zLMqo437B@4JW`_`B!!>Cs=lq#~U#w>raEtD~nfzZ;#ypIRK$LJ!5krltnX(PBa#?ACkj zlVV2b-`1zD3lkk{=mBAqJ&hfQW+x{Utc91)R*)#;MDx5)>V~wS>wL ze@^HU=sk|9x;a3A_XX%K@8q!LK(63i^{{X!p?c|i>13dqYz#DeN2tcY6T+4$Tyq+N z*?J-lL;J<_Vw@JDt2;cso=zPlAC||Ofk#8kvBa}z%?4C3XB)HFx;!fqAL?a& z`m2{kIj6*8x4K*xI|zS>J#&OHr7xNFCgsK@Kp7i%woaSpm#cT&D}0@Plu0L=m`WWID?Ea@89D#$<4lRBF0?c+a_G10DcEkHYSYngsucNPRyw zc&WXPhw;Kz>}pZtwd>P>m(^^Qyv!a%e`k($c+du<?fjZ zy!%=tPi?I}Rtv;CB@DG{V?xc+sr7S*d~!n;uiYcs+ftv9C{k4|QO5uStUrQ_XqkRL zc8J`1brLfRnsw-U_{eiRaex<9DCP-zwLk8u+rr=v6C=N+AKvHr{O%u_<0S`jls&8Gg4bZ4G;X2rtP2ghMgr1^P@z6qtW?OncXkx58Nduyhi}{#i8+ZzZZ<0 zis!u`4e!^|Og^;-cPAdw9=M6ijYnM7y$#7dG4*G_eF^A)W)hlF+NFH2=Z!qphi1*w zYJ9QSY*ShzomlmbUhB5TcJQ%vB|j=|0ksO+)8`7dv#K$=L^+!^h-D75>2!B+nrc)B zF<8dhEQd9Y=GZHD>4!)`GEnI41^o^7h9Dz2sWzg&VXm`~rqi`-k-wk-Yq07zXQH2oL$SlP18Kl!iYa^6( z8G+KYun4m7ZOgZ?t49*pP;xjp>yY#2=smIr->h|Igx@9?+IrQ3VA6|i@F=P zrRFro+})MJH*M|tP-cc>@pwHyvTigG?5~n0f)Hi>>Tj;O?57Tc2#;((Jz>DvqY1MQ z^0+4I_VHV5MW6hH-;>TnrQln~Zp_Jrtfh_#PSXg9OZ9@M7WmlVLYPT%)$t#>KHXJq zOxysxfo#;Srft?6XmslOzM!pMScqdhneVDe!fOc;7uzS@7a2|$-6{WyL&tX_SOV^> zzK{5+;mz2ZDdCk(3dD^YJ-f1#zl(Gubsy~v!8G1gH~P_isf2QkWcRvV4e2yrVmzYa z8ru(_U_u)mctH}J@^9WHjN@oAnG#R) zHp2zE|Ln5=@g^!UaUAKThMut+yX<(_l~7aw$YK_Hf45?}`OW9jF74QvqV_>V#0RXo z$XgxCj^8#fc=a4asrGU`(qDUz{DNcE<1-3W`d-D*AnE7B%t%q>1Sli?d2!E|hGLL9 z<_&jetB%CE&QDLBmu7UpBC(foT(rws2FarP#>6(IS_;U0*k$BA2d#X0)N}XccfM_p z4qtrRD^XLDE2fk2JqR^P2d;s^GJKQVDKxn|boK#39v*q;+aug~=x6*qoyS!oKSunlQ?^K1y#G-V1uNxx7JZF)0^yF(S*?bLN5)1u*|4K@1UuV0MZpqMnpthKpG+H_9 zS)tA~U`L&&FOBdUof;Am7hnkE#UyLNpg|tDMSP!NFK8`l2b$X%xs|*&$+a_hq5iyb zl%)uPejA(7#D0j;+!$~f{JG-=x_cPB%oZ1*Rz6Q&@RQIz%Y;oAYP^<}IewoII02&@V_IWz z9JEV*=g{&(-vFMG*wR-iImBoFB~8(k0NmqE?s;rbieao_&|u&zX|)C!%2z%?w$&@R z_l3HjR^K79?oHm53PrzH+E@_gHt8|70t^h6LR$sNCDr5V`uXY`xCl>+_0_T8JnGoDlea5oZ0U9R z$>%4Ol_kvxNuT;}kNU28-(RSlq)ZARITnOK{g01U3k;YfzX~>zWkrazBz{sHFwp^b z9o&9c7W>qonFFvxwP#?`Ms3b@b;jqLSqOvNIOu-s6@YziauFcGPTUNsC`QpJ0qq5( z@#!kY#~adEtt1(3?#~M(jT$~<2&rp}x6d4nmTvS4!#vnt>|ITV`fla(-X6H zcdBtThEW&%C9X2O;ksMMP=zl!pSZITs=J7`hmn$#{76M2q2qp-Q#X z73ll(Ba}<*cSX{%f)9n0Qg;lhrNs@P^MS8Muic9XK9}GG@NBJ|&n~BV?#vRh>R4bp zVJRHZ_jP0YRb2?+MbO|oLvPMjS92~J4yoJ;{cZ)g!eK-ft+^e2S!I+EE#dj0Ln2S- zcRV~}vj`KgSoPrwAu)E(m5l;LwG2dYpWFFTCjVm6EYBm(mI`2@RJX=njtiyc1FGhufs@r>l}XT01Q|8PnMrHi$H^o_qvk%R5P3W*KU0Yj!1;{i zOn6|Mj&h}#apmk^{>@5zp3TZ97n4Yt1RpXT7^a})le$^N!xG0Dz)%1{LIE!}ezjeI zld@nD(lW|1l1Y%N=Sj}DD1rPiEq9s^k*HX*uO67&JezO2<2urqu-f7uIBs3=d94{tDNFlhI=SU8Da}{m zTaE|fZ_{GmZW`FQmWuDhHMvEsXq0vZR&+@MOsJrUtP)RCk9!t}mjw1% zjQ05agipZCnsl?`IMdG(Z7~4KA+JlSSBm~+2 z4bmIr)Uf|?xDpi*6b)pa^L29Lc2EmbAjfz9NqgY`C`fJ{dJZW^HS4dSr#bW{{Ie5(n~71;2TRmjZz7<| z4QpyNCru-4&5btJ{qHl$g>O-?ZVI)vf*PXybLoq?P++1@Y+Q4z8Krt%e67BR$9bLrA9P#Ahc1gh zsYOqAnJ>+NUMjL6jW^@A3S=)0r!p&4#{3znTB=pOWi`UY%HdplAqql zOF8F3@&A6qd2t7JqVbE}3D-9`mmw4}Gb;m))R_@ytSD6UGRIVEMzv zl}x-ZW*VcypPHyv{&bLkPgCBr#`#}SsCJ&ER<_Ix<01&EUFSw1;s}^Hqitf(;`jX) zpw`&z`=8Fjj|$%BZtmc+8rtu8j)NEtOZOo#L=HiIy2cX?6!LueakyAnLhp97Sz@EGHQ;%i2?et zWR#ul=ij>zJGNz0>)VhH!d;0utSuPCxOx-@EjV53Rt5hA&9h0&<5ASXtTxL4)aZiU z0@Zup+sJ9AmDe-#2wg~Ksw3fdCHZ>pTKK~cId3z}9Zza03jo)G2K%okx@-AG2@xc4 zqnNUvAlLei=(e6sv`CA9d>bz=47nQSRinAV)x@s)5uK~KP<{nj3JP}9CDi$fp-AYF z#y8n7Q_Gt3&BHUb7UII5-$0Z3L(3+JDOR3H3wF$w@4lL4sjo%an(AmJ<#J-dp{No{ za5@f%xY?qcK-?xgPym!aJSjwzTW=qtSZQ}+E;uvK zgagpAor%|e*3%s(z@O@J5(%#P)J$9*?T-d!G%SH1GNn)^$`rR1$u_aC)>HvQ&`_|(61II zzDjilf+>)A1$d4^G}pMAOY7g#J7iD3Yfo|64`j*1%mjVXYv607ySSicy8D^Dok*MZ2Ua(KtTZ{vXk?sT>iR!WnLW0g;#`am`Bg(2bFE=^vTAYhiBxynq zU`8^-8}6$}oqOnlt|1{e6F021a!R42(TKs=7~r`6{Yn4KDgUQB{${@CT1v0k)cvf% z&qW2R!%I6xg{F&t;wuL?yWjy^B=}*2PcdjPu3}Xdz?) z-2@|gU00Uo-7p1z$8-B}bl$ojZbZ%Q{jsX{{YW&=4Cu#=y6Q*D4mj3LVEe>Z={tMU zx@c8=FJNFN%Rt^PM*VMQ9~;xRoiq6HnM=NovPtD{Uoj?hO){e?Hq1OqGI@P4QoTnH z747X!@#rJQugxcFxJz*A7%8-**t-IGW4EY0Qh^M9Wn#%xKCQ*>8m9^)qmTgn=w!Kd znN5rHXTUR|zahw8m|R`1@Uy53fjM~>GB4}iRLmW=YRFDv66EYruCq~?Uulo`C4rF= zW^f5WlyxrAgS+3Og`2@h=`B-Gp4`{F#iG9`0GxND>fQkbHT!#l3Luru4K)}eT~=X) zthK{R^WRNUE$aa%`lFS9frg?dl97ABgqs!5^!ThP)A?vQ_=mnr-d-%yQ-YI$V2;~lHm#F>8y88dvR@t2%F*DnuB zMHramxPhX2yJMyD1Bm?x`cVJDtC-6iU}>BfpMZNWPxGu<@!xYi!lLUjV$awpXsJ|c zB)AZ3T|Cqf9({yg5`3d`SwGDNDN12LN#SJ+U7!z{VPR5@^*LL zasRLJ>qXJc&TDVM(3qcbRIYEjOgJ7rcl+CTC>9_QTQCGjopCF!8Q%TM6P5-pwYORd z0H_U223&0tsKKRS*9vf0C^pUyA^kHM~gf=-am4{Zff&a z4!DU=hP&CTM^Yrf5lf$s<3qu7rhfN|lqM%SrMqb$I3$IEIU*DJk4e`eo6^C+2 znTReM57y)k>N*7Is4oo+*XCvrO&8sld?uCC3zt)z{h5;cL1*i-rV|xhSbj2uKz~qE_U@ekl@WJ z_fHmgBG5ZNdvd>ZoQ$V$%@~29x^-9I1f;+7$lfSwdfQkt`(wp*5I@xqpV6s(+bL6N z1WfC$9@k5f^TwcenDjrwD|gp1zB3cj)AhzqU6@f+wYpv29YGe9CDC$c!OG39NY!P& z$kV)Hx+@S5%^Ce*u4^tF@3SZW+sM?%yc_Jv7VBOe6FDclzn5dVwFlC(my>n-GML<0 zGi<+a_D?iJpLpxe_CUekic<4R5q%4uD_D>3+h{CeA3I!`P@2l1wo(xmRj@`Cp36t@ zoy;nolCRArwm63c(geblZS)U)aCyxf5xNmivRrrq-JN z_Es8+IPu1))wRMTcnWdkV`tKIw4<1US4CB`n)^B?tXh;(&nrbX*RLx(_gm$RnKZBC z`~SGW^LUp`Ml#Kfxl5*gm~%Mc zx`y+FjG7AjHlxqYfP&S=ig&r;vV{tBy(Z@xSh68Xg436(*lWS(dl8lDx)te-F50Y_ zi?pMJb+w*B7JSz*c$)HFFi`3-eF}(C16FGCN1I^2bQ37Gl&i|JduzYXq|g6(kl{t# zhbrbjUfMy8b`_XEp4zhG3PjUN-od2ZdB?APUYHs~vR zd3?*6ujCL`Z<)JRs{a)Ag-TY`Yo7Dpv7mTfnwx&hX92f8&y+A%`9@XivhHy8ezARF zN9RZ)LnSv>&9<6biy&KHmoTgAtB*g-=p%4f&P9GJg|zn{tjp0`+BJ(q!F489{#NaQ zEVdE^qPxc%0xF6b10MG18mx)*-PNxYmG|no%{&!X-!MLrCQ_rL6+UMY;q~_Kw+gR= z8bL%(bjt!a!f)93RR-plM`Yyay_7!K6yR@2VPwR*QKA29mk6HHgL%A9vZ=3E&ecC7 z@%lCxsM=iN(6t&;E*uWs)xV}|#E&ueh#l|4XEbNHs_kDr&__oeDAY@m-1;?@B6vu2Mtgujxobb}S12*c43` zP3^!BQ^CSj42$2(=}bccTh)Hy9Uc~~#_oi-{7U7IaAMF6voKD8mkfv2SXhtn+~L>i z?0v8}&rQS=<Z&0`a0?}l^y*E32-C&}}eK40)!EZ_}UYj-io5YAG0F)Wt)ZCl*J!yBu8dZ*Dyr7kUYYt#94=y;$H7 z;{IzG5uS6;92?dIVPSV2nOn{ei}0c=Ls`KWlF*6+pxGPC6%tV4cbYW)l^33c?f-D+ zaYi_@i>IzCVPz;kEggF4acYv9@v(KE0@J@5nJretU$8JV zX5;ctIvKunW7H5koX+14IqTK@-p_O~$5|a2;3sJ4z$+h)6GJ=i@Fe7H{Cu)|{=R0p zFFD2i$ zs3H7inCYy6eDtv3+=TQAm_tTkM>kYbF924~nG^V(Ru1QB^E<^@e5oA&z@i_iDL+2W z22Q$$YP4}USTyeHAYk5#n`ls|UKGVE$I}KO=R3Ng zToMx%hi86gBMW2{f|QO}STyI#MwUu2{-ldSP8rUdZx}SSy^-|<^E^;|tCTbU zI+B!EWR}mVJ}?*{brjD2;RPME59ST*u|W-t_5L90o%Q6%C^TUSz_9cO22Fft^7T@Y z#v#0s-}KkGLfFpo(qu}R4#SOGsN1I|n#bs^`+h6!dW1nJ!7Q z0x@7{5p7JoAkLz*i}K*Kr7}ZDIST7#OfvOTIq55bB%?`vt@d-W+VK}u|ECH)Ef^jc z#(^J9|LlEshK`&0#q|Q!#scPfW}DP~`<9)V-6fEaNoUl5P@naDUT z*dHKjTKl|99?o<~FN~DC~<%o38)vjaYAn2PP_!ntKz85b9yxJQRh$UpP0IowAtBLP735Avyh+| zvzVX~=FCWXT+5YWR;Bpw95hP|G(PQs7VporqQ|?yZj&Z1VjSP?JmoJno=+?fUM`7O z%;kk38cszRi&P8-;&q_iPzUaGCHz-uPi=lXn`)cW35UCH>h!5Gj4Dtx=nJy2$42;A zxP>Dlqt-Vvi{Hm$2=R5%6<-OC(g;cZmlS;9qWUKKrTf(G~ z`P7i5=AeH5f1s1Agj9u!fsS0qdTcY#?RnX$OTVmxQ<#cFQD6TZ!L}#jOj7p1re>c} z|L?hJj& z|F<;{D7RiZMhtnThGk|_4VI??Mjm(INk_RGRwqu)P&+1%y19HZ071egqeYaT!cBOF_P?x}k iy|Wu~Olj$-NKz@BaWAaL>H} literal 0 HcmV?d00001 diff --git a/www/img/icon-egifter.png b/www/img/icon-egifter.png new file mode 100644 index 0000000000000000000000000000000000000000..42ebb25c5cf0aa23812f3380c950fdccb44f6326 GIT binary patch literal 8025 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 bn$!LbT?UmdRe+dG00000NkvXXu0mjf^}=3S literal 0 HcmV?d00001 diff --git a/www/views/review.html b/www/views/review.html index 69c0c31f0..e9e5c6d7c 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -48,8 +48,10 @@

{{vm.destination.balanceAmount}} {{vm.destination.balanceCurrency}}

- + ng-if="vm.thirdParty && vm.thirdParty.id === 'bip70' + && (vm.thirdParty.name === 'BitPay' || vm.thirdParty.name === 'eGifter')"> + +

{{vm.destination.name}}

Payment expires: {{vm.remainingTimeStr}}

Payment request has expired

diff --git a/www/views/thirdparty/egifter-header.html b/www/views/thirdparty/egifter-header.html index a260e0726..97d38603f 100644 --- a/www/views/thirdparty/egifter-header.html +++ b/www/views/thirdparty/egifter-header.html @@ -1,3 +1,3 @@
- +
\ No newline at end of file From 9420b6372825ee6e01ae4af41c7dc3662cb23a23 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 7 Aug 2018 20:11:21 +0900 Subject: [PATCH 138/240] Fix shapeshift, remove the cancelTx. Remove the timer with the getStatus --- src/js/controllers/review.controller.js | 16 ++++++++++------ src/js/services/shapeshiftService.js | 24 +++++++++--------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 919ac3542..62b5fa1b6 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -487,12 +487,16 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit return; } - $ionicLoading.hide(); - shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, function onShiftIt(shapeshiftData) { - vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; - tx.toAddress = shapeshiftData.toAddress; - vm.destination.address = toAddress; - vm.destination.kind = 'shapeshift'; + shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, function onShiftIt(err, shapeshiftData) { + if (err && err != null) { + $ionicLoading.hide(); + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString()); + } else { + vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; + tx.toAddress = shapeshiftData.toAddress; + vm.destination.address = toAddress; + vm.destination.kind = 'shapeshift'; + } }); }); }); diff --git a/src/js/services/shapeshiftService.js b/src/js/services/shapeshiftService.js index 329cadbb3..4a77d8db0 100644 --- a/src/js/services/shapeshiftService.js +++ b/src/js/services/shapeshiftService.js @@ -45,7 +45,7 @@ angular.module('copayApp.services').factory('shapeshiftService', function ($http }; function checkForError(data) { - if (data.error) return true; + if (data.err) return true; return false; } @@ -55,8 +55,7 @@ angular.module('copayApp.services').factory('shapeshiftService', function ($http root.returnAddress = returnAddress; root.coinIn = coinIn; root.coinOut = coinOut; - var validate = shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut); - validate.then(function (valid) { + shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut).then(function (valid) { var tx = ShapeShift(); var coin; console.log("Starting"); @@ -64,7 +63,7 @@ angular.module('copayApp.services').factory('shapeshiftService', function ($http console.log("Got txData", txData); if (txData['fixedTxData']) { txData = txData.fixedTxData; - if (checkForError(txData)) return; + if (checkForError(txData)) return cb(txData.err); //console.log(txData) var coinPair = txData.pair.split('_'); txData.depositType = coinPair[0].toUpperCase(); @@ -76,20 +75,17 @@ angular.module('copayApp.services').factory('shapeshiftService', function ($http root.txFixedPending = true; } else if (txData['normalTxData']) { - txData = txData.normalTxData; - if (checkForError(txData)) return; + if (checkForError(txData)) return cb(txData.err); coin = root.coins[txData.depositType.toUpperCase()].name.toLowerCase(); txData.depositQR = coin + ":" + txData.deposit; - } else if (txData['cancelTxData']) { - - if (checkForError(txData.cancelTxData)) return; + txData = txData.cancelTxData; + if (checkForError(txData)) return cb(txData.err); if (root.txFixedPending) { root.txFixedPending = false; } root.ShiftState = 'Shift'; - return; } root.depositInfo = txData; //console.log(root.marketData); @@ -101,8 +97,8 @@ angular.module('copayApp.services').factory('shapeshiftService', function ($http ongoingProcess.set('connectingShapeshift', false); root.ShiftState = 'Cancel'; - root.GetStatus(); - root.txInterval=$interval(root.GetStatus, 8000); + //root.GetStatus(); + //root.txInterval=$interval(root.GetStatus, 8000); var shapeshiftData = { coinIn: coinIn, @@ -118,14 +114,12 @@ angular.module('copayApp.services').factory('shapeshiftService', function ($http ongoingProcess.set('connectingShapeshift', false); // return; // } - cb(shapeshiftData); - + cb(null, shapeshiftData); }); }) }; function ShapeShift() { - if (root.ShiftState === 'Cancel') return shapeshiftApiService.CancelTx(root); if (parseFloat(root.amount) > 0) return shapeshiftApiService.FixedAmountTx(root); return shapeshiftApiService.NormalTx(root); } From af90c53e502759a1432360338082f1d77fd0d432 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 7 Aug 2018 20:14:48 +0900 Subject: [PATCH 139/240] Add memo expended --- src/js/controllers/review.controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 62b5fa1b6..bb160b3d1 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -492,6 +492,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit $ionicLoading.hide(); popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString()); } else { + vm.memoExpanded = !!vm.memo; vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; tx.toAddress = shapeshiftData.toAddress; vm.destination.address = toAddress; From 07ac4e77387dedf63b871bc3b65eae82ad5ad441 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 7 Aug 2018 20:50:56 +0900 Subject: [PATCH 140/240] Show memo for shapeshift --- src/js/controllers/review.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index bb160b3d1..9004ce585 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -492,8 +492,8 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit $ionicLoading.hide(); popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString()); } else { - vm.memoExpanded = !!vm.memo; vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; + vm.memoExpanded = !!vm.memo; tx.toAddress = shapeshiftData.toAddress; vm.destination.address = toAddress; vm.destination.kind = 'shapeshift'; From 9f418583abd989dfbc98452cbdc46cfb036e1494 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Tue, 7 Aug 2018 16:11:47 +0200 Subject: [PATCH 141/240] formatted amount controller update + css + renames + included in views --- src/js/directives/formattedAmount.js | 29 +++++++++++++------ src/sass/components/components.scss | 2 +- .../{amount.scss => formatted-amount.scss} | 4 ++- www/views/amount.html | 12 ++++---- www/views/includes/formatted-amount.html | 2 +- www/views/includes/walletHistory.html | 5 ++-- www/views/includes/walletList.html | 8 +++-- www/views/tx-details.html | 8 ++--- www/views/walletDetails.html | 27 ++++++++--------- 9 files changed, 58 insertions(+), 39 deletions(-) rename src/sass/components/{amount.scss => formatted-amount.scss} (89%) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index ac74afd2b..3c787521a 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -15,16 +15,27 @@ angular.module('bitcoincom.directives') return { restrict: 'E', scope: { - value: '=', - currency: '=', - sizeEqual: '=' + value: '@', + currency: '@', + sizeEqual: '@' }, templateUrl: 'views/includes/formatted-amount.html', controller: function($scope, $timeout) { + if (!$scope.currency && $scope.value) { // If there is no currency available.. + // Try to extract currency from value.. + var currencySplit = $scope.value.split(" "); + if (currencySplit.length === 2) { + $scope.value = currencySplit[0]; + $scope.currency = currencySplit[1]; + } + } + $scope.displaySizeEqual = !!$scope.sizeEqual; configService.whenAvailable(function(config) { + console.log("WAIT!!"); $timeout(function() { + console.log("FIRED!!"); var decimalPlaces = { '0': ['BIF', 'CLP', 'DJF', 'GNF', 'ILS', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'], '3': ['BHD', 'IQD', 'JOD', 'KWD', 'OMR', 'TND'], @@ -44,9 +55,9 @@ angular.module('bitcoincom.directives') }; var getDecimalPlaces = function(currency) { - if (decimalPlaces['0'].indexOf($scope.currency.toUpperCase()) > -1) return '0'; - if (decimalPlaces['3'].indexOf($scope.currency.toUpperCase()) > -1) return '3'; - if (decimalPlaces['8'].indexOf($scope.currency.toUpperCase()) > -1) return '8'; + if (decimalPlaces['0'].indexOf(currency.toUpperCase()) > -1) return '0'; + if (decimalPlaces['3'].indexOf(currency.toUpperCase()) > -1) return '3'; + if (decimalPlaces['8'].indexOf(currency.toUpperCase()) > -1) return '8'; return '2'; }; @@ -59,7 +70,7 @@ angular.module('bitcoincom.directives') case '3': var valueProcessing = parseFloat(parseFloat(value).toFixed(3)); - var valueFormatted = localizeNumbers(valueProcessing); + var valueFormatted = localizeNumbers(valueProcessing, 3); buildAmount(valueFormatted, '', ''); break; @@ -78,11 +89,11 @@ angular.module('bitcoincom.directives') default: var valueProcessing = parseFloat(parseFloat(value).toFixed(2)); - var valueFormatted = localizeNumbers(valueProcessing); + var valueFormatted = localizeNumbers(valueProcessing, 2); buildAmount(valueFormatted, '', ''); break; } - } + }; formatNumbers($scope.currency, $scope.value); $scope.$watchGroup(['currency', 'value'], function() { diff --git a/src/sass/components/components.scss b/src/sass/components/components.scss index 0af55e5be..fb53508b0 100644 --- a/src/sass/components/components.scss +++ b/src/sass/components/components.scss @@ -8,4 +8,4 @@ @import "action-minor"; @import "expand-content"; @import "fee-summary"; -@import "amount.scss"; +@import "formatted-amount"; diff --git a/src/sass/components/amount.scss b/src/sass/components/formatted-amount.scss similarity index 89% rename from src/sass/components/amount.scss rename to src/sass/components/formatted-amount.scss index 363d38a20..6678572c9 100644 --- a/src/sass/components/amount.scss +++ b/src/sass/components/formatted-amount.scss @@ -1,4 +1,6 @@ -.amount { +.formatted-amount { + display: inline-block; + .start, .middle, .end, diff --git a/www/views/amount.html b/www/views/amount.html index 48637ec1b..ee969762a 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -16,11 +16,13 @@
- {{vm.amount || '0'}} {{vm.unit}} + + +
- {{vm.globalResult}} {{vm.unit}} +
- {{vm.alternativeAmount || '0.00'}} {{vm.alternativeUnit}} +
@@ -43,7 +45,7 @@
- Available Funds:{{vm.availableFunds}} + Available Funds:
@@ -53,7 +55,7 @@
diff --git a/www/views/includes/formatted-amount.html b/www/views/includes/formatted-amount.html index 361dededc..20063458d 100644 --- a/www/views/includes/formatted-amount.html +++ b/www/views/includes/formatted-amount.html @@ -1,4 +1,4 @@ -
{{start}}{{middle}}{{end}}{{currency}}
\ No newline at end of file diff --git a/www/views/includes/walletHistory.html b/www/views/includes/walletHistory.html index 4a40e93d8..296b35b1a 100644 --- a/www/views/includes/walletHistory.html +++ b/www/views/includes/walletHistory.html @@ -64,17 +64,16 @@ - (possible double spend) - {{btx.amountValueStr}} {{btx.amountUnitStr}} +
- {{btx.alternativeAmountStr}} +
diff --git a/www/views/includes/walletList.html b/www/views/includes/walletList.html index f7a061740..d5fb65302 100644 --- a/www/views/includes/walletList.html +++ b/www/views/includes/walletList.html @@ -7,8 +7,12 @@ Incomplete - {{wallet.status.totalBalanceStr ? wallet.status.totalBalanceStr : ( wallet.cachedBalance ? wallet.cachedBalance + (wallet.cachedBalanceUpdatedOn ? ' · ' + ( wallet.cachedBalanceUpdatedOn * 1000 | amTimeAgo) : '') : '' ) }} - {{wallet.status.totalBalanceAlternative ? wallet.status.totalBalanceAlternative : ( wallet.cachedBalance ? wallet.cachedBalance + (wallet.cachedBalanceUpdatedOn ? ' · ' + ( wallet.cachedBalanceUpdatedOn * 1000 | amTimeAgo) : '') : '' ) }} {{wallet.status.alternativeIsoCode}} + + + + + + Scanning funds... [Balance Hidden] diff --git a/www/views/tx-details.html b/www/views/tx-details.html index e3a7a06b4..467ce28e3 100644 --- a/www/views/tx-details.html +++ b/www/views/tx-details.html @@ -24,13 +24,13 @@ Receiving
-
{{btx.amountValueStr}} {{btx.amountUnitStr}}
+
- {{btx.alternativeAmountStr}} + ... - {{rate| currency:'':2}} {{alternativeIsoCode}} ({{rateDate | amDateFormat:'MM/DD/YYYY HH:mm a'}}) + ({{rateDate | amDateFormat:'MM/DD/YYYY HH:mm a'}})
@@ -115,7 +115,7 @@ Fee {{btx.feeStr || '...'}} - {{btx.feeFiatStr || '...'}} - {{btx.feeRateStr}} of the transaction + ...- {{btx.feeRateStr}} of the transaction
diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index 0f9e4961c..12506e05d 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -34,12 +34,12 @@ on-hold="hideToggle()" ng-style="{'transform': amountScale}" ng-class="{amount__balance: amountIsCollapsible}"> - {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}} +
- {{status.totalBalanceStr}} +
@@ -47,13 +47,14 @@ ng-show="selectedPriceDisplay=='crypto' && !updateStatusError && !wallet.balanceHidden && !wallet.scanning" on-hold="hideToggle()" ng-style="{'transform': amountScale}" + ng-if="status.totalBalanceStr" ng-class="{amount__balance: amountIsCollapsible}"> - {{status.totalBalanceStr}} +
- {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}} +
@@ -86,7 +87,7 @@   - {{status.spendableBalanceAlternative}} {{status.alternativeIsoCode}} +
@@ -127,12 +128,12 @@ on-hold="hideToggle()" ng-style="{'transform': amountScale}" ng-class="{amount__balance: amountIsCollapsible}"> - {{status.totalBalanceStr}} +
- {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}} +
@@ -142,12 +143,12 @@ on-hold="hideToggle()" ng-style="{'transform': amountScale}" ng-class="{amount__balance: amountIsCollapsible}"> - {{status.totalBalanceAlternative}} {{status.alternativeIsoCode}} +
- {{status.totalBalanceStr}} +
@@ -180,7 +181,7 @@   - {{status.spendableBalanceAlternative}} {{status.alternativeIsoCode}} +
@@ -189,7 +190,7 @@
@@ -95,8 +95,8 @@ {{wallet.name || wallet.id}}

- {{wallet.status.totalBalanceAlternative}} {{wallet.status.alternativeIsoCode}} - {{wallet.status.totalBalanceStr}} + + [Balance Hidden] From 29a5a37c0274e8d81f011f9060c0981a6e875d11 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 8 Aug 2018 12:24:13 +1200 Subject: [PATCH 143/240] Formatter displays blank amount when value is NaN. --- src/js/directives/formattedAmount.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index 3c787521a..e223a8fd5 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -62,6 +62,11 @@ angular.module('bitcoincom.directives') }; var formatNumbers = function(currency, value) { + if (isNaN(parseFloat($scope.value))) { + buildAmount('', '', ''); + return; + } + switch (getDecimalPlaces(currency)) { case '0': var valueFormatted = localizeNumbers(Math.round(parseFloat(value))); From 12aa9a3b18f51937b890f6801376d1a7da1cf269 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 8 Aug 2018 13:28:44 +1200 Subject: [PATCH 144/240] formatted-amount displays "-.--" when it does not have a value. --- src/js/directives/formattedAmount.js | 56 +++++++++++++++++++--------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index e223a8fd5..ccf6c1963 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -60,46 +60,68 @@ angular.module('bitcoincom.directives') if (decimalPlaces['8'].indexOf(currency.toUpperCase()) > -1) return '8'; return '2'; }; + + var getDecimalSeparator = function() { + var testNum = 1.5; + var testString = testNum.toLocaleString(uxLanguage.getCurrentLanguage()); + // Some environments let you set decimal separators that are more than one character + var separator = /^1(.+)5$/.exec(testString)[1] + return separator; + }; var formatNumbers = function(currency, value) { - if (isNaN(parseFloat($scope.value))) { - buildAmount('', '', ''); - return; - } + var parsed = parseFloat(value); + var valueFormatted = ''; + var valueProcessing = ''; switch (getDecimalPlaces(currency)) { case '0': - var valueFormatted = localizeNumbers(Math.round(parseFloat(value))); - buildAmount(valueFormatted, '', ''); + if (isNaN(parsed)) { + buildAmount('-', '', ''); + } else { + valueFormatted = localizeNumbers(Math.round(parsed)); + buildAmount(valueFormatted, '', ''); + } break; case '3': - var valueProcessing = parseFloat(parseFloat(value).toFixed(3)); - var valueFormatted = localizeNumbers(valueProcessing, 3); - buildAmount(valueFormatted, '', ''); + if (isNaN(parsed)) { + buildAmount('-' + getDecimalSeparator() + '---', '', ''); + } else { + valueProcessing = parsed.toFixed(3); + valueFormatted = localizeNumbers(valueProcessing, 3); + buildAmount(valueFormatted, '', ''); + } break; case '8': - var valueFormatted = parseFloat(value).toFixed(8); - if (parseFloat(value) == 0) { + if (isNaN(parsed)) { + buildAmount('-' + getDecimalSeparator() + '---', '', ''); + } else if (parsed === 0) { buildAmount('0', '', ''); } else { - var valueFormatted = localizeNumbers(valueFormatted, 8); + valueFormatted = parsed.toFixed(8); + valueFormatted = localizeNumbers(valueFormatted, 8); var start = valueFormatted.slice(0, -5); var middle = valueFormatted.slice(-5, -2); var end = valueFormatted.substr(valueFormatted.length - 2); buildAmount(start, middle, end); + } break; - default: - var valueProcessing = parseFloat(parseFloat(value).toFixed(2)); - var valueFormatted = localizeNumbers(valueProcessing, 2); - buildAmount(valueFormatted, '', ''); + default: // 2 + if (isNaN(parsed)) { + buildAmount('-' + getDecimalSeparator() + '--', '', ''); + } else { + valueProcessing = parseFloat(parsed.toFixed(2)); + valueFormatted = localizeNumbers(valueProcessing, 2); + buildAmount(valueFormatted, '', ''); + } break; } }; - + formatNumbers($scope.currency, $scope.value); $scope.$watchGroup(['currency', 'value'], function() { formatNumbers($scope.currency, $scope.value); From 820c1a393358900820dd7f21d4d355177687a900 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 8 Aug 2018 13:31:02 +1200 Subject: [PATCH 145/240] Wallet details does not display any balance text if it does not have balance yet. --- www/views/walletDetails.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index 12506e05d..1ebc5929f 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -30,7 +30,7 @@

@@ -44,7 +44,7 @@
Date: Wed, 8 Aug 2018 13:38:43 +1200 Subject: [PATCH 146/240] More selective display of balances Wallet Details screen when fiat balance isn't available. --- www/views/walletDetails.html | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index 1ebc5929f..74ac4da7b 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -30,11 +30,13 @@
- + + +
- + + +
Date: Wed, 8 Aug 2018 15:36:18 +1200 Subject: [PATCH 147/240] ShapeshiftService fix for app startup. --- src/js/services/shapeshiftService.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/services/shapeshiftService.js b/src/js/services/shapeshiftService.js index 5396f4fa5..1ce9672ce 100644 --- a/src/js/services/shapeshiftService.js +++ b/src/js/services/shapeshiftService.js @@ -136,4 +136,6 @@ angular.module('copayApp.services').factory('shapeshiftService', function ($http } }); }; + + return root; }); From 5d3e11b7bbb0a48f1ecd3eae76ecf1b7ffabd22c Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 8 Aug 2018 16:10:58 +1200 Subject: [PATCH 148/240] Fixed alignment of buttons in Wallet Details on wide screens.. --- src/sass/views/walletDetails.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index 6b760bbc4..0843a1c07 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -193,7 +193,10 @@ } .send-receive-buttons { - max-width: 600px; + display: flex; + flex-direction: row; + justify-content: space-evenly; + width: 100%; position: absolute; bottom: 20px; @@ -207,6 +210,7 @@ padding: 0 15px 0 15px; text-align: center; width: 100%; + max-width: 300px; font-size: 19px; font-weight: bolder; min-height: auto; From 939263c3856a8fbeb8d2aa4183350ec4f70baa61 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 8 Aug 2018 16:30:57 +1200 Subject: [PATCH 149/240] Alignment and size of Receive and Send buttons on the Home tab matches the Wallet Details tab: equal spacing. --- src/sass/views/tab-home.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/sass/views/tab-home.scss b/src/sass/views/tab-home.scss index 708ff4fad..55080ab7b 100644 --- a/src/sass/views/tab-home.scss +++ b/src/sass/views/tab-home.scss @@ -70,8 +70,12 @@ } } .buttons { + display: flex; + flex-direction: row; + justify-content: space-evenly; margin: 6px auto -12px; - max-width: 600px; + text-align: center; + width: 100%; >.col { padding: 5px 10px; margin-bottom: 0; @@ -82,6 +86,7 @@ padding: 0 15px 0 15px; text-align: center; width: 100%; + max-width: 300px; font-size: 19px; font-weight: bolder; min-height: auto; From fcb4d4b004e4ccede6874b9dd088f332ac4d1e69 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 8 Aug 2018 18:22:43 +1200 Subject: [PATCH 150/240] Text is now visible on Send and Receive buttons in Wallet Details screen when activated. --- src/sass/views/walletDetails.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index 0843a1c07..79326951e 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -261,6 +261,12 @@ .no-alternative { padding-top: 45px; } + + .button-white-outline.button-outline.activated { + color: $v-primary-color; + border-color: white; + } + .item.item-footer { font-weight: lighter; } From 6981a5af9fd44bc72f5ff7306b58aa5238cca14e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 8 Aug 2018 15:52:20 +0900 Subject: [PATCH 151/240] show only when it is ready --- src/js/directives/formattedAmount.js | 3 +++ www/views/includes/formatted-amount.html | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index ccf6c1963..b990596c4 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -21,6 +21,8 @@ angular.module('bitcoincom.directives') }, templateUrl: 'views/includes/formatted-amount.html', controller: function($scope, $timeout) { + $scope.canShow = false; + if (!$scope.currency && $scope.value) { // If there is no currency available.. // Try to extract currency from value.. var currencySplit = $scope.value.split(" "); @@ -120,6 +122,7 @@ angular.module('bitcoincom.directives') } break; } + $scope.canShow = true; }; formatNumbers($scope.currency, $scope.value); diff --git a/www/views/includes/formatted-amount.html b/www/views/includes/formatted-amount.html index 20063458d..24149849d 100644 --- a/www/views/includes/formatted-amount.html +++ b/www/views/includes/formatted-amount.html @@ -1,4 +1,4 @@
+ ng-class="{ 'size-equal': displaySizeEqual }" ng-show="canShow"> {{start}}{{middle}}{{end}}{{currency}}
\ No newline at end of file From 3d9b24575b44de7912fc9dd110f5d35a2024f057 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 8 Aug 2018 18:57:55 +1200 Subject: [PATCH 152/240] In Review Transaction screen, the note is now visible above the fees. --- src/sass/views/review.scss | 4 ++++ www/views/review.html | 1 + 2 files changed, 5 insertions(+) diff --git a/src/sass/views/review.scss b/src/sass/views/review.scss index 79bca1896..110090ebc 100644 --- a/src/sass/views/review.scss +++ b/src/sass/views/review.scss @@ -11,6 +11,10 @@ bottom: 92px; } + .fee-summary-spacer { + height: 15px; + } + .shapeshift-banner, .bitpay-banner, .egifter-banner { box-shadow: none; } diff --git a/www/views/review.html b/www/views/review.html index e9e5c6d7c..8444142d1 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -80,6 +80,7 @@
+
From ae6dcea81a378613a7fefc7439ccf8a2f71eadb1 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 8 Aug 2018 18:58:26 +1200 Subject: [PATCH 153/240] Fix for getting Shapeshift working. --- src/js/services/shapeshiftService.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/services/shapeshiftService.js b/src/js/services/shapeshiftService.js index 5396f4fa5..1ce9672ce 100644 --- a/src/js/services/shapeshiftService.js +++ b/src/js/services/shapeshiftService.js @@ -136,4 +136,6 @@ angular.module('copayApp.services').factory('shapeshiftService', function ($http } }); }; + + return root; }); From ab13478f799af7f763233bcd4ca3a8b28c7a3a6a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 8 Aug 2018 16:03:34 +0900 Subject: [PATCH 154/240] Add shapeshift service item --- src/js/services/shapeshiftService.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/js/services/shapeshiftService.js b/src/js/services/shapeshiftService.js index 1ce9672ce..7cf6b4e03 100644 --- a/src/js/services/shapeshiftService.js +++ b/src/js/services/shapeshiftService.js @@ -137,5 +137,17 @@ angular.module('copayApp.services').factory('shapeshiftService', function ($http }); }; + var servicesItem = { + name: 'shapeshift', + title: 'Shapeshift', + icon: 'icon-shapeshift', + sref: 'tabs.shapeshift', + }; + + var register = function() { + servicesService.register(servicesItem); + }; + + register(); return root; }); From 644b3e6113b3d67bded857339cd4a0b8e2d61572 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 8 Aug 2018 16:24:47 +0900 Subject: [PATCH 155/240] height space in the bottom (wallet selection & review) --- src/sass/shame.scss | 4 + src/sass/views/review.scss | 4 - www/css/main.css | 174 ++++++++++++++++++++++++++++------ www/views/review.html | 2 +- www/views/walletSelector.html | 1 + 5 files changed, 153 insertions(+), 32 deletions(-) diff --git a/src/sass/shame.scss b/src/sass/shame.scss index 5a17c5f1f..dbbc222d7 100644 --- a/src/sass/shame.scss +++ b/src/sass/shame.scss @@ -468,3 +468,7 @@ input[type=file] { .white-space-initial { white-space: initial; } + +.height-spacer { + height: 15px; +} diff --git a/src/sass/views/review.scss b/src/sass/views/review.scss index 110090ebc..79bca1896 100644 --- a/src/sass/views/review.scss +++ b/src/sass/views/review.scss @@ -11,10 +11,6 @@ bottom: 92px; } - .fee-summary-spacer { - height: 15px; - } - .shapeshift-banner, .bitpay-banner, .egifter-banner { box-shadow: none; } diff --git a/www/css/main.css b/www/css/main.css index 350d5bbce..9845340ae 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -10253,6 +10253,46 @@ ion-view.deflash-blue:before, ion-view#view-amount:before, ion-view#view-confirm color: #fff; } .button-white.activated { color: #FFF; } + .button-white-outline { + border-color: #FFFFFF; + background-color: transparent; + color: #FFFFFF; + background: none; + box-shadow: none; } + .button-white-outline:hover { + color: #FFFFFF; + text-decoration: none; } + .button-white-outline.active, .button-white-outline.activated { + border-color: #FFF; + background-color: #FAFAFA; } + .button-white-outline.button-outline { + border-color: #FFFFFF; + background: transparent; + color: #FFFFFF; } + .button-white-outline.button-outline.active, .button-white-outline.button-outline.activated { + background-color: #FFFFFF; + box-shadow: none; + color: #fff; } + .button-grey-outline { + border-color: #727272; + background-color: transparent; + color: #727272; + background: none; + box-shadow: none; } + .button-grey-outline:hover { + color: #727272; + text-decoration: none; } + .button-grey-outline.active, .button-grey-outline.activated { + border-color: #727272; + background-color: #FAFAFA; } + .button-grey-outline.button-outline { + border-color: #727272; + background: transparent; + color: #727272; } + .button-grey-outline.button-outline.active, .button-grey-outline.button-outline.activated { + background-color: #727272; + box-shadow: none; + color: #fff; } .button-clear { background: none !important; } @@ -10271,8 +10311,8 @@ qrcode { content: ""; background-size: 100% 100%; display: block; - left: 88px; - margin-top: 88px; + left: calc(50% - 22px); + margin-top: calc(50% - 22px); width: 44px; height: 44px; position: absolute; } @@ -10886,6 +10926,28 @@ qrcode { width: 100%; display: block; } +#tab-home .buttons { + display: flex; + flex-direction: row; + justify-content: space-evenly; + margin: 6px auto -12px; + text-align: center; + width: 100%; } + #tab-home .buttons > .col { + padding: 5px 10px; + margin-bottom: 0; } + #tab-home .buttons .button { + border: 2px solid; + border-radius: 47px; + padding: 0 15px 0 15px; + text-align: center; + width: 100%; + max-width: 300px; + font-size: 19px; + font-weight: bolder; + min-height: auto; + line-height: 36px; } + #tab-home .wallet-coin-logo { vertical-align: middle; margin-right: 5px; } @@ -11871,10 +11933,11 @@ qrcode { height: 100%; height: calc(100% - env(safe-area-inset-bottom) * 2); } #walletDetails .bp-content.status-bar { - margin-top: 20px; } + margin-top: 20px; + margin-top: env(safe-area-inset-top); } #walletDetails .bar-header { border: 0; - background: none; } + background: #eeb640; } #walletDetails .bar-header .title, #walletDetails .bar-header .button { color: #fff; } #walletDetails .bar-header .button { @@ -11886,7 +11949,7 @@ qrcode { top: 0; margin-bottom: 16px; } #walletDetails ion-content.collapsible { - margin-top: 210px; } + margin-top: 230px; } #walletDetails ion-content .scroll { background: #f8f8f9; min-height: 300px; } @@ -11911,11 +11974,32 @@ qrcode { height: 200px; -webkit-transform: translateY(100px); transform: translateY(100px); } + #walletDetails .amount-wrapper .send-receive-buttons { + display: flex; + flex-direction: row; + justify-content: space-evenly; + width: 100%; + position: absolute; + bottom: 20px; } + #walletDetails .amount-wrapper .send-receive-buttons > .col { + padding: 5px 10px; + margin-bottom: 0; } + #walletDetails .amount-wrapper .send-receive-buttons .button { + border: 2px solid; + border-radius: 47px; + padding: 0 15px 0 15px; + text-align: center; + width: 100%; + max-width: 300px; + font-size: 19px; + font-weight: bolder; + min-height: auto; + line-height: 36px; } #walletDetails .amount { width: 100%; text-align: center; color: #fff; - height: 210px; + height: 230px; padding-top: 40px; display: block; align-items: center; @@ -13975,11 +14059,6 @@ slide-to-accept-success { display: flex; align-items: center; justify-content: center; } - slide-to-accept-success .slide-success__windows-background { - background: #11D1A6; - height: 100%; - width: 100%; - position: fixed; } slide-to-accept-success .slide-success__background { height: 10vmax; width: 10vmax; @@ -13996,8 +14075,10 @@ slide-to-accept-success { slide-to-accept-success .slide-success__content { position: relative; z-index: 1; - margin-top: -20vh; } + margin-top: -10vh; } slide-to-accept-success .slide-success__content > img { + width: 45vw; + max-width: 166px; margin-bottom: 1.8rem; -webkit-transform: translateY(5rem); transform: translateY(5rem); @@ -14010,7 +14091,7 @@ slide-to-accept-success { opacity: 1; } slide-to-accept-success .slide-success__content__header { color: #FFFFFF; - font-size: 26px; + font-size: 29px; -webkit-transform: translateY(5rem); transform: translateY(5rem); opacity: 0; @@ -14020,6 +14101,22 @@ slide-to-accept-success { -webkit-transform: translateY(0); transform: translateY(0); opacity: 1; } + slide-to-accept-success .slide-success__content__share { + transition: transform 400ms ease, opacity 400ms ease; + transition-delay: 600ms; + opacity: 0; + margin-top: 15vh; } + slide-to-accept-success .slide-success__content__share span { + color: #FFF; + font-size: 22px; + height: 28px; } + slide-to-accept-success .slide-success__content__share img { + height: 28px; + width: auto; + vertical-align: bottom; + margin-right: 4px; } + slide-to-accept-success .slide-success__content__share.reveal { + opacity: 0.79; } slide-to-accept-success .slide-success__footer { position: absolute; left: 0; @@ -14042,11 +14139,11 @@ slide-to-accept-success { slide-to-accept-success .slide-success__footer__btn { display: block; color: #FFFFFF; - font-size: 18px; + font-size: 22px; font-weight: 600; letter-spacing: 2.86px; - padding: 1rem 0 1.1rem; - border-top: 1px solid rgba(255, 255, 255, 0.45); + padding: 2rem 0 2.1rem; + border-top: 1px solid rgba(255, 255, 255, 0.25); cursor: pointer; } #tx-details .action-created.action-accepted { @@ -14988,17 +15085,10 @@ account-selector { height: 100%; } #custom-amount .address .qr-code { text-align: center; - margin-top: 24vh; - margin-bottom: 7vh; } - @media (max-height: 800px) { - #custom-amount .address .qr-code { - margin-top: 18vh; } } - @media (max-height: 700px) { - #custom-amount .address .qr-code { - margin-top: 14vh; } } - @media (max-height: 600px) { - #custom-amount .address .qr-code { - margin-top: 8vh; } } + margin-top: 6px; } + #custom-amount .address .qr-code qrcode canvas { + height: 30vh; + max-height: 220px; } #custom-amount .address .info { position: absolute; width: 100%; @@ -15038,6 +15128,28 @@ account-selector { margin-left: 10px; } #custom-amount .address .address-types { text-align: center; } + #custom-amount .address .amount { + margin-top: 20vh; + margin-bottom: 4vh; + width: 100%; + text-align: center; + display: block; + align-items: center; + justify-content: center; } + @media (max-height: 800px) { + #custom-amount .address .amount { + margin-top: 12vh; + margin-bottom: 6vh; } } + @media (max-height: 700px) { + #custom-amount .address .amount { + margin-top: 10vh; + margin-bottom: 4vh; } } + @media (max-height: 600px) { + #custom-amount .address .amount { + margin-top: 6vh; + margin-bottom: 2vh; } } + #custom-amount .address .amount-alternative { + line-height: 36px; } #pin { background-color: #FAFAFA; @@ -15262,6 +15374,8 @@ log-options #check-bar .checkbox-icon { #view-review .fee-summary { position: absolute; bottom: 92px; } + #view-review .fee-summary-spacer { + height: 15px; } #view-review .shapeshift-banner, #view-review .bitpay-banner, #view-review .egifter-banner { box-shadow: none; } #view-review .warning { @@ -15644,6 +15758,9 @@ input[type=number]::-webkit-inner-spin-button, input[type=number]::-webkit-outer .size-24 { font-size: 24px; } +.size-25 { + font-size: 25px; } + .size-28 { font-size: 28px; } @@ -15818,3 +15935,6 @@ input[type=file] { .white-space-initial { white-space: initial; } + +.height-spacer { + height: 15px; } diff --git a/www/views/review.html b/www/views/review.html index 8444142d1..2349c315d 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -80,7 +80,7 @@
-
+
diff --git a/www/views/walletSelector.html b/www/views/walletSelector.html index b375ddb8c..2da97eadb 100644 --- a/www/views/walletSelector.html +++ b/www/views/walletSelector.html @@ -55,5 +55,6 @@
+
\ No newline at end of file From 60028c44bba14440c26c55a6f552ac71a1d88020 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 8 Aug 2018 16:59:00 +0900 Subject: [PATCH 156/240] remove the header for shapeshift in the amount view --- www/views/amount.html | 1 - 1 file changed, 1 deletion(-) diff --git a/www/views/amount.html b/www/views/amount.html index 48637ec1b..270dbb3fb 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -6,7 +6,6 @@ -
From 753d2aea69a2e1daed1e244a0abea2062778eb96 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 8 Aug 2018 15:31:36 +0200 Subject: [PATCH 160/240] remove bottom menu on wallet selector + remove swipe back feature on review screen --- www/views/review.html | 2 +- www/views/walletSelector.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/www/views/review.html b/www/views/review.html index 2349c315d..cb7f21fbc 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -1,4 +1,4 @@ - + {{'Review Transaction' | translate}} diff --git a/www/views/walletSelector.html b/www/views/walletSelector.html index 2da97eadb..2e4c4bc31 100644 --- a/www/views/walletSelector.html +++ b/www/views/walletSelector.html @@ -1,4 +1,4 @@ - + {{sendFlowTitle}} From 51921a1a1d4207013a9fa89bdc5340ab6e3bead4 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Wed, 8 Aug 2018 22:32:18 +0900 Subject: [PATCH 161/240] Fix shapeshift flow go back available --- src/js/controllers/shapeshift.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/js/controllers/shapeshift.js b/src/js/controllers/shapeshift.js index ade7afb5b..4bf3db764 100644 --- a/src/js/controllers/shapeshift.js +++ b/src/js/controllers/shapeshift.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('shapeshiftController', function($scope, $state, $interval, profileService, walletService, popupService, lodash, $ionicNavBarDelegate) { +angular.module('copayApp.controllers').controller('shapeshiftController', function($scope, $state, $timeout, $ionicHistory, profileService, walletService, popupService, lodash, $ionicNavBarDelegate) { var walletsBtc = []; var walletsBch = []; @@ -66,8 +66,14 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi var params = { thirdParty: JSON.stringify({id: 'shapeshift'}) }; + $state.go('tabs.home').then(function() { - $state.transitionTo('tabs.send.origin', params); + $ionicHistory.clearHistory(); + $state.go('tabs.send').then(function() { + $timeout(function () { + $state.transitionTo('tabs.send.origin', params); + }, 60); + }); }); } }); From 8229d51fdd4590ce932ae57d7bfbd23e57602a11 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 8 Aug 2018 16:35:49 +0200 Subject: [PATCH 162/240] sendflow service --- src/js/services/sendFlowService.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/js/services/sendFlowService.js diff --git a/src/js/services/sendFlowService.js b/src/js/services/sendFlowService.js new file mode 100644 index 000000000..d5c4df109 --- /dev/null +++ b/src/js/services/sendFlowService.js @@ -0,0 +1,24 @@ +'use strict'; + +angular.module('copayApp.services').factory('sendFlowService', function ($log) { + var vm = this; + + vm.amount = false; + vm.fromWalletId = false; + vm.thirdParty = false; + vm.sendMax = false; + vm.toAddress = false; + vm.toWalletId = false; + + vm.initialize = function() { + $log.debug("Reinitialize Send Flow variables"); + vm.amount = false; + vm.fromWalletId = false; + vm.thirdParty = false; + vm.sendMax = false; + vm.toAddress = false; + vm.toWalletId = false; + }; + + return vm; +}); \ No newline at end of file From f7ecdb2f2fee02c47b38c4c54fbadee711a9b9fa Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 8 Aug 2018 17:10:47 +0200 Subject: [PATCH 163/240] sendflow service integrated into flow --- src/js/controllers/amount.js | 69 ++++++++++--------- src/js/controllers/review.controller.js | 30 ++++---- src/js/controllers/tab-send.js | 2 +- src/js/controllers/walletDetails.js | 7 +- .../controllers/walletSelectorController.js | 12 ++-- src/js/services/incomingData.js | 6 +- src/js/services/sendFlowService.js | 7 ++ 7 files changed, 73 insertions(+), 60 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 8438260bc..68d4e869b 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -2,7 +2,7 @@ angular.module('copayApp.controllers').controller('amountController', amountController); -function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, shapeshiftService, txFormatService, platformInfo, profileService, walletService, $window) { +function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, profileService, walletService, $window) { var vm = this; vm.allowSend = false; @@ -69,12 +69,12 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, initCurrencies(); - passthroughParams = data.stateParams; + passthroughParams = sendFlowService; - vm.fromWalletId = data.stateParams.fromWalletId; - vm.toWalletId = data.stateParams.toWalletId; - vm.minAmount = parseFloat(data.stateParams.minAmount); - vm.maxAmount = parseFloat(data.stateParams.maxAmount); + vm.fromWalletId = passthroughParams.fromWalletId; + vm.toWalletId = passthroughParams.toWalletId; + vm.minAmount = parseFloat(passthroughParams.minAmount); + vm.maxAmount = parseFloat(passthroughParams.maxAmount); if (passthroughParams.thirdParty) { vm.thirdParty = JSON.parse(passthroughParams.thirdParty); // Parse stringified JSON-object @@ -96,7 +96,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } } - vm.isRequestingSpecificAmount = !data.stateParams.fromWalletId; + vm.isRequestingSpecificAmount = !passthroughParams.fromWalletId; var config = configService.getSync().wallet.settings; @@ -177,8 +177,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, // currency have preference var fiatName; - if (data.stateParams.currency) { - fiatCode = data.stateParams.currency; + if (passthroughParams.currency) { + fiatCode = passthroughParams.currency; altUnitIndex = unitIndex unitIndex = availableUnits.length; } else { @@ -205,8 +205,8 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, var fromWallet = profileService.getWallet(passthroughParams.fromWalletId); updateAvailableFundsFromWallet(fromWallet); } - }; - }; + } + } function goBack() { if (vm.thirdParty && vm.thirdParty.id === 'shapeshift') { @@ -227,18 +227,18 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, $timeout(function() { $scope.$apply(); }); - }; + } function processClipboard() { if (!isNW) return; var value = nodeWebkitService.readFromClipboard(); if (value && evaluate(value) > 0) paste(evaluate(value)); - }; + } function sendMax() { useSendMax = true; finish(); - }; + } function updateUnitUI() { vm.unit = availableUnits[unitIndex].shortName; @@ -246,7 +246,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, processAmount(); $log.debug('Update unit coin @amount unit:' + vm.unit + " alternativeUnit:" + vm.alternativeUnit); - }; + } function changeUnit() { @@ -267,7 +267,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, updateAvailableFundsStringIfNeeded(); updateUnitUI(); - }; + } function pushDigit(digit) { if (vm.amount && digit != '.') { @@ -291,7 +291,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.amount = (vm.amount + digit).replace('..', '.'); processAmount(); - }; + } function pushOperator(operator) { if (!vm.amount || vm.amount.length == 0) return; @@ -303,18 +303,18 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } else { return val.slice(0, -1) + operator; } - }; - }; + } + } function isOperator(val) { var regex = /[\/\-\+\x\*]/; return regex.test(val); - }; + } function isExpression(val) { var regex = /^\.?\d+(\.?\d+)?([\/\-\+\*x]\d?\.?\d+)+$/; return regex.test(val); - }; + } function removeDigit() { vm.amount = (vm.amount).toString().slice(0, -1); @@ -339,7 +339,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, function close() { altCurrencyModal.remove(); altCurrencyModal = null; - }; + } function processAmount() { var formatedValue = format(vm.amount); @@ -409,22 +409,22 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } else { vm.errorMessage = ''; } - }; + } function processResult(val) { if (availableUnits[unitIndex].isFiat) return $filter('formatFiatAmount')(val); else return txFormatService.formatAmount(val.toFixed(unitDecimals) * unitToSatoshi, true); - }; + } function fromFiat(val) { return parseFloat((rateService.fromFiat(val, fiatCode, availableUnits[altUnitIndex].id) * satToUnit).toFixed(unitDecimals)); - }; + } function toFiat(val) { if (!rateService.getRate(fiatCode)) return; return parseFloat((rateService.toFiat(val * unitToSatoshi, fiatCode, availableUnits[unitIndex].id)).toFixed(2)); - }; + } function evaluate(val) { var result; @@ -435,7 +435,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } if (!lodash.isFinite(result)) return 0; return result; - }; + } function format(val) { if (!val) return; @@ -445,7 +445,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, if (isOperator(lodash.last(val))) result = result.slice(0, -1); return result.replace('x', '*'); - }; + } function finish() { var unit = availableUnits[unitIndex]; @@ -467,16 +467,18 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, }; if (vm.thirdParty) { - confirmData['thirdParty'] = JSON.stringify(this.thirdParty); + confirmData['thirdParty'] = this.thirdParty; } + sendFlowService.map(confirmData); + if (!confirmData.fromWalletId) { $state.transitionTo('tabs.paymentRequest.confirm', confirmData); } else { $state.transitionTo('tabs.send.review', confirmData); $scope.useSendMax = null; } - }; + } // Currency @@ -495,7 +497,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, {isoCode: 'CNY', order: 7}, {isoCode: 'KRW', order: 8}, {isoCode: 'HKD', order: 9}, - ] + ]; function initCurrencies() { var unusedCurrencyList = [{ @@ -558,7 +560,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, $timeout(function() { $scope.$apply(); }); - }; + } function save(newAltCurrency) { var opts = { @@ -584,7 +586,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, updateUnitUI(); close(); }); - }; + } function updateAvailableFundsStringIfNeeded() { if (passthroughParams.fromWalletId && availableSatoshis !== null) { @@ -639,5 +641,4 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.availableFunds = availableFundsInCrypto; } } - } diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index f565448ee..858e115c1 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -4,7 +4,7 @@ angular .module('copayApp.controllers') .controller('reviewController', reviewController); -function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, configService, feeService, gettextCatalog, $interval, $ionicHistory, $ionicModal, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, shapeshiftService, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { +function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, configService, feeService, gettextCatalog, $interval, $ionicHistory, $ionicModal, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, sendFlowService, shapeshiftService, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) { var vm = this; vm.buttonText = ''; @@ -51,7 +51,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit // Functions vm.onSuccessConfirm = onSuccessConfirm; - + var sendFlowData; var config = null; var countDown = null; var defaults = {}; @@ -75,17 +75,18 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit function onBeforeEnter(event, data) { defaults = configService.getDefaults(); - originWalletId = data.stateParams.fromWalletId; - satoshis = parseInt(data.stateParams.amount, 10); - toAddress = data.stateParams.toAddress; - destinationWalletId = data.stateParams.toWalletId; + sendFlowData = sendFlowService; + originWalletId = sendFlowData.fromWalletId; + satoshis = parseInt(sendFlowData.amount, 10); + toAddress = sendFlowData.toAddress; + destinationWalletId = sendFlowData.toWalletId; vm.originWallet = profileService.getWallet(originWalletId); vm.origin.currency = vm.originWallet.coin.toUpperCase(); coin = vm.originWallet.coin; - if (data.stateParams.thirdParty) { - vm.thirdParty = JSON.parse(data.stateParams.thirdParty); // Parse stringified JSON-object + if (sendFlowData.thirdParty) { + // vm.thirdParty = JSON.parse(sendFlowData.thirdParty); // Parse stringified JSON-object if (vm.thirdParty) { handleThirdPartyInitIfBip70(); handleThirdPartyInitIfShapeshift(); @@ -105,7 +106,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit updateSendAmounts(); getOriginWalletBalance(vm.originWallet); handleDestinationAsAddress(toAddress, coin); - handleDestinationAsWallet(data.stateParams.toWalletId); + handleDestinationAsWallet(sendFlowData.toWalletId); createVanityTransaction(data); }); } @@ -221,10 +222,10 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit // Grab stateParams tx = { - amount: parseInt(data.stateParams.amount), - sendMax: data.stateParams.sendMax === 'true' ? true : false, - fromWalletId: data.stateParams.fromWalletId, - toAddress: data.stateParams.toAddress, + amount: parseInt(sendFlowData.amount), + sendMax: sendFlowData.sendMax === 'true' ? true : false, + fromWalletId: sendFlowData.fromWalletId, + toAddress: sendFlowData.toAddress, paypro: txPayproData, feeLevel: configFeeLevel, @@ -241,7 +242,6 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit }; - if (data.stateParams.requiredFeeRate) { vm.usingMerchantFee = true; tx.feeRate = parseInt(data.stateParams.requiredFeeRate); @@ -251,7 +251,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit tx.feeLevel = 'normal'; } - var B = data.stateParams.coin === 'bch' ? bitcoreCash : bitcore; + var B = tx.coin === 'bch' ? bitcoreCash : bitcore; var networkName; try { if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer diff --git a/src/js/controllers/tab-send.js b/src/js/controllers/tab-send.js index 465941c35..0c0c1bfe1 100644 --- a/src/js/controllers/tab-send.js +++ b/src/js/controllers/tab-send.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, $ionicLoading, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, bwcError, gettextCatalog, scannerService, configService, bitcoinCashJsService, $ionicPopup, $ionicNavBarDelegate, clipboardService) { +angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, $ionicLoading, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, sendFlowService, bwcError, gettextCatalog, scannerService, configService, bitcoinCashJsService, $ionicPopup, $ionicNavBarDelegate, clipboardService) { var clipboardHasAddress = false; var clipboardHasContent = false; var originalList; diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index d3308ff1e..9eef7a5c9 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -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, feeService, appConfigService, rateService) { +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, sendFlowService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService, appConfigService, rateService) { var HISTORY_SHOW_LIMIT = 10; var currentTxHistoryPage = 0; @@ -471,9 +471,8 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); } $scope.goToSend = function() { - $state.go('tabs.home', { - walletId: $scope.wallet.id - }).then(function () { + sendFlowService.fromWalletId = $scope.wallet.id; + $state.go('tabs.home').then(function () { $ionicHistory.clearHistory(); $state.go('tabs.send'); }); diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index a4ff3ab3e..a26e07d8f 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $rootScope, $state, $log, $ionicHistory, configService, gettextCatalog, profileService, txFormatService) { +angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $rootScope, $state, $log, $ionicHistory, sendFlowService, configService, gettextCatalog, profileService, txFormatService) { var fromWalletId = ''; var priceDisplayAsFiat = false; @@ -26,10 +26,14 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu // nop } - $scope.params = $state.params; + $scope.params = sendFlowService; $scope.coin = false; // Wallets to show (for destination screen or contacts) - $scope.type = data.stateParams && data.stateParams['fromWalletId'] ? 'destination' : 'origin'; // origin || destination - fromWalletId = data.stateParams && data.stateParams.fromWalletId; + $scope.type = $scope.params['fromWalletId'] ? 'destination' : 'origin'; // origin || destination + fromWalletId = $scope.params['fromWalletId']; + + if ($scope.type === 'destination' && $scope.params.toAddress) { + $state.transitionTo(getNextStep()); + } if ($scope.params.coin) { $scope.coin = $scope.params.coin; // Contacts have a coin embedded diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index 946144dce..91de99cf3 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, bitcoreCash, $rootScope, payproService, scannerService, appConfigService, popupService, gettextCatalog, bitcoinCashJsService) { +angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, bitcoreCash, $rootScope, payproService, scannerService, sendFlowService, appConfigService, popupService, gettextCatalog, bitcoinCashJsService) { var root = {}; @@ -105,9 +105,10 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat params.thirdParty = []; params.thirdParty.id = serviceId; params.thirdParty.data = serviceData; - params.thirdParty = JSON.stringify(params.thirdParty); + sendFlowService.map(params); $state.transitionTo('tabs.send.amount', params); } else { + sendFlowService.map(params); $state.transitionTo('tabs.send.origin', params); } }, 100); @@ -458,6 +459,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat 'notify': $state.current.name == 'tabs.send' ? false : true }).then(function() { $timeout(function() { + sendFlowService.map(stateParams); $state.transitionTo('tabs.send.origin', stateParams); }); }); diff --git a/src/js/services/sendFlowService.js b/src/js/services/sendFlowService.js index d5c4df109..09e55e7ec 100644 --- a/src/js/services/sendFlowService.js +++ b/src/js/services/sendFlowService.js @@ -20,5 +20,12 @@ angular.module('copayApp.services').factory('sendFlowService', function ($log) { vm.toWalletId = false; }; + vm.map = function(params) { + Object.keys(params).map(function(key, index) { + vm[key] = params[key]; + }); + console.log(vm); + }; + return vm; }); \ No newline at end of file From 220610271558cff7fe347da994eedc0319108fdc Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 8 Aug 2018 17:27:15 +0200 Subject: [PATCH 164/240] sendflow service integrated into flow --- .../controllers/walletSelectorController.js | 1 + src/js/services/sendFlowService.js | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index a26e07d8f..69430dbb9 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -191,6 +191,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu }; $scope.goBack = function() { + sendFlowService.previousState(); $ionicHistory.goBack(); } diff --git a/src/js/services/sendFlowService.js b/src/js/services/sendFlowService.js index 09e55e7ec..58b457b3a 100644 --- a/src/js/services/sendFlowService.js +++ b/src/js/services/sendFlowService.js @@ -4,13 +4,15 @@ angular.module('copayApp.services').factory('sendFlowService', function ($log) { var vm = this; vm.amount = false; + vm.fromWalletId = false; + vm.previousStates = []; vm.thirdParty = false; vm.sendMax = false; vm.toAddress = false; vm.toWalletId = false; - vm.initialize = function() { + vm.clear = function() { $log.debug("Reinitialize Send Flow variables"); vm.amount = false; vm.fromWalletId = false; @@ -18,13 +20,28 @@ angular.module('copayApp.services').factory('sendFlowService', function ($log) { vm.sendMax = false; vm.toAddress = false; vm.toWalletId = false; + vm.previousStates = []; }; vm.map = function(params) { + + var tempState = {}; + Object.keys(vm).map(function(key, index) { + if (typeof vm[key] !== 'function' && key !== 'previousStates') { + tempState[key] = vm[key]; + } + }); + vm.previousStates.push(tempState); + Object.keys(params).map(function(key, index) { vm[key] = params[key]; }); - console.log(vm); + }; + + vm.previousState = function() { + if (vm.previousStates.length) { + vm.map(vm.previousStates.pop()); + } }; return vm; From 02e8e0fbf026f44f5e999eaabb867ae835fa231f Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 8 Aug 2018 17:29:28 +0200 Subject: [PATCH 165/240] clear send flow on home and wallet details --- src/js/controllers/tab-home.js | 4 +++- src/js/controllers/walletDetails.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index 07b01b974..7671d524a 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -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, bannerService, 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, sendFlowService, storageService, txpModalService, appConfigService, startupService, addressbookService, bwcError, nextStepsService, buyAndSellService, homeIntegrationsService, bitpayCardService, pushNotificationsService, timeService, bitcoincomService, pricechartService, firebaseEventsService, servicesService, shapeshiftService, $ionicNavBarDelegate, signVerifyMessageService) { var wallet; var listeners = []; var notifications = []; @@ -31,6 +31,8 @@ angular.module('copayApp.controllers').controller('tabHomeController', }); $scope.$on("$ionicView.beforeEnter", function(event, data) { + sendFlowService.clear(); + if (!$scope.homeTip) { storageService.getHomeTipAccepted(function(error, value) { $scope.homeTip = (value == 'accepted') ? false : true; diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 9eef7a5c9..5200c5987 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -374,6 +374,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun }); $scope.$on("$ionicView.beforeEnter", function(event, data) { + sendFlowService.clear(); configService.whenAvailable(function (config) { $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; From d81f194f87a262d4de7b43e2fa3401c7cdc52017 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 07:35:47 +1200 Subject: [PATCH 166/240] Fix for when an amount changes from having a separate currency value, to the currency being one string with the amount. --- src/js/directives/formattedAmount.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index c5b2dfcd4..c39007d66 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -63,14 +63,15 @@ angular.module('bitcoincom.directives') }; var formatNumbers = function() { - - if (!$scope.currency && $scope.value) { // If there is no currency available.. - // Try to extract currency from value.. - var currencySplit = $scope.value.split(" "); - if (currencySplit.length === 2) { - $scope.currency = currencySplit[1]; - } + + // During watch, may be changed from having a separate currency value, + // to both being in value. Don't want to use previous currency value. + // Try to extract currency from value.. + var currencySplit = $scope.value.split(" "); + if (currencySplit.length === 2) { + $scope.currency = currencySplit[1]; } + var parsed = parseFloat($scope.value); var valueFormatted = ''; From 1c3b11322238da4677371833f18b32fee368a814 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 07:49:13 +1200 Subject: [PATCH 167/240] Removed unused config service. --- src/js/directives/formattedAmount.js | 203 +++++++++++++-------------- 1 file changed, 100 insertions(+), 103 deletions(-) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index c39007d66..6269954fb 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -11,7 +11,7 @@ * @example */ angular.module('bitcoincom.directives') - .directive('formattedAmount', function(configService, uxLanguage) { + .directive('formattedAmount', function(uxLanguage) { return { restrict: 'E', scope: { @@ -25,110 +25,107 @@ angular.module('bitcoincom.directives') $scope.displaySizeEqual = !!$scope.sizeEqual; - configService.whenAvailable(function onConfigServiceAvailable(config) { + $timeout(function onFormattedAmountTimeout() { - $timeout(function onFormattedAmountTimeout() { - - var decimalPlaces = { - '0': ['BIF', 'CLP', 'DJF', 'GNF', 'ILS', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'], - '3': ['BHD', 'IQD', 'JOD', 'KWD', 'OMR', 'TND'], - '8': ['BCH', 'BTC'] - }; - var localizeNumbers = function(x, minimumFractionDigits = 0, useGrouping = true) { - return parseFloat(x).toLocaleString(uxLanguage.getCurrentLanguage(), { - minimumFractionDigits: minimumFractionDigits, - useGrouping: useGrouping - }); - }; - - var buildAmount = function(start, middle, end) { - $scope.start = start; - $scope.middle = middle; - $scope.end = end; - }; - - var getDecimalPlaces = function(currency) { - if (decimalPlaces['0'].indexOf(currency.toUpperCase()) > -1) return '0'; - if (decimalPlaces['3'].indexOf(currency.toUpperCase()) > -1) return '3'; - if (decimalPlaces['8'].indexOf(currency.toUpperCase()) > -1) return '8'; - return '2'; - }; - - var getDecimalSeparator = function() { - var testNum = 1.5; - var testString = testNum.toLocaleString(uxLanguage.getCurrentLanguage()); - // Some environments let you set decimal separators that are more than one character - var separator = /^1(.+)5$/.exec(testString)[1] - return separator; - }; - - var formatNumbers = function() { - - // During watch, may be changed from having a separate currency value, - // to both being in value. Don't want to use previous currency value. - // Try to extract currency from value.. - var currencySplit = $scope.value.split(" "); - if (currencySplit.length === 2) { - $scope.currency = currencySplit[1]; - } - - - var parsed = parseFloat($scope.value); - var valueFormatted = ''; - var valueProcessing = ''; - switch (getDecimalPlaces($scope.currency)) { - case '0': - if (isNaN(parsed)) { - buildAmount('-', '', ''); - } else { - valueFormatted = localizeNumbers(Math.round(parsed)); - buildAmount(valueFormatted, '', ''); - } - break; - - case '3': - if (isNaN(parsed)) { - buildAmount('-' + getDecimalSeparator() + '---', '', ''); - } else { - valueProcessing = parsed.toFixed(3); - valueFormatted = localizeNumbers(valueProcessing, 3); - buildAmount(valueFormatted, '', ''); - } - break; - - case '8': - if (isNaN(parsed)) { - buildAmount('-' + getDecimalSeparator() + '---', '', ''); - } else if (parsed === 0) { - buildAmount('0', '', ''); - } else { - valueFormatted = parsed.toFixed(8); - valueFormatted = localizeNumbers(valueFormatted, 8); - var start = valueFormatted.slice(0, -5); - var middle = valueFormatted.slice(-5, -2); - var end = valueFormatted.substr(valueFormatted.length - 2); - buildAmount(start, middle, end); - - } - break; - - default: // 2 - if (isNaN(parsed)) { - buildAmount('-' + getDecimalSeparator() + '--', '', ''); - } else { - valueProcessing = parseFloat(parsed.toFixed(2)); - valueFormatted = localizeNumbers(valueProcessing, 2); - buildAmount(valueFormatted, '', ''); - } - break; - } - $scope.canShow = true; - }; - - formatNumbers(); - $scope.$watchGroup(['currency', 'value'], function onFormattedAmountWatch() { - formatNumbers(); + var decimalPlaces = { + '0': ['BIF', 'CLP', 'DJF', 'GNF', 'ILS', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'], + '3': ['BHD', 'IQD', 'JOD', 'KWD', 'OMR', 'TND'], + '8': ['BCH', 'BTC'] + }; + var localizeNumbers = function(x, minimumFractionDigits = 0, useGrouping = true) { + return parseFloat(x).toLocaleString(uxLanguage.getCurrentLanguage(), { + minimumFractionDigits: minimumFractionDigits, + useGrouping: useGrouping }); + }; + + var buildAmount = function(start, middle, end) { + $scope.start = start; + $scope.middle = middle; + $scope.end = end; + }; + + var getDecimalPlaces = function(currency) { + if (decimalPlaces['0'].indexOf(currency.toUpperCase()) > -1) return '0'; + if (decimalPlaces['3'].indexOf(currency.toUpperCase()) > -1) return '3'; + if (decimalPlaces['8'].indexOf(currency.toUpperCase()) > -1) return '8'; + return '2'; + }; + + var getDecimalSeparator = function() { + var testNum = 1.5; + var testString = testNum.toLocaleString(uxLanguage.getCurrentLanguage()); + // Some environments let you set decimal separators that are more than one character + var separator = /^1(.+)5$/.exec(testString)[1] + return separator; + }; + + var formatNumbers = function() { + + // During watch, may be changed from having a separate currency value, + // to both being in value. Don't want to use previous currency value. + // Try to extract currency from value.. + var currencySplit = $scope.value.split(" "); + if (currencySplit.length === 2) { + $scope.currency = currencySplit[1]; + } + + + var parsed = parseFloat($scope.value); + var valueFormatted = ''; + var valueProcessing = ''; + switch (getDecimalPlaces($scope.currency)) { + case '0': + if (isNaN(parsed)) { + buildAmount('-', '', ''); + } else { + valueFormatted = localizeNumbers(Math.round(parsed)); + buildAmount(valueFormatted, '', ''); + } + break; + + case '3': + if (isNaN(parsed)) { + buildAmount('-' + getDecimalSeparator() + '---', '', ''); + } else { + valueProcessing = parsed.toFixed(3); + valueFormatted = localizeNumbers(valueProcessing, 3); + buildAmount(valueFormatted, '', ''); + } + break; + + case '8': + if (isNaN(parsed)) { + buildAmount('-' + getDecimalSeparator() + '---', '', ''); + } else if (parsed === 0) { + buildAmount('0', '', ''); + } else { + valueFormatted = parsed.toFixed(8); + valueFormatted = localizeNumbers(valueFormatted, 8); + var start = valueFormatted.slice(0, -5); + var middle = valueFormatted.slice(-5, -2); + var end = valueFormatted.substr(valueFormatted.length - 2); + buildAmount(start, middle, end); + + } + break; + + default: // 2 + if (isNaN(parsed)) { + buildAmount('-' + getDecimalSeparator() + '--', '', ''); + } else { + valueProcessing = parseFloat(parsed.toFixed(2)); + valueFormatted = localizeNumbers(valueProcessing, 2); + buildAmount(valueFormatted, '', ''); + } + break; + } + $scope.canShow = true; + }; + + formatNumbers(); + $scope.$watchGroup(['currency', 'value'], function onFormattedAmountWatch() { + formatNumbers(); }); }); } From 58d99231ae449d140e01a6b5cd5722fc973c570a Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 10:46:07 +1200 Subject: [PATCH 168/240] Handle undefined currency. --- src/js/directives/formattedAmount.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index 6269954fb..5bd307de3 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -69,6 +69,7 @@ angular.module('bitcoincom.directives') if (currencySplit.length === 2) { $scope.currency = currencySplit[1]; } + $scope.currency = $scope.currency || ''; var parsed = parseFloat($scope.value); From dff0d51ef3699df1d5ccaa5299637510b63c0ab6 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 11:10:26 +1200 Subject: [PATCH 169/240] Refactored sendFlowService and cleared its state before entering the send tab. --- src/js/controllers/addressbookView.js | 3 +- src/js/controllers/amount.js | 1 + src/js/controllers/shapeshift.js | 3 +- src/js/controllers/tab-home.js | 8 +- src/js/controllers/tab-send.js | 7 ++ src/js/controllers/tabsController.js | 9 ++- src/js/controllers/walletDetails.js | 10 ++- src/js/services/sendFlowService.js | 106 +++++++++++++++++--------- www/views/tab-home.html | 2 +- www/views/tab-send.html | 21 ++++- www/views/tabs.html | 2 +- 11 files changed, 124 insertions(+), 48 deletions(-) diff --git a/src/js/controllers/addressbookView.js b/src/js/controllers/addressbookView.js index aab6fb5b4..5f6881d10 100644 --- a/src/js/controllers/addressbookView.js +++ b/src/js/controllers/addressbookView.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('addressbookViewController', function($scope, $state, $timeout, lodash, addressbookService, popupService, $ionicHistory, platformInfo, gettextCatalog, configService, bitcoinCashJsService) { +angular.module('copayApp.controllers').controller('addressbookViewController', function($scope, sendFlowService, $state, $timeout, lodash, addressbookService, popupService, $ionicHistory, platformInfo, gettextCatalog, configService, bitcoinCashJsService) { var config = configService.getSync(); var defaults = configService.getDefaults(); @@ -22,6 +22,7 @@ angular.module('copayApp.controllers').controller('addressbookViewController', f $scope.sendTo = function() { $ionicHistory.removeBackView(); + sendFlowService.clear(); $state.go('tabs.send'); $timeout(function() { var to = ''; diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 68d4e869b..8ff214d87 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -209,6 +209,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } function goBack() { + sendFlowService.previousState(); if (vm.thirdParty && vm.thirdParty.id === 'shapeshift') { $state.go('tabs.send').then(function() { $ionicHistory.clearHistory(); diff --git a/src/js/controllers/shapeshift.js b/src/js/controllers/shapeshift.js index 4bf3db764..fcb43026d 100644 --- a/src/js/controllers/shapeshift.js +++ b/src/js/controllers/shapeshift.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('shapeshiftController', function($scope, $state, $timeout, $ionicHistory, profileService, walletService, popupService, lodash, $ionicNavBarDelegate) { +angular.module('copayApp.controllers').controller('shapeshiftController', function($scope, sendFlowService, $state, $timeout, $ionicHistory, profileService, walletService, popupService, lodash, $ionicNavBarDelegate) { var walletsBtc = []; var walletsBch = []; @@ -67,6 +67,7 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi thirdParty: JSON.stringify({id: 'shapeshift'}) }; + sendFlowService.clear(); $state.go('tabs.home').then(function() { $ionicHistory.clearHistory(); $state.go('tabs.send').then(function() { diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index 7671d524a..3d63ae41f 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -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, bannerService, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, sendFlowService, storageService, txpModalService, appConfigService, startupService, addressbookService, bwcError, nextStepsService, buyAndSellService, homeIntegrationsService, bitpayCardService, pushNotificationsService, timeService, bitcoincomService, pricechartService, firebaseEventsService, servicesService, shapeshiftService, $ionicNavBarDelegate, signVerifyMessageService) { + function($rootScope, sendFlowService, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, bannerService, 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) { var wallet; var listeners = []; var notifications = []; @@ -31,7 +31,6 @@ angular.module('copayApp.controllers').controller('tabHomeController', }); $scope.$on("$ionicView.beforeEnter", function(event, data) { - sendFlowService.clear(); if (!$scope.homeTip) { storageService.getHomeTipAccepted(function(error, value) { @@ -119,6 +118,11 @@ angular.module('copayApp.controllers').controller('tabHomeController', return timeService.withinPastDay(time); }; + $scope.startFreshSend = function() { + sendFlowService.clear(); + $state.go('tabs.send'); + } + $scope.openExternalLink = function() { var url = 'https://github.com/Bitcoin-com/Wallet/releases/latest'; var optIn = true; diff --git a/src/js/controllers/tab-send.js b/src/js/controllers/tab-send.js index 0c0c1bfe1..33befb8f2 100644 --- a/src/js/controllers/tab-send.js +++ b/src/js/controllers/tab-send.js @@ -187,6 +187,13 @@ angular.module('copayApp.controllers').controller('tabSendController', function( }); }; + $scope.startWalletToWalletTransfer = function() { + console.log('startWalletToWalletTransfer()'); + $state.transitionTo('tabs.send.wallet-to-wallet', { + fromWalletId: sendFlowService.fromWalletId + }); + } + // This could probably be enhanced refactoring the routes abstract states $scope.createWallet = function() { $state.go('tabs.home').then(function() { diff --git a/src/js/controllers/tabsController.js b/src/js/controllers/tabsController.js index a186879e3..b3de6c70f 100644 --- a/src/js/controllers/tabsController.js +++ b/src/js/controllers/tabsController.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, platformInfo, incomingData, lodash, popupService, gettextCatalog, scannerService) { +angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, platformInfo, incomingData, lodash, popupService, gettextCatalog, scannerService, sendFlowService) { $scope.onScan = function(data) { if (!incomingData.redir(data)) { @@ -15,6 +15,11 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro }; }; + $scope.startFreshSend = function() { + sendFlowService.clear(); + $state.go('tabs.send'); + }; + $scope.importInit = function() { $scope.fromOnboarding = $stateParams.fromOnboarding; $timeout(function() { @@ -23,7 +28,7 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro }; $scope.chooseScanner = function() { - + sendFlowService.clear(); var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP; if (!isWindowsPhoneApp) { diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 5200c5987..b159f7bc4 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -471,13 +471,20 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun function rgbToHex(r, g, b) { return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); } + $scope.goToSend = function() { - sendFlowService.fromWalletId = $scope.wallet.id; + sendFlowService.startSend({ + fromWalletId: $scope.wallet.id + }); + + // Go home first so that the Home tab works properly $state.go('tabs.home').then(function () { $ionicHistory.clearHistory(); $state.go('tabs.send'); }); + }; + $scope.goToReceive = function() { $state.go('tabs.home', { walletId: $scope.wallet.id @@ -488,6 +495,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun }); }); }; + $scope.goToBuy = function() { $state.go('tabs.home', { walletId: $scope.wallet.id diff --git a/src/js/services/sendFlowService.js b/src/js/services/sendFlowService.js index 58b457b3a..1af112ed2 100644 --- a/src/js/services/sendFlowService.js +++ b/src/js/services/sendFlowService.js @@ -1,48 +1,78 @@ 'use strict'; -angular.module('copayApp.services').factory('sendFlowService', function ($log) { - var vm = this; +(function(){ - vm.amount = false; +angular + .module('copayApp.services') + .factory('sendFlowService', sendFlowService); + + function sendFlowService($log) { - vm.fromWalletId = false; - vm.previousStates = []; - vm.thirdParty = false; - vm.sendMax = false; - vm.toAddress = false; - vm.toWalletId = false; + var service = { + amount: '', + fromWalletId: '', + sendMax: false, + thirdParty: null, + toAddress: '', + toWalletId: '', + previousStates: [], - vm.clear = function() { - $log.debug("Reinitialize Send Flow variables"); - vm.amount = false; - vm.fromWalletId = false; - vm.thirdParty = false; - vm.sendMax = false; - vm.toAddress = false; - vm.toWalletId = false; - vm.previousStates = []; - }; + // Functions + clear: clear, + map: map, + previousState: previousState, + startSend: startSend + }; - vm.map = function(params) { + return service; - var tempState = {}; - Object.keys(vm).map(function(key, index) { - if (typeof vm[key] !== 'function' && key !== 'previousStates') { - tempState[key] = vm[key]; - } - }); - vm.previousStates.push(tempState); - - Object.keys(params).map(function(key, index) { - vm[key] = params[key]; - }); - }; - - vm.previousState = function() { - if (vm.previousStates.length) { - vm.map(vm.previousStates.pop()); + function clear() { + $log.debug("Reinitialize Send Flow variables with clear()"); + service.amount = ''; + service.fromWalletId = ''; + service.sendMax = false; + service.thirdParty = null; + service.toAddress = ''; + service.toWalletId = ''; + service.previousStates = []; } + + /** + * Clears all previous state + * @param {} params + */ + function startSend(params) { + console.log('startSend()'); + clear(); + Object.keys(params).forEach(function forNewParam(key) { + service[key] = params[key]; + }); + } + + function map(params) { + + var currentState = {}; + Object.keys(service).forEach(function forCurrentParam(key) { + if (typeof service[key] !== 'function' && key !== 'previousStates') { + currentState[key] = service[key]; + } + }); + service.previousStates.push(currentState); + + // Do we want to inherit the previous state here, or clear first before adding new params? + + Object.keys(params).forEach(function forNewParam(key) { + service[key] = params[key]; + }); + }; + + function previousState() { + if (service.previousStates.length) { + map(service.previousStates.pop()); + } else { + clear(); + } + }; }; - return vm; -}); \ No newline at end of file +})(); \ No newline at end of file diff --git a/www/views/tab-home.html b/www/views/tab-home.html index 618f2e128..8ef922918 100644 --- a/www/views/tab-home.html +++ b/www/views/tab-home.html @@ -34,7 +34,7 @@ Buy Bitcoin
+ ng-click="startFreshSend()"> Send
diff --git a/www/views/tab-send.html b/www/views/tab-send.html index 20198c7f0..3378b53ed 100644 --- a/www/views/tab-send.html +++ b/www/views/tab-send.html @@ -4,6 +4,25 @@
+ +
+
+
From:
+
+
+ +
+
+

{{fromWallet.name}}

+ + +
+
+
+
+
@@ -22,7 +41,7 @@
- diff --git a/www/views/tabs.html b/www/views/tabs.html index 3c2683a13..39d0acf73 100644 --- a/www/views/tabs.html +++ b/www/views/tabs.html @@ -11,7 +11,7 @@ - + From be2bc4cddb5b774c49baa14c51d89c2ad013c6b5 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 11:11:10 +1200 Subject: [PATCH 170/240] Showing From wallet in send tab. --- src/js/controllers/tab-send.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/js/controllers/tab-send.js b/src/js/controllers/tab-send.js index 33befb8f2..caa03e92b 100644 --- a/src/js/controllers/tab-send.js +++ b/src/js/controllers/tab-send.js @@ -208,6 +208,13 @@ angular.module('copayApp.controllers').controller('tabSendController', function( }; $scope.$on("$ionicView.beforeEnter", function(event, data) { + console.log('tab-send.beforeEnter sendFlowService.fromWalletId:', sendFlowService.fromWalletId); + var fromWalletId = sendFlowService.fromWalletId; + if (fromWalletId) { + $scope.fromWallet = profileService.getWallet(fromWalletId); + } else { + $scope.fromWallet = null; + } $scope.isIOS = platformInfo.isIOS && platformInfo.isCordova; $scope.showWalletsBch = $scope.showWalletsBtc = $scope.showWallets = false; From 1d025cbf35f9bf7fa8eb36e5bd708dea5a1f149f Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 11:42:25 +1200 Subject: [PATCH 171/240] Fixes for Android 4.4 --- src/js/directives/formattedAmount.js | 6 +++--- src/js/services/clipboardService.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index 5bd307de3..d3e2e6347 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -32,10 +32,10 @@ angular.module('bitcoincom.directives') '3': ['BHD', 'IQD', 'JOD', 'KWD', 'OMR', 'TND'], '8': ['BCH', 'BTC'] }; - var localizeNumbers = function(x, minimumFractionDigits = 0, useGrouping = true) { + var localizeNumbers = function(x, minimumFractionDigits) { return parseFloat(x).toLocaleString(uxLanguage.getCurrentLanguage(), { minimumFractionDigits: minimumFractionDigits, - useGrouping: useGrouping + useGrouping: true }); }; @@ -80,7 +80,7 @@ angular.module('bitcoincom.directives') if (isNaN(parsed)) { buildAmount('-', '', ''); } else { - valueFormatted = localizeNumbers(Math.round(parsed)); + valueFormatted = localizeNumbers(Math.round(parsed), 0); buildAmount(valueFormatted, '', ''); } break; diff --git a/src/js/services/clipboardService.js b/src/js/services/clipboardService.js index 075cb749a..77ad3d018 100644 --- a/src/js/services/clipboardService.js +++ b/src/js/services/clipboardService.js @@ -13,7 +13,7 @@ angular.module('copayApp.services').factory('clipboardService', function ($http, nodeWebkitService.writeToClipboard(data); } else if (navigator && navigator.clipboard) { $log.debug("Use navigator clipboard.") - navigator.clipboard.writeText(data).catch(err => { + navigator.clipboard.writeText(data).catch(function onClipboardError(err) { $log.debug("Clipboard writing is not supported in your browser.."); }); } else if (clipboard.supported) { From 408d46e4d342eab340ea0033d16d2d43492a71b7 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 11:43:20 +1200 Subject: [PATCH 172/240] Hid the from wallet in the send screen for now, because it requires other layout changes to fit everything in, especially on small screens. --- src/js/controllers/tab-send.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/js/controllers/tab-send.js b/src/js/controllers/tab-send.js index caa03e92b..33befb8f2 100644 --- a/src/js/controllers/tab-send.js +++ b/src/js/controllers/tab-send.js @@ -208,13 +208,6 @@ angular.module('copayApp.controllers').controller('tabSendController', function( }; $scope.$on("$ionicView.beforeEnter", function(event, data) { - console.log('tab-send.beforeEnter sendFlowService.fromWalletId:', sendFlowService.fromWalletId); - var fromWalletId = sendFlowService.fromWalletId; - if (fromWalletId) { - $scope.fromWallet = profileService.getWallet(fromWalletId); - } else { - $scope.fromWallet = null; - } $scope.isIOS = platformInfo.isIOS && platformInfo.isCordova; $scope.showWalletsBch = $scope.showWalletsBtc = $scope.showWallets = false; From be0c18244e8be335a62f15788e661eb0fa27b0d3 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 12:12:29 +1200 Subject: [PATCH 173/240] Review Transaction screen now displaying BCH addresses in cashaddr format. --- src/js/controllers/review.controller.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 858e115c1..886da26b8 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -403,16 +403,20 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit // Check if the recipient is a contact addressbookService.get(originCoin + address, function(err, contact) { if (!err && contact) { - handleDestinationAsContact(contact); + handleDestinationAsAddressOfContact(contact); } else { - vm.destination.address = address; + if (originCoin === 'bch') { + vm.destination.address = bitcoinCashJsService.readAddress(address).cashaddr; + } else { + vm.destination.address = address; + } vm.destination.kind = 'address'; } }); } - function handleDestinationAsContact(contact) { + function handleDestinationAsAddressOfContact(contact) { vm.destination.kind = 'contact'; vm.destination.name = contact.name; vm.destination.email = contact.email; From bf9b467bfe9324f493c0b90d6942ba459d5c5f66 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 13:01:48 +1200 Subject: [PATCH 174/240] Renamed sendFlowService functions to be more consistent. --- src/js/controllers/amount.js | 5 +- src/js/controllers/tab-send.js | 3 ++ .../controllers/walletSelectorController.js | 10 ++-- src/js/services/incomingData.js | 6 +-- src/js/services/sendFlowService.js | 48 ++++++++++++------- 5 files changed, 46 insertions(+), 26 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 8ff214d87..6f513663c 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -66,6 +66,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } function onBeforeEnter(event, data) { + console.log('amount onBeforeEnter sendflow ', sendFlowService.getState()); initCurrencies(); @@ -209,7 +210,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } function goBack() { - sendFlowService.previousState(); + sendFlowService.popState(); if (vm.thirdParty && vm.thirdParty.id === 'shapeshift') { $state.go('tabs.send').then(function() { $ionicHistory.clearHistory(); @@ -471,7 +472,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, confirmData['thirdParty'] = this.thirdParty; } - sendFlowService.map(confirmData); + sendFlowService.pushState(confirmData); if (!confirmData.fromWalletId) { $state.transitionTo('tabs.paymentRequest.confirm', confirmData); diff --git a/src/js/controllers/tab-send.js b/src/js/controllers/tab-send.js index 33befb8f2..3f2c1a250 100644 --- a/src/js/controllers/tab-send.js +++ b/src/js/controllers/tab-send.js @@ -189,6 +189,8 @@ angular.module('copayApp.controllers').controller('tabSendController', function( $scope.startWalletToWalletTransfer = function() { console.log('startWalletToWalletTransfer()'); + var params = sendFlowService.getState(); + sendFlowService.pushState(params); $state.transitionTo('tabs.send.wallet-to-wallet', { fromWalletId: sendFlowService.fromWalletId }); @@ -208,6 +210,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function( }; $scope.$on("$ionicView.beforeEnter", function(event, data) { + console.log('tab-send onBeforeEnter sendflow ', sendFlowService.getState()); $scope.isIOS = platformInfo.isIOS && platformInfo.isCordova; $scope.showWalletsBch = $scope.showWalletsBtc = $scope.showWallets = false; diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index 69430dbb9..1c23bb24b 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -8,6 +8,8 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu var unitsFromSatoshis = 0; $scope.$on("$ionicView.beforeEnter", function(event, data) { + console.log('walletSelector onBeforeEnter sendflow', sendFlowService.getState()); + var config = configService.getSync().wallet.settings; priceDisplayAsFiat = config.priceDisplay === 'fiat'; unitDecimals = config.unitDecimals; @@ -182,16 +184,18 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu $scope.useWallet = function(wallet) { + var params = sendFlowService.getState(); if ($scope.type === 'origin') { // we're on the origin screen, set wallet to send from - $scope.params['fromWalletId'] = wallet.id; + params.fromWalletId = wallet.id; } else { // we're on the destination screen, set wallet to send to - $scope.params['toWalletId'] = wallet.id; + params.toWalletId = wallet.id; } + sendFlowService.pushState(params); $state.transitionTo(getNextStep(), $scope.params); }; $scope.goBack = function() { - sendFlowService.previousState(); + sendFlowService.popState(); $ionicHistory.goBack(); } diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index 91de99cf3..934f53cc7 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -105,10 +105,10 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat params.thirdParty = []; params.thirdParty.id = serviceId; params.thirdParty.data = serviceData; - sendFlowService.map(params); + sendFlowService.pushState(params); $state.transitionTo('tabs.send.amount', params); } else { - sendFlowService.map(params); + sendFlowService.pushState(params); $state.transitionTo('tabs.send.origin', params); } }, 100); @@ -459,7 +459,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat 'notify': $state.current.name == 'tabs.send' ? false : true }).then(function() { $timeout(function() { - sendFlowService.map(stateParams); + sendFlowService.pushState(stateParams); // Need to do more here $state.transitionTo('tabs.send.origin', stateParams); }); }); diff --git a/src/js/services/sendFlowService.js b/src/js/services/sendFlowService.js index 1af112ed2..9daf0f4db 100644 --- a/src/js/services/sendFlowService.js +++ b/src/js/services/sendFlowService.js @@ -19,15 +19,17 @@ angular // Functions clear: clear, + getState: getState, map: map, - previousState: previousState, + popState: popState, + pushState: pushState, startSend: startSend }; return service; function clear() { - $log.debug("Reinitialize Send Flow variables with clear()"); + console.log("sendFlow clear()"); service.amount = ''; service.fromWalletId = ''; service.sendMax = false; @@ -38,41 +40,51 @@ angular } /** - * Clears all previous state - * @param {} params + * Handy for debugging */ - function startSend(params) { - console.log('startSend()'); - clear(); - Object.keys(params).forEach(function forNewParam(key) { - service[key] = params[key]; - }); - } - - function map(params) { - + function getState() { var currentState = {}; Object.keys(service).forEach(function forCurrentParam(key) { if (typeof service[key] !== 'function' && key !== 'previousStates') { currentState[key] = service[key]; } }); - service.previousStates.push(currentState); + return currentState; + } - // Do we want to inherit the previous state here, or clear first before adding new params? + /** + * Clears all previous state + */ + function startSend(params) { + console.log('startSend()'); + clear(); + map(params); + } + function map(params) { Object.keys(params).forEach(function forNewParam(key) { service[key] = params[key]; }); }; - function previousState() { + function popState() { + console.log('sendFlow pop'); if (service.previousStates.length) { - map(service.previousStates.pop()); + var params = service.previousStates.pop(); + clear(); + map(params); } else { clear(); } }; + + function pushState(params) { + console.log('sendFlow push'); + var currentParams = getState(); + service.previousStates.push(currentParams); + clear(); + map(params); + }; }; })(); \ No newline at end of file From b426209efb2f7a49e4338fd845edbfd593731a6c Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 13:06:50 +1200 Subject: [PATCH 175/240] Fix for clearing current state only. --- src/js/services/sendFlowService.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/js/services/sendFlowService.js b/src/js/services/sendFlowService.js index 9daf0f4db..29686b15b 100644 --- a/src/js/services/sendFlowService.js +++ b/src/js/services/sendFlowService.js @@ -30,13 +30,18 @@ angular function clear() { console.log("sendFlow clear()"); + clearCurrent(); + service.previousStates = []; + } + + function clearCurrent() { + console.log("sendFlow clearCurrent()"); service.amount = ''; service.fromWalletId = ''; service.sendMax = false; service.thirdParty = null; service.toAddress = ''; service.toWalletId = ''; - service.previousStates = []; } /** @@ -71,7 +76,7 @@ angular console.log('sendFlow pop'); if (service.previousStates.length) { var params = service.previousStates.pop(); - clear(); + clearCurrent(); map(params); } else { clear(); @@ -82,7 +87,7 @@ angular console.log('sendFlow push'); var currentParams = getState(); service.previousStates.push(currentParams); - clear(); + clearCurrent(); map(params); }; }; From a2dff98c63c67b19c93fad058fa504e9818e0418 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 13:12:44 +1200 Subject: [PATCH 176/240] Back button on Review Transaction screen works with sendFlowService. --- src/js/controllers/review.controller.js | 6 ++++++ www/views/review.html | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 886da26b8..e2e393501 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -49,6 +49,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit vm.memoExpanded = false; // Functions + vm.goBack = goBack; vm.onSuccessConfirm = onSuccessConfirm; var sendFlowData; @@ -395,6 +396,11 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit }; } + function goBack() { + sendFlowService.popState(); + $ionicHistory.goBack(); + } + function handleDestinationAsAddress(address, originCoin) { if (!address) { return; diff --git a/www/views/review.html b/www/views/review.html index cb7f21fbc..1eb648a01 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -3,8 +3,7 @@ {{'Review Transaction' | translate}} - - + From 0410182d4f151b1880443cc52898250c5cd3bc92 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 13:24:13 +1200 Subject: [PATCH 177/240] Incoming data working with sendFlowService. --- src/js/services/incomingData.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index 934f53cc7..76b5885d4 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -82,7 +82,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat }); // Timeout is required to enable the "Back" button $timeout(function() { - var params = {}; + var params = sendFlowService.getState(); if (amount) { params.amount = amount; From 188ddcba586054f87566f536f358708bb46435a2 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 13:25:06 +1200 Subject: [PATCH 178/240] Review transation copying state at startup so it doesn't contaminate the sendFlowController. --- src/js/controllers/review.controller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index e2e393501..2b42339ce 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -75,8 +75,9 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit function onBeforeEnter(event, data) { + console.log('walletSelector onBeforeEnter sendflow ', sendFlowService.getState()); defaults = configService.getDefaults(); - sendFlowData = sendFlowService; + sendFlowData = sendFlowService.getState(); originWalletId = sendFlowData.fromWalletId; satoshis = parseInt(sendFlowData.amount, 10); toAddress = sendFlowData.toAddress; From e18364329805475d33a42e64b10cbf3e6b1c7baf Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 9 Aug 2018 12:35:39 +0900 Subject: [PATCH 179/240] Remove from the route the stateparams, adapt some controllers in the new sendflowservice --- src/js/controllers/addressbookView.js | 8 +++++-- src/js/controllers/amount.js | 13 ++-------- src/js/controllers/shapeshift.js | 10 ++++---- src/js/controllers/tab-send.js | 24 +++++++++++++++---- .../controllers/walletSelectorController.js | 8 ++++--- src/js/routes.js | 8 +++---- src/js/services/incomingData.js | 12 ++++++---- 7 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/js/controllers/addressbookView.js b/src/js/controllers/addressbookView.js index 5f6881d10..89c1cd924 100644 --- a/src/js/controllers/addressbookView.js +++ b/src/js/controllers/addressbookView.js @@ -32,12 +32,16 @@ angular.module('copayApp.controllers').controller('addressbookViewController', f } else { to = $scope.addressbookEntry.address; } - $state.transitionTo('tabs.send.amount', { + + var stateParams = { toAddress: to, toName: $scope.addressbookEntry.name, toEmail: $scope.addressbookEntry.email, coin: $scope.addressbookEntry.coin - }); + }; + + sendFlowService.pushState(stateParams); + $state.transitionTo('tabs.send.origin'); }, 100); }; diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 6f513663c..d07f77c3f 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -78,7 +78,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.maxAmount = parseFloat(passthroughParams.maxAmount); if (passthroughParams.thirdParty) { - vm.thirdParty = JSON.parse(passthroughParams.thirdParty); // Parse stringified JSON-object + vm.thirdParty = passthroughParams.thirdParty; // Parse stringified JSON-object if (vm.thirdParty) { if (vm.thirdParty.id === 'shapeshift') { if (!vm.thirdParty.data) { @@ -211,16 +211,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, function goBack() { sendFlowService.popState(); - if (vm.thirdParty && vm.thirdParty.id === 'shapeshift') { - $state.go('tabs.send').then(function() { - $ionicHistory.clearHistory(); - $state.go('tabs.home').then(function() { - $state.transitionTo('tabs.shapeshift'); - }); - }); - } else { - $ionicHistory.goBack(); - } + $ionicHistory.goBack(); } function paste(value) { diff --git a/src/js/controllers/shapeshift.js b/src/js/controllers/shapeshift.js index fcb43026d..4ede8a94f 100644 --- a/src/js/controllers/shapeshift.js +++ b/src/js/controllers/shapeshift.js @@ -63,16 +63,18 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi }; $scope.shapeshift = function() { - var params = { - thirdParty: JSON.stringify({id: 'shapeshift'}) + var stateParams = { + thirdParty: { + id: 'shapeshift' + } }; - sendFlowService.clear(); $state.go('tabs.home').then(function() { $ionicHistory.clearHistory(); $state.go('tabs.send').then(function() { $timeout(function () { - $state.transitionTo('tabs.send.origin', params); + sendFlowService.pushState(stateParams); + $state.transitionTo('tabs.send.origin'); }, 60); }); }); diff --git a/src/js/controllers/tab-send.js b/src/js/controllers/tab-send.js index 3f2c1a250..55087859d 100644 --- a/src/js/controllers/tab-send.js +++ b/src/js/controllers/tab-send.js @@ -179,11 +179,18 @@ angular.module('copayApp.controllers').controller('tabSendController', function( } $log.debug('Got toAddress:' + toAddress + ' | ' + item.name); + + var stateParams = sendFlowService.getState(); + stateParams.toAddress = toAddress, + stateParams.coin = item.coin; + sendFlowService.pushState(stateParams); + + if (!stateParams.fromWalletId) { // If we have no toAddress or fromWallet + $state.transitionTo('tabs.send.origin'); + } else { + $state.transitionTo('tabs.send.amount'); + } - return $state.transitionTo('tabs.send.origin', { - toAddress: toAddress, - coin: item.coin - }); }); }; @@ -210,6 +217,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function( }; $scope.$on("$ionicView.beforeEnter", function(event, data) { + console.log(data); console.log('tab-send onBeforeEnter sendflow ', sendFlowService.getState()); $scope.isIOS = platformInfo.isIOS && platformInfo.isCordova; $scope.showWalletsBch = $scope.showWalletsBtc = $scope.showWallets = false; @@ -225,5 +233,11 @@ angular.module('copayApp.controllers').controller('tabSendController', function( $scope.displayBalanceAsFiat = _config.wallet.settings.priceDisplay === 'fiat'; }); + var state = sendFlowService.getState(); + + if (data.direction == "back") { + sendFlowService.clear(); + } + }); -}); +}); \ No newline at end of file diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index 1c23bb24b..f4448486f 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -10,6 +10,8 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu $scope.$on("$ionicView.beforeEnter", function(event, data) { console.log('walletSelector onBeforeEnter sendflow', sendFlowService.getState()); + var stateParams = sendFlowService.getState(); + var config = configService.getSync().wallet.settings; priceDisplayAsFiat = config.priceDisplay === 'fiat'; unitDecimals = config.unitDecimals; @@ -20,7 +22,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu $scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer'); break; case 'tabs.send.destination': - if (data.stateParams.fromWalletId) { + if (stateParams.fromWalletId) { $scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer'); } break; @@ -47,7 +49,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu $scope.isPaymentRequest = true; } if ($scope.params.thirdParty) { - $scope.thirdParty = JSON.parse($scope.params.thirdParty); // Parse stringified JSON-object + $scope.thirdParty = $scope.params.thirdParty; } }); @@ -96,7 +98,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu function getNextStep() { if ($scope.thirdParty) { - $scope.params.thirdParty = JSON.stringify($scope.thirdParty) // re-stringify JSON-object + $scope.params.thirdParty = $scope.thirdParty } if (!$scope.params.toWalletId && !$scope.params.toAddress) { // If we have no toAddress or fromWallet return 'tabs.send.destination'; diff --git a/src/js/routes.js b/src/js/routes.js index dea46a2cf..5dbcbe6a3 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -287,7 +287,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr */ .state('tabs.send.amount', { - url: '/amount/:thirdParty/:fromWalletId/:maxAmount/:minAmount/:toWalletId/:toAddress', + url: '/amount', views: { 'tab-send@tabs': { controller: 'amountController', @@ -306,7 +306,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }) .state('tabs.send.origin', { - url: '/origin/:thirdParty/:amount/:toAddress/:toWalletId/:coin', + url: '/origin', views: { 'tab-send@tabs': { controller: 'walletSelectorController', @@ -315,7 +315,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }) .state('tabs.send.destination', { - url: '/destination/:thirdParty/:amount/:fromWalletId', + url: '/destination', views: { 'tab-send@tabs': { controller: 'walletSelectorController', @@ -324,7 +324,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }) .state('tabs.send.confirm', { - url: '/confirm/:thirdParty/:amount/:fromWalletId/:toWalletId/:toAddress', + url: '/confirm', views: { 'tab-send@tabs': { controller: 'confirmController', diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index 76b5885d4..86c928a5e 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -106,10 +106,10 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat params.thirdParty.id = serviceId; params.thirdParty.data = serviceData; sendFlowService.pushState(params); - $state.transitionTo('tabs.send.amount', params); + $state.transitionTo('tabs.send.amount'); } else { sendFlowService.pushState(params); - $state.transitionTo('tabs.send.origin', params); + $state.transitionTo('tabs.send.origin'); } }, 100); } @@ -388,11 +388,13 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat 'notify': $state.current.name == 'tabs.send' ? false : true }); $timeout(function() { - $state.transitionTo('tabs.send.origin', { + var stateParams = { toAddress: toAddress, coin: coin, noPrefix: 1 - }); + }; + sendFlowService.pushState(stateParams); + $state.transitionTo('tabs.send.origin'); }, 100); } @@ -460,7 +462,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat }).then(function() { $timeout(function() { sendFlowService.pushState(stateParams); // Need to do more here - $state.transitionTo('tabs.send.origin', stateParams); + $state.transitionTo('tabs.send.origin'); }); }); } From 647e1261698eba7a9526d5abe93ff85a76ce2b3d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 9 Aug 2018 12:49:35 +0900 Subject: [PATCH 180/240] Fix style css for the from wallet & activate the from wallet in the send tab --- src/js/controllers/tab-send.js | 3 ++ src/sass/views/tab-send.scss | 14 ++++---- www/css/main.css | 64 ++++++++++++---------------------- 3 files changed, 32 insertions(+), 49 deletions(-) diff --git a/src/js/controllers/tab-send.js b/src/js/controllers/tab-send.js index 55087859d..911548455 100644 --- a/src/js/controllers/tab-send.js +++ b/src/js/controllers/tab-send.js @@ -33,6 +33,9 @@ angular.module('copayApp.controllers').controller('tabSendController', function( text = text.substring(0, 200); } + var stateParams = sendFlowService.getState(); + $scope.fromWallet = profileService.getWallet(stateParams.fromWalletId); + $scope.clipboardHasAddress = false; $scope.clipboardHasContent = false; if ((text.indexOf('bitcoincash:') === 0 || text[0] === 'C' || text[0] === 'H' || text[0] === 'p' || text[0] === 'q') && text.replace('bitcoincash:', '').length === 42) { // CashAddr diff --git a/src/sass/views/tab-send.scss b/src/sass/views/tab-send.scss index 82b6f8d02..bf77984fd 100644 --- a/src/sass/views/tab-send.scss +++ b/src/sass/views/tab-send.scss @@ -2,15 +2,15 @@ @extend .deflash-blue; &-header{ - height: 300px; + //height: 300px; width: 100%; } &-contacts { - height: calc(100vh - 300px - 50px - 44px); /* screen size - button container - bottom-tab-menu - header top */ + //height: calc(100vh - 300px - 50px - 44px); /* screen size - button container - bottom-tab-menu - header top */ &.ios { - height: calc(100vh - 300px - 50px - 44px - 18px); // Remove the notification-bar height on iOS + //height: calc(100vh - 300px - 50px - 44px - 18px); // Remove the notification-bar height on iOS } - overflow: scroll; + //overflow: scroll; } .input { @@ -223,12 +223,12 @@ } } #tab-send-header { - height: 270px; + //height: 270px; } #tab-send-contacts { - height: calc(100vh - 270px - 50px - 44px); /* screen size - button container - bottom-tab-menu - header top */ + //height: calc(100vh - 270px - 50px - 44px); /* screen size - button container - bottom-tab-menu - header top */ &.ios { - height: calc(100vh - 270px - 50px - 44px - 18px); // Remove the notification-bar height on iOS + //height: calc(100vh - 270px - 50px - 44px - 18px); // Remove the notification-bar height on iOS } } } diff --git a/www/css/main.css b/www/css/main.css index 9845340ae..8602ba35d 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -11168,16 +11168,8 @@ qrcode { background-color: #fab915 !important; } #tab-send-header { - height: 300px; width: 100%; } -#tab-send-contacts { - height: calc(100vh - 300px - 50px - 44px); - /* screen size - button container - bottom-tab-menu - header top */ - overflow: scroll; } - #tab-send-contacts.ios { - height: calc(100vh - 300px - 50px - 44px - 18px); } - #tab-send .input { width: 100%; } #tab-send .input input { @@ -11358,14 +11350,7 @@ qrcode { #tab-send .send-wrapper .buttons .button-qr { height: 60px; } #tab-send .send-wrapper .buttons .button-qr span { - font-size: 16px; } - #tab-send #tab-send-header { - height: 270px; } - #tab-send #tab-send-contacts { - height: calc(100vh - 270px - 50px - 44px); - /* screen size - button container - bottom-tab-menu - header top */ } - #tab-send #tab-send-contacts.ios { - height: calc(100vh - 270px - 50px - 44px - 18px); } } + font-size: 16px; } } #wallet-origin-destination .header--request { padding: 30px 24px; @@ -15374,8 +15359,6 @@ log-options #check-bar .checkbox-icon { #view-review .fee-summary { position: absolute; bottom: 92px; } - #view-review .fee-summary-spacer { - height: 15px; } #view-review .shapeshift-banner, #view-review .bitpay-banner, #view-review .egifter-banner { box-shadow: none; } #view-review .warning { @@ -15551,31 +15534,28 @@ ion-content.padded-bottom-cta-with-summary { .fee-summary .amount .fee-crypto { color: #A7A7A7; } -.amount .start, -.amount .middle, -.amount .end, -.amount .currency { +.formatted-amount { display: inline-block; } - -.amount .start { - font-size: 1em; } - -.amount .middle { - font-size: 0.7857em; - margin-left: 5px; } - -.amount .end { - font-size: 0.7857em; - margin-left: 5px; } - -.amount.size-equal .middle, -.amount.size-equal .end { - font-size: 1em; } - -.amount .currency { - font-size: 1em; - margin-left: 5px; - text-transform: uppercase; } + .formatted-amount .start, + .formatted-amount .middle, + .formatted-amount .end, + .formatted-amount .currency { + display: inline-block; } + .formatted-amount .start { + font-size: 1em; } + .formatted-amount .middle { + font-size: 0.7857em; + margin-left: 5px; } + .formatted-amount .end { + font-size: 0.7857em; + margin-left: 5px; } + .formatted-amount.size-equal .middle, + .formatted-amount.size-equal .end { + font-size: 1em; } + .formatted-amount .currency { + font-size: 1em; + margin-left: 5px; + text-transform: uppercase; } /* This is for rules that don't yet have a home. * Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/ From a490f0ce1f42960fd87df7a3e561239fe5e42ee2 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 9 Aug 2018 13:06:52 +0900 Subject: [PATCH 181/240] dead code --- src/js/controllers/tab-send.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/js/controllers/tab-send.js b/src/js/controllers/tab-send.js index 911548455..81b3e4ca5 100644 --- a/src/js/controllers/tab-send.js +++ b/src/js/controllers/tab-send.js @@ -236,8 +236,6 @@ angular.module('copayApp.controllers').controller('tabSendController', function( $scope.displayBalanceAsFiat = _config.wallet.settings.priceDisplay === 'fiat'; }); - var state = sendFlowService.getState(); - if (data.direction == "back") { sendFlowService.clear(); } From 7ce562ec648746153ae86e95c989d7f791469baa Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 16:11:29 +1200 Subject: [PATCH 182/240] Fix for Shapeshift now appearing again on Review Transaction screen. --- src/js/controllers/review.controller.js | 8 +++----- src/js/controllers/shapeshift.js | 2 ++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 2b42339ce..0e5ceedce 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -88,11 +88,9 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit coin = vm.originWallet.coin; if (sendFlowData.thirdParty) { - // vm.thirdParty = JSON.parse(sendFlowData.thirdParty); // Parse stringified JSON-object - if (vm.thirdParty) { - handleThirdPartyInitIfBip70(); - handleThirdPartyInitIfShapeshift(); - } + vm.thirdParty = sendFlowData.thirdParty; + handleThirdPartyInitIfBip70(); + handleThirdPartyInitIfShapeshift(); } configService.get(function onConfig(err, configCache) { diff --git a/src/js/controllers/shapeshift.js b/src/js/controllers/shapeshift.js index 4ede8a94f..3538ebe1a 100644 --- a/src/js/controllers/shapeshift.js +++ b/src/js/controllers/shapeshift.js @@ -69,6 +69,8 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi } }; + // Starting new send flow, so ensure everything is reset + sendFlowService.clear(); $state.go('tabs.home').then(function() { $ionicHistory.clearHistory(); $state.go('tabs.send').then(function() { From 0ea97fc1c61fa3860e2eed4b80c202b3503c3d64 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 9 Aug 2018 13:14:44 +0900 Subject: [PATCH 183/240] Fix state pop on the send flow --- src/js/controllers/review.controller.js | 1 - src/js/controllers/walletSelectorController.js | 16 +++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 2b42339ce..70a349b37 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -398,7 +398,6 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit } function goBack() { - sendFlowService.popState(); $ionicHistory.goBack(); } diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index f4448486f..74ed874d4 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -7,9 +7,16 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu var unitDecimals = 0; var unitsFromSatoshis = 0; - $scope.$on("$ionicView.beforeEnter", function(event, data) { + $scope.$on("$ionicView.beforeEnter", onBeforeEnter); + $scope.$on("$ionicView.enter", onEnter); + + function onBeforeEnter(event, data) { console.log('walletSelector onBeforeEnter sendflow', sendFlowService.getState()); + if (data.direction == "back") { + sendFlowService.popState(); + } + var stateParams = sendFlowService.getState(); var config = configService.getSync().wallet.settings; @@ -51,9 +58,9 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu if ($scope.params.thirdParty) { $scope.thirdParty = $scope.params.thirdParty; } - }); + }; - $scope.$on("$ionicView.enter", function(event, data) { + function onEnter (event, data) { configService.whenAvailable(function(config) { $scope.selectedPriceDisplay = config.wallet.settings.priceDisplay; }); @@ -65,7 +72,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu prepareWalletLists(); formatRequestedAmount(); - }); + }; function formatRequestedAmount() { if ($scope.params.amount) { @@ -197,7 +204,6 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu }; $scope.goBack = function() { - sendFlowService.popState(); $ionicHistory.goBack(); } From 77f62100760a60fb46d2d3e578d86dc6f900c15b Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 9 Aug 2018 13:15:21 +0900 Subject: [PATCH 184/240] Fix pop state on the send flow --- src/js/controllers/amount.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index d07f77c3f..b945be92c 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -68,6 +68,10 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, function onBeforeEnter(event, data) { console.log('amount onBeforeEnter sendflow ', sendFlowService.getState()); + if (data.direction == "back") { + sendFlowService.popState(); + } + initCurrencies(); passthroughParams = sendFlowService; @@ -210,7 +214,6 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } function goBack() { - sendFlowService.popState(); $ionicHistory.goBack(); } From cbf66a2176224d8190d0eac14bb47a9c7983aaba Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 17:54:20 +1200 Subject: [PATCH 185/240] Fix for formatting amounts on Android 4.4. --- src/js/directives/formattedAmount.js | 48 ++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index d3e2e6347..c0c341631 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -33,10 +33,15 @@ angular.module('bitcoincom.directives') '8': ['BCH', 'BTC'] }; var localizeNumbers = function(x, minimumFractionDigits) { - return parseFloat(x).toLocaleString(uxLanguage.getCurrentLanguage(), { + var parsed = parseFloat(x); + var opts = { minimumFractionDigits: minimumFractionDigits, useGrouping: true - }); + }; + var lang = uxLanguage.getCurrentLanguage(); + var localized = parsed.toLocaleString(lang, opts); + var corrected = ensureEnoughFractionalDigits(localized, x, minimumFractionDigits); + return corrected; }; var buildAmount = function(start, middle, end) { @@ -61,7 +66,6 @@ angular.module('bitcoincom.directives') }; var formatNumbers = function() { - // During watch, may be changed from having a separate currency value, // to both being in value. Don't want to use previous currency value. // Try to extract currency from value.. @@ -128,6 +132,44 @@ angular.module('bitcoincom.directives') $scope.$watchGroup(['currency', 'value'], function onFormattedAmountWatch() { formatNumbers(); }); + + /** + * On Android 4.4, toLocaleString() only returns 3 fractional digits when 8 is specified. + */ + function ensureEnoughFractionalDigits(localizedString, number, desiredFractionDigits) { + if (desiredFractionDigits === 0) { + // Assume it is OK + return localizedString; + } + var fractionalRe = /^(\d*\D)(\d+)$/; + var match = fractionalRe.exec(localizedString); + if (match.length !== 3) { + // Don't know what's happening, just return what we have + return localizedString; + } + + var decimals = match[2]; + var decimalCount = decimals.length; + if (decimalCount >= desiredFractionDigits) { + // Everything is OK. + return localizedString; + } + + if (typeof number !== 'number') { + number = parseFloat(number); + } + + var fixed = number.toFixed(desiredFractionDigits); + var fixedMatch = fractionalRe.exec(fixed); + if (fixedMatch.length !== 3) { + // Don't know what's happening, just return what we have + return localizedString; + } + + // Keeps locale decimal separator. + var enough = match[1] + fixedMatch[2]; + return enough; + } }); } }; From c68b1e77f66f22458c476d8af07c63ff38fc6e40 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 9 Aug 2018 15:09:43 +0900 Subject: [PATCH 186/240] clean controller and remove old firebase event --- src/js/controllers/backup.js | 5 +++-- src/js/controllers/confirm.js | 4 +--- src/js/controllers/create.js | 4 ++-- src/js/controllers/tab-home.js | 4 +--- src/js/routes.js | 7 ++++++- src/js/services/firebaseEventsService.js | 2 +- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/js/controllers/backup.js b/src/js/controllers/backup.js index 0eec71f12..6ec7089c6 100644 --- a/src/js/controllers/backup.js +++ b/src/js/controllers/backup.js @@ -1,7 +1,7 @@ 'use strict'; angular.module('copayApp.controllers').controller('backupController', - function($scope, $timeout, $log, $state, $stateParams, $ionicHistory, lodash, profileService, bwcService, walletService, ongoingProcess, popupService, gettextCatalog, $ionicModal, firebaseEventsService) { + function($scope, $timeout, $log, $state, $stateParams, $ionicHistory, lodash, profileService, bwcService, walletService, ongoingProcess, popupService, gettextCatalog, $ionicModal) { if ($state.current.name == 'onboarding.backup') { $scope.onboarding = true; @@ -89,7 +89,8 @@ angular.module('copayApp.controllers').controller('backupController', $scope.setFlow(2); }) } else { - firebaseEventsService.logEvent('backed_up_wallet'); + + //firebaseEventsService.logEvent('backed_up_wallet'); openConfirmBackupModal(); } }; diff --git a/src/js/controllers/confirm.js b/src/js/controllers/confirm.js index c98064fcc..76950df73 100644 --- a/src/js/controllers/confirm.js +++ b/src/js/controllers/confirm.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, $ionicLoading, ionicToast, addressbookService, gettextCatalog, walletService, platformInfo, lodash, configService, $stateParams, $window, $state, $log, profileService, bitcore, bitcoreCash, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService, feeService, bitcoinCashJsService, bwcError, txConfirmNotification, externalLinkService, firebaseEventsService, soundService, clipboardService) { +angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $timeout, $ionicScrollDelegate, $ionicLoading, ionicToast, addressbookService, gettextCatalog, walletService, platformInfo, lodash, configService, $state, $log, profileService, bitcore, bitcoreCash, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, feeService, bitcoinCashJsService, bwcError, txConfirmNotification, soundService, clipboardService) { var countDown = null; var FEE_TOO_HIGH_LIMIT_PER = 15; @@ -708,8 +708,6 @@ angular.module('copayApp.controllers').controller('confirmController', function( }], [channel, "adjust"]); window.BitAnalytics.LogEventHandlers.postEvent(log); - // Should be removed - firebaseEventsService.logEvent('sent_bitcoin', { coin: $scope.wallet.coin }); $timeout(function() { $scope.$digest(); }, 100); diff --git a/src/js/controllers/create.js b/src/js/controllers/create.js index a25300a4a..a21053e2d 100644 --- a/src/js/controllers/create.js +++ b/src/js/controllers/create.js @@ -1,7 +1,7 @@ 'use strict'; angular.module('copayApp.controllers').controller('createController', - function($scope, $rootScope, $timeout, $log, lodash, $state, $ionicScrollDelegate, $ionicHistory, profileService, configService, gettextCatalog, ledger, trezor, intelTEE, derivationPathHelper, ongoingProcess, walletService, storageService, popupService, appConfigService, pushNotificationsService, firebaseEventsService, $ionicNavBarDelegate) { + function($scope, $timeout, $log, lodash, $state, $ionicScrollDelegate, $ionicHistory, profileService, configService, gettextCatalog, ledger, trezor, intelTEE, derivationPathHelper, ongoingProcess, walletService, popupService, appConfigService, pushNotificationsService, $ionicNavBarDelegate) { /* For compressed keys, m*73 + n*34 <= 496 */ var COPAYER_PAIR_LIMITS = { @@ -268,7 +268,7 @@ angular.module('copayApp.controllers').controller('createController', }, 100); } else { - firebaseEventsService.logEvent('wallet_created', { coin: opts.coin }); + //firebaseEventsService.logEvent('wallet_created', { coin: opts.coin }); $state.go('tabs.home'); } } diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index 3d63ae41f..32e320932 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -1,7 +1,7 @@ 'use strict'; angular.module('copayApp.controllers').controller('tabHomeController', - function($rootScope, sendFlowService, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, bannerService, 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, sendFlowService, $timeout, $scope, $state, $stateParams, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, bannerService, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, storageService, txpModalService, appConfigService, startupService, addressbookService, bwcError, nextStepsService, buyAndSellService, homeIntegrationsService, bitpayCardService, pushNotificationsService, timeService, $ionicNavBarDelegate) { var wallet; var listeners = []; var notifications = []; @@ -98,8 +98,6 @@ angular.module('copayApp.controllers').controller('tabHomeController', } $scope.showServices = true; - pushNotificationsService.init(); - firebaseEventsService.init(); $timeout(function() { $ionicScrollDelegate.resize(); diff --git a/src/js/routes.js b/src/js/routes.js index 5dbcbe6a3..b5a9c91fe 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -1207,7 +1207,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }); }) - .run(function($rootScope, $state, $location, $log, $timeout, startupService, ionicToast, fingerprintService, $ionicHistory, $ionicPlatform, $window, appConfigService, lodash, platformInfo, profileService, uxLanguage, gettextCatalog, openURLService, storageService, scannerService, configService, emailService, /* plugins START HERE => */ buydotbitcoindotcomService, glideraService, amazonService, bitpayCardService, applicationService, mercadoLibreService, rateService) { + .run(function($rootScope, $state, $location, $log, $timeout, startupService, ionicToast, fingerprintService, $ionicHistory, $ionicPlatform, $window, appConfigService, lodash, platformInfo, profileService, uxLanguage, gettextCatalog, openURLService, storageService, scannerService, configService, emailService, /* plugins START HERE => */ buydotbitcoindotcomService, pushNotificationsService, glideraService, amazonService, bitpayCardService, applicationService, mercadoLibreService, rateService) { $ionicPlatform.ready(function() { @@ -1231,6 +1231,11 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }); + configService.whenAvailable(function(config) { + pushNotificationsService.init(); + }); + //firebaseEventsService.init(); + var channel = "ga"; if (platformInfo.isCordova) { channel = "firebase"; diff --git a/src/js/services/firebaseEventsService.js b/src/js/services/firebaseEventsService.js index d81e682d7..d6ce6f1cc 100644 --- a/src/js/services/firebaseEventsService.js +++ b/src/js/services/firebaseEventsService.js @@ -1,5 +1,5 @@ 'use strict'; -angular.module('copayApp.services').factory('firebaseEventsService', function firebaseEventsService($log, $state, $ionicHistory, sjcl, platformInfo, lodash, appConfigService, profileService, configService) { +angular.module('copayApp.services').factory('firebaseEventsService', function firebaseEventsService($log, platformInfo) { var root = {}; var useEvents = platformInfo.isCordova && !platformInfo.isWP; From b742eadd4e1f24329326ea7ca5711f1013d7bd61 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 9 Aug 2018 15:13:29 +0900 Subject: [PATCH 187/240] clean and remove bug home --- src/js/controllers/tab-home.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index 32e320932..b6fec52d4 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -20,7 +20,12 @@ angular.module('copayApp.controllers').controller('tabHomeController', $scope.bannerUrl = ''; - $scope.$on("$ionicView.afterEnter", function() { + $scope.$on("$ionicView.beforeEnter", onBeforeEnter); + $scope.$on("$ionicView.enter", onEnter); + $scope.$on("$ionicView.afterEnter", onAfterEnter); + $scope.$on("$ionicView.leave", onLeave); + + function onAfterEnter () { startupService.ready(); bannerService.getBanner(function (banner) { @@ -28,9 +33,9 @@ angular.module('copayApp.controllers').controller('tabHomeController', $scope.bannerUrl = banner.url; $scope.bannerIsLoading = false; }); - }); + }; - $scope.$on("$ionicView.beforeEnter", function(event, data) { + function onBeforeEnter (event, data) { if (!$scope.homeTip) { storageService.getHomeTipAccepted(function(error, value) { @@ -52,11 +57,10 @@ angular.module('copayApp.controllers').controller('tabHomeController', } }); } - }); - - $scope.$on("$ionicView.enter", function(event, data) { + }; + + function onEnter(event, data) { $ionicNavBarDelegate.showBar(true); - updateAllWallets(); addressbookService.list(function(err, ab) { if (err) $log.error(err); @@ -104,13 +108,13 @@ angular.module('copayApp.controllers').controller('tabHomeController', $scope.$apply(); }, 10); }); - }); + }; - $scope.$on("$ionicView.leave", function(event, data) { + function onLeave (event, data) { lodash.each(listeners, function(x) { x(); }); - }); + }; $scope.createdWithinPastDay = function(time) { return timeService.withinPastDay(time); From 9076ad209809c5feef52c7690eab1ef6a0fa2676 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 9 Aug 2018 15:35:03 +0900 Subject: [PATCH 188/240] add update wallet --- src/js/controllers/tab-home.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index b6fec52d4..51d500de7 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -61,6 +61,7 @@ angular.module('copayApp.controllers').controller('tabHomeController', function onEnter(event, data) { $ionicNavBarDelegate.showBar(true); + updateAllWallets(); addressbookService.list(function(err, ab) { if (err) $log.error(err); From 4cf68076a01a0b3e27ff9286113e8c3f5c330066 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Thu, 9 Aug 2018 15:35:31 +0900 Subject: [PATCH 189/240] remove comment on bitanalytics --- bitanalytics/bitanalytics.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bitanalytics/bitanalytics.js b/bitanalytics/bitanalytics.js index c8c0d8870..d04b2a0bd 100644 --- a/bitanalytics/bitanalytics.js +++ b/bitanalytics/bitanalytics.js @@ -6256,7 +6256,6 @@ var ClickAction = /** @class */ (function (_super) { // Add event listener to all the elements found for (var i = 0; i < elements.length; i++) { var element = elements[i]; - console.log('init ' + this.name); element.addEventListener('click', this.listener); } }; @@ -6413,7 +6412,6 @@ var AdjustChannel = /** @class */ (function (_super) { _this.eventTypes = config.eventTypes; var os = _this.adjustedOs(config.os); _this.advertisingId = _this.getAdvertisingId(os); - console.log('Advertising ID for adjust: ' + _this.advertisingId); // TODO: Different initialisation for Cordova. var sessionParams = { app_version: config.appVersion, @@ -6656,7 +6654,7 @@ var MixpanelChannel = /** @class */ (function (_super) { function MixpanelChannel(name, config) { var _this = _super.call(this, name) || this; if (!config.token) { - throw new DOMException('[BitAnalytics] Config incorrect.'); + throw new Error('[BitAnalytics] Config incorrect.'); } _this.mixpanelInstance = mixpanel; mixpanel.init(config.token, config.config); @@ -7037,7 +7035,7 @@ var LogEventHandlers = /** @class */ (function () { _this.channels.push(channel); } catch (error) { - console.log('[BitAnalytics] ' + error.name + ': ' + error.message); + console.log(error.message); } }); }; From de33fe58681546f6929dd15d46852aaeebd68784 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 18:58:21 +1200 Subject: [PATCH 190/240] Refactored formattedAmount. --- src/js/directives/formattedAmount.js | 333 ++++++++++++++------------- 1 file changed, 170 insertions(+), 163 deletions(-) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index c0c341631..cbe288fa5 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -1,5 +1,6 @@ 'use strict'; +(function(){ /** * @desc amount directive that can be used to display formatted financial values * size-equal attribute is optional, defaults to false. @@ -10,168 +11,174 @@ * @example * @example */ -angular.module('bitcoincom.directives') - .directive('formattedAmount', function(uxLanguage) { - return { - restrict: 'E', - scope: { - value: '@', - currency: '@', - sizeEqual: '@' - }, - templateUrl: 'views/includes/formatted-amount.html', - controller: function($scope, $timeout) { - $scope.canShow = false; - - $scope.displaySizeEqual = !!$scope.sizeEqual; - - $timeout(function onFormattedAmountTimeout() { - - var decimalPlaces = { - '0': ['BIF', 'CLP', 'DJF', 'GNF', 'ILS', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'], - '3': ['BHD', 'IQD', 'JOD', 'KWD', 'OMR', 'TND'], - '8': ['BCH', 'BTC'] - }; - var localizeNumbers = function(x, minimumFractionDigits) { - var parsed = parseFloat(x); - var opts = { - minimumFractionDigits: minimumFractionDigits, - useGrouping: true - }; - var lang = uxLanguage.getCurrentLanguage(); - var localized = parsed.toLocaleString(lang, opts); - var corrected = ensureEnoughFractionalDigits(localized, x, minimumFractionDigits); - return corrected; - }; - - var buildAmount = function(start, middle, end) { - $scope.start = start; - $scope.middle = middle; - $scope.end = end; - }; - - var getDecimalPlaces = function(currency) { - if (decimalPlaces['0'].indexOf(currency.toUpperCase()) > -1) return '0'; - if (decimalPlaces['3'].indexOf(currency.toUpperCase()) > -1) return '3'; - if (decimalPlaces['8'].indexOf(currency.toUpperCase()) > -1) return '8'; - return '2'; - }; - - var getDecimalSeparator = function() { - var testNum = 1.5; - var testString = testNum.toLocaleString(uxLanguage.getCurrentLanguage()); - // Some environments let you set decimal separators that are more than one character - var separator = /^1(.+)5$/.exec(testString)[1] - return separator; - }; - - var formatNumbers = function() { - // During watch, may be changed from having a separate currency value, - // to both being in value. Don't want to use previous currency value. - // Try to extract currency from value.. - var currencySplit = $scope.value.split(" "); - if (currencySplit.length === 2) { - $scope.currency = currencySplit[1]; - } - $scope.currency = $scope.currency || ''; - - - var parsed = parseFloat($scope.value); - var valueFormatted = ''; - var valueProcessing = ''; - switch (getDecimalPlaces($scope.currency)) { - case '0': - if (isNaN(parsed)) { - buildAmount('-', '', ''); - } else { - valueFormatted = localizeNumbers(Math.round(parsed), 0); - buildAmount(valueFormatted, '', ''); - } - break; - - case '3': - if (isNaN(parsed)) { - buildAmount('-' + getDecimalSeparator() + '---', '', ''); - } else { - valueProcessing = parsed.toFixed(3); - valueFormatted = localizeNumbers(valueProcessing, 3); - buildAmount(valueFormatted, '', ''); - } - break; - - case '8': - if (isNaN(parsed)) { - buildAmount('-' + getDecimalSeparator() + '---', '', ''); - } else if (parsed === 0) { - buildAmount('0', '', ''); - } else { - valueFormatted = parsed.toFixed(8); - valueFormatted = localizeNumbers(valueFormatted, 8); - var start = valueFormatted.slice(0, -5); - var middle = valueFormatted.slice(-5, -2); - var end = valueFormatted.substr(valueFormatted.length - 2); - buildAmount(start, middle, end); - - } - break; - - default: // 2 - if (isNaN(parsed)) { - buildAmount('-' + getDecimalSeparator() + '--', '', ''); - } else { - valueProcessing = parseFloat(parsed.toFixed(2)); - valueFormatted = localizeNumbers(valueProcessing, 2); - buildAmount(valueFormatted, '', ''); - } - break; - } - $scope.canShow = true; - }; - - formatNumbers(); - $scope.$watchGroup(['currency', 'value'], function onFormattedAmountWatch() { - formatNumbers(); - }); - - /** - * On Android 4.4, toLocaleString() only returns 3 fractional digits when 8 is specified. - */ - function ensureEnoughFractionalDigits(localizedString, number, desiredFractionDigits) { - if (desiredFractionDigits === 0) { - // Assume it is OK - return localizedString; - } - var fractionalRe = /^(\d*\D)(\d+)$/; - var match = fractionalRe.exec(localizedString); - if (match.length !== 3) { - // Don't know what's happening, just return what we have - return localizedString; - } - - var decimals = match[2]; - var decimalCount = decimals.length; - if (decimalCount >= desiredFractionDigits) { - // Everything is OK. - return localizedString; - } - - if (typeof number !== 'number') { - number = parseFloat(number); - } - - var fixed = number.toFixed(desiredFractionDigits); - var fixedMatch = fractionalRe.exec(fixed); - if (fixedMatch.length !== 3) { - // Don't know what's happening, just return what we have - return localizedString; - } - - // Keeps locale decimal separator. - var enough = match[1] + fixedMatch[2]; - return enough; - } - }); + angular + .module('bitcoincom.directives') + .directive('formattedAmount', function() { + return { + restrict: 'E', + scope: { + value: '@', + currency: '@', + sizeEqual: '@' + }, + templateUrl: 'views/includes/formatted-amount.html', + controller: formattedAmountController } - }; + }); + + function formattedAmountController($scope, $timeout, uxLanguage) { + $scope.canShow = false; + + $scope.displaySizeEqual = !!$scope.sizeEqual; + + $timeout(function onFormattedAmountTimeout() { + + var decimalPlaces = { + '0': ['BIF', 'CLP', 'DJF', 'GNF', 'ILS', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'], + '3': ['BHD', 'IQD', 'JOD', 'KWD', 'OMR', 'TND'], + '8': ['BCH', 'BTC'] + }; + + formatNumbers(); + $scope.$watchGroup(['currency', 'value'], function onFormattedAmountWatch() { + formatNumbers(); + }); + + function buildAmount(start, middle, end) { + $scope.start = start; + $scope.middle = middle; + $scope.end = end; + }; + + /** + * On Android 4.4, toLocaleString() only returns 3 fractional digits when 8 is specified. + */ + function ensureEnoughFractionDigits(localizedString, number, desiredFractionDigits) { + if (desiredFractionDigits === 0) { + // Assume it is OK + return localizedString; + } + var fractionalRe = /^-*(\d*\D)(\d+)$/; + var match = fractionalRe.exec(localizedString); + if (!match || match.length !== 3) { + // Don't know what's happening, just return what we have + return localizedString; + } + + var decimals = match[2]; + var decimalCount = decimals.length; + if (decimalCount >= desiredFractionDigits) { + // Everything is OK. + return localizedString; + } + + if (typeof number !== 'number') { + number = parseFloat(number); + } + + var fixed = number.toFixed(desiredFractionDigits); + var fixedMatch = fractionalRe.exec(fixed); + if (!fixedMatch || fixedMatch.length !== 3) { + // Don't know what's happening, just return what we have + return localizedString; + } + + // Keeps locale decimal separator. + var enough = match[1] + fixedMatch[2]; + return enough; + } + + function formatNumbers() { + // During watch, may be changed from having a separate currency value, + // to both being in value. Don't want to use previous currency value. + // Try to extract currency from value.. + var currencySplit = $scope.value.split(" "); + if (currencySplit.length === 2) { + $scope.currency = currencySplit[1]; + } + $scope.currency = $scope.currency || ''; + + + var parsed = parseFloat($scope.value); + var valueFormatted = ''; + var valueProcessing = ''; + switch (getDecimalPlaces($scope.currency)) { + case '0': + if (isNaN(parsed)) { + buildAmount('-', '', ''); + } else { + valueFormatted = localizeNumbers(Math.round(parsed), 0); + buildAmount(valueFormatted, '', ''); + } + break; + + case '3': + if (isNaN(parsed)) { + buildAmount('-' + getDecimalSeparator() + '---', '', ''); + } else { + valueProcessing = parsed.toFixed(3); + valueFormatted = localizeNumbers(valueProcessing, 3); + buildAmount(valueFormatted, '', ''); + } + break; + + case '8': + if (isNaN(parsed)) { + buildAmount('-' + getDecimalSeparator() + '---', '', ''); + } else if (parsed === 0) { + buildAmount('0', '', ''); + } else { + valueFormatted = parsed.toFixed(8); + valueFormatted = localizeNumbers(valueFormatted, 8); + var start = valueFormatted.slice(0, -5); + var middle = valueFormatted.slice(-5, -2); + var end = valueFormatted.substr(valueFormatted.length - 2); + buildAmount(start, middle, end); + + } + break; + + default: // 2 + if (isNaN(parsed)) { + buildAmount('-' + getDecimalSeparator() + '--', '', ''); + } else { + valueProcessing = parseFloat(parsed.toFixed(2)); + valueFormatted = localizeNumbers(valueProcessing, 2); + buildAmount(valueFormatted, '', ''); + } + break; + } + $scope.canShow = true; + }; + + function getDecimalPlaces(currency) { + if (decimalPlaces['0'].indexOf(currency.toUpperCase()) > -1) return '0'; + if (decimalPlaces['3'].indexOf(currency.toUpperCase()) > -1) return '3'; + if (decimalPlaces['8'].indexOf(currency.toUpperCase()) > -1) return '8'; + return '2'; + }; + + function getDecimalSeparator() { + var testNum = 1.5; + var testString = testNum.toLocaleString(uxLanguage.getCurrentLanguage()); + // Some environments let you set decimal separators that are more than one character + var separator = /^1(.+)5$/.exec(testString)[1] + return separator; + }; + + function localizeNumbers(x, minimumFractionDigits) { + var parsed = parseFloat(x); + var opts = { + minimumFractionDigits: minimumFractionDigits, + useGrouping: true + }; + var lang = uxLanguage.getCurrentLanguage(); + var localized = parsed.toLocaleString(lang, opts); + var corrected = ensureEnoughFractionDigits(localized, x, minimumFractionDigits); + return corrected; + }; + + }); } -); \ No newline at end of file + +})(); \ No newline at end of file From ac8cdcf5be83bf28f87ed0f76b3308318cd92652 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 9 Aug 2018 18:59:03 +1200 Subject: [PATCH 191/240] Android 4.4 fix for Amount screen, removal of  . --- www/views/amount.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/views/amount.html b/www/views/amount.html index 57a8e46ce..bceda146f 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -37,14 +37,14 @@
- Available Funds: + Available Funds:
From 420305cf017555a82db7513feb1342500ea94e90 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 9 Aug 2018 10:15:55 +0200 Subject: [PATCH 192/240] shapeshift moved to default services and communities fix --- src/js/controllers/tab-home.js | 4 ++-- src/js/services/servicesService.js | 7 ++++++- src/js/services/shapeshiftService.js | 12 ------------ 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index 51d500de7..9a8c28b9a 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -1,7 +1,7 @@ 'use strict'; angular.module('copayApp.controllers').controller('tabHomeController', - function($rootScope, sendFlowService, $timeout, $scope, $state, $stateParams, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, bannerService, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, storageService, txpModalService, appConfigService, startupService, addressbookService, bwcError, nextStepsService, buyAndSellService, homeIntegrationsService, bitpayCardService, pushNotificationsService, timeService, $ionicNavBarDelegate) { + function($rootScope, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, bannerService, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, sendFlowService, storageService, txpModalService, appConfigService, startupService, addressbookService, bwcError, nextStepsService, buyAndSellService, homeIntegrationsService, bitpayCardService, pushNotificationsService, timeService, bitcoincomService, pricechartService, firebaseEventsService, servicesService, shapeshiftService, $ionicNavBarDelegate, signVerifyMessageService) { var wallet; var listeners = []; var notifications = []; @@ -58,7 +58,7 @@ angular.module('copayApp.controllers').controller('tabHomeController', }); } }; - + function onEnter(event, data) { $ionicNavBarDelegate.showBar(true); updateAllWallets(); diff --git a/src/js/services/servicesService.js b/src/js/services/servicesService.js index f2344dd01..316009957 100644 --- a/src/js/services/servicesService.js +++ b/src/js/services/servicesService.js @@ -1,7 +1,12 @@ 'use strict' angular.module('copayApp.services').factory('servicesService', function(configService, $log, lodash) { var root = {}; - var services = []; + var services = [{ + name: 'shapeshift', + title: 'Shapeshift', + icon: 'icon-shapeshift', + sref: 'tabs.shapeshift', + }]; root.register = function(serviceInfo) { $log.info('Adding Services entry:' + serviceInfo.name); diff --git a/src/js/services/shapeshiftService.js b/src/js/services/shapeshiftService.js index 7cf6b4e03..1ce9672ce 100644 --- a/src/js/services/shapeshiftService.js +++ b/src/js/services/shapeshiftService.js @@ -137,17 +137,5 @@ angular.module('copayApp.services').factory('shapeshiftService', function ($http }); }; - var servicesItem = { - name: 'shapeshift', - title: 'Shapeshift', - icon: 'icon-shapeshift', - sref: 'tabs.shapeshift', - }; - - var register = function() { - servicesService.register(servicesItem); - }; - - register(); return root; }); From 25965f626874c5e1c2cc6d0ece67795682e5e4f5 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 9 Aug 2018 10:17:29 +0200 Subject: [PATCH 193/240] shapeshift moved to default services and communities fix --- src/js/controllers/tab-home.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index 9a8c28b9a..9c4f2bee9 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -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, bannerService, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, sendFlowService, 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, communityService, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, sendFlowService, storageService, txpModalService, appConfigService, startupService, addressbookService, bwcError, nextStepsService, buyAndSellService, homeIntegrationsService, bitpayCardService, pushNotificationsService, timeService, bitcoincomService, pricechartService, firebaseEventsService, servicesService, shapeshiftService, $ionicNavBarDelegate, signVerifyMessageService) { var wallet; var listeners = []; var notifications = []; From ee0042b6e7c6efe5442aa3f3885cd4ae1be2c7f2 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 9 Aug 2018 13:39:27 +0200 Subject: [PATCH 194/240] sendMax bugfix when returning to previous screen --- src/js/controllers/amount.js | 14 ++++++++++---- src/js/controllers/review.controller.js | 2 +- www/views/amount.html | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 73ebfd150..8e458045e 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -29,6 +29,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, vm.finish = finish; vm.goBack = goBack; vm.loadMore = loadMore; + vm.next = next; vm.openPopup = openPopup; vm.pushDigit = pushDigit; vm.removeDigit = removeDigit; @@ -478,7 +479,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, // Currency - var next = 10; + var nextCurrencies = 10; var completeAlternativeList = []; var popularCurrencyList = [ @@ -537,12 +538,17 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, function loadMore() { $timeout(function() { - vm.altCurrencyList = completeAlternativeList.slice(0, next); - next += 10; + vm.altCurrencyList = completeAlternativeList.slice(0, nextCurrencies); + nextCurrencies += 10; vm.listComplete = vm.altCurrencyList.length >= completeAlternativeList.length; $scope.$broadcast('scroll.infiniteScrollComplete'); }, 100); - }; + } + + function next() { + useSendMax = false; + vm.finish(); + } function findCurrency(search) { if (!search) initCurrencies(); diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index a3b015348..afe3cd97d 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -223,7 +223,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit // Grab stateParams tx = { amount: parseInt(sendFlowData.amount), - sendMax: sendFlowData.sendMax === 'true' ? true : false, + sendMax: sendFlowData.sendMax, fromWalletId: sendFlowData.fromWalletId, toAddress: sendFlowData.toAddress, paypro: txPayproData, diff --git a/www/views/amount.html b/www/views/amount.html index bceda146f..8d263eae3 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -90,7 +90,7 @@ +
From afb3a1d49fe7115cd339f19e7e13da370c668333 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 10 Aug 2018 12:46:00 +1200 Subject: [PATCH 197/240] wallet-balance directive displaying basic crypto balance. --- src/js/directives/walletBalanceDirective.js | 88 +++++++++++++++++++++ src/sass/components/components.scss | 1 + src/sass/components/wallet-balance.scss | 3 + www/views/includes/wallet-balance.html | 3 + www/views/tab-send.html | 3 +- 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/js/directives/walletBalanceDirective.js create mode 100644 src/sass/components/wallet-balance.scss create mode 100644 www/views/includes/wallet-balance.html diff --git a/src/js/directives/walletBalanceDirective.js b/src/js/directives/walletBalanceDirective.js new file mode 100644 index 000000000..aaf9de99b --- /dev/null +++ b/src/js/directives/walletBalanceDirective.js @@ -0,0 +1,88 @@ +'use strict'; + +(function(){ + + angular + .module('bitcoincom.directives') + .directive('walletBalance', function() { + return { + restrict: 'E', + scope: { + displayAsFiat: '@', + totalBalanceSat: '@', + wallet: '@' + }, + templateUrl: 'views/includes/wallet-balance.html', + controller: walletBalanceController + } + }); + + function walletBalanceController($log, $scope, $timeout, uxLanguage) { + console.log('walletBalanceController'); + var cryptoBalanceHasBeenDisplayed = false; + + formatBalance(); + $scope.$watchGroup(['displayAsFiat', 'totalBalanceSat'], function onWalletBalanceWatch() { + formatBalance(); + }); + + function displayCryptoBalance(wallet) { + console.log('displayCryptoBalance()'); + if (wallet.status) { + if (wallet.status.totalBalanceStr) { + $scope.displayAmount = wallet.status.totalBalanceStr; + $scope.cachedBalanceUpdatedOn = ''; + console.log('Displaying wallet.status.totalBalanceStr'); + + } else if (wallet.status.cachedBalance) { + $scope.displayAmount = wallet.status.cachedBalance; + $scope.cachedBalanceUpdatedOn = wallet.status.cachedBalanceUpdatedOn; + console.log('Displaying wallet.status.cachedBalance'); + + } else { + $scope.displayAmount = ''; + $scope.cachedBalanceUpdatedOn = ''; + console.log('Displaying "" from status'); + } + } else if (wallet.cachedBalance) { + $scope.displayAmount = cachedBalance; + $scope.cachedBalanceUpdatedOn = ''; + console.log('Displaying cachedBalance'); + + } else { + $scope.displayAmount = ''; + $scope.cachedBalanceUpdatedOn = ''; + console.log('Displaying "" without status'); + } + cryptoBalanceHasBeenDisplayed = true; + } + + function displayFiatBalance(wallet) { + + } + + function formatBalance() { + //console.log('formatBalance() with wallet:', $scope.wallet,); + console.log('formatBalance() with displayAsFiat: "' + $scope.displayAsFiat + '"'); + var wallet = null; + try { + wallet = JSON.parse($scope.wallet); + } catch (e) { + $log.error('Error parsing wallet to display balance.', e); + $scope.displayAmount = ''; + $scope.cachedBalanceUpdatedOn = ''; + } + + if (!$scope.displayAsFiat || $scope.displayAsFiat && !cryptoBalanceHasBeenDisplayed) { + displayCryptoBalance(wallet); + } + + if ($scope.displayAsFiat) { + displayFiatBalance(wallet); + } + + + } + } +})(); + diff --git a/src/sass/components/components.scss b/src/sass/components/components.scss index fb53508b0..4d2bd695e 100644 --- a/src/sass/components/components.scss +++ b/src/sass/components/components.scss @@ -9,3 +9,4 @@ @import "expand-content"; @import "fee-summary"; @import "formatted-amount"; +@import "wallet-balance"; diff --git a/src/sass/components/wallet-balance.scss b/src/sass/components/wallet-balance.scss new file mode 100644 index 000000000..faf4e8611 --- /dev/null +++ b/src/sass/components/wallet-balance.scss @@ -0,0 +1,3 @@ +.wallet-balance-directive { + display: inline-block; +} \ No newline at end of file diff --git a/www/views/includes/wallet-balance.html b/www/views/includes/wallet-balance.html new file mode 100644 index 000000000..0cd1ee659 --- /dev/null +++ b/www/views/includes/wallet-balance.html @@ -0,0 +1,3 @@ +
+ {{displayAmount}}{{· (cachedBalanceUpdatedOn * 1000 | amTimeAgo)}} +
\ No newline at end of file diff --git a/www/views/tab-send.html b/www/views/tab-send.html index 3378b53ed..236c3db6f 100644 --- a/www/views/tab-send.html +++ b/www/views/tab-send.html @@ -16,8 +16,9 @@ >

{{fromWallet.name}}

+ - +
From 355ed27b87accb55bc22901366b57666649744ce Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 10 Aug 2018 13:04:44 +1200 Subject: [PATCH 198/240] wallet balance directive displaying fiat balance from wallet status. --- src/js/directives/walletBalanceDirective.js | 62 ++++++++++++--------- www/views/includes/wallet-balance.html | 2 +- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/js/directives/walletBalanceDirective.js b/src/js/directives/walletBalanceDirective.js index aaf9de99b..a13d30a98 100644 --- a/src/js/directives/walletBalanceDirective.js +++ b/src/js/directives/walletBalanceDirective.js @@ -28,37 +28,41 @@ function displayCryptoBalance(wallet) { console.log('displayCryptoBalance()'); - if (wallet.status) { - if (wallet.status.totalBalanceStr) { - $scope.displayAmount = wallet.status.totalBalanceStr; - $scope.cachedBalanceUpdatedOn = ''; - console.log('Displaying wallet.status.totalBalanceStr'); - } else if (wallet.status.cachedBalance) { - $scope.displayAmount = wallet.status.cachedBalance; - $scope.cachedBalanceUpdatedOn = wallet.status.cachedBalanceUpdatedOn; - console.log('Displaying wallet.status.cachedBalance'); - - } else { - $scope.displayAmount = ''; - $scope.cachedBalanceUpdatedOn = ''; - console.log('Displaying "" from status'); - } - } else if (wallet.cachedBalance) { - $scope.displayAmount = cachedBalance; - $scope.cachedBalanceUpdatedOn = ''; - console.log('Displaying cachedBalance'); - - } else { - $scope.displayAmount = ''; - $scope.cachedBalanceUpdatedOn = ''; - console.log('Displaying "" without status'); + if (wallet.status && wallet.status.totalBalanceStr) { + setDisplay(wallet.status.totalBalanceStr, ''); + cryptoBalanceHasBeenDisplayed = true; + return; } - cryptoBalanceHasBeenDisplayed = true; + + if (wallet.cachedBalance) { + setDisplay(wallet.cachedBalance, wallet.cachedBalanceUpdatedOn); + return; + } + + if (wallet.cachedStatus && wallet.cachedStatus.totalBalanceStr) { + setDisplay(wallet.cachedStatus.totalBalanceStr, ''); + return; + } + + setDisplay('', ''); } function displayFiatBalance(wallet) { - + var displayAmount = ''; + if (wallet.status && wallet.status.alternativeBalanceAvailable) { + displayAmount = wallet.status.totalBalanceAlternative + ' ' + wallet.status.alternativeIsoCode; + setDisplay(displayAmount, ''); + return; + } + + if (wallet.cachedStatus && wallet.cachedStatus.alternativeBalanceAvailable) { + displayAmount = wallet.cachedStatus.totalBalanceAlternative + ' ' + wallet.cachedStatus.alternativeIsoCode; + setDisplay(displayAmount, ''); + return; + } + + getFiatBalance(wallet); } function formatBalance() { @@ -80,8 +84,14 @@ if ($scope.displayAsFiat) { displayFiatBalance(wallet); } + } + function getFiatBalance(wallet) { + } + function setDisplay(amount, cachedBalanceUpdatedOn) { + $scope.displayAmount = amount; + $scope.cachedBalanceUpdatedOn = cachedBalanceUpdatedOn; } } })(); diff --git a/www/views/includes/wallet-balance.html b/www/views/includes/wallet-balance.html index 0cd1ee659..03baa8cac 100644 --- a/www/views/includes/wallet-balance.html +++ b/www/views/includes/wallet-balance.html @@ -1,3 +1,3 @@ -
+
{{displayAmount}}{{· (cachedBalanceUpdatedOn * 1000 | amTimeAgo)}}
\ No newline at end of file From d7285a72e46858c559e38180b118e93590ca07da Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 10 Aug 2018 13:26:12 +1200 Subject: [PATCH 199/240] wallet-balance directive retrieves alternative amount when needed. --- src/js/directives/walletBalanceDirective.js | 26 +++++++++++++-------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/js/directives/walletBalanceDirective.js b/src/js/directives/walletBalanceDirective.js index a13d30a98..d318b370d 100644 --- a/src/js/directives/walletBalanceDirective.js +++ b/src/js/directives/walletBalanceDirective.js @@ -17,8 +17,7 @@ } }); - function walletBalanceController($log, $scope, $timeout, uxLanguage) { - console.log('walletBalanceController'); + function walletBalanceController($log, $scope, txFormatService) { var cryptoBalanceHasBeenDisplayed = false; formatBalance(); @@ -29,7 +28,7 @@ function displayCryptoBalance(wallet) { console.log('displayCryptoBalance()'); - if (wallet.status && wallet.status.totalBalanceStr) { + if (wallet.status && wallet.status.isValid && wallet.status.totalBalanceStr) { setDisplay(wallet.status.totalBalanceStr, ''); cryptoBalanceHasBeenDisplayed = true; return; @@ -40,7 +39,7 @@ return; } - if (wallet.cachedStatus && wallet.cachedStatus.totalBalanceStr) { + if (wallet.cachedStatus && wallet.status.isValid && wallet.cachedStatus.totalBalanceStr) { setDisplay(wallet.cachedStatus.totalBalanceStr, ''); return; } @@ -50,13 +49,13 @@ function displayFiatBalance(wallet) { var displayAmount = ''; - if (wallet.status && wallet.status.alternativeBalanceAvailable) { + if (wallet.status && wallet.status.isValid && wallet.status.alternativeBalanceAvailable) { displayAmount = wallet.status.totalBalanceAlternative + ' ' + wallet.status.alternativeIsoCode; setDisplay(displayAmount, ''); return; } - if (wallet.cachedStatus && wallet.cachedStatus.alternativeBalanceAvailable) { + if (wallet.cachedStatus && wallet.cachedStatus.isValid && wallet.cachedStatus.alternativeBalanceAvailable) { displayAmount = wallet.cachedStatus.totalBalanceAlternative + ' ' + wallet.cachedStatus.alternativeIsoCode; setDisplay(displayAmount, ''); return; @@ -66,15 +65,12 @@ } function formatBalance() { - //console.log('formatBalance() with wallet:', $scope.wallet,); - console.log('formatBalance() with displayAsFiat: "' + $scope.displayAsFiat + '"'); var wallet = null; try { wallet = JSON.parse($scope.wallet); } catch (e) { $log.error('Error parsing wallet to display balance.', e); - $scope.displayAmount = ''; - $scope.cachedBalanceUpdatedOn = ''; + setDisplay('', ''); } if (!$scope.displayAsFiat || $scope.displayAsFiat && !cryptoBalanceHasBeenDisplayed) { @@ -87,6 +83,16 @@ } function getFiatBalance(wallet) { + if (!(wallet.status && wallet.status.isValid)) { + $log.warn('Abandoning call to get fiat balance, because no valid wallet status.'); + return; + } + + txFormatService.formatAlternativeStr(wallet.coin, wallet.status.totalBalanceSat, function onFormatAlernativeStr(formatted) { + if (formatted) { + setDisplay(formatted, ''); + } + }); } function setDisplay(amount, cachedBalanceUpdatedOn) { From 82ede65363e42463926eb655b65d12e6b3f82a10 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 10 Aug 2018 10:43:54 +0900 Subject: [PATCH 200/240] New decimal on the review screen --- www/views/review.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/views/review.html b/www/views/review.html index 1eb648a01..db6d279d4 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -11,8 +11,8 @@

{{vm.sendingTitle}}

-

{{vm.primaryAmount}} {{vm.primaryCurrency}}

-

{{vm.secondaryAmount}} {{vm.secondaryCurrency}}

+

+

From 82c0746ff808555e040f6e5b2a11eb5b7b63c57a Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 10 Aug 2018 13:48:59 +1200 Subject: [PATCH 201/240] wallet-balance directive uses formatted-amount. --- src/js/directives/walletBalanceDirective.js | 6 ++++-- www/views/includes/wallet-balance.html | 2 +- www/views/tab-send.html | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/js/directives/walletBalanceDirective.js b/src/js/directives/walletBalanceDirective.js index d318b370d..5b2a41b6c 100644 --- a/src/js/directives/walletBalanceDirective.js +++ b/src/js/directives/walletBalanceDirective.js @@ -65,19 +65,21 @@ } function formatBalance() { + var displayAsFiat = $scope.displayAsFiat === 'true'; var wallet = null; try { wallet = JSON.parse($scope.wallet); } catch (e) { $log.error('Error parsing wallet to display balance.', e); setDisplay('', ''); + return; } - if (!$scope.displayAsFiat || $scope.displayAsFiat && !cryptoBalanceHasBeenDisplayed) { + if (!displayAsFiat || displayAsFiat && !cryptoBalanceHasBeenDisplayed) { displayCryptoBalance(wallet); } - if ($scope.displayAsFiat) { + if (displayAsFiat) { displayFiatBalance(wallet); } } diff --git a/www/views/includes/wallet-balance.html b/www/views/includes/wallet-balance.html index 03baa8cac..0818bf343 100644 --- a/www/views/includes/wallet-balance.html +++ b/www/views/includes/wallet-balance.html @@ -1,3 +1,3 @@
- {{displayAmount}}{{· (cachedBalanceUpdatedOn * 1000 | amTimeAgo)}} + {{· (cachedBalanceUpdatedOn * 1000 | amTimeAgo)}}
\ No newline at end of file diff --git a/www/views/tab-send.html b/www/views/tab-send.html index 236c3db6f..a4051b8f1 100644 --- a/www/views/tab-send.html +++ b/www/views/tab-send.html @@ -16,7 +16,7 @@ >

{{fromWallet.name}}

- +
From b70bd804dc09c40b430daf9664da1d2ec359c7dc Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 10 Aug 2018 10:56:01 +0900 Subject: [PATCH 202/240] crypto decimal --- www/views/review.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/views/review.html b/www/views/review.html index db6d279d4..f2970da51 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -89,7 +89,7 @@
Fee: Less than 1 cent
Fee: {{vm.feeFiat}} {{vm.feeCurrency}}
- {{vm.feeCrypto}} {{vm.origin.currency}} +
From 4c9ff1b28b65323e285a8d5fbf1d702f18820e0f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 10 Aug 2018 11:11:53 +0900 Subject: [PATCH 203/240] Handle error in the review screen --- src/js/controllers/review.controller.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index afe3cd97d..d41d4a9b0 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -116,7 +116,9 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit if (!tx || !vm.originWallet) return; if (vm.paymentExpired) { - popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.')); + popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.', function () { + $ionicHistory.goBack(); + })); vm.sendStatus = ''; $timeout(function() { $scope.$apply(); @@ -486,20 +488,26 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) { if (err) { ongoingProcess.set('connectingShapeshift', false); - popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString()); + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { + $ionicHistory.goBack(); + }); return; } walletService.getAddress(toWallet, false, function onWithdrawalWalletAddress(err, withdrawalAddr) { if (err) { ongoingProcess.set('connectingShapeshift', false); - popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString()); + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { + $ionicHistory.goBack(); + }); return; } shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, function onShiftIt(err, shapeshiftData) { if (err && err != null) { ongoingProcess.set('connectingShapeshift', false); - popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString()); + popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString(), function () { + $ionicHistory.goBack(); + }); } else { vm.memo = 'ShapeShift Order:\nhttps://www.shapeshift.io/#/status/' + shapeshiftData.orderId; vm.memoExpanded = !!vm.memo; @@ -640,7 +648,9 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit $timeout(function() { $scope.$apply(); }); - popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg)); + popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg), function () { + $ionicHistory.goBack(); + }); }; function setupTx(tx) { @@ -825,7 +835,9 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit if (tx.sendMax && sendMaxInfo.amount == 0) { ongoingProcess.set('calculatingFee', false); setNotReady(gettextCatalog.getString('Insufficient confirmed funds')); - popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee')); + popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Not enough funds for fee'), function () { + $ionicHistory.goBack(); + }); return cb('no_funds'); } From 18c8f4cf07929d0cab601964ded35f7cf2ff78f5 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 10 Aug 2018 14:17:42 +1200 Subject: [PATCH 204/240] formatted-amount for transaction proposals and wallet activity. --- www/views/includes/txp.html | 2 +- www/views/includes/walletActivity.html | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/www/views/includes/txp.html b/www/views/includes/txp.html index 4a1f07aaf..82391bfa2 100644 --- a/www/views/includes/txp.html +++ b/www/views/includes/txp.html @@ -24,7 +24,7 @@ (possible double spend) - – {{tx.amountStr}} + –
diff --git a/www/views/includes/walletActivity.html b/www/views/includes/walletActivity.html index 3a2b78ab1..e1fdb504f 100644 --- a/www/views/includes/walletActivity.html +++ b/www/views/includes/walletActivity.html @@ -10,7 +10,7 @@
Payment Sent
- {{notification.amountStr}} +
@@ -19,7 +19,7 @@
Payment Received
- {{notification.amountStr}} +
@@ -27,7 +27,7 @@ Proposal Deleted: {{notification.message}}
- {{notification.amountStr}}: + :
@@ -35,7 +35,7 @@ Proposal Rejected: {{notification.message}}
- {{notification.amountStr}}: + :
@@ -43,7 +43,7 @@ New Proposal: {{notification.message}}
- {{notification.amountStr}} +
@@ -51,7 +51,7 @@ Proposal Accepted: {{notification.message}}
- {{notification.amountStr}} +
From 0410ad149109f7e458f11d6f1c2f81d3eacc2c35 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 10 Aug 2018 11:21:29 +0900 Subject: [PATCH 205/240] Add tracking shapeshift --- src/js/routes.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/js/routes.js b/src/js/routes.js index b5a9c91fe..d2b78aac3 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -1277,6 +1277,13 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr }); window.BitAnalytics.ActionHandlers.trackAction(actionTabOpen); + var actionShapeShiftStart = new window.BitAnalytics.ActionFactory.createAction('click', { + name: 'shapeshift_start_click', + class: 'track_shapeshift_start_click', + channels: [channel] + }); + window.BitAnalytics.ActionHandlers.trackAction(actionShapeShiftStart); + // Init language uxLanguage.init(function (lang) { From 4dd334087b6f84537c3e7d05a772acd5279a2df3 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 10 Aug 2018 14:24:41 +1200 Subject: [PATCH 206/240] Removed formatted amount from the entry of the Amount screen as it was causing a bug. --- www/views/amount.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/views/amount.html b/www/views/amount.html index 8d263eae3..ab56052ab 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -16,12 +16,12 @@
- + {{vm.amount || '0'}} {{vm.unit}}
- + {{vm.alternativeAmount || '0.00'}} {{vm.alternativeUnit}}
From 8d404f19081d93169d72632386ea087a42be2185 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 10 Aug 2018 16:19:24 +1200 Subject: [PATCH 207/240] Handle null wallet. --- src/js/directives/walletBalanceDirective.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/js/directives/walletBalanceDirective.js b/src/js/directives/walletBalanceDirective.js index 5b2a41b6c..4c42a42f9 100644 --- a/src/js/directives/walletBalanceDirective.js +++ b/src/js/directives/walletBalanceDirective.js @@ -66,6 +66,11 @@ function formatBalance() { var displayAsFiat = $scope.displayAsFiat === 'true'; + if (!$scope.wallet) { + setDisplay('', ''); + return; + } + var wallet = null; try { wallet = JSON.parse($scope.wallet); From 3ff0f50a7b5de1e49a89661268a37dccc2fbaaa3 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 10 Aug 2018 16:20:26 +1200 Subject: [PATCH 208/240] Added quotes. --- www/views/tab-send.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/views/tab-send.html b/www/views/tab-send.html index a4051b8f1..43918cac8 100644 --- a/www/views/tab-send.html +++ b/www/views/tab-send.html @@ -16,7 +16,7 @@ >

{{fromWallet.name}}

- +
From ee84235d3adf8e149bdf8769877359ce0e5c14b0 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 10 Aug 2018 14:05:46 +0900 Subject: [PATCH 209/240] Top bar message --- src/js/controllers/walletSelectorController.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index 74ed874d4..89d77cc3e 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -34,6 +34,9 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu } break; default: + if (!stateParams.thirdParty) { + $scope.sendFlowTitle = gettextCatalog.getString('Send'); + } // nop } From 812ed5f1cd772c6b03191d16c819f9dd814bc265 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 10 Aug 2018 17:13:24 +1200 Subject: [PATCH 210/240] wallet-balance workaround for wallet not being stringify-able, and hence not being interpolatable, and hence not being able to pass it to the directive. --- src/js/directives/walletBalanceDirective.js | 55 ++++++++++++--------- www/views/tab-send.html | 8 ++- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/js/directives/walletBalanceDirective.js b/src/js/directives/walletBalanceDirective.js index 4c42a42f9..1fea59203 100644 --- a/src/js/directives/walletBalanceDirective.js +++ b/src/js/directives/walletBalanceDirective.js @@ -10,7 +10,12 @@ scope: { displayAsFiat: '@', totalBalanceSat: '@', - wallet: '@' + // The Wallet object is sometimes not stringify()-able, so not interpolatable, + // so can't be passed to a directive. + walletStatus: '@', + walletCachedBalance: '@', + walletCachedBalanceUpdatedOn: '@', + walletCachedStatus: '@' }, templateUrl: 'views/includes/wallet-balance.html', controller: walletBalanceController @@ -25,38 +30,38 @@ formatBalance(); }); - function displayCryptoBalance(wallet) { + function displayCryptoBalance(walletStatus, walletCachedBalance, walletCachedBalanceUpdatedOn, walletCachedStatus) { console.log('displayCryptoBalance()'); - if (wallet.status && wallet.status.isValid && wallet.status.totalBalanceStr) { - setDisplay(wallet.status.totalBalanceStr, ''); + if (walletStatus && walletStatus.isValid && walletStatus.totalBalanceStr) { + setDisplay(walletStatus.totalBalanceStr, ''); cryptoBalanceHasBeenDisplayed = true; return; } - if (wallet.cachedBalance) { - setDisplay(wallet.cachedBalance, wallet.cachedBalanceUpdatedOn); + if (walletCachedBalance) { + setDisplay(walletCachedBalance, walletCachedBalanceUpdatedOn); return; } - if (wallet.cachedStatus && wallet.status.isValid && wallet.cachedStatus.totalBalanceStr) { - setDisplay(wallet.cachedStatus.totalBalanceStr, ''); + if (walletCachedStatus && walletCachedStatus.isValid && walletCachedStatus.totalBalanceStr) { + setDisplay(walletCachedStatus.totalBalanceStr, ''); return; } setDisplay('', ''); } - function displayFiatBalance(wallet) { + function displayFiatBalance(walletStatus, walletCachedStatus) { var displayAmount = ''; - if (wallet.status && wallet.status.isValid && wallet.status.alternativeBalanceAvailable) { - displayAmount = wallet.status.totalBalanceAlternative + ' ' + wallet.status.alternativeIsoCode; + if (walletStatus && walletStatus.isValid && walletStatus.alternativeBalanceAvailable) { + displayAmount = walletStatus.totalBalanceAlternative + ' ' + walletStatus.alternativeIsoCode; setDisplay(displayAmount, ''); return; } - if (wallet.cachedStatus && wallet.cachedStatus.isValid && wallet.cachedStatus.alternativeBalanceAvailable) { - displayAmount = wallet.cachedStatus.totalBalanceAlternative + ' ' + wallet.cachedStatus.alternativeIsoCode; + if (walletCachedStatus && walletCachedStatus.isValid && walletCachedStatus.alternativeBalanceAvailable) { + displayAmount = walletCachedStatus.totalBalanceAlternative + ' ' + walletCachedStatus.alternativeIsoCode; setDisplay(displayAmount, ''); return; } @@ -66,26 +71,30 @@ function formatBalance() { var displayAsFiat = $scope.displayAsFiat === 'true'; - if (!$scope.wallet) { - setDisplay('', ''); - return; + + var walletStatusObj = null; + var walletCachedBalance = null; + var walletCachedBalanceUpdatedOn = null; + var walletCachedStatusObj = null; + + try { + walletStatusObj = JSON.parse($scope.walletStatus); + } catch (e) { + $log.warn('Failed to parse walletStatus.', e); } - var wallet = null; try { - wallet = JSON.parse($scope.wallet); + walletCachedStatusObj = JSON.parse($scope.walletCachedStatus); } catch (e) { - $log.error('Error parsing wallet to display balance.', e); - setDisplay('', ''); - return; + $log.warn('Failed to parse walletCachedStatus.', e); } if (!displayAsFiat || displayAsFiat && !cryptoBalanceHasBeenDisplayed) { - displayCryptoBalance(wallet); + displayCryptoBalance(walletStatusObj, walletCachedBalance, walletCachedBalanceUpdatedOn, walletCachedStatusObj); } if (displayAsFiat) { - displayFiatBalance(wallet); + displayFiatBalance(walletStatusObj, walletCachedStatusObj); } } diff --git a/www/views/tab-send.html b/www/views/tab-send.html index 43918cac8..339ac3556 100644 --- a/www/views/tab-send.html +++ b/www/views/tab-send.html @@ -16,7 +16,13 @@ >

{{fromWallet.name}}

- +
From d88d3365ad746e976a8cb9aa48b86096f77b6ba5 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 10 Aug 2018 19:09:55 +1200 Subject: [PATCH 211/240] Fix for long amounts on iPhone 5 / SE. Fix for buttons below amounts on iPhone 5 / SE. --- src/sass/views/amount.scss | 60 +++++++++++++++++++++----------------- www/views/amount.html | 27 +++++++++-------- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/src/sass/views/amount.scss b/src/sass/views/amount.scss index ca32c6ac4..bf4d3506a 100644 --- a/src/sass/views/amount.scss +++ b/src/sass/views/amount.scss @@ -305,7 +305,8 @@ &.very-long { input, .unit, .primary-amount-display { - font-size: 0.9em; + font-size: 1.2em; // OK for iPhone 5 / SE with BCH to 8dp + @media (min-width: 375px) { font-size: 1.3em; @@ -382,41 +383,46 @@ .available-funds { color: #6F6F70; + text-align: left; + } + + .change-currency { + text-align: right; } .warning { color: $v-warning-color-2; } - .extra, - button.extra { - /*display: flex;*/ - flex: 0 1 auto; - } - - button.extra { - background: none; - border: none; - color: #000; - font-family: 'ProximaNova'; - font-size: 14px; + .extra { + flex: 1; line-height: normal; - min-height: auto; - min-width: auto; - padding: 0; - } - .button .icon:before { - font-size: 14px; - line-height: normal; - } - + .button { + background: none; + border: none; + border-radius: 0; + color: #000; + font-family: 'ProximaNova'; + font-size: 14px; + line-height: normal; + min-height: auto; + min-width: auto; + padding: 0; + } - .button { - span { - display: flex; - align-items: center; - justify-content: center; + .button .icon:before { + font-size: 14px; + line-height: normal; + } + + + .button { + span { + display: flex; + align-items: center; + justify-content: center; + } } } } diff --git a/www/views/amount.html b/www/views/amount.html index 8d263eae3..b69431b6b 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -14,14 +14,14 @@
+ ng-class="{long: vm.amount.length > 5, 'very-long': vm.amount.length > 8}"> - + {{vm.amount || '0'}} {{vm.unit}}
- + {{vm.alternativeAmount || '0.00'}} {{vm.alternativeUnit}}
@@ -34,17 +34,20 @@
- -
+ +
+
- Available Funds: + Available Funds: +
From 9e118350007d5231348c4e8d1dbe7ef7231bfd37 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 10 Aug 2018 19:45:43 +1200 Subject: [PATCH 212/240] Equal sized digits on Wallet Details alternative balance. --- www/views/walletDetails.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index 2480099b5..6e05862aa 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -46,7 +46,7 @@ class="size-14 amount-alternative" ng-if="status.totalBalanceStr && wallet.network == 'livenet'" ng-style="{opacity: altAmountOpacity}"> - +
From 9e28c4ec4539a32bfa33b90ef005b3bfc357a0ae Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 10 Aug 2018 17:20:04 +0900 Subject: [PATCH 213/240] Fix bug send max with thirdparty (Shapeshift) --- src/js/controllers/amount.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 8e458045e..00c9461f5 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -464,7 +464,7 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, }; if (vm.thirdParty) { - confirmData['thirdParty'] = this.thirdParty; + confirmData.thirdParty = vm.thirdParty; } sendFlowService.pushState(confirmData); From dd75f581e7d068d8e0582bf6509d8e72535addca Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Fri, 10 Aug 2018 10:47:16 +0200 Subject: [PATCH 214/240] move stateParams and fromWallet outside of the clipboar function --- src/js/controllers/tab-send.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/js/controllers/tab-send.js b/src/js/controllers/tab-send.js index 81b3e4ca5..3f1da01fc 100644 --- a/src/js/controllers/tab-send.js +++ b/src/js/controllers/tab-send.js @@ -28,14 +28,15 @@ angular.module('copayApp.controllers').controller('tabSendController', function( }; $scope.$on("$ionicView.enter", function(event, data) { + + var stateParams = sendFlowService.getState(); + $scope.fromWallet = profileService.getWallet(stateParams.fromWalletId); + clipboardService.readFromClipboard(function(text) { if (text.length > 200) { text = text.substring(0, 200); } - var stateParams = sendFlowService.getState(); - $scope.fromWallet = profileService.getWallet(stateParams.fromWalletId); - $scope.clipboardHasAddress = false; $scope.clipboardHasContent = false; if ((text.indexOf('bitcoincash:') === 0 || text[0] === 'C' || text[0] === 'H' || text[0] === 'p' || text[0] === 'q') && text.replace('bitcoincash:', '').length === 42) { // CashAddr From 3365a61d6234c72fcfdddd0dd6e434a97ad6e59f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 10 Aug 2018 17:47:21 +0900 Subject: [PATCH 215/240] invalidate cache --- src/js/controllers/tab-home.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index 9c4f2bee9..318fcece2 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -214,6 +214,7 @@ angular.module('copayApp.controllers').controller('tabHomeController', var j = 0; lodash.each(wallets, function(wallet) { + walletService.invalidateCache(wallet); // Temporary solution, to have the good balance, when we ask to reload the wallets. walletService.getStatus(wallet, {}, function(err, status) { if (err) { From 5185c6ee3ccb579064e29c8f42ad57d3320a9904 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 10 Aug 2018 18:41:47 +0900 Subject: [PATCH 216/240] Fix amount --- src/js/directives/formattedAmount.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index 1a05fd360..9f70b37ed 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -90,7 +90,7 @@ // to both being in value. Don't want to use previous currency value. // Try to extract currency from value.. var currencySplit = $scope.value.split(" "); - if (currencySplit.length === 2) { + if (currencySplit.length === 2 && !$scope.currency) { $scope.currency = currencySplit[1]; } $scope.currency = $scope.currency || ''; From daf9bb535653cedec9f5ea1c484d10fcf42ddf2e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 10 Aug 2018 18:43:28 +0900 Subject: [PATCH 217/240] Notify to reload home tab --- src/js/controllers/tab-receive.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/js/controllers/tab-receive.js b/src/js/controllers/tab-receive.js index 190f1b752..c822b4521 100644 --- a/src/js/controllers/tab-receive.js +++ b/src/js/controllers/tab-receive.js @@ -158,6 +158,10 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi soundService.play('misc/payment_received.mp3'); } + // Notify new tx + $scope.$emit('bwsEvent', $scope.wallet.id); + + $scope.$apply(function () { $scope.showingPaymentReceived = true; }); From 949cbc76979d6ec964594c97cd5630d6b647c879 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Fri, 10 Aug 2018 19:04:53 +0900 Subject: [PATCH 218/240] Cleaning code --- src/js/controllers/walletSelectorController.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index 89d77cc3e..ba5b4c418 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -17,7 +17,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu sendFlowService.popState(); } - var stateParams = sendFlowService.getState(); + $scope.params = sendFlowService.getState(); var config = configService.getSync().wallet.settings; priceDisplayAsFiat = config.priceDisplay === 'fiat'; @@ -29,18 +29,17 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu $scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer'); break; case 'tabs.send.destination': - if (stateParams.fromWalletId) { + if ($scope.params.fromWalletId) { $scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer'); } break; default: - if (!stateParams.thirdParty) { + if (!$scope.params.thirdParty) { $scope.sendFlowTitle = gettextCatalog.getString('Send'); } // nop } - $scope.params = sendFlowService; $scope.coin = false; // Wallets to show (for destination screen or contacts) $scope.type = $scope.params['fromWalletId'] ? 'destination' : 'origin'; // origin || destination fromWalletId = $scope.params['fromWalletId']; From e4c5da0d4414a00e4eace80139859322c57e2350 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Fri, 10 Aug 2018 12:07:24 +0200 Subject: [PATCH 219/240] thirdParty fix --- src/js/services/incomingData.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index 86c928a5e..4c2b71de8 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -447,7 +447,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat amount: thirdPartyData.amount, toAddress: thirdPartyData.toAddress, coin: coin, - thirdParty: JSON.stringify(thirdPartyData) + thirdParty: thirdPartyData }; // fee @@ -455,6 +455,8 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat stateParams.requiredFeeRate = thirdPartyData.requiredFeeRate * 1024; } + sendFlowService.pushState(thirdPartyData); + scannerService.pausePreview(); $state.go('tabs.send', {}, { 'reload': true, From f86ab3faecc9febf2b5de91c1001ddc85eb5c6f9 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Fri, 10 Aug 2018 12:42:28 +0200 Subject: [PATCH 220/240] revert code cleanup --- src/js/controllers/walletSelectorController.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index ba5b4c418..89d77cc3e 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -17,7 +17,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu sendFlowService.popState(); } - $scope.params = sendFlowService.getState(); + var stateParams = sendFlowService.getState(); var config = configService.getSync().wallet.settings; priceDisplayAsFiat = config.priceDisplay === 'fiat'; @@ -29,17 +29,18 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu $scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer'); break; case 'tabs.send.destination': - if ($scope.params.fromWalletId) { + if (stateParams.fromWalletId) { $scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer'); } break; default: - if (!$scope.params.thirdParty) { + if (!stateParams.thirdParty) { $scope.sendFlowTitle = gettextCatalog.getString('Send'); } // nop } + $scope.params = sendFlowService; $scope.coin = false; // Wallets to show (for destination screen or contacts) $scope.type = $scope.params['fromWalletId'] ? 'destination' : 'origin'; // origin || destination fromWalletId = $scope.params['fromWalletId']; From d28ba24e1c63500c1129c0e1aa2dfd5d297487d2 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Fri, 10 Aug 2018 15:10:44 +0200 Subject: [PATCH 221/240] receive specific amount fix to use sendFlowService --- src/js/controllers/tab-receive.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js/controllers/tab-receive.js b/src/js/controllers/tab-receive.js index c822b4521..0e80f4382 100644 --- a/src/js/controllers/tab-receive.js +++ b/src/js/controllers/tab-receive.js @@ -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, soundService, clipboardService) { +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, sendFlowService, txFormatService, soundService, clipboardService) { var listeners = []; $scope.bchAddressType = { type: 'cashaddr' }; @@ -18,9 +18,10 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi $scope.displayBalanceAsFiat = true; $scope.requestSpecificAmount = function() { - $state.go('tabs.paymentRequest.amount', { + sendFlowService.pushState({ toWalletId: $scope.wallet.credentials.walletId }); + $state.go('tabs.paymentRequest.amount'); }; $scope.setAddress = function(newAddr, copyAddress) { From 5445ddbecd62bc3585bc6c08883b02d865ce07d0 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 13 Aug 2018 10:00:39 +1200 Subject: [PATCH 222/240] Fix for wallet selection header when in Shapeshift flow. Also fixed a coin already present bug that occurred in subsequent Shapeshift flows. --- src/js/controllers/amount.js | 5 +-- src/js/controllers/review.controller.js | 4 +- src/js/controllers/tab-send.js | 8 ++-- .../controllers/walletSelectorController.js | 24 +++++----- src/js/services/incomingData.js | 5 ++- src/js/services/sendFlowService.js | 44 +++++++++++-------- 6 files changed, 47 insertions(+), 43 deletions(-) diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 00c9461f5..c2dcc6cd4 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -67,15 +67,14 @@ function amountController(configService, $filter, gettextCatalog, $ionicHistory, } function onBeforeEnter(event, data) { - console.log('amount onBeforeEnter sendflow ', sendFlowService.getState()); - if (data.direction == "back") { sendFlowService.popState(); } + console.log('amount onBeforeEnter after back sendflow ', sendFlowService.state); initCurrencies(); - passthroughParams = sendFlowService; + passthroughParams = sendFlowService.getStateClone(); vm.fromWalletId = passthroughParams.fromWalletId; vm.toWalletId = passthroughParams.toWalletId; diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index d41d4a9b0..3c528c1f9 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -75,9 +75,9 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit function onBeforeEnter(event, data) { - console.log('walletSelector onBeforeEnter sendflow ', sendFlowService.getState()); + console.log('walletSelector onBeforeEnter sendflow ', sendFlowService.state); defaults = configService.getDefaults(); - sendFlowData = sendFlowService.getState(); + sendFlowData = sendFlowService.getStateClone(); originWalletId = sendFlowData.fromWalletId; satoshis = parseInt(sendFlowData.amount, 10); toAddress = sendFlowData.toAddress; diff --git a/src/js/controllers/tab-send.js b/src/js/controllers/tab-send.js index 3f1da01fc..9ac6c35cb 100644 --- a/src/js/controllers/tab-send.js +++ b/src/js/controllers/tab-send.js @@ -29,7 +29,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function( $scope.$on("$ionicView.enter", function(event, data) { - var stateParams = sendFlowService.getState(); + var stateParams = sendFlowService.getStateClone(); $scope.fromWallet = profileService.getWallet(stateParams.fromWalletId); clipboardService.readFromClipboard(function(text) { @@ -184,7 +184,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function( $log.debug('Got toAddress:' + toAddress + ' | ' + item.name); - var stateParams = sendFlowService.getState(); + var stateParams = sendFlowService.getStateClone(); stateParams.toAddress = toAddress, stateParams.coin = item.coin; sendFlowService.pushState(stateParams); @@ -200,7 +200,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function( $scope.startWalletToWalletTransfer = function() { console.log('startWalletToWalletTransfer()'); - var params = sendFlowService.getState(); + var params = sendFlowService.getStateClone(); sendFlowService.pushState(params); $state.transitionTo('tabs.send.wallet-to-wallet', { fromWalletId: sendFlowService.fromWalletId @@ -222,7 +222,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function( $scope.$on("$ionicView.beforeEnter", function(event, data) { console.log(data); - console.log('tab-send onBeforeEnter sendflow ', sendFlowService.getState()); + console.log('tab-send onBeforeEnter sendflow ', sendFlowService.state); $scope.isIOS = platformInfo.isIOS && platformInfo.isCordova; $scope.showWalletsBch = $scope.showWalletsBtc = $scope.showWallets = false; diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index ba5b4c418..7a7c5a463 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -11,13 +11,12 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu $scope.$on("$ionicView.enter", onEnter); function onBeforeEnter(event, data) { - console.log('walletSelector onBeforeEnter sendflow', sendFlowService.getState()); - if (data.direction == "back") { sendFlowService.popState(); } + console.log('walletSelector onBeforeEnter after back sendflow', sendFlowService.state); - $scope.params = sendFlowService.getState(); + $scope.params = sendFlowService.getStateClone(); var config = configService.getSync().wallet.settings; priceDisplayAsFiat = config.priceDisplay === 'fiat'; @@ -29,7 +28,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu $scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer'); break; case 'tabs.send.destination': - if ($scope.params.fromWalletId) { + if ($scope.params.fromWalletId && !$scope.params.thirdParty) { $scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer'); } break; @@ -45,7 +44,7 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu fromWalletId = $scope.params['fromWalletId']; if ($scope.type === 'destination' && $scope.params.toAddress) { - $state.transitionTo(getNextStep()); + $state.transitionTo(getNextStep($scope.params)); } if ($scope.params.coin) { @@ -105,13 +104,10 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu } } - function getNextStep() { - if ($scope.thirdParty) { - $scope.params.thirdParty = $scope.thirdParty - } - if (!$scope.params.toWalletId && !$scope.params.toAddress) { // If we have no toAddress or fromWallet + function getNextStep(params) { + if (!params.toWalletId && !params.toAddress) { // If we have no toAddress or fromWallet return 'tabs.send.destination'; - } else if (!$scope.params.amount) { // If we have no amount + } else if (!params.amount) { // If we have no amount return 'tabs.send.amount'; } else { // If we do have them return 'tabs.send.review'; @@ -195,14 +191,16 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu $scope.useWallet = function(wallet) { - var params = sendFlowService.getState(); + var params = sendFlowService.getStateClone(); if ($scope.type === 'origin') { // we're on the origin screen, set wallet to send from params.fromWalletId = wallet.id; } else { // we're on the destination screen, set wallet to send to params.toWalletId = wallet.id; } sendFlowService.pushState(params); - $state.transitionTo(getNextStep(), $scope.params); + var nextStep = getNextStep(params); + console.log('walletSelector nextStep', nextStep); + $state.transitionTo(nextStep, $scope.params); }; $scope.goBack = function() { diff --git a/src/js/services/incomingData.js b/src/js/services/incomingData.js index 4c2b71de8..9fad8b0f5 100644 --- a/src/js/services/incomingData.js +++ b/src/js/services/incomingData.js @@ -82,7 +82,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat }); // Timeout is required to enable the "Back" button $timeout(function() { - var params = sendFlowService.getState(); + var params = sendFlowService.getStateClone(); if (amount) { params.amount = amount; @@ -455,7 +455,8 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat stateParams.requiredFeeRate = thirdPartyData.requiredFeeRate * 1024; } - sendFlowService.pushState(thirdPartyData); + // This does not make sense, thirdPartyData gets added by stateParams below + //sendFlowService.pushState(thirdPartyData); scannerService.pausePreview(); $state.go('tabs.send', {}, { diff --git a/src/js/services/sendFlowService.js b/src/js/services/sendFlowService.js index 29686b15b..fcb061c26 100644 --- a/src/js/services/sendFlowService.js +++ b/src/js/services/sendFlowService.js @@ -9,17 +9,21 @@ angular function sendFlowService($log) { var service = { - amount: '', - fromWalletId: '', - sendMax: false, - thirdParty: null, - toAddress: '', - toWalletId: '', + // A separate state variable so we can ensure it is cleared of everything, + // even other properties added that this service does not know about. (such as "coin") + state: { + amount: '', + fromWalletId: '', + sendMax: false, + thirdParty: null, + toAddress: '', + toWalletId: '' + }, previousStates: [], // Functions clear: clear, - getState: getState, + getStateClone: getStateClone, map: map, popState: popState, pushState: pushState, @@ -36,22 +40,24 @@ angular function clearCurrent() { console.log("sendFlow clearCurrent()"); - service.amount = ''; - service.fromWalletId = ''; - service.sendMax = false; - service.thirdParty = null; - service.toAddress = ''; - service.toWalletId = ''; + service.state = { + amount: '', + fromWalletId: '', + sendMax: false, + thirdParty: null, + toAddress: '', + toWalletId: '' + } } /** * Handy for debugging */ - function getState() { + function getStateClone() { var currentState = {}; - Object.keys(service).forEach(function forCurrentParam(key) { - if (typeof service[key] !== 'function' && key !== 'previousStates') { - currentState[key] = service[key]; + Object.keys(service.state).forEach(function forCurrentParam(key) { + if (typeof service.state[key] !== 'function' && key !== 'previousStates') { + currentState[key] = service.state[key]; } }); return currentState; @@ -68,7 +74,7 @@ angular function map(params) { Object.keys(params).forEach(function forNewParam(key) { - service[key] = params[key]; + service.state[key] = params[key]; }); }; @@ -85,7 +91,7 @@ angular function pushState(params) { console.log('sendFlow push'); - var currentParams = getState(); + var currentParams = getStateClone(); service.previousStates.push(currentParams); clearCurrent(); map(params); From 84fd483f1911b584bd4d8134028916ad79ee569a Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 13 Aug 2018 10:47:00 +1200 Subject: [PATCH 223/240] Fix for removing dark portion of Review Transaction screen below personal note. --- src/sass/views/review.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sass/views/review.scss b/src/sass/views/review.scss index 79bca1896..c530a1cef 100644 --- a/src/sass/views/review.scss +++ b/src/sass/views/review.scss @@ -1,5 +1,4 @@ #view-review { - background-color: #494949; slide-to-accept, slide-to-accept-success { margin-bottom: constant(safe-area-inset-bottom); /* iOS 11.0 */ From 80c9d682b59628c88b03b8034149e2eaa8ffff2d Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 13 Aug 2018 11:23:15 +1200 Subject: [PATCH 224/240] Fix in formatted-amount of display of subcent fees in transactions details page. --- src/js/directives/formattedAmount.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index 9f70b37ed..68bb50781 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -86,15 +86,24 @@ } function formatNumbers() { + console.log('formatNumbers() "' + $scope.value + '", "' + $scope.currency + '"'); + // Might get "< 0.01 USD" being passed in. // During watch, may be changed from having a separate currency value, // to both being in value. Don't want to use previous currency value. // Try to extract currency from value.. var currencySplit = $scope.value.split(" "); - if (currencySplit.length === 2 && !$scope.currency) { - $scope.currency = currencySplit[1]; + if (currencySplit.length >= 2 && !$scope.currency) { + $scope.currency = currencySplit[currencySplit.length - 1]; } $scope.currency = $scope.currency || ''; + // Redo this when we have proper formatting for low fees + if ($scope.value.indexOf("<") === 0) { + buildAmount($scope.value, '', ''); + $scope.currency = ''; + $scope.canShow = true; + return; + } var parsed = parseFloat($scope.value); var valueFormatted = ''; From 098d454b8ba5e467be1bb30d8f193cdbc15b19b6 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 13 Aug 2018 15:43:20 +1200 Subject: [PATCH 225/240] Fixed formatting of numbers in the thousands. --- src/js/directives/formattedAmount.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index 68bb50781..55051f07d 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -105,6 +105,9 @@ return; } + // Remove thousands separators for parseFloat() + $scope.value = $scope.value.replace(',', ''); + var parsed = parseFloat($scope.value); var valueFormatted = ''; var valueProcessing = ''; From a29a41b6472f996330121ad2ddc3b039cd27878d Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 13 Aug 2018 15:48:20 +1200 Subject: [PATCH 226/240] Removed console.log(). --- src/js/directives/formattedAmount.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index 55051f07d..f66a0ac78 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -86,7 +86,6 @@ } function formatNumbers() { - console.log('formatNumbers() "' + $scope.value + '", "' + $scope.currency + '"'); // Might get "< 0.01 USD" being passed in. // During watch, may be changed from having a separate currency value, // to both being in value. Don't want to use previous currency value. From 430f4644eb191dc240ae0a11add7ef79fecbfe0a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Mon, 13 Aug 2018 13:32:20 +0900 Subject: [PATCH 227/240] Fix formatted amount. --- src/js/directives/formattedAmount.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index f66a0ac78..1f51d33e3 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -90,11 +90,12 @@ // During watch, may be changed from having a separate currency value, // to both being in value. Don't want to use previous currency value. // Try to extract currency from value.. + $scope.currency = $scope.currency || ''; + var currencySplit = $scope.value.split(" "); - if (currencySplit.length >= 2 && !$scope.currency) { + if (currencySplit.length >= 2 && $scope.currency.length === 0) { $scope.currency = currencySplit[currencySplit.length - 1]; } - $scope.currency = $scope.currency || ''; // Redo this when we have proper formatting for low fees if ($scope.value.indexOf("<") === 0) { From 6de4f2ecda3fb91e251c79126953c49bd739f671 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 13 Aug 2018 16:45:46 +1200 Subject: [PATCH 228/240] Enlarged Wallet Details header to fit Receive and Send buttons without covering the unlocked balance. --- src/js/controllers/walletDetails.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index e6a8e2351..1aaef7581 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -304,6 +304,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun }; function refreshAmountSection(scrollPos) { + var AMOUNT_HEIGHT_BASE = 270; $scope.showBalanceButton = false; if ($scope.status) { $scope.showBalanceButton = ($scope.status.totalBalanceSat != $scope.status.spendableAmount); @@ -315,16 +316,16 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun } scrollPos = scrollPos || 0; - var amountHeight = 210 - scrollPos; + var amountHeight = AMOUNT_HEIGHT_BASE - scrollPos; if (amountHeight < 80) { amountHeight = 80; } var contentMargin = amountHeight; - if (contentMargin > 210) { - contentMargin = 210; + if (contentMargin > AMOUNT_HEIGHT_BASE) { + contentMargin = AMOUNT_HEIGHT_BASE; } - var amountScale = (amountHeight / 210); + var amountScale = (amountHeight / AMOUNT_HEIGHT_BASE); if (amountScale < 0.5) { amountScale = 0.5; } From 0b71daf04d6a3c7241a5c785843bd42488445c7b Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 13 Aug 2018 16:51:38 +1200 Subject: [PATCH 229/240] Removed previous fix as it is superceded by a better one. --- src/sass/views/walletDetails.scss | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/sass/views/walletDetails.scss b/src/sass/views/walletDetails.scss index 79326951e..858229d85 100644 --- a/src/sass/views/walletDetails.scss +++ b/src/sass/views/walletDetails.scss @@ -262,11 +262,6 @@ padding-top: 45px; } - .button-white-outline.button-outline.activated { - color: $v-primary-color; - border-color: white; - } - .item.item-footer { font-weight: lighter; } From 52e1b5e8116969ade33515e62ab5038da8045d41 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Mon, 13 Aug 2018 14:13:49 +0900 Subject: [PATCH 230/240] Manage the currency state --- src/js/directives/formattedAmount.js | 26 ++++++++++++++++-------- www/views/includes/formatted-amount.html | 2 +- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index 1f51d33e3..f81615593 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -26,9 +26,14 @@ } }); - function formattedAmountController($scope, $timeout, uxLanguage) { - $scope.canShow = false; + function formattedAmountController($scope, uxLanguage) { + $scope.vm = {}; + var vm = $scope.vm; + vm.currency = ''; + vm.value = ''; + + $scope.canShow = false $scope.displaySizeEqual = !!$scope.sizeEqual; var decimalPlaces = { @@ -90,17 +95,19 @@ // During watch, may be changed from having a separate currency value, // to both being in value. Don't want to use previous currency value. // Try to extract currency from value.. - $scope.currency = $scope.currency || ''; - - var currencySplit = $scope.value.split(" "); - if (currencySplit.length >= 2 && $scope.currency.length === 0) { - $scope.currency = currencySplit[currencySplit.length - 1]; + if (!$scope.currency || $scope.currency.length === 0) { + var currencySplit = $scope.value.split(" "); + if (currencySplit.length >= 2) { + vm.currency = currencySplit[currencySplit.length - 1]; + } + } else { + vm.currency = $scope.currency; } // Redo this when we have proper formatting for low fees if ($scope.value.indexOf("<") === 0) { buildAmount($scope.value, '', ''); - $scope.currency = ''; + vm.currency = ''; $scope.canShow = true; return; } @@ -111,7 +118,7 @@ var parsed = parseFloat($scope.value); var valueFormatted = ''; var valueProcessing = ''; - switch (getDecimalPlaces($scope.currency)) { + switch (getDecimalPlaces(vm.currency)) { case '0': if (isNaN(parsed)) { buildAmount('-', '', ''); @@ -158,6 +165,7 @@ break; } $scope.canShow = true; + $scope.$apply(); }; function getDecimalPlaces(currency) { diff --git a/www/views/includes/formatted-amount.html b/www/views/includes/formatted-amount.html index 24149849d..45451a718 100644 --- a/www/views/includes/formatted-amount.html +++ b/www/views/includes/formatted-amount.html @@ -1,4 +1,4 @@
- {{start}}{{middle}}{{end}}{{currency}} + {{start}}{{middle}}{{end}}{{vm.currency}}
\ No newline at end of file From c54cb55c51e4737a89e05ec92dfd0784a078319a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Mon, 13 Aug 2018 14:44:07 +0900 Subject: [PATCH 231/240] Custom amount crypto decimal --- www/views/customAmount.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/www/views/customAmount.html b/www/views/customAmount.html index b361a6da9..a7130a729 100644 --- a/www/views/customAmount.html +++ b/www/views/customAmount.html @@ -32,15 +32,15 @@
- {{amountUnitStr}} +
- {{altAmountStr | uppercase}} +
- {{altAmountStr | uppercase}} +
- {{amountUnitStr}} +
From 9c999d0cd6754e81bf06fda79e492dd1e7606815 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Mon, 13 Aug 2018 15:35:26 +0900 Subject: [PATCH 232/240] Update appConfig.json --- app-template/bitcoincom/appConfig.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app-template/bitcoincom/appConfig.json b/app-template/bitcoincom/appConfig.json index 084a586ce..4b3281f6e 100644 --- a/app-template/bitcoincom/appConfig.json +++ b/app-template/bitcoincom/appConfig.json @@ -24,9 +24,9 @@ "windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c", "pushSenderId": "1036948132229", "description": "A Secure Bitcoin Wallet", - "version": "4.13.2", - "fullVersion": "4.13-rc3", - "androidVersion": "413200", + "version": "5.0.0", + "fullVersion": "5.0-rc1", + "androidVersion": "500000", "_extraCSS": "", "_enabledExtensions": { "coinbase": false, From 30230304b695bdb76606849778f2ee7e625bf5fd Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Mon, 13 Aug 2018 16:13:29 +0900 Subject: [PATCH 233/240] Fix height header --- src/js/controllers/walletDetails.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 1aaef7581..ec787a5f4 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -304,10 +304,13 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun }; function refreshAmountSection(scrollPos) { - var AMOUNT_HEIGHT_BASE = 270; + var AMOUNT_HEIGHT_BASE = 210; $scope.showBalanceButton = false; if ($scope.status) { $scope.showBalanceButton = ($scope.status.totalBalanceSat != $scope.status.spendableAmount); + if ($scope.showBalanceButton) { + AMOUNT_HEIGHT_BASE = 270; + } } if (!$scope.amountIsCollapsible) { var t = ($scope.showBalanceButton ? 15 : 45); From 1dad3ab3fd3a3981658ca3537d151d4d9ac433be Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 13 Aug 2018 13:48:41 +0200 Subject: [PATCH 234/240] Many translation fixes. Missing translations in template.pot and added some in views --- i18n/po/template.pot | 40 ++++++++++++++++++++++--- src/js/controllers/review.controller.js | 1 + www/views/amount.html | 2 +- www/views/review.html | 14 ++++----- www/views/tab-send.html | 2 +- www/views/tx-details.html | 4 +-- 6 files changed, 48 insertions(+), 15 deletions(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index d8281988f..bdabbc318 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -72,6 +72,26 @@ msgstr "" msgid "Instant transactions with low fees" msgstr "" +#: www/views/walletSelector.html:49 +msgid "Insufficient funds" +msgstr "" + +#: www/views/amount.html:42 +msgid "Change Currency" +msgstr "" + +#: www/views/amount.html:49 +msgid "Available Funds" +msgstr "" + +#: www/views/amount.html:59 +msgid "Use All Available Funds" +msgstr "" + +#: www/views/amount.html:99 +msgid "Next" +msgstr "" + #: www/views/preferencesBitpayServices.html:23 msgid "Accounts" msgstr "" @@ -1287,6 +1307,7 @@ msgstr "" #: www/views/modals/txp-details.html:74 #: www/views/topup.html:34 #: www/views/tx-details.html:52 +#: www/views/review.html:22 msgid "From" msgstr "" @@ -3072,6 +3093,14 @@ msgstr "" msgid "This bitcoin payment request has expired." msgstr "" +#: www/views/review.html:55 +msgid "Payment expires:" +msgstr "" + +#: www/views/review.html:56 +msgid "Payment request has expired" +msgstr "" + #: www/views/join.html:133 #: www/views/tab-create-personal.html:103 #: www/views/tab-create-shared.html:132 @@ -3805,10 +3834,6 @@ msgstr "" msgid "You are shifting" msgstr "" -#: www/views/review.html:22 -msgid "From:" -msgstr "" - #: www/views/review.html:36 msgid "To:" msgstr "" @@ -3817,6 +3842,13 @@ msgstr "" msgid "Add personal note" msgstr "" +#: www/views/review.html:87 +msgid "Suggested by merchant:" +msgstr "" + +#: src/js/controllers/review.controller.js:37 +msgid "Enter text here" +msgstr "" #: www/views/review.html:57 msgid "Personal note:" diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 3c528c1f9..c1f120241 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -34,6 +34,7 @@ function reviewController(addressbookService, bitcoinCashJsService, bitcore, bit }; vm.originWallet = null; vm.paymentExpired = false; + vm.personalNotePlaceholder = gettextCatalog.getString('Enter text here'); vm.primaryAmount = ''; vm.primaryCurrency = ''; vm.usingMerchantFee = false; diff --git a/www/views/amount.html b/www/views/amount.html index b69431b6b..939937be8 100644 --- a/www/views/amount.html +++ b/www/views/amount.html @@ -46,7 +46,7 @@
- Available Funds: + Available Funds:
diff --git a/www/views/review.html b/www/views/review.html index f2970da51..50fcfbcac 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -18,7 +18,7 @@
-
From:
+
From:
@@ -52,7 +52,7 @@

{{vm.destination.name}}

-

Payment expires: {{vm.remainingTimeStr}}

+

Payment expires: {{vm.remainingTimeStr}}

Payment request has expired

@@ -67,14 +67,14 @@ ng-class="{ 'expand-content-revealed': vm.memoExpanded }" ng-click="vm.memoExpanded = !vm.memoExpanded"> - Add personal note + Add personal note
Personal Note:
- +
@@ -84,10 +84,10 @@
-
Suggested by merchant:
+
Suggested by merchant:
-
Fee: Less than 1 cent
-
Fee: {{vm.feeFiat}} {{vm.feeCurrency}}
+
Fee: Less than 1 cent
+
Fee: {{vm.feeFiat}} {{vm.feeCurrency}}
diff --git a/www/views/tab-send.html b/www/views/tab-send.html index 339ac3556..41f5c4451 100644 --- a/www/views/tab-send.html +++ b/www/views/tab-send.html @@ -7,7 +7,7 @@
-
From:
+
From:
diff --git a/www/views/tx-details.html b/www/views/tx-details.html index 467ce28e3..3a690aff9 100644 --- a/www/views/tx-details.html +++ b/www/views/tx-details.html @@ -24,9 +24,9 @@ Receiving
-
+
- + ... From f51542874b6213a433a929811e464ab2045b458b Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 13 Aug 2018 14:12:36 +0200 Subject: [PATCH 235/240] small fix: minus before alternative currency --- www/views/includes/walletHistory.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/views/includes/walletHistory.html b/www/views/includes/walletHistory.html index 296b35b1a..3be30c2b1 100644 --- a/www/views/includes/walletHistory.html +++ b/www/views/includes/walletHistory.html @@ -73,7 +73,7 @@
- +
From 14406f6bc4b295a919fd7a2a5ef8ce346a8edfa0 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 13 Aug 2018 15:28:22 +0200 Subject: [PATCH 236/240] template.pot fix --- i18n/po/template.pot | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index bdabbc318..277be03bb 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -1368,10 +1368,6 @@ msgid "Get news and updates from BitPay" msgstr "" #: www/views/onboarding/welcome.html:8 -msgctxt "button" -msgid "Get started" -msgstr "" - #: www/views/bitpayCard.html:49 msgid "Get started" msgstr "" @@ -2663,10 +2659,6 @@ msgstr "" msgid "Send Bitcoin faster!" msgstr "" -#: www/views/tab-send.html:46 -msgid "Send Bitcoin faster!" -msgstr "" - #: www/views/tab-send.html:50 msgid "Save frequently used addresses and send them Bitcoin in just one tap" msgstr "" @@ -2689,6 +2681,7 @@ msgstr "" #: www/views/tab-send.html:72 #: www/views/shapeshift.html:23 +#: www/views/tab-send.html:33 msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." msgstr "" @@ -3142,10 +3135,6 @@ msgstr "" msgid "To get started, buy bitcoin or share your address. You can receive bitcoin from any wallet or service." msgstr "" -#: www/views/tab-send.html:33 -msgid "To get started, you'll need to create a bitcoin wallet and get some bitcoin." -msgstr "" - #: src/js/services/bitpayAccountService.js:73 msgid "To {{reason}} you must first add your BitPay account - {{email}}" msgstr "" From 5123ba90070fb8e66c75a8c89cc0e1a8534c16b8 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 13 Aug 2018 15:33:48 +0200 Subject: [PATCH 237/240] template.pot fix --- i18n/po/template.pot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index 277be03bb..40d9ba501 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -3808,7 +3808,7 @@ msgid "Your Bitcoin Wallets are ready!" msgstr "" #: src/js/controllers/amount.js:49 -msgid "Address doesn\'t contain currency information, please make sure you are sending the correct currency." +msgid "Address doesn't contain currency information, please make sure you are sending the correct currency." msgstr "" #: www/views/review.html:4 From 587971724e3ed12a48c0d7b38381c50e0bc31520 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Mon, 13 Aug 2018 16:52:00 +0200 Subject: [PATCH 238/240] template.pot change --- i18n/po/template.pot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index 40d9ba501..060a35ff7 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -3808,7 +3808,7 @@ msgid "Your Bitcoin Wallets are ready!" msgstr "" #: src/js/controllers/amount.js:49 -msgid "Address doesn't contain currency information, please make sure you are sending the correct currency." +msgid "Address does not contain currency information, please make sure you are sending the correct currency." msgstr "" #: www/views/review.html:4 From 947c2d324f4103c34a0f7a03e2f3f642b1c746ac Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dominguez Date: Tue, 14 Aug 2018 11:38:24 +0900 Subject: [PATCH 239/240] Upgrade text --- i18n/po/template.pot | 2 +- src/js/controllers/walletSelectorController.js | 4 ++-- www/views/tab-send.html | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index d8281988f..725aa3de7 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -2631,7 +2631,7 @@ msgid "Paste Address" msgstr "" #: www/views/tab-send.html:27 -msgid "Wallet to Wallet Transfer" +msgid "Transfer between wallets" msgstr "" #: www/views/tab-send.html:35 diff --git a/src/js/controllers/walletSelectorController.js b/src/js/controllers/walletSelectorController.js index a4ff3ab3e..42506dace 100644 --- a/src/js/controllers/walletSelectorController.js +++ b/src/js/controllers/walletSelectorController.js @@ -15,11 +15,11 @@ angular.module('copayApp.controllers').controller('walletSelectorController', fu switch($state.current.name) { case 'tabs.send.wallet-to-wallet': - $scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer'); + $scope.sendFlowTitle = gettextCatalog.getString('Transfer between wallets'); break; case 'tabs.send.destination': if (data.stateParams.fromWalletId) { - $scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer'); + $scope.sendFlowTitle = gettextCatalog.getString('Transfer between wallets'); } break; default: diff --git a/www/views/tab-send.html b/www/views/tab-send.html index 20198c7f0..8f1fc1a93 100644 --- a/www/views/tab-send.html +++ b/www/views/tab-send.html @@ -24,7 +24,7 @@
From cf6658c83afa08e8028656e937640b66e79b50b7 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Tue, 14 Aug 2018 14:48:07 +1200 Subject: [PATCH 240/240] Removed $apply() inside $watch. --- src/js/directives/formattedAmount.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/js/directives/formattedAmount.js b/src/js/directives/formattedAmount.js index f81615593..5ee9be050 100644 --- a/src/js/directives/formattedAmount.js +++ b/src/js/directives/formattedAmount.js @@ -165,7 +165,6 @@ break; } $scope.canShow = true; - $scope.$apply(); }; function getDecimalPlaces(currency) {