From 8a0575d238690d7dec073b8adcbc120fe0923693 Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Wed, 18 Jul 2018 16:41:52 +0800 Subject: [PATCH 01/94] Add empty view and controller for review transaction route --- src/js/controllers/review.js | 5 + src/js/routes.js | 12 + www/css/main.css | 1162 ++++++++++++++++++---------------- www/index.html | 7 +- www/views/review.html | 45 ++ 5 files changed, 670 insertions(+), 561 deletions(-) create mode 100644 src/js/controllers/review.js create mode 100644 www/views/review.html diff --git a/src/js/controllers/review.js b/src/js/controllers/review.js new file mode 100644 index 000000000..1effff0ba --- /dev/null +++ b/src/js/controllers/review.js @@ -0,0 +1,5 @@ +'use strict'; + +angular.module('copayApp.controllers').controller('reviewController', 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) { + +}); diff --git a/src/js/routes.js b/src/js/routes.js index 8277314e5..79b88e2f7 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -316,6 +316,18 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } } }) + .state('tabs.send.review', { + url: '/review', + views: { + 'tab-send@tabs': { + controller: 'reviewController', + templateUrl: 'views/review.html' + } + }, + params: { + paypro: null + } + }) /* * diff --git a/www/css/main.css b/www/css/main.css index b4e67edac..a87bc4be9 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 { @@ -10071,10 +10071,12 @@ 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-white.button-standard, +.onboarding .button.button-green.button-standard, .onboarding .button.button-assertive.button-standard { width: 85%; max-width: 300px; @@ -10118,10 +10120,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 +10187,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 +10260,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; } @@ -10237,6 +10316,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 +10343,345 @@ 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: #494949; } + #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; - 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; - 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; - 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; - 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) { - #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) { - #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) { - #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; - 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; - font-size: 18px; - font-weight: lighter; - position: absolute; - bottom: 0; - width: 100%; - color: #667; } - @media (min-height: 667px) { - #view-amount .scroll-content .keypad-container .keypad { - 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-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 .scroll-content .keypad-container .keypad { - font-size: 12px; } } + #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; + 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; + 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; + 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) { + #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) { + #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) { + #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; + 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; + font-size: 18px; + font-weight: lighter; + position: absolute; + bottom: 0; + width: 100%; + color: #667; } + @media (min-height: 667px) { + #view-amount .scroll-content .keypad-container .keypad { + 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 { + font-size: 12px; } } + #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 +10701,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 +10814,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 +11039,154 @@ 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; } + #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 +11196,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 +11215,28 @@ 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); } } + .settings .icon-bitpay { background-image: url("../img/icon-bitpay.svg"); } @@ -11604,7 +11745,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 +11760,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 { @@ -12211,7 +12354,6 @@ a.item { position: relative; height: 70px; border-color: #fab915; - background-color: #fab915; padding-top: 20px; margin-bottom: 50px; text-align: center; } @@ -13013,74 +13155,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 +13170,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 +13202,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 +13220,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 +13840,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 +13902,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 +14199,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; } 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 @@ - + diff --git a/www/views/review.html b/www/views/review.html new file mode 100644 index 000000000..d68eca05b --- /dev/null +++ b/www/views/review.html @@ -0,0 +1,45 @@ + + + + {{'Review'|translate}} + + + + + + + Review + + + {{buttonText}} + + + {{buttonText}} + + + Payment Sent + Proposal Created + Transaction Created + + + + + + From 155ea281d8ede8cc8c619a339aa4d389684e5431 Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Wed, 18 Jul 2018 17:24:13 +0800 Subject: [PATCH 02/94] Adds header component --- src/sass/components/components.scss | 1 + src/sass/components/header.scss | 36 +++++++++++++++++++++++++++++ src/sass/main.scss | 1 + www/css/main.css | 24 +++++++++++++++++++ www/views/review.html | 8 ++++++- 5 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/sass/components/components.scss create mode 100644 src/sass/components/header.scss diff --git a/src/sass/components/components.scss b/src/sass/components/components.scss new file mode 100644 index 000000000..833f565dd --- /dev/null +++ b/src/sass/components/components.scss @@ -0,0 +1 @@ +@import "header"; diff --git a/src/sass/components/header.scss b/src/sass/components/header.scss new file mode 100644 index 000000000..1c178dd07 --- /dev/null +++ b/src/sass/components/header.scss @@ -0,0 +1,36 @@ +.header { + padding: 29px 12px 65px; + background-color: #FAB915; + color: #FFFFFF; + + .title { + font-size: 18px; + font-weight: 400; + line-height: 1em; + color: #FFFFFF; + text-align: center; + + + .content { + margin-top: 23px; + } + } + + .content { + text-align: center; + + p { + margin: 0; + line-height: 1em; + font-size: 18px; + + &.large { + font-size: 29px; + font-weight: 600; + } + + + p { + margin-top: 8px; + } + } + } +} \ No newline at end of file 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"; diff --git a/www/css/main.css b/www/css/main.css index a87bc4be9..2e2726888 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -15066,6 +15066,30 @@ log-options #check-bar .checkbox-icon { border-radius: 3px; display: inline-block; } +.header { + padding: 29px 12px 65px; + 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; } + /* This is for rules that don't yet have a home. * Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/ */ diff --git a/www/views/review.html b/www/views/review.html index d68eca05b..cc57c79b8 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -8,7 +8,13 @@ - Review +
+

You are sending

+
+

13.98 USD

+

0.014 BCH

+
+
Date: Wed, 18 Jul 2018 11:54:41 +0200 Subject: [PATCH 03/94] 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 46906352e5c1d4bab852192e14af7b2cbdc77c60 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 18 Jul 2018 16:47:15 +0200 Subject: [PATCH 04/94] 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 05/94] 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 a2f6277e7eaca60b0bb90139ccc35abefda4bf9c Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Thu, 19 Jul 2018 17:41:02 +0800 Subject: [PATCH 06/94] Added content-frame, item and ion-content components, added dummy wallet interfaces --- src/sass/components/components.scss | 3 ++ src/sass/components/content-frame.scss | 11 ++++++ src/sass/components/ion-content.scss | 13 ++++++ src/sass/components/item.scss | 39 ++++++++++++++++++ src/sass/icons.scss | 7 ++++ src/sass/views/review.scss | 8 ++++ src/sass/views/views.scss | 1 + www/css/main.css | 55 ++++++++++++++++++++++++++ www/views/review.html | 44 ++++++++++++++++++++- 9 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 src/sass/components/content-frame.scss create mode 100644 src/sass/components/ion-content.scss create mode 100644 src/sass/components/item.scss create mode 100644 src/sass/views/review.scss diff --git a/src/sass/components/components.scss b/src/sass/components/components.scss index 833f565dd..fa0d9008b 100644 --- a/src/sass/components/components.scss +++ b/src/sass/components/components.scss @@ -1 +1,4 @@ @import "header"; +@import "content-frame"; +@import "item"; +@import "ion-content"; diff --git a/src/sass/components/content-frame.scss b/src/sass/components/content-frame.scss new file mode 100644 index 000000000..1c6831271 --- /dev/null +++ b/src/sass/components/content-frame.scss @@ -0,0 +1,11 @@ +.content-frame { + &.negative-top { + margin-top: -20px; + + .card { + &:first-child { + margin-top: 0; + } + } + } +} \ No newline at end of file diff --git a/src/sass/components/ion-content.scss b/src/sass/components/ion-content.scss new file mode 100644 index 000000000..7be47c9ab --- /dev/null +++ b/src/sass/components/ion-content.scss @@ -0,0 +1,13 @@ +/* +* Extends Ionic v1 ion-content +*/ + +ion-content { + &.bg-neutral { + background-color: #F2F2F2; + } + + &.padded-bottom-cta { + bottom: 92px; + } +} \ No newline at end of file diff --git a/src/sass/components/item.scss b/src/sass/components/item.scss new file mode 100644 index 000000000..0d8ece804 --- /dev/null +++ b/src/sass/components/item.scss @@ -0,0 +1,39 @@ +/* +* Extends Ionic v1 item +*/ + +.item { + &.item-compact { + padding: 11px 13px; + } + &.item-gutterless { + padding: 0; + } + + .item-content { + &.item-content-compact { + min-height: 69px; + padding: 13px 11px 13px 68px; + + > img, + > 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%); + } + } + } + + .highlight { + color: #FAB915; + } + } +} \ No newline at end of file diff --git a/src/sass/icons.scss b/src/sass/icons.scss index 7d14f8886..ee270408f 100644 --- a/src/sass/icons.scss +++ b/src/sass/icons.scss @@ -88,6 +88,13 @@ background-image: url('../img/icon-faucet.svg'); background-size: 70%; } + + &.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; + } } } diff --git a/src/sass/views/review.scss b/src/sass/views/review.scss new file mode 100644 index 000000000..e1d4ebd07 --- /dev/null +++ b/src/sass/views/review.scss @@ -0,0 +1,8 @@ +#view-review { + background-color: #494949; + + slide-to-accept, slide-to-accept-success { + 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..1e54062f9 100644 --- a/src/sass/views/views.scss +++ b/src/sass/views/views.scss @@ -48,3 +48,4 @@ @import "includes/logOptions"; @import "includes/checkBar"; @import "cashScan"; +@import "review"; diff --git a/www/css/main.css b/www/css/main.css index 2e2726888..7b40c47c7 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -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 { @@ -15062,6 +15067,14 @@ 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 */ } + .gravatar { border-radius: 3px; display: inline-block; } @@ -15090,6 +15103,48 @@ log-options #check-bar .checkbox-icon { .header .content p + p { margin-top: 8px; } +.content-frame.negative-top { + margin-top: -20px; } + .content-frame.negative-top .card:first-child { + margin-top: 0; } + +/* +* Extends Ionic v1 item +*/ +.item.item-compact { + padding: 11px 13px; } + +.item.item-gutterless { + padding: 0; } + +.item .item-content.item-content-compact { + min-height: 69px; + padding: 13px 11px 13px 68px; } + .item .item-content.item-content-compact > img:first-child, + .item .item-content.item-content-compact > 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 .highlight { + color: #FAB915; } + +/* +* Extends Ionic v1 ion-content +*/ +ion-content.bg-neutral { + background-color: #F2F2F2; } + +ion-content.padded-bottom-cta { + bottom: 92px; } + /* This is for rules that don't yet have a home. * Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/ */ diff --git a/www/views/review.html b/www/views/review.html index cc57c79b8..563ddf6d6 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -1,4 +1,4 @@ - + {{'Review'|translate}} @@ -7,7 +7,7 @@ - +

You are sending

@@ -15,6 +15,46 @@

0.014 BCH

+ +
+
+
From:
+
+
+ +
+
+

Personal Wallet (BCH)

+

128.67

+
+
+
+
+
From:
+
+
+ +
+
+

Personal Wallet (BTC)

+

128.67

+
+
+
+
+
To:
+
+
+ +

Satoshi Nakamoto

+

128.67

+
+
+
+
Date: Thu, 19 Jul 2018 20:21:58 +0800 Subject: [PATCH 07/94] Adds address component --- src/sass/components/address.scss | 27 ++++++++++++++++++++++++ src/sass/components/components.scss | 1 + src/sass/components/item.scss | 13 ++++++++++-- www/css/main.css | 32 ++++++++++++++++++++++++++--- www/views/review.html | 9 +++++--- 5 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 src/sass/components/address.scss diff --git a/src/sass/components/address.scss b/src/sass/components/address.scss new file mode 100644 index 000000000..2848deb82 --- /dev/null +++ b/src/sass/components/address.scss @@ -0,0 +1,27 @@ +.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; + + &.expanded { + white-space: pre-wrap; + word-break: break-all; + } + + .prefix { + color: #000000; + } + + .mid { + color: #919191; + } + + .suffix { + color: #000000; + } +} \ No newline at end of file diff --git a/src/sass/components/components.scss b/src/sass/components/components.scss index fa0d9008b..b547defad 100644 --- a/src/sass/components/components.scss +++ b/src/sass/components/components.scss @@ -2,3 +2,4 @@ @import "content-frame"; @import "item"; @import "ion-content"; +@import "address"; diff --git a/src/sass/components/item.scss b/src/sass/components/item.scss index 0d8ece804..bb75ae8e0 100644 --- a/src/sass/components/item.scss +++ b/src/sass/components/item.scss @@ -11,7 +11,7 @@ } .item-content { - &.item-content-compact { + &.item-content-avatar { min-height: 69px; padding: 13px 11px 13px 68px; @@ -32,8 +32,17 @@ } } + &.item-content-compact { + min-height: 0; + padding: 13px 11px; + } + .highlight { - color: #FAB915; + color: #FAB915 + } + + + .item-content { + padding-top: 0; } } } \ No newline at end of file diff --git a/www/css/main.css b/www/css/main.css index 7b40c47c7..4f9410e68 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -15117,11 +15117,11 @@ log-options #check-bar .checkbox-icon { .item.item-gutterless { padding: 0; } -.item .item-content.item-content-compact { +.item .item-content.item-content-avatar { min-height: 69px; padding: 13px 11px 13px 68px; } - .item .item-content.item-content-compact > img:first-child, - .item .item-content.item-content-compact > i:first-child { + .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; @@ -15133,9 +15133,16 @@ log-options #check-bar .checkbox-icon { 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 */ @@ -15145,6 +15152,25 @@ ion-content.bg-neutral { ion-content.padded-bottom-cta { bottom: 92px; } +.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; } + /* This is for rules that don't yet have a home. * Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/ */ diff --git a/www/views/review.html b/www/views/review.html index 563ddf6d6..9df212be9 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -20,7 +20,7 @@
From:
-
+
From:
-
+
@@ -47,11 +47,14 @@
To:
-
+

Satoshi Nakamoto

128.67

+
+
qz9cqq5pryv9hnqwa8q8mccmynk9uf4vlu5nxerpzmc
+
From c3cded5cb06ca88e32b50d5b33dbf583e450fbba Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 19 Jul 2018 15:50:09 +0200 Subject: [PATCH 08/94] 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 da853bcbf685f0bcec66a108bea5b2ecf3772730 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 20 Jul 2018 18:15:25 +1200 Subject: [PATCH 09/94] 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 6efd338f87c94913ad5c745aba5eb6108ed1f1be Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Fri, 20 Jul 2018 15:56:39 +0800 Subject: [PATCH 10/94] Adds action-minor component --- src/sass/components/action-minor.scss | 23 +++++++++++++++++++++++ src/sass/components/components.scss | 1 + www/css/main.css | 15 +++++++++++++++ www/img/icon-bookmark.svg | 3 +++ www/views/review.html | 4 ++++ 5 files changed, 46 insertions(+) create mode 100644 src/sass/components/action-minor.scss create mode 100644 www/img/icon-bookmark.svg diff --git a/src/sass/components/action-minor.scss b/src/sass/components/action-minor.scss new file mode 100644 index 000000000..74fbe5639 --- /dev/null +++ b/src/sass/components/action-minor.scss @@ -0,0 +1,23 @@ +.action-minor { + margin: 20px 14px; + font-size: 14px; + + &.mt-negative { + margin-top: -10px; + } + + &.right { + text-align: right; + } + + > .action-icon { + width: 15px; + height: 15px; + vertical-align: middle; + margin-right: 3px; + } + + > .action-text { + vertical-align: middle; + } +} \ No newline at end of file diff --git a/src/sass/components/components.scss b/src/sass/components/components.scss index b547defad..c0224e56f 100644 --- a/src/sass/components/components.scss +++ b/src/sass/components/components.scss @@ -3,3 +3,4 @@ @import "item"; @import "ion-content"; @import "address"; +@import "action-minor"; diff --git a/www/css/main.css b/www/css/main.css index 4f9410e68..b34db534e 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -15171,6 +15171,21 @@ ion-content.padded-bottom-cta { .address .suffix { color: #000000; } +.action-minor { + margin: 20px 14px; + font-size: 14px; } + .action-minor.mt-negative { + margin-top: -10px; } + .action-minor.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; } + /* This is for rules that don't yet have a home. * Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/ */ diff --git a/www/img/icon-bookmark.svg b/www/img/icon-bookmark.svg new file mode 100644 index 000000000..b1ad892fd --- /dev/null +++ b/www/img/icon-bookmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/www/views/review.html b/www/views/review.html index 9df212be9..2bef9a95e 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -57,6 +57,10 @@
+
+ + Add a personal note +
Date: Mon, 23 Jul 2018 14:20:46 +0200 Subject: [PATCH 11/94] "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 12/94] 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 13/94] 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 4b18e4b1c3ac8fce0ec76a7b922880cfeb8fb381 Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Tue, 24 Jul 2018 15:27:21 +0800 Subject: [PATCH 14/94] Adds expand-content component, update various UI measurements --- src/sass/components/action-minor.scss | 5 +- src/sass/components/card.scss | 5 ++ src/sass/components/components.scss | 7 +- src/sass/components/content-frame.scss | 2 +- src/sass/components/expand-content.scss | 26 ++++++++ src/sass/components/header.scss | 2 +- src/sass/directives/directives.scss | 1 + src/sass/directives/elastic.scss | 4 ++ www/css/main.css | 88 ++++++++++++++++--------- www/img/icon-bookmark.svg | 10 ++- www/views/review.html | 42 ++++++------ 11 files changed, 133 insertions(+), 59 deletions(-) create mode 100644 src/sass/components/card.scss create mode 100644 src/sass/components/expand-content.scss create mode 100644 src/sass/directives/elastic.scss diff --git a/src/sass/components/action-minor.scss b/src/sass/components/action-minor.scss index 74fbe5639..f158fe845 100644 --- a/src/sass/components/action-minor.scss +++ b/src/sass/components/action-minor.scss @@ -3,10 +3,10 @@ font-size: 14px; &.mt-negative { - margin-top: -10px; + margin-top: 0; } - &.right { + &.text-right { text-align: right; } @@ -19,5 +19,6 @@ > .action-text { vertical-align: middle; + color: #444444; } } \ No newline at end of file diff --git a/src/sass/components/card.scss b/src/sass/components/card.scss new file mode 100644 index 000000000..6df235ab8 --- /dev/null +++ b/src/sass/components/card.scss @@ -0,0 +1,5 @@ +.card { + &.card-gutter-compact { + margin: 10px 12px; + } +} \ No newline at end of file diff --git a/src/sass/components/components.scss b/src/sass/components/components.scss index c0224e56f..180279125 100644 --- a/src/sass/components/components.scss +++ b/src/sass/components/components.scss @@ -1,6 +1,9 @@ -@import "header"; -@import "content-frame"; @import "item"; @import "ion-content"; +@import "card"; + +@import "header"; +@import "content-frame"; @import "address"; @import "action-minor"; +@import "expand-content"; diff --git a/src/sass/components/content-frame.scss b/src/sass/components/content-frame.scss index 1c6831271..5766b246b 100644 --- a/src/sass/components/content-frame.scss +++ b/src/sass/components/content-frame.scss @@ -1,6 +1,6 @@ .content-frame { &.negative-top { - margin-top: -20px; + margin-top: -40px; .card { &:first-child { diff --git a/src/sass/components/expand-content.scss b/src/sass/components/expand-content.scss new file mode 100644 index 000000000..934a2beec --- /dev/null +++ b/src/sass/components/expand-content.scss @@ -0,0 +1,26 @@ +.expand-content-frame { + position: relative; + + .expand-content-trigger { + position: absolute; + top: 0; + transition: opacity 0.3s ease; + right: 0; + + &.expand-content-revealed { + opacity: 0; + } + } + + .expand-content { + opacity: 0; + transform-origin: 100% 0%; + transform: scale(0,0); + transition: opacity 0.3s ease, transform 0.3s ease; + + &.expand-content-revealed { + opacity: 1; + transform: scale(1,1); + } + } +} \ No newline at end of file diff --git a/src/sass/components/header.scss b/src/sass/components/header.scss index 1c178dd07..fad1f1812 100644 --- a/src/sass/components/header.scss +++ b/src/sass/components/header.scss @@ -1,5 +1,5 @@ .header { - padding: 29px 12px 65px; + padding: 29px 12px 61px; background-color: #FAB915; color: #FFFFFF; diff --git a/src/sass/directives/directives.scss b/src/sass/directives/directives.scss index 9159d3f23..954b86c3a 100644 --- a/src/sass/directives/directives.scss +++ b/src/sass/directives/directives.scss @@ -1 +1,2 @@ @import "gravatar"; +@import "elastic"; \ No newline at end of file diff --git a/src/sass/directives/elastic.scss b/src/sass/directives/elastic.scss new file mode 100644 index 000000000..8e8aba4fa --- /dev/null +++ b/src/sass/directives/elastic.scss @@ -0,0 +1,4 @@ +.elastic { + width: 100%; + font-size: 14px; +} \ No newline at end of file diff --git a/www/css/main.css b/www/css/main.css index b34db534e..df8779400 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -15079,34 +15079,9 @@ log-options #check-bar .checkbox-icon { border-radius: 3px; display: inline-block; } -.header { - padding: 29px 12px 65px; - 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: -20px; } - .content-frame.negative-top .card:first-child { - margin-top: 0; } +.elastic { + width: 100%; + font-size: 14px; } /* * Extends Ionic v1 item @@ -15152,6 +15127,38 @@ ion-content.bg-neutral { ion-content.padded-bottom-cta { bottom: 92px; } +.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; @@ -15175,8 +15182,8 @@ ion-content.padded-bottom-cta { margin: 20px 14px; font-size: 14px; } .action-minor.mt-negative { - margin-top: -10px; } - .action-minor.right { + margin-top: 0; } + .action-minor.text-right { text-align: right; } .action-minor > .action-icon { width: 15px; @@ -15184,7 +15191,26 @@ ion-content.padded-bottom-cta { vertical-align: middle; margin-right: 3px; } .action-minor > .action-text { - vertical-align: middle; } + 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); } /* This is for rules that don't yet have a home. * Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/ diff --git a/www/img/icon-bookmark.svg b/www/img/icon-bookmark.svg index b1ad892fd..5db1f9047 100644 --- a/www/img/icon-bookmark.svg +++ b/www/img/icon-bookmark.svg @@ -1,3 +1,11 @@ - + + + + diff --git a/www/views/review.html b/www/views/review.html index 2bef9a95e..9de1a403e 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -7,9 +7,9 @@ - +
-

You are sending

13.98 USD

0.014 BCH

@@ -17,7 +17,7 @@
-
+
From:
@@ -31,21 +31,8 @@
-
-
From:
-
-
- -
-
-

Personal Wallet (BTC)

-

128.67

-
-
-
-
-
To:
+
+
To:
@@ -57,9 +44,22 @@
-
- - Add a personal note +
+
+ + Add a personal note +
+
+
Personal Note:
+
+
+ +
+
+
From fd4adbfb57be5d678a5319c9e17f4e2c75b771b4 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Tue, 24 Jul 2018 14:24:22 +0200 Subject: [PATCH 15/94] 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 4093e2ba71dff9800d24d3fcdff5980bb58c5dcf Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 25 Jul 2018 11:26:33 +0200 Subject: [PATCH 16/94] 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 17/94] 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 79d6b4d7adbd808fd99609e5d8c8e9bbce2ab7bc Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 25 Jul 2018 16:05:07 +0200 Subject: [PATCH 18/94] confirm screen: display address + back button logic in origin/destination screens --- src/js/controllers/confirm.js | 10 ++++++++-- src/js/controllers/sendFlowController.js | 7 ++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/js/controllers/confirm.js b/src/js/controllers/confirm.js index b57d2ca08..36035d627 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, 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, bitcoinCashJsService, bwcError, txConfirmNotification, externalLinkService, firebaseEventsService, soundService) { var countDown = null; var FEE_TOO_HIGH_LIMIT_PER = 15; @@ -173,6 +173,7 @@ angular.module('copayApp.controllers').controller('confirmController', function( setupTx(tx); } } catch (e) { + console.log(e); var message = gettextCatalog.getString('Invalid address'); popupService.showAlert(null, message, function () { $ionicHistory.nextViewOptions({ @@ -187,7 +188,12 @@ angular.module('copayApp.controllers').controller('confirmController', function( } }); - var setupTx = function(networkName, data) { + var setupTx = function(tx) { + if (tx.coin === 'bch') { + tx.displayAddress = bitcoinCashJsService.readAddress(tx.toAddress).cashaddr; + } else { + tx.displayAddress = entry.address; + } // Other Scope vars $scope.isCordova = isCordova; diff --git a/src/js/controllers/sendFlowController.js b/src/js/controllers/sendFlowController.js index 400751867..a4d9d6537 100644 --- a/src/js/controllers/sendFlowController.js +++ b/src/js/controllers/sendFlowController.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('sendFlowController', function($scope, $rootScope, $state, $stateParams, $log, configService, gettextCatalog, profileService) { +angular.module('copayApp.controllers').controller('sendFlowController', function($scope, $rootScope, $state, $stateParams, $log, $ionicHistory, configService, gettextCatalog, profileService) { $scope.$on("$ionicView.beforeEnter", function(event, data) { var config = configService.getSync().wallet.settings; @@ -60,4 +60,9 @@ angular.module('copayApp.controllers').controller('sendFlowController', function } $state.transitionTo(getNextStep(), $scope.params); }; + + $scope.goBack = function() { + $ionicHistory.goBack(); + } + }); \ No newline at end of file From aff0dd31831f62cf00fd49b2465d3680c2039aa9 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 25 Jul 2018 17:25:38 +0200 Subject: [PATCH 19/94] fixes for other cases, address to address + contacts + thirdParty stub --- src/js/controllers/confirm.js | 38 +++++++++++- src/js/controllers/sendFlowController.js | 28 ++++++--- src/js/controllers/tab-send.js | 76 +++++------------------- src/js/routes.js | 4 +- www/views/tab-send.html | 2 +- www/views/wallet-origin-destination.html | 6 +- 6 files changed, 77 insertions(+), 77 deletions(-) diff --git a/src/js/controllers/confirm.js b/src/js/controllers/confirm.js index 36035d627..762bfe42b 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, 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) { +angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, $ionicLoading, 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) { var countDown = null; var FEE_TOO_HIGH_LIMIT_PER = 15; @@ -116,9 +116,36 @@ angular.module('copayApp.controllers').controller('confirmController', function( }); }); }; + + $scope.getContacts = function(addr) { + addressbookService.list(function(err, ab) { + if (err) $log.error(err); + + $scope.hasContacts = lodash.isEmpty(ab) ? false : true; + if (!$scope.hasContacts) return cb(); + + var completeContacts = []; + lodash.each(ab, function(v, k) { + completeContacts.push({ + name: lodash.isObject(v) ? v.name : v, + address: k, + email: lodash.isObject(v) ? v.email : null, + recipientType: 'contact', + coin: v.coin, + displayCoin: (v.coin == 'bch' + ? (config.bitcoinCashAlias || defaults.bitcoinCashAlias) + : (config.bitcoinAlias || defaults.bitcoinAlias)).toUpperCase() + }); + }); + + return cb(); + }); + } + $scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.fromWallet = profileService.getWallet(data.stateParams.fromWalletId); // Wallet to send from + // Grab stateParams tx = { amount: parseInt(data.stateParams.amount), @@ -173,7 +200,6 @@ angular.module('copayApp.controllers').controller('confirmController', function( setupTx(tx); } } catch (e) { - console.log(e); var message = gettextCatalog.getString('Invalid address'); popupService.showAlert(null, message, function () { $ionicHistory.nextViewOptions({ @@ -195,6 +221,14 @@ angular.module('copayApp.controllers').controller('confirmController', function( 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 $scope.isCordova = isCordova; $scope.showAddress = false; diff --git a/src/js/controllers/sendFlowController.js b/src/js/controllers/sendFlowController.js index a4d9d6537..a6965bd96 100644 --- a/src/js/controllers/sendFlowController.js +++ b/src/js/controllers/sendFlowController.js @@ -4,16 +4,32 @@ angular.module('copayApp.controllers').controller('sendFlowController', function $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'); + } + $scope.params = $stateParams; + $scope.coin = false; // Wallets to show (for destination screen or contacts) + $scope.type = data.stateParams && data.stateParams['fromWalletId'] ? 'destination' : 'origin'; // origin || destination + + if ($scope.params.coin) { + $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'); $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.requestAmount = (($stateParams.amount) * (1 / config.unitToSatoshi)).toFixed(config.unitDecimals); $scope.isPaymentRequest = true; } + if ($scope.params.thirdParty) { + // Third Party Service + if ($scope.params.thirdParty.id === 'shapeshift') { + + } + } }); $scope.$on("$ionicView.enter", function(event, data) { @@ -21,8 +37,6 @@ angular.module('copayApp.controllers').controller('sendFlowController', function $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 = []; // empty wallets for origin screen if ($scope.type === 'origin') { diff --git a/src/js/controllers/tab-send.js b/src/js/controllers/tab-send.js index 8e0ae6b92..465941c35 100644 --- a/src/js/controllers/tab-send.js +++ b/src/js/controllers/tab-send.js @@ -54,42 +54,6 @@ 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; - // }; $scope.findContact = function(search) { @@ -178,10 +142,7 @@ angular.module('copayApp.controllers').controller('tabSendController', function( coin: v.coin, displayCoin: (v.coin == 'bch' ? (config.bitcoinCashAlias || defaults.bitcoinCashAlias) - : (config.bitcoinAlias || defaults.bitcoinAlias)).toUpperCase(), - getAddress: function(cb) { - return cb(null, k); - }, + : (config.bitcoinAlias || defaults.bitcoinAlias)).toUpperCase() }); }); originalList = completeContacts; @@ -202,35 +163,26 @@ angular.module('copayApp.controllers').controller('tabSendController', function( }; $scope.searchBlurred = function() { - if ($scope.formData.search == null || $scope.formData.search.length == 0) { + if ($scope.formData.search == null || $scope.formData.search.length === 0) { $scope.searchFocus = false; } }; - $scope.goToAmount = function(item) { - $timeout(function() { - item.getAddress(function(err, addr) { - if (err || !addr) { - //Error is already formated - return popupService.showAlert(err); - } + $scope.sendToContact = function (item) { + $timeout(function () { + var toAddress = item.address; - if (item.recipientType && item.recipientType == 'contact') { - if (addr.indexOf('bch') == 0 || addr.indexOf('btc') == 0) { - addr = addr.substring(3); - } + if (item.recipientType && item.recipientType === 'contact') { + if (toAddress.indexOf('bch') === 0 || toAddress.indexOf('btc') === 0) { + toAddress = toAddress.substring(3); } + } - $log.debug('Got toAddress:' + addr + ' | ' + item.name); - return $state.transitionTo('tabs.send.amount', { - recipientType: item.recipientType, - displayAddress: item.coin == 'bch' ? bitcoinCashJsService.translateAddresses(addr).cashaddr : addr, - toAddress: addr, - toName: item.name, - toEmail: item.email, - toColor: item.color, - coin: item.coin - }); + $log.debug('Got toAddress:' + toAddress + ' | ' + item.name); + + return $state.transitionTo('tabs.send.origin', { + toAddress: toAddress, + coin: item.coin }); }); }; diff --git a/src/js/routes.js b/src/js/routes.js index de77a34d5..0fa92750e 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/:recipientType/:toAddress/:toName/:toEmail/:toColor/:coin/:fixedUnit/:fromWalletId/:toWalletId/:minShapeshiftAmount/:maxShapeshiftAmount/:shapeshiftOrderId/:displayAddress/:noPrefix', + url: '/amount/:thirdParty/:fromWalletId/:toWalletId/:toAddress', views: { 'tab-send@tabs': { controller: 'amountController', @@ -305,7 +305,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }) .state('tabs.send.origin', { - url: '/origin/:thirdParty/:amount/:toAddress/:toWalletId', + url: '/origin/:thirdParty/:amount/:toAddress/:toWalletId/:coin', views: { 'tab-send@tabs': { controller: 'sendFlowController', diff --git a/www/views/tab-send.html b/www/views/tab-send.html index a1e8a778f..7bf4d47be 100644 --- a/www/views/tab-send.html +++ b/www/views/tab-send.html @@ -90,7 +90,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 2492a405a1ae6f86f9d1098052134d55db2cab76 Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Thu, 26 Jul 2018 14:29:53 +0800 Subject: [PATCH 20/94] Adds fee-summary bar, adds amount directive, adds support for fee-summary overlapping with content on scrollable small screens --- src/js/directives/amount.js | 21 +++++++++++++ src/sass/components/amount.scss | 24 ++++++++++++++ src/sass/components/components.scss | 2 ++ src/sass/components/fee-summary.scss | 33 +++++++++++++++++++ src/sass/components/ion-content.scss | 4 +++ src/sass/views/review.scss | 5 +++ www/css/main.css | 47 ++++++++++++++++++++++++++++ www/views/includes/amount.html | 6 ++++ www/views/review.html | 13 +++++++- 9 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/js/directives/amount.js create mode 100644 src/sass/components/amount.scss create mode 100644 src/sass/components/fee-summary.scss create mode 100644 www/views/includes/amount.html diff --git a/src/js/directives/amount.js b/src/js/directives/amount.js new file mode 100644 index 000000000..e86bec2fe --- /dev/null +++ b/src/js/directives/amount.js @@ -0,0 +1,21 @@ +'use strict'; +angular.module('bitcoincom.directives') + .directive('amount', [ + '$timeout', + function($timeout) { + return { + restrict: 'E', + scope: { + value: '=', + currency: '=' + }, + templateUrl: 'views/includes/amount.html', + controller: ['$scope', function($scope) { + var valueFormatted = parseFloat($scope.value).toFixed(8); + $scope.start = valueFormatted.slice(0, -5); + $scope.middle = valueFormatted.slice(-5, -2); + $scope.end = valueFormatted.substr(valueFormatted.length - 2); + }] + }; + } +]); \ No newline at end of file diff --git a/src/sass/components/amount.scss b/src/sass/components/amount.scss new file mode 100644 index 000000000..eb0768f4f --- /dev/null +++ b/src/sass/components/amount.scss @@ -0,0 +1,24 @@ +.amount { + .start, + .middle, + .end, + .currency { + display: inline-block; + } + + .start { + font-size: 14px; + } + + .middle { + font-size: 11px; + } + + .end { + font-size: 11px; + } + + .currency { + font-size: 14px; + } +} \ No newline at end of file diff --git a/src/sass/components/components.scss b/src/sass/components/components.scss index 180279125..8d8346265 100644 --- a/src/sass/components/components.scss +++ b/src/sass/components/components.scss @@ -7,3 +7,5 @@ @import "address"; @import "action-minor"; @import "expand-content"; +@import "fee-summary"; +@import "amount"; diff --git a/src/sass/components/fee-summary.scss b/src/sass/components/fee-summary.scss new file mode 100644 index 000000000..5f5236d0c --- /dev/null +++ b/src/sass/components/fee-summary.scss @@ -0,0 +1,33 @@ +.fee-summary { + position: relative; + display: flex; + justify-content: space-between; + width: 100%; + padding: 5px 12px 15px; + box-sizing: border-box; + background-color: #F2F2F2; + + &:before { + content: ''; + position: absolute; + left: 0; + top: -15px; + width: 100%; + height: 15px; + background: linear-gradient(to bottom, rgba(242,242,242,0) 0%,rgba(242,242,242,1) 100%); + } + + .fee-fiat { + &.positive { + color: #70955F; + } + + &.negative { + color: #C24633; + } + } + + .fee-crypto { + color: #BCBCBC; + } +} \ No newline at end of file diff --git a/src/sass/components/ion-content.scss b/src/sass/components/ion-content.scss index 7be47c9ab..56f3960a0 100644 --- a/src/sass/components/ion-content.scss +++ b/src/sass/components/ion-content.scss @@ -10,4 +10,8 @@ ion-content { &.padded-bottom-cta { bottom: 92px; } + + &.padded-bottom-cta-with-summary { + bottom: 134px; + } } \ No newline at end of file diff --git a/src/sass/views/review.scss b/src/sass/views/review.scss index e1d4ebd07..67733fe22 100644 --- a/src/sass/views/review.scss +++ b/src/sass/views/review.scss @@ -5,4 +5,9 @@ margin-bottom: constant(safe-area-inset-bottom); /* iOS 11.0 */ margin-bottom: env(safe-area-inset-bottom); /* iOS 11.2 */ } + + .fee-summary { + position: absolute; + bottom: 92px; + } } \ No newline at end of file diff --git a/www/css/main.css b/www/css/main.css index df8779400..063f60ff7 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -15074,6 +15074,9 @@ log-options #check-bar .checkbox-icon { /* iOS 11.0 */ margin-bottom: env(safe-area-inset-bottom); /* iOS 11.2 */ } + #view-review .fee-summary { + position: absolute; + bottom: 92px; } .gravatar { border-radius: 3px; @@ -15127,6 +15130,9 @@ ion-content.bg-neutral { ion-content.padded-bottom-cta { bottom: 92px; } +ion-content.padded-bottom-cta-with-summary { + bottom: 134px; } + .card.card-gutter-compact { margin: 10px 12px; } @@ -15212,6 +15218,47 @@ ion-content.padded-bottom-cta { 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: #BCBCBC; } + +.amount .start, +.amount .middle, +.amount .end, +.amount .currency { + display: inline-block; } + +.amount .start { + font-size: 14px; } + +.amount .middle { + font-size: 11px; } + +.amount .end { + font-size: 11px; } + +.amount .currency { + font-size: 14px; } + /* This is for rules that don't yet have a home. * Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/ */ diff --git a/www/views/includes/amount.html b/www/views/includes/amount.html new file mode 100644 index 000000000..791983c06 --- /dev/null +++ b/www/views/includes/amount.html @@ -0,0 +1,6 @@ +
+ {{start}} + {{middle}} + {{end}} + {{currency}} +
\ No newline at end of file diff --git a/www/views/review.html b/www/views/review.html index 9de1a403e..4995d3d25 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -7,7 +7,7 @@ -
@@ -63,6 +63,17 @@
+ +
+
Fee: Less than 1 cent
+
+ +
+
+ Date: Fri, 27 Jul 2018 12:29:34 +0800 Subject: [PATCH 21/94] Updates amount font size --- src/sass/components/amount.scss | 8 ++++---- src/sass/components/fee-summary.scss | 2 +- www/css/main.css | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/sass/components/amount.scss b/src/sass/components/amount.scss index eb0768f4f..3280c0706 100644 --- a/src/sass/components/amount.scss +++ b/src/sass/components/amount.scss @@ -7,18 +7,18 @@ } .start { - font-size: 14px; + font-size: 1em; } .middle { - font-size: 11px; + font-size: 0.7857em; } .end { - font-size: 11px; + font-size: 0.7857em; } .currency { - font-size: 14px; + font-size: 1em; } } \ No newline at end of file diff --git a/src/sass/components/fee-summary.scss b/src/sass/components/fee-summary.scss index 5f5236d0c..404643a82 100644 --- a/src/sass/components/fee-summary.scss +++ b/src/sass/components/fee-summary.scss @@ -28,6 +28,6 @@ } .fee-crypto { - color: #BCBCBC; + color: #A7A7A7; } } \ No newline at end of file diff --git a/www/css/main.css b/www/css/main.css index 063f60ff7..7a064f812 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -15239,7 +15239,7 @@ ion-content.padded-bottom-cta-with-summary { .fee-summary .fee-fiat.negative { color: #C24633; } .fee-summary .fee-crypto { - color: #BCBCBC; } + color: #A7A7A7; } .amount .start, .amount .middle, @@ -15248,16 +15248,16 @@ ion-content.padded-bottom-cta-with-summary { display: inline-block; } .amount .start { - font-size: 14px; } + font-size: 1em; } .amount .middle { - font-size: 11px; } + font-size: 0.7857em; } .amount .end { - font-size: 11px; } + font-size: 0.7857em; } .amount .currency { - font-size: 14px; } + font-size: 1em; } /* This is for rules that don't yet have a home. * Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/ From 47de79cc64ec2ce6856960938a7b854a0679a0ab Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Fri, 27 Jul 2018 17:03:09 +0800 Subject: [PATCH 22/94] Adds support for 0, 2, 3 and 8 decimal places and locale commas and decimals --- src/js/directives/amount.js | 89 ++++++++++++++++++++++++++------- src/sass/components/amount.scss | 3 ++ www/css/main.css | 9 ++-- www/views/includes/amount.html | 5 +- www/views/review.html | 4 +- 5 files changed, 84 insertions(+), 26 deletions(-) diff --git a/src/js/directives/amount.js b/src/js/directives/amount.js index e86bec2fe..ff61288b3 100644 --- a/src/js/directives/amount.js +++ b/src/js/directives/amount.js @@ -1,21 +1,76 @@ 'use strict'; + +/** + * @desc amount directive that can be used to display formatted financial values + * @example + */ angular.module('bitcoincom.directives') - .directive('amount', [ - '$timeout', - function($timeout) { - return { - restrict: 'E', - scope: { - value: '=', - currency: '=' - }, - templateUrl: 'views/includes/amount.html', - controller: ['$scope', function($scope) { - var valueFormatted = parseFloat($scope.value).toFixed(8); - $scope.start = valueFormatted.slice(0, -5); - $scope.middle = valueFormatted.slice(-5, -2); - $scope.end = valueFormatted.substr(valueFormatted.length - 2); - }] + .directive('amount', [ + '$timeout', + function($timeout) { + return { + restrict: 'E', + scope: { + value: '@', + currency: '@' + }, + templateUrl: 'views/includes/amount.html', + controller: ['$scope', function($scope) { + 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'; + }; + + switch (getDecimalPlaces($scope.currency)) { + case '0': + var valueFormatted = numberWithCommas(Math.round(parseFloat($scope.value))); + buildAmount(valueFormatted, '', ''); + break; + + case '2': + var valueProcessing = parseFloat(parseFloat($scope.value).toFixed(2)); + var valueFormatted = numberWithCommas(valueProcessing); + buildAmount(valueFormatted, '', ''); + break; + + case '3': + var valueProcessing = parseFloat(parseFloat($scope.value).toFixed(3)); + var valueFormatted = numberWithCommas(valueProcessing); + buildAmount(valueFormatted, '', ''); + break; + + case '8': + var valueFormatted = parseFloat($scope.value).toFixed(8); + if (parseFloat($scope.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; + } + }] + }; + } ]); \ No newline at end of file diff --git a/src/sass/components/amount.scss b/src/sass/components/amount.scss index 3280c0706..d8fe552a2 100644 --- a/src/sass/components/amount.scss +++ b/src/sass/components/amount.scss @@ -12,13 +12,16 @@ .middle { font-size: 0.7857em; + margin-left: 5px; } .end { font-size: 0.7857em; + margin-left: 5px; } .currency { font-size: 1em; + margin-left: 5px; } } \ No newline at end of file diff --git a/www/css/main.css b/www/css/main.css index 7a064f812..3b7ddca9d 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -15251,13 +15251,16 @@ ion-content.padded-bottom-cta-with-summary { font-size: 1em; } .amount .middle { - font-size: 0.7857em; } + font-size: 0.7857em; + margin-left: 5px; } .amount .end { - font-size: 0.7857em; } + font-size: 0.7857em; + margin-left: 5px; } .amount .currency { - font-size: 1em; } + font-size: 1em; + margin-left: 5px; } /* This is for rules that don't yet have a home. * Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/ diff --git a/www/views/includes/amount.html b/www/views/includes/amount.html index 791983c06..5d006fe46 100644 --- a/www/views/includes/amount.html +++ b/www/views/includes/amount.html @@ -1,6 +1,3 @@
- {{start}} - {{middle}} - {{end}} - {{currency}} + {{start}}{{middle}}{{end}}{{currency}}
\ No newline at end of file diff --git a/www/views/review.html b/www/views/review.html index 4995d3d25..64c256fd8 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -69,8 +69,8 @@
+ value="{{fee.value}}" + currency="{{fee.currency}}">
From 2b32fe9bdccb603cef9d85f5d4c1bf09197dcd07 Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Fri, 27 Jul 2018 17:04:53 +0800 Subject: [PATCH 23/94] Changes amount directive scope values to bind to parent scope property --- src/js/directives/amount.js | 4 ++-- www/views/review.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/directives/amount.js b/src/js/directives/amount.js index ff61288b3..bf519c0d0 100644 --- a/src/js/directives/amount.js +++ b/src/js/directives/amount.js @@ -11,8 +11,8 @@ angular.module('bitcoincom.directives') return { restrict: 'E', scope: { - value: '@', - currency: '@' + value: '=', + currency: '=' }, templateUrl: 'views/includes/amount.html', controller: ['$scope', function($scope) { diff --git a/www/views/review.html b/www/views/review.html index 64c256fd8..4995d3d25 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -69,8 +69,8 @@
+ value="fee.value" + currency="fee.currency">
From 7669ab247981245fbe611d59cb86bbd3372da2b4 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 30 Jul 2018 19:28:30 +1200 Subject: [PATCH 24/94] 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 2b96293c80302181f52b0c4d6701b342fee974ab Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Mon, 30 Jul 2018 16:40:16 +0800 Subject: [PATCH 25/94] Move amount directive to a separate branch --- src/js/directives/amount.js | 76 ----------------------------- src/sass/components/amount.scss | 27 ---------- src/sass/components/components.scss | 1 - www/css/main.css | 21 -------- 4 files changed, 125 deletions(-) delete mode 100644 src/js/directives/amount.js delete mode 100644 src/sass/components/amount.scss diff --git a/src/js/directives/amount.js b/src/js/directives/amount.js deleted file mode 100644 index bf519c0d0..000000000 --- a/src/js/directives/amount.js +++ /dev/null @@ -1,76 +0,0 @@ -'use strict'; - -/** - * @desc amount directive that can be used to display formatted financial values - * @example - */ -angular.module('bitcoincom.directives') - .directive('amount', [ - '$timeout', - function($timeout) { - return { - restrict: 'E', - scope: { - value: '=', - currency: '=' - }, - templateUrl: 'views/includes/amount.html', - controller: ['$scope', function($scope) { - 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'; - }; - - switch (getDecimalPlaces($scope.currency)) { - case '0': - var valueFormatted = numberWithCommas(Math.round(parseFloat($scope.value))); - buildAmount(valueFormatted, '', ''); - break; - - case '2': - var valueProcessing = parseFloat(parseFloat($scope.value).toFixed(2)); - var valueFormatted = numberWithCommas(valueProcessing); - buildAmount(valueFormatted, '', ''); - break; - - case '3': - var valueProcessing = parseFloat(parseFloat($scope.value).toFixed(3)); - var valueFormatted = numberWithCommas(valueProcessing); - buildAmount(valueFormatted, '', ''); - break; - - case '8': - var valueFormatted = parseFloat($scope.value).toFixed(8); - if (parseFloat($scope.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; - } - }] - }; - } -]); \ No newline at end of file diff --git a/src/sass/components/amount.scss b/src/sass/components/amount.scss deleted file mode 100644 index d8fe552a2..000000000 --- a/src/sass/components/amount.scss +++ /dev/null @@ -1,27 +0,0 @@ -.amount { - .start, - .middle, - .end, - .currency { - display: inline-block; - } - - .start { - font-size: 1em; - } - - .middle { - font-size: 0.7857em; - margin-left: 5px; - } - - .end { - font-size: 0.7857em; - margin-left: 5px; - } - - .currency { - font-size: 1em; - margin-left: 5px; - } -} \ No newline at end of file diff --git a/src/sass/components/components.scss b/src/sass/components/components.scss index 8d8346265..a689138bf 100644 --- a/src/sass/components/components.scss +++ b/src/sass/components/components.scss @@ -8,4 +8,3 @@ @import "action-minor"; @import "expand-content"; @import "fee-summary"; -@import "amount"; diff --git a/www/css/main.css b/www/css/main.css index 3b7ddca9d..f059eeaac 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -15241,27 +15241,6 @@ ion-content.padded-bottom-cta-with-summary { .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 .currency { - font-size: 1em; - margin-left: 5px; } - /* This is for rules that don't yet have a home. * Our goal is to delete this file. Search the regex: /class=".*CLASS.*?"/ */ From 5c4890fac48c60f6428e3acc73bcc96fdf6873f4 Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Mon, 30 Jul 2018 17:34:20 +0800 Subject: [PATCH 26/94] Removes amount directive template --- www/views/includes/amount.html | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 www/views/includes/amount.html diff --git a/www/views/includes/amount.html b/www/views/includes/amount.html deleted file mode 100644 index 5d006fe46..000000000 --- a/www/views/includes/amount.html +++ /dev/null @@ -1,3 +0,0 @@ -
- {{start}}{{middle}}{{end}}{{currency}} -
\ No newline at end of file From 51bd97012173e0487a11a3224a6100bf648b5897 Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Mon, 30 Jul 2018 17:46:00 +0800 Subject: [PATCH 27/94] Merges amount directive From 1895e0dbeb3fbee50bb2e7141d1d2b26edabd260 Mon Sep 17 00:00:00 2001 From: Sam Cheng Hung Date: Tue, 31 Jul 2018 16:23:45 +0800 Subject: [PATCH 28/94] Adds watcher for changes to value or currency scope variables --- src/js/directives/amount.js | 65 ++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/src/js/directives/amount.js b/src/js/directives/amount.js index f991f0a28..9622ca09d 100644 --- a/src/js/directives/amount.js +++ b/src/js/directives/amount.js @@ -48,37 +48,44 @@ angular.module('bitcoincom.directives') return '2'; }; - switch (getDecimalPlaces($scope.currency)) { - case '0': - var valueFormatted = numberWithCommas(Math.round(parseFloat($scope.value))); - buildAmount(valueFormatted, '', ''); - break; - - case '2': - var valueProcessing = parseFloat(parseFloat($scope.value).toFixed(2)); - var valueFormatted = numberWithCommas(valueProcessing); - buildAmount(valueFormatted, '', ''); - break; - - case '3': - var valueProcessing = parseFloat(parseFloat($scope.value).toFixed(3)); - var valueFormatted = numberWithCommas(valueProcessing); - buildAmount(valueFormatted, '', ''); - break; - - case '8': - var valueFormatted = parseFloat($scope.value).toFixed(8); - if (parseFloat($scope.value) == 0) { - buildAmount('0', '', ''); - } else { + var formatNumbers = function(currency, value) { + switch (getDecimalPlaces(currency)) { + case '0': + var valueFormatted = numberWithCommas(Math.round(parseFloat(value))); 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; + 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); + }); }] }; } From 281b969fc325f97e7ed287086960338fbc1ada07 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Tue, 31 Jul 2018 15:03:20 +0200 Subject: [PATCH 29/94] 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 30/94] 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 f3a350f6645ddcdfd03b2f926e8af2b67dd93110 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 1 Aug 2018 11:33:24 +1200 Subject: [PATCH 31/94] Redirecting to new Review screen when sending max. --- src/js/controllers/amount.js | 2 +- src/js/controllers/review.controller.js | 9 +++++++++ src/js/controllers/review.js | 5 ----- src/js/routes.js | 1 + 4 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 src/js/controllers/review.controller.js delete mode 100644 src/js/controllers/review.js diff --git a/src/js/controllers/amount.js b/src/js/controllers/amount.js index 52695e829..399acde49 100644 --- a/src/js/controllers/amount.js +++ b/src/js/controllers/amount.js @@ -508,7 +508,7 @@ angular.module('copayApp.controllers').controller('amountController', function($ } } - $state.transitionTo('tabs.send.confirm', 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 new file mode 100644 index 000000000..217e15b00 --- /dev/null +++ b/src/js/controllers/review.controller.js @@ -0,0 +1,9 @@ +'use strict'; + +angular + .module('copayApp.controllers') + .controller('reviewController', reviewController); + +function reviewController() { + var vm = this; +} diff --git a/src/js/controllers/review.js b/src/js/controllers/review.js deleted file mode 100644 index 1effff0ba..000000000 --- a/src/js/controllers/review.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -angular.module('copayApp.controllers').controller('reviewController', 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) { - -}); diff --git a/src/js/routes.js b/src/js/routes.js index 79b88e2f7..99513c278 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -321,6 +321,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr views: { 'tab-send@tabs': { controller: 'reviewController', + controllerAs: 'vm', templateUrl: 'views/review.html' } }, From 52f1e93e5b7688364a5fc7a73442731a6325c55b Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 1 Aug 2018 11:36:25 +1200 Subject: [PATCH 32/94] Header text. --- i18n/po/template.pot | 4 ++++ www/views/review.html | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index a23e5dbae..caa984046 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 "" + +#: www/views/review.html:4 +msgid "Review Transaction" +msgstr "" \ No newline at end of file diff --git a/www/views/review.html b/www/views/review.html index 4995d3d25..35fb6a51a 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -1,7 +1,7 @@ - {{'Review'|translate}} + {{'Review Transaction' | translate}} From d6176e0f3ee97514017195d0a2bea83d45ae0b57 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 1 Aug 2018 14:28:26 +1200 Subject: [PATCH 33/94] Getting crypto value. --- src/js/controllers/review.controller.js | 46 ++++++++++++++++++++++++- src/js/routes.js | 2 +- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 217e15b00..937790d9c 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -4,6 +4,50 @@ angular .module('copayApp.controllers') .controller('reviewController', reviewController); -function reviewController() { +function reviewController(configService, $log, $scope,) { var vm = this; + + vm.primaryAmount = '0'; + vm.primaryCurrency = ''; + + vm.secondaryAmount = ''; + vm.secondaryCurrency = ''; + + var config; + var amount = { + crypto: { + quantity: 0, + currency: '' + }, + fiat: null + }; + var priceDisplayIsFiat = true; + + $scope.$on("$ionicView.beforeEnter", onBeforeEnter); + + + function onBeforeEnter(event, data) { + + amount.crypto.quantity = data.stateParams.toAmount; + amount.crypto.currency = data.stateParams.coin.toUpperCase(); + console.log('cryptoAmount', cryptoAmount); + //vm.amount = cryptoAmount.toFixed(8); + console.log('vm.amount:', vm.amount); + + vm.secondaryAmount = amount.crypto.quantity; + vm.secondaryCurrency = amount.crypto.currency; + + configService.get(function onConfig(err, configCache) { + if (err) { + $log.err('Error getting config.', err); + return; + } else { + console.log('Got config.'); + config = configCache; + // Use this later if have time + priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; + } + }); + } + } diff --git a/src/js/routes.js b/src/js/routes.js index 99513c278..92193090a 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -317,7 +317,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }) .state('tabs.send.review', { - url: '/review', + url: '/review/:coin/:fromWalletId/:toAmount/:useSendMax', views: { 'tab-send@tabs': { controller: 'reviewController', From 38c3f2fb70f97f16029c1ea7482025c2d2658b49 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Wed, 1 Aug 2018 17:17:34 +1200 Subject: [PATCH 34/94] Displaying the amount. --- src/js/controllers/review.controller.js | 55 +++++++++++++++++++------ src/js/routes.js | 2 +- www/views/review.html | 4 +- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 937790d9c..4995e9dc2 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -4,33 +4,37 @@ angular .module('copayApp.controllers') .controller('reviewController', reviewController); -function reviewController(configService, $log, $scope,) { +function reviewController(configService, $log, $scope, txFormatService) { var vm = this; - vm.primaryAmount = '0'; + vm.primaryAmount = ''; vm.primaryCurrency = ''; vm.secondaryAmount = ''; vm.secondaryCurrency = ''; - var config; - var amount = { - crypto: { - quantity: 0, - currency: '' - }, - fiat: null - }; - var priceDisplayIsFiat = true; + + var coin = ''; + //var config = null; + var satoshis = null; + + //var priceDisplayIsFiat = true; $scope.$on("$ionicView.beforeEnter", onBeforeEnter); function onBeforeEnter(event, data) { - amount.crypto.quantity = data.stateParams.toAmount; + satoshis = parseInt(data.stateParams.amount, 10); + coin = data.stateParams.coin; + + updateAmount(); + + + /* + //amount.crypto.quantity = ; amount.crypto.currency = data.stateParams.coin.toUpperCase(); - console.log('cryptoAmount', cryptoAmount); + console.log('crypto:', JSON.stringify(amount.crypto)); //vm.amount = cryptoAmount.toFixed(8); console.log('vm.amount:', vm.amount); @@ -48,6 +52,31 @@ function reviewController(configService, $log, $scope,) { priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; } }); + */ } + function updateAmount() { + if (typeof satoshis !== 'number') { + return; + } + + var amountStr = txFormatService.formatAmountStr(coin, satoshis); + var amountParts = amountStr.split(' '); + vm.primaryAmount = amountParts[0]; + vm.primaryCurrency = amountParts[1]; + txFormatService.formatAlternativeStr(coin, satoshis, function(v) { + if (!v) { + vm.secondaryAmount = ''; + vm.secondaryCurrency = ''; + return; + } + vm.secondaryAmount = vm.primaryAmount; + vm.secondaryCurrency = vm.primaryCurrency; + + var fiatParts = v.split(' '); + vm.primaryAmount = fiatParts[0]; + vm.primaryCurrency = fiatParts[1]; + }); + } + } diff --git a/src/js/routes.js b/src/js/routes.js index 92193090a..d38e0e0de 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -317,7 +317,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }) .state('tabs.send.review', { - url: '/review/:coin/:fromWalletId/:toAmount/:useSendMax', + url: '/review/:coin/:fromWalletId/:amount/:useSendMax', views: { 'tab-send@tabs': { controller: 'reviewController', diff --git a/www/views/review.html b/www/views/review.html index 35fb6a51a..ebec1581b 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -11,8 +11,8 @@ ng-init="memoExpanded = false">
-

13.98 USD

-

0.014 BCH

+

+

From 31d33ba65628e1cb14e10011b15a86e61617e7a2 Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Wed, 1 Aug 2018 11:53:58 +0200 Subject: [PATCH 35/94] 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 0caa3fb92afbdc9e5c918612b5945b009da2ff4d Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 2 Aug 2018 09:48:12 +1200 Subject: [PATCH 36/94] From wallet details. --- src/js/controllers/review.controller.js | 118 ++++++++++++++++++------ www/views/review.html | 14 +-- 2 files changed, 96 insertions(+), 36 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 4995e9dc2..a38084b91 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -4,68 +4,116 @@ angular .module('copayApp.controllers') .controller('reviewController', reviewController); -function reviewController(configService, $log, $scope, txFormatService) { +function reviewController(configService, gettextCatalog, profileService, $scope, txFormatService) { var vm = this; + vm.origin = { + balanceAmount: '', + balanceCurrency: '', + color: '', + currency: '', + name: '', + }; vm.primaryAmount = ''; vm.primaryCurrency = ''; - vm.secondaryAmount = ''; vm.secondaryCurrency = ''; - var coin = ''; - //var config = null; + var originWalletId = ''; + var priceDisplayIsFiat = true; var satoshis = null; + var toAddress = ''; + var toWalletId = ''; - //var priceDisplayIsFiat = true; + + $scope.$on("$ionicView.beforeEnter", onBeforeEnter); function onBeforeEnter(event, data) { - satoshis = parseInt(data.stateParams.amount, 10); coin = data.stateParams.coin; - - updateAmount(); - - - /* - //amount.crypto.quantity = ; - amount.crypto.currency = data.stateParams.coin.toUpperCase(); - console.log('crypto:', JSON.stringify(amount.crypto)); - //vm.amount = cryptoAmount.toFixed(8); - console.log('vm.amount:', vm.amount); - - vm.secondaryAmount = amount.crypto.quantity; - vm.secondaryCurrency = amount.crypto.currency; + originWalletId = data.stateParams.fromWalletId; + satoshis = parseInt(data.stateParams.amount, 10); + toAddress = data.stateParams.toAddress; + + var originWallet = profileService.getWallet(originWalletId); + vm.origin.currency = originWallet.coin.toUpperCase(); + vm.origin.color = originWallet.color; + vm.origin.name = originWallet.name; configService.get(function onConfig(err, configCache) { if (err) { $log.err('Error getting config.', err); - return; } else { console.log('Got config.'); - config = configCache; + //config = configCache; // Use this later if have time - priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; + priceDisplayIsFiat = configCache.wallet.settings.priceDisplay === 'fiat'; } + updateSendAmounts(); + getOriginWalletBalance(originWallet); }); - */ } - function updateAmount() { + function getOriginWalletBalance(originWallet) { + console.log('origin wallet error:', originWallet.error); + var balanceCryptoAmount = ''; + var balanceCryptoCurrencyCode = ''; + var balanceFiatAmount = ''; + var balanceFiatCurrency = '' + + var originWalletStatus = null; + if (originWallet.status.isValid) { + originWalletStatus = originWallet.status; + } else if (originWallet.cachedStatus.isValid) { + originWalletStatus = originWallet.cachedStatus; + } else { + vm.origin.balanceAmount = ''; + vm.origin.balanceCurrency = ''; + return; + } + + if (originWalletStatus) { + var cryptoBalanceParts = originWalletStatus.spendableBalanceStr.split(' '); + balanceCryptoAmount = cryptoBalanceParts[0]; + balanceCryptoCurrencyCode = cryptoBalanceParts.length > 1 ? cryptoBalanceParts[1] : ''; + + if (originWalletStatus.alternativeBalanceAvailable) { + balanceFiatAmount = originWalletStatus.spendableBalanceAlternative; + balanceFiatCurrency = originWalletStatus.alternativeIsoCode; + } + } + + if (priceDisplayIsFiat) { + vm.origin.balanceAmount = balanceFiatAmount ? balanceFiatAmount : balanceCryptoAmount; + vm.origin.balanceCurrency = balanceFiatAmount ? balanceFiatCurrency : balanceCryptoCurrencyCode; + } else { + vm.origin.balanceAmount = balanceCryptoAmount; + vm.origin.balanceCurrency = balanceCryptoCurrencyCode; + } + } + + function updateSendAmounts() { if (typeof satoshis !== 'number') { return; } + var cryptoAmount = ''; + var cryptoCurrencyCode = ''; var amountStr = txFormatService.formatAmountStr(coin, satoshis); - var amountParts = amountStr.split(' '); - vm.primaryAmount = amountParts[0]; - vm.primaryCurrency = amountParts[1]; + if (amountStr) { + var amountParts = amountStr.split(' '); + cryptoAmount = amountParts[0]; + cryptoCurrencyCode = amountParts.length > 1 ? amountParts[1] : ''; + } + // Want to avoid flashing of amount strings so do all formatting after this has returned. txFormatService.formatAlternativeStr(coin, satoshis, function(v) { if (!v) { + vm.primaryAmount = cryptoAmount; + vm.primaryCurrency = cryptoCurrencyCode; vm.secondaryAmount = ''; vm.secondaryCurrency = ''; return; @@ -74,8 +122,20 @@ function reviewController(configService, $log, $scope, txFormatService) { vm.secondaryCurrency = vm.primaryCurrency; var fiatParts = v.split(' '); - vm.primaryAmount = fiatParts[0]; - vm.primaryCurrency = fiatParts[1]; + var fiatAmount = fiatParts[0]; + var fiatCurrency = fiatParts.length > 1 ? fiatParts[1] : ''; + + if (priceDisplayIsFiat) { + vm.primaryAmount = fiatAmount; + vm.primaryCurrency = fiatCurrency; + vm.secondaryAmount = cryptoAmount; + vm.secondaryCurrency = cryptoCurrencyCode; + } else { + vm.primaryAmount = cryptoAmount; + vm.primaryCurrency = cryptoCurrencyCode; + vm.secondaryAmount = fiatAmount; + vm.secondaryCurrency = fiatCurrency; + } }); } diff --git a/www/views/review.html b/www/views/review.html index ebec1581b..d75ce9e48 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -10,24 +10,24 @@
-
-

-

+
+

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

+

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

-
From:
+
From:
-

Personal Wallet (BCH)

-

128.67

+

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

+

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

From e58c3bf438676226f44a140aba7630ef8d84a79d Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 2 Aug 2018 10:07:25 +1200 Subject: [PATCH 37/94] From wallet currency colour. --- src/js/controllers/review.controller.js | 6 ++++-- www/views/review.html | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index a38084b91..1e51bd5d0 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -12,6 +12,7 @@ function reviewController(configService, gettextCatalog, profileService, $scope, balanceCurrency: '', color: '', currency: '', + currencyColor: '', name: '', }; vm.primaryAmount = ''; @@ -44,14 +45,15 @@ function reviewController(configService, gettextCatalog, profileService, $scope, vm.origin.color = originWallet.color; vm.origin.name = originWallet.name; - configService.get(function onConfig(err, configCache) { + configService.get(function onConfig(err, config) { if (err) { $log.err('Error getting config.', err); } else { console.log('Got config.'); //config = configCache; // Use this later if have time - priceDisplayIsFiat = configCache.wallet.settings.priceDisplay === 'fiat'; + priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; + vm.origin.currencyColor = originWallet.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; } updateSendAmounts(); getOriginWalletBalance(originWallet); diff --git a/www/views/review.html b/www/views/review.html index d75ce9e48..2ee10e40a 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -26,7 +26,7 @@ style="background-color: {{vm.origin.color}}" >
-

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

+

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

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

From 438ab94404512feb5901f508d7f790af85d04000 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 2 Aug 2018 10:20:03 +1200 Subject: [PATCH 38/94] Strings for translation. --- i18n/po/template.pot | 25 +++++++++++++++++++++++++ src/js/controllers/review.controller.js | 5 ++--- www/views/review.html | 3 ++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/i18n/po/template.pot b/i18n/po/template.pot index caa984046..7406fcb74 100644 --- a/i18n/po/template.pot +++ b/i18n/po/template.pot @@ -3702,4 +3702,29 @@ msgstr "" #: www/views/review.html:4 msgid "Review Transaction" +msgstr "" + +#: www/views/review.html:14 +msgid "You are sending" +msgstr "" + +#: www/views/review.html:22 +msgid "From:" +msgstr "" + +#: www/views/review.html:36 +msgid "To:" +msgstr "" + +#: www/views/review.html:53 +msgid "Add personal note" +msgstr "" + + +#: www/views/review.html:57 +msgid "Personal note:" +msgstr "" + +#: www/views/review.html:69 +msgid "Less than 1 cent" msgstr "" \ No newline at end of file diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 1e51bd5d0..f0c443e96 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -7,6 +7,8 @@ angular function reviewController(configService, gettextCatalog, profileService, $scope, txFormatService) { var vm = this; + vm.feeCrypto = ''; + vm.feeFiat = ''; vm.origin = { balanceAmount: '', balanceCurrency: '', @@ -49,9 +51,6 @@ function reviewController(configService, gettextCatalog, profileService, $scope, if (err) { $log.err('Error getting config.', err); } else { - console.log('Got config.'); - //config = configCache; - // Use this later if have time priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; vm.origin.currencyColor = originWallet.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; } diff --git a/www/views/review.html b/www/views/review.html index 2ee10e40a..15c6d5978 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -11,6 +11,7 @@ ng-init="memoExpanded = false">
+

You are sending

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

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

@@ -49,7 +50,7 @@ ng-class="{ 'expand-content-revealed': memoExpanded }" ng-click="memoExpanded = !memoExpanded"> - Add a personal note + Add personal note
From ff643bf479958364a4a82dae6b7de24d6c065ea9 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 2 Aug 2018 11:48:41 +1200 Subject: [PATCH 39/94] Starting on destination UI. --- src/js/controllers/review.controller.js | 17 ++++++++++++++++- www/views/review.html | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index f0c443e96..9394b5ef2 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -7,6 +7,13 @@ angular function reviewController(configService, gettextCatalog, profileService, $scope, txFormatService) { var vm = this; + vm.destination = { + address: '', + balanceAmount: '', + balanceCurrecy: '', + color: '', + name: '' + }; vm.feeCrypto = ''; vm.feeFiat = ''; vm.origin = { @@ -27,7 +34,7 @@ function reviewController(configService, gettextCatalog, profileService, $scope, var priceDisplayIsFiat = true; var satoshis = null; var toAddress = ''; - var toWalletId = ''; + var destinationWalletId = ''; @@ -47,6 +54,14 @@ function reviewController(configService, gettextCatalog, profileService, $scope, vm.origin.color = originWallet.color; vm.origin.name = originWallet.name; + destinationWalletId = data.stateParams.toWalletId; + if (destinationWalletId) { + var destinationWallet = profileService.getWallet(destinationWalletId); + vm.destination.color = destinationWallet.color; + vm.destination.name = destinationWallet.name; + + } + configService.get(function onConfig(err, config) { if (err) { $log.err('Error getting config.', err); diff --git a/www/views/review.html b/www/views/review.html index 15c6d5978..0ec643b8b 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -37,7 +37,7 @@
-

Satoshi Nakamoto

+

{{vm.destination.name}}

128.67

From 927c1e947847eba1ef284d060a307832b7d92528 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 2 Aug 2018 15:44:59 +1200 Subject: [PATCH 40/94] 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 41/94] 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 42/94] 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 e525b2e8addcb9fcd5d97c6a67c3cbc254c59c17 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 2 Aug 2018 18:14:54 +1200 Subject: [PATCH 43/94] UI for destination as wallet. --- src/js/controllers/review.controller.js | 85 ++++++++++++++++--------- src/js/routes.js | 2 +- www/views/review.html | 13 ++-- 3 files changed, 66 insertions(+), 34 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 9394b5ef2..686cb5787 100644 --- a/src/js/controllers/review.controller.js +++ b/src/js/controllers/review.controller.js @@ -10,8 +10,12 @@ function reviewController(configService, gettextCatalog, profileService, $scope, vm.destination = { address: '', balanceAmount: '', - balanceCurrecy: '', + balanceCurrency: '', + coin: '', color: '', + currency: '', + currencyColor: '', + kind: '', // 'address', 'contact', 'wallet' name: '' }; vm.feeCrypto = ''; @@ -29,6 +33,7 @@ function reviewController(configService, gettextCatalog, profileService, $scope, vm.secondaryAmount = ''; vm.secondaryCurrency = ''; + var config = null; var coin = ''; var originWalletId = ''; var priceDisplayIsFiat = true; @@ -54,61 +59,83 @@ function reviewController(configService, gettextCatalog, profileService, $scope, vm.origin.color = originWallet.color; vm.origin.name = originWallet.name; - destinationWalletId = data.stateParams.toWalletId; - if (destinationWalletId) { - var destinationWallet = profileService.getWallet(destinationWalletId); - vm.destination.color = destinationWallet.color; - vm.destination.name = destinationWallet.name; - - } - - configService.get(function onConfig(err, config) { + configService.get(function onConfig(err, configCache) { if (err) { $log.err('Error getting config.', err); } else { + config = configCache; priceDisplayIsFiat = config.wallet.settings.priceDisplay === 'fiat'; vm.origin.currencyColor = originWallet.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; } updateSendAmounts(); getOriginWalletBalance(originWallet); + handleDestinationAsWallet(data.stateParams.toWalletId); }); } function getOriginWalletBalance(originWallet) { - console.log('origin wallet error:', originWallet.error); + var balanceText = getWalletBalanceDisplayText(originWallet); + vm.origin.balanceAmount = balanceText.amount; + vm.origin.balanceCurrecny = balanceText.currency; + } + + function getWalletBalanceDisplayText(wallet) { var balanceCryptoAmount = ''; var balanceCryptoCurrencyCode = ''; var balanceFiatAmount = ''; var balanceFiatCurrency = '' + var displayAmount = ''; + var displayCurrency = ''; - var originWalletStatus = null; - if (originWallet.status.isValid) { - originWalletStatus = originWallet.status; - } else if (originWallet.cachedStatus.isValid) { - originWalletStatus = originWallet.cachedStatus; - } else { - vm.origin.balanceAmount = ''; - vm.origin.balanceCurrency = ''; - return; + var walletStatus = null; + if (wallet.status.isValid) { + walletStatus = wallet.status; + } else if (wallet.cachedStatus.isValid) { + walletStatus = wallet.cachedStatus; } - if (originWalletStatus) { - var cryptoBalanceParts = originWalletStatus.spendableBalanceStr.split(' '); + if (walletStatus) { + var cryptoBalanceParts = walletStatus.spendableBalanceStr.split(' '); balanceCryptoAmount = cryptoBalanceParts[0]; balanceCryptoCurrencyCode = cryptoBalanceParts.length > 1 ? cryptoBalanceParts[1] : ''; - if (originWalletStatus.alternativeBalanceAvailable) { - balanceFiatAmount = originWalletStatus.spendableBalanceAlternative; - balanceFiatCurrency = originWalletStatus.alternativeIsoCode; + if (walletStatus.alternativeBalanceAvailable) { + balanceFiatAmount = walletStatus.spendableBalanceAlternative; + balanceFiatCurrency = walletStatus.alternativeIsoCode; } } if (priceDisplayIsFiat) { - vm.origin.balanceAmount = balanceFiatAmount ? balanceFiatAmount : balanceCryptoAmount; - vm.origin.balanceCurrency = balanceFiatAmount ? balanceFiatCurrency : balanceCryptoCurrencyCode; + displayAmount = balanceFiatAmount ? balanceFiatAmount : balanceCryptoAmount; + displayCurrency = balanceFiatAmount ? balanceFiatCurrency : balanceCryptoCurrencyCode; } else { - vm.origin.balanceAmount = balanceCryptoAmount; - vm.origin.balanceCurrency = balanceCryptoCurrencyCode; + displayAmount = balanceCryptoAmount; + displayCurrency = balanceCryptoCurrencyCode; + } + + return { + amount: displayAmount, + currency: displayCurrency + }; + } + + function handleDestinationAsWallet(walletId) { + destinationWalletId = walletId; + if (destinationWalletId) { + var destinationWallet = profileService.getWallet(destinationWalletId); + vm.destination.coin = destinationWallet.coin; + vm.destination.color = destinationWallet.color; + vm.destination.currency = destinationWallet.coin.toUpperCase(); + vm.destination.kind = 'wallet'; + vm.destination.name = destinationWallet.name; + + if (config) { + vm.destination.currencyColor = vm.destination.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; + } + + var balanceText = getWalletBalanceDisplayText(destinationWallet); + vm.destination.balanceAmount = balanceText.amount; + vm.destination.balanceCurrency = balanceText.currency; } } diff --git a/src/js/routes.js b/src/js/routes.js index d38e0e0de..38941021c 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -317,7 +317,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }) .state('tabs.send.review', { - url: '/review/:coin/:fromWalletId/:amount/:useSendMax', + url: '/review/:amount/:fromWalletId/:sendMax/:toWalletId', views: { 'tab-send@tabs': { controller: 'reviewController', diff --git a/www/views/review.html b/www/views/review.html index 0ec643b8b..c1974a0dc 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -36,11 +36,16 @@
To:
- -

{{vm.destination.name}}

-

128.67

+ + +
+
+

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

+

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

-
+
qz9cqq5pryv9hnqwa8q8mccmynk9uf4vlu5nxerpzmc
From aeb6efcdd0aef5153396b304564c31b391785cc1 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 2 Aug 2018 19:51:50 +1200 Subject: [PATCH 44/94] UI for sending to contact. --- src/js/controllers/review.controller.js | 71 +++++++++++++++++-------- src/js/routes.js | 2 +- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/js/controllers/review.controller.js b/src/js/controllers/review.controller.js index 686cb5787..b81645488 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(configService, gettextCatalog, profileService, $scope, txFormatService) { +function reviewController(addressbookService, configService, profileService, $log, $scope, txFormatService) { var vm = this; vm.destination = { @@ -42,22 +42,20 @@ function reviewController(configService, gettextCatalog, profileService, $scope, var destinationWalletId = ''; - - $scope.$on("$ionicView.beforeEnter", onBeforeEnter); function onBeforeEnter(event, data) { - coin = data.stateParams.coin; originWalletId = data.stateParams.fromWalletId; satoshis = parseInt(data.stateParams.amount, 10); - toAddress = data.stateParams.toAddress; + toAddress = data.stateParams.toAddr; var originWallet = profileService.getWallet(originWalletId); vm.origin.currency = originWallet.coin.toUpperCase(); vm.origin.color = originWallet.color; vm.origin.name = originWallet.name; + coin = originWallet.coin; configService.get(function onConfig(err, configCache) { if (err) { @@ -69,6 +67,7 @@ function reviewController(configService, gettextCatalog, profileService, $scope, } updateSendAmounts(); getOriginWalletBalance(originWallet); + handleDestinationAsAddress(toAddress, coin); handleDestinationAsWallet(data.stateParams.toWalletId); }); } @@ -76,7 +75,7 @@ function reviewController(configService, gettextCatalog, profileService, $scope, function getOriginWalletBalance(originWallet) { var balanceText = getWalletBalanceDisplayText(originWallet); vm.origin.balanceAmount = balanceText.amount; - vm.origin.balanceCurrecny = balanceText.currency; + vm.origin.balanceCurrency = balanceText.currency; } function getWalletBalanceDisplayText(wallet) { @@ -119,24 +118,54 @@ function reviewController(configService, gettextCatalog, profileService, $scope, }; } + function handleDestinationAsAddress(address, originCoin) { + if (!address) { + return; + } + + // 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'; + } + }); + + } + + function handleDestinationAsContact(contact) { + vm.destination.kind = 'contact'; + vm.destination.name = contact.name; + vm.destination.color = contact.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; + vm.destination.currency = contact.coin.toUpperCase(); + vm.destination.currencyColor = vm.destination.color; + } + function handleDestinationAsWallet(walletId) { destinationWalletId = walletId; - if (destinationWalletId) { - var destinationWallet = profileService.getWallet(destinationWalletId); - vm.destination.coin = destinationWallet.coin; - vm.destination.color = destinationWallet.color; - vm.destination.currency = destinationWallet.coin.toUpperCase(); - vm.destination.kind = 'wallet'; - vm.destination.name = destinationWallet.name; - - if (config) { - vm.destination.currencyColor = vm.destination.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; - } - - var balanceText = getWalletBalanceDisplayText(destinationWallet); - vm.destination.balanceAmount = balanceText.amount; - vm.destination.balanceCurrency = balanceText.currency; + if (!destinationWalletId) { + return; } + + console.log('destination is wallet'); + var destinationWallet = profileService.getWallet(destinationWalletId); + vm.destination.coin = destinationWallet.coin; + vm.destination.color = destinationWallet.color; + vm.destination.currency = destinationWallet.coin.toUpperCase(); + vm.destination.kind = 'wallet'; + vm.destination.name = destinationWallet.name; + + if (config) { + vm.destination.currencyColor = vm.destination.coin === 'btc' ? config.bitcoinWalletColor : config.bitcoinCashWalletColor; + } + + var balanceText = getWalletBalanceDisplayText(destinationWallet); + vm.destination.balanceAmount = balanceText.amount; + vm.destination.balanceCurrency = balanceText.currency; } function updateSendAmounts() { diff --git a/src/js/routes.js b/src/js/routes.js index 38941021c..4807fab5c 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -317,7 +317,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } }) .state('tabs.send.review', { - url: '/review/:amount/:fromWalletId/:sendMax/:toWalletId', + url: '/review/:amount/:fromWalletId/:sendMax/:toAddr/:toWalletId', views: { 'tab-send@tabs': { controller: 'reviewController', From 4a1fd498f5952f18bb09f6e98ef13ba640477df4 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Thu, 2 Aug 2018 20:05:52 +1200 Subject: [PATCH 45/94] UI for sending to address. --- www/views/review.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/views/review.html b/www/views/review.html index c1974a0dc..36bb67410 100644 --- a/www/views/review.html +++ b/www/views/review.html @@ -35,7 +35,8 @@
To:
-
+
Date: Thu, 2 Aug 2018 15:15:23 +0200 Subject: [PATCH 46/94] 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 47/94] 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 48/94] 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 eef84b25f80063b547b078406247e4e10a350fee Mon Sep 17 00:00:00 2001 From: Sebastiaan Pasma Date: Thu, 2 Aug 2018 18:30:35 +0200 Subject: [PATCH 49/94] 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 50/94] 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 51/94] 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 e649da69ef124605199f3d69085a739fb176f53f Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Fri, 3 Aug 2018 17:11:47 +1200 Subject: [PATCH 52/94] 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 53/94] 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 54/94] 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 55/94] 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 56/94] 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 57/94] 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 58/94] 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 59/94] 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 60/94] 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 61/94] 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 62/94] 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 63/94] 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 64/94] 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 65/94] 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 66/94] 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 67/94] 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 68/94] 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 69/94] 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 70/94] 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 71/94] 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 72/94] 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 73/94] 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 74/94] 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 75/94] 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 76/94] 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 77/94] 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 78/94] 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 79/94] 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 80/94] 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 81/94] 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 82/94] 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 83/94] 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 84/94] 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 85/94] 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 86/94] 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 87/94] 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 88/94] 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 89/94] 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 90/94] 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 91/94] 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 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 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 92/94] 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 93/94] 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 94/94] 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';