Merge branch 'wallet/sprint/20' into wallet/dev

This commit is contained in:
Jean-Baptiste Dominguez 2018-08-09 15:52:48 +09:00 committed by GitHub
commit c8d7e88f81
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
105 changed files with 5417 additions and 1644 deletions

View file

@ -163,7 +163,7 @@ module.exports = function(grunt) {
},
bitanalytics: {
src: [
'bitanalytics/bitanalytics-0.1.0.js'
'bitanalytics/bitanalytics.js'
],
dest: 'www/lib/bitanalytics.js'
},

View file

@ -268,5 +268,33 @@ div.onboarding-topic {
display: block;
float: left;
max-height: 100%;
max-width: 100%;
max-width: 100%;
}
.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;
}
.egifter-banner {
background: #1A3A8B;
padding: 10px;
box-shadow: 0px 5px 10px 0px #cccccc;
height: 5em;
text-align: center;
}
.egifter-logo {
max-height: 100%;
max-width: 100%;
height: 4em;
}

View file

@ -72,7 +72,7 @@
<plugin name="cordova-plugin-queries-schemes" spec="~0.1.5" />
<plugin name="cordova-plugin-firebase" spec="https://github.com/arnesson/cordova-plugin-firebase.git" />
<plugin name="cordova-plugin-wkwebview-inputfocusfix" spec="https://github.com/onderceylan/cordova-plugin-wkwebview-inputfocusfix.git" />
<plugin name="cordova-plugin-media" spec="~5.0.2">
<plugin name="cordova-plugin-media-fork" spec="~5.0.3">
<variable name="KEEP_AVAUDIOSESSION_ALWAYS_ACTIVE" value="NO" />
</plugin>
<!-- Supported Platforms -->

View file

@ -6256,7 +6256,6 @@ var ClickAction = /** @class */ (function (_super) {
// Add event listener to all the elements found
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
console.log('init ' + this.name);
element.addEventListener('click', this.listener);
}
};
@ -6276,7 +6275,7 @@ var ClickAction = /** @class */ (function (_super) {
}(action_1.default));
exports.default = ClickAction;
},{"../action":4,"../log-event":15,"../log-event-handlers":14}],6:[function(require,module,exports){
},{"../action":4,"../log-event":16,"../log-event-handlers":15}],6:[function(require,module,exports){
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
@ -6315,7 +6314,7 @@ var BitAnalytics = /** @class */ (function () {
exports.default = BitAnalytics;
BitAnalytics.main();
},{"./action-factory":2,"./action-handlers":3,"./channels/adjust-channel":9,"./channels/mixpanel-channel":12,"./log-event":15,"./log-event-handlers":14}],7:[function(require,module,exports){
},{"./action-factory":2,"./action-handlers":3,"./channels/adjust-channel":9,"./channels/mixpanel-channel":12,"./log-event":16,"./log-event-handlers":15}],7:[function(require,module,exports){
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
@ -6413,7 +6412,6 @@ var AdjustChannel = /** @class */ (function (_super) {
_this.eventTypes = config.eventTypes;
var os = _this.adjustedOs(config.os);
_this.advertisingId = _this.getAdvertisingId(os);
console.log('Advertising ID for adjust: ' + _this.advertisingId);
// TODO: Different initialisation for Cordova.
var sessionParams = {
app_version: config.appVersion,
@ -6560,11 +6558,10 @@ var FirebaseChannel = /** @class */ (function (_super) {
var keys = Object.keys(params);
var keysLength = keys.length;
var sanitized = {};
for (var i = 0; i < keysLength; i++) {
var key = keys[i];
keys.map(function (key) {
var cleanKey = key.replace('-', '_').replace(/[\W]+/g, '');
sanitized[cleanKey] = params[key];
}
});
return sanitized;
};
return FirebaseChannel;
@ -6588,13 +6585,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
var channel_1 = __importDefault(require("../channel"));
var ga_1 = __importDefault(require("../external-libs/ga"));
var GoogleAnalyticsChannel = /** @class */ (function (_super) {
__extends(GoogleAnalyticsChannel, _super);
function GoogleAnalyticsChannel(name, config) {
var _this = _super.call(this, name) || this;
_this.dataLayer = null;
_this.gaInstance = null;
_this.trackingId = '';
_this.eventLabels = ['id'];
if (!config.trackingId) {
throw new Error('[BitAnalytics] Google Analytics config is missing tracking ID.');
@ -6602,8 +6598,12 @@ var GoogleAnalyticsChannel = /** @class */ (function (_super) {
if (config.eventLabels) {
_this.eventLabels = config.eventLabels;
}
_this.trackingId = config.trackingId;
_this.setUpGa();
_this.gaInstance = new ga_1.default({
trackID: config.trackingId,
appVersion: config.appVersion,
appName: config.appName || 'App'
});
_this.isReady = true;
return _this;
}
/**
@ -6612,49 +6612,26 @@ var GoogleAnalyticsChannel = /** @class */ (function (_super) {
*
*/
GoogleAnalyticsChannel.prototype.postEvent = function (name, params) {
// Default Google Analytics Events
// https://developers.google.com/analytics/devguides/collection/gtagjs/events
// Useful to convert to these, or start with these?
if (this.isReady) {
params.event_category = name;
var category = name;
var action = name;
var label = name;
var value = params['value'] || '';
for (var _i = 0, _a = this.eventLabels; _i < _a.length; _i++) {
var eventLabel = _a[_i];
if (params[eventLabel]) {
params.event_label = params[eventLabel];
label = params[eventLabel];
break;
}
}
this.gtag('event', name, params);
this.gaInstance.event(category, action, label, value);
}
};
/**
*
* Private methods
*
*/
/**
* Mimics function in the tracking snippet
*/
GoogleAnalyticsChannel.prototype.gtag = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
console.log(arguments);
window.dataLayer.push(arguments);
};
GoogleAnalyticsChannel.prototype.setUpGa = function () {
// From what GA recommends to insert into page
window.dataLayer = window.dataLayer || [];
this.gtag('js', new Date());
this.gtag('config', this.trackingId);
this.isReady = true;
};
return GoogleAnalyticsChannel;
}(channel_1.default));
exports.default = GoogleAnalyticsChannel;
},{"../channel":8}],12:[function(require,module,exports){
},{"../channel":8,"../external-libs/ga":14}],12:[function(require,module,exports){
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
@ -6677,7 +6654,7 @@ var MixpanelChannel = /** @class */ (function (_super) {
function MixpanelChannel(name, config) {
var _this = _super.call(this, name) || this;
if (!config.token) {
throw new DOMException('[BitAnalytics] Config incorrect.');
throw new Error('[BitAnalytics] Config incorrect.');
}
_this.mixpanelInstance = mixpanel;
mixpanel.init(config.token, config.config);
@ -6746,6 +6723,207 @@ exports.default = MixpanelChannel;
},{}],14:[function(require,module,exports){
"use strict";
/*
* name: nwjs-analytics -Node-Webkit Google Analytics integration
* version: 1.0.2
* github: https://github.com/Daaru00/nwjs-analytics
*/
function GA(opt) {
this.apiVersion = opt.apiVersion || '1';
this.trackID = opt.trackID || 'UA-XXXXXXXX-X';
this.clientID = opt.clientID || null;
this.userID = opt.userID || null;
this.appName = opt.appName || 'App';
this.appVersion = opt.appVersion || '1.0.0';
this.debug = opt.debug || false;
this.performanceTracking = opt.performanceTracking || true;
this.errorTracking = opt.errorTracking || true;
this.userLanguage = opt.userLanguage || "en";
this.currency = opt.currency || "EUR";
this.lastScreenName = opt.lastScreenName || '';
}
GA.prototype.sendRequest = function (data, callback) {
var ga = this;
if (!this.clientID || this.clientID == null)
this.clientID = this.generateClientID();
if (!this.userID || this.userID == null)
this.userID = this.generateClientID();
var postData = "v=" + this.apiVersion
+ "&an=" + this.appName
+ "&av=" + this.appVersion
+ "&tid=" + this.trackID
+ "&cid=" + this.clientID
+ "&sr=" + this.getScreenResolution()
+ "&vp=" + this.getViewportSize();
Object.keys(data).forEach(function (key) {
var val = data[key];
if (typeof val != "undefined")
postData += "&" + key + "=" + val;
});
var http = new XMLHttpRequest();
var url = "https://www.google-analytics.com";
if (!this.debug)
url += "/collect";
else
url += "/debug/collect";
http.open("GET", url + "?" + postData, true);
http.onreadystatechange = function () {
if (ga.debug)
console.log(http.response);
if (http.readyState == 4 && http.status == 200) {
if (callback)
callback(true);
}
else {
if (callback)
callback(false);
}
};
http.send();
};
GA.prototype.generateClientID = function () {
var id = "";
var possibilities = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 5; i++)
id += possibilities.charAt(Math.floor(Math.random() * possibilities.length));
return id;
};
GA.prototype.getScreenResolution = function () {
return screen.width + "x" + screen.height;
};
GA.prototype.getColorDept = function () {
return screen.colorDepth + "-bits";
};
GA.prototype.getUserAgent = function () {
return navigator.userAgent;
};
GA.prototype.getViewportSize = function () {
return window.screen.availWidth + "x" + window.screen.availHeight;
};
/*
* Measurement Protocol
* [https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide]
*/
GA.prototype.screenView = function (screename) {
var data = {
't': 'screenview',
'cd': screename
};
this.sendRequest(data);
this.lastScreenName = screename;
};
GA.prototype.event = function (category, action, label, value) {
var data = {
't': 'event',
'ec': category,
'ea': action,
};
if (label) {
data['el'] = label;
}
if (value) {
data['ev'] = value;
}
if (this.lastScreenName) {
data['cd'] = this.lastScreenName;
}
this.sendRequest(data);
};
GA.prototype.exception = function (msg, fatal) {
var data = {
't': 'exception',
'exd': msg,
'exf': fatal || 0
};
this.sendRequest(data);
};
GA.prototype.timing = function (category, variable, time, label) {
var data = {
't': 'timing',
'utc': category,
'utv': variable,
'utt': time,
'utl': label,
};
this.sendRequest(data);
},
GA.prototype.ecommerce = {
transactionID: false,
generateTransactionID: function () {
var id = "";
var possibilities = "0123456789";
for (var i = 0; i < 5; i++)
id += possibilities.charAt(Math.floor(Math.random() * possibilities.length));
return id;
},
transaction: function (total, items) {
var t_id = "";
if (!this.ecommerce.transactionID)
t_id = this.ecommerce.generateTransactionID();
else
t_id = this.ecommerce.transactionID;
var data = {
't': 'transaction',
'ti': t_id,
'tr': total,
'cu': this.currency,
};
this.sendRequest(data);
items.forEach(function (item) {
var data = {
't': 'item',
'ti': t_id,
'in': item.name,
'ip': item.price,
'iq': item.qty,
'ic': item.id,
'cu': this.currency
};
this.sendRequest(data);
});
}
},
GA.prototype.custom = function (data) {
this.sendRequest(data);
};
module.exports = GA;
/*
* Performance Tracking
*/
/*window.addEventListener("load", function() {
if(ga.performanceTracking) {
setTimeout(function() {
var timing = window.performance.timing;
var userTime = timing.loadEventEnd - timing.navigationStart;
ga.timing("performance", "pageload", userTime);
}, 0);
}
}, false);*/
/*
* Error Reporting
*/
/*window.onerror = function (msg, url, lineNo, columnNo, error) {
var message = [
'Message: ' + msg,
'Line: ' + lineNo,
'Column: ' + columnNo,
'Error object: ' + JSON.stringify(error)
].join(' - ');
if(ga.errorTracking)
{
setTimeout(function() {
ga.exception(message.toString());
}, 0);
}
return false;
};*/
},{}],15:[function(require,module,exports){
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
@ -6857,7 +7035,7 @@ var LogEventHandlers = /** @class */ (function () {
_this.channels.push(channel);
}
catch (error) {
console.log('[BitAnalytics] ' + error.name + ': ' + error.message);
console.log(error.message);
}
});
};
@ -6865,7 +7043,7 @@ var LogEventHandlers = /** @class */ (function () {
}());
exports.default = LogEventHandlers;
},{"./channel-factory":7}],15:[function(require,module,exports){
},{"./channel-factory":7}],16:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var LogEvent = /** @class */ (function () {

View file

@ -197,6 +197,20 @@ msgstr ""
msgid "Alternative Currency"
msgstr ""
#: www/views/tab-settings.html:75
msgid "Price Display"
msgstr ""
#: src/js/controllers/tab-settings.js:19
#: www/views/preferencesPriceDisplay.html:12
msgid "Fiat"
msgstr ""
#: src/js/controllers/tab-settings.js:19
#: www/views/preferencesPriceDisplay.html:15
msgid "Cryptocurrency"
msgstr ""
#: src/js/controllers/buyAmazon.js:98
msgid "Amazon.com is not available at this moment. Please try back later."
msgstr ""
@ -428,6 +442,7 @@ msgid "Buy &amp; Sell Bitcoin"
msgstr ""
#: www/views/tab-send.html:35
#: src/js/services/buyAndSellService.js:26
msgid "Buy Bitcoin"
msgstr ""
@ -652,6 +667,7 @@ msgstr ""
#: src/js/controllers/copayers.js:79
#: src/js/controllers/export.js:193
#: src/js/controllers/confirm.js:41
#: www/views/includes/copyToClipboard.html:4
msgid "Copied to clipboard"
msgstr ""
@ -2158,6 +2174,10 @@ msgstr ""
msgid "Payment Sent"
msgstr ""
#: www/views/includes/slideToAcceptSuccess.html:12
msgid "Share this transaction"
msgstr ""
#: www/views/modals/txp-details.html:32
msgid "Payment accepted, but not yet broadcasted"
msgstr ""
@ -2175,7 +2195,7 @@ msgid "Payment details"
msgstr ""
#: www/views/modals/paypro.html:6
msgid "Payment request"
msgid "Payment Request"
msgstr ""
#: www/views/mercadoLibreCards.html:22
@ -2647,6 +2667,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 ""
@ -3108,6 +3129,26 @@ msgstr ""
msgid "Top up {{amountStr}} to debit card ({{cardLastNumber}})"
msgstr ""
#: www/views/shapeshift.html:30
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 ""
#: www/views/buyAmazon.html:61
#: www/views/buyMercadoLibre.html:60
#: www/views/modals/wallet-balance.html:23
@ -3699,3 +3740,92 @@ msgstr ""
#: www/views/includes/walletInfo.html:18
msgid "{{wallet.m}}-of-{{wallet.n}}"
msgstr ""
#: src/js/services/shapeshiftService.js:8
msgid "Shapeshift"
msgstr ""
#: www/views/includes/community.html:3
msgid "Community"
msgstr ""
#: src/js/services/communityService.js:40
msgid "Bitcoin Cash Reddit"
msgstr ""
#: src/js/services/communityService.js:47
msgid "Bitcoin.com Twitter"
msgstr ""
#: www/views/includes/nextSteps.html:3
msgid "Explore Bitcoin.com"
msgstr ""
#: src/js/services/bitcoincomService.js:21
msgid "Bitcoin Cash Games"
msgstr ""
#: src/js/services/bitcoincomService.js:28
msgid "News"
msgstr ""
#: src/js/services/bitcoincomService.js:35
msgid "Mining Pool"
msgstr ""
#: src/js/services/bitcoincomService.js:42
msgid "Tools"
msgstr ""
#: src/js/services/bitcoincomService.js:49
msgid "Bitcoin Price Charts"
msgstr ""
#: src/js/services/bitcoincomService.js:56
msgid "Free Bitcoin Cash"
msgstr ""
#: www/views/tab-home.html:30
msgid "Your Bitcoin Wallets are ready!"
msgstr ""
#: src/js/controllers/amount.js:49
msgid "Address doesn\'t contain currency information, please make sure you are sending the correct currency."
msgstr ""
#: www/views/review.html:4
msgid "Review Transaction"
msgstr ""
#: 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 ""
#: 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 ""
#: src/js/services/incomingData.js:129
msgid "This invoice is no longer accepting payments"
msgstr ""

View file

@ -21,6 +21,9 @@ angular.module('copayApp.controllers').controller('addressbookAddController', fu
$timeout(function() {
var form = addressbookForm;
if (data && form) {
if (data.result) {
data = data.result;
}
data = data.replace(/^bitcoin(cash)?:/, '');
form.address.$setViewValue(data);
form.address.$isValid = true;
@ -36,9 +39,9 @@ angular.module('copayApp.controllers').controller('addressbookAddController', fu
addressbook.address = translated.legacy;
}
var channel = "firebase";
if (platformInfo.isNW) {
channel = "ga";
var channel = "ga";
if (platformInfo.isCordova) {
channel = "firebase";
}
var log = new window.BitAnalytics.LogEvent("contact_created", [{
"coin": $scope.addressbookEntry.coin

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('addressbookViewController', function($scope, $state, $timeout, lodash, addressbookService, popupService, $ionicHistory, platformInfo, gettextCatalog, configService, bitcoinCashJsService) {
angular.module('copayApp.controllers').controller('addressbookViewController', function($scope, sendFlowService, $state, $timeout, lodash, addressbookService, popupService, $ionicHistory, platformInfo, gettextCatalog, configService, bitcoinCashJsService) {
var config = configService.getSync();
var defaults = configService.getDefaults();
@ -22,6 +22,7 @@ angular.module('copayApp.controllers').controller('addressbookViewController', f
$scope.sendTo = function() {
$ionicHistory.removeBackView();
sendFlowService.clear();
$state.go('tabs.send');
$timeout(function() {
var to = '';
@ -31,12 +32,16 @@ angular.module('copayApp.controllers').controller('addressbookViewController', f
} else {
to = $scope.addressbookEntry.address;
}
$state.transitionTo('tabs.send.amount', {
var stateParams = {
toAddress: to,
toName: $scope.addressbookEntry.name,
toEmail: $scope.addressbookEntry.email,
coin: $scope.addressbookEntry.coin
});
};
sendFlowService.pushState(stateParams);
$state.transitionTo('tabs.send.origin');
}, 100);
};

View file

@ -1,68 +1,167 @@
'use strict';
angular.module('copayApp.controllers').controller('amountController', function($scope, $filter, $timeout, $ionicModal, $ionicScrollDelegate, $ionicHistory, storageService, walletService, gettextCatalog, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, txFormatService, ongoingProcess, popupService, bwcError, payproService, profileService, bitcore, amazonService, nodeWebkitService) {
angular.module('copayApp.controllers').controller('amountController', amountController);
function amountController(configService, $filter, gettextCatalog, $ionicHistory, $ionicModal, $ionicScrollDelegate, lodash, $log, nodeWebkitService, rateService, $scope, $state, $timeout, sendFlowService, shapeshiftService, txFormatService, platformInfo, profileService, walletService, $window) {
var vm = this;
vm.allowSend = false;
vm.altCurrencyList = [];
vm.alternativeAmount = '';
vm.alternativeUnit = '';
vm.amount = '0';
vm.availableFunds = '';
// 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.isRequestingSpecificAmount = false;
vm.listComplete = false;
vm.lastUsedPopularList = [];
vm.maxAmount = 0;
vm.minAmount = 0;
vm.thirdParty = false;
vm.unit = '';
vm.changeUnit = changeUnit;
vm.close = close;
vm.findCurrency = findCurrency;
vm.finish = finish;
vm.goBack = goBack;
vm.loadMore = loadMore;
vm.openPopup = openPopup;
vm.pushDigit = pushDigit;
vm.removeDigit = removeDigit;
vm.save = save;
vm.sendMax = sendMax;
vm.errorMessage = '';
$scope.$on('$ionicView.beforeEnter', onBeforeEnter);
$scope.$on('$ionicView.leave', onLeave);
var _id;
var unitToSatoshi;
var satToUnit;
var unitDecimals;
var satToBtc;
var SMALL_FONT_SIZE_LIMIT = 10;
var LENGTH_EXPRESSION_LIMIT = 19;
var LENGTH_BEFORE_COMMA_EXPRESSION_LIMIT = 8;
var LENGTH_AFTER_COMMA_EXPRESSION_LIMIT = 8;
var isNW = platformInfo.isNW;
var unitIndex = 0;
var altCurrencyModal = null;
var altUnitIndex = 0;
var availableFundsInCrypto = '';
var availableFundsInFiat = '';
var availableSatoshis = null;
var availableUnits = [];
var fiatCode;
var isNW = platformInfo.isNW;
var isAndroid = platformInfo.isAndroid;
var isIos = platformInfo.isIOS;
var lastUsedAltCurrencyList = [];
var passthroughParams = {};
var satToUnit;
var unitDecimals;
var unitIndex = 0;
var unitToSatoshi;
var useSendMax = false;
var fixedUnit;
$scope.amountModel = { amount: 0 };
$scope.isChromeApp = platformInfo.isChromeApp;
$scope.isAndroid = platformInfo.isAndroid;
$scope.isIos = platformInfo.isIOS;
$scope.$on('$ionicView.leave', function() {
function onLeave() {
angular.element($window).off('keydown');
});
}
$scope.$on("$ionicView.beforeEnter", function(event, data) {
function onBeforeEnter(event, data) {
console.log('amount onBeforeEnter sendflow ', sendFlowService.getState());
if (data.direction == "back") {
sendFlowService.popState();
}
initCurrencies();
if (data.stateParams.shapeshiftOrderId && data.stateParams.shapeshiftOrderId.length > 0) {
$scope.minShapeshiftAmount = parseFloat(data.stateParams.minShapeshiftAmount);
$scope.maxShapeshiftAmount = parseFloat(data.stateParams.maxShapeshiftAmount);
$scope.shapeshiftOrderId = data.stateParams.shapeshiftOrderId;
}
passthroughParams = sendFlowService;
// To get the wallet from with the new flow
$scope.fromWalletId = data.stateParams.fromWalletId;
vm.fromWalletId = passthroughParams.fromWalletId;
vm.toWalletId = passthroughParams.toWalletId;
vm.minAmount = parseFloat(passthroughParams.minAmount);
vm.maxAmount = parseFloat(passthroughParams.maxAmount);
if (data.stateParams.noPrefix) {
$scope.showWarningMessage = data.stateParams.noPrefix != 0;
if ($scope.showWarningMessage) {
var message = 'Address doesn\'t contain currency information, please make sure you are sending the correct currency.';
popupService.showAlert('', message, function() {}, 'Ok');
if (passthroughParams.thirdParty) {
vm.thirdParty = 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) {
vm.thirdParty.data['minAmount'] = vm.minAmount = parseFloat(data.minimum);
vm.thirdParty.data['maxAmount'] = vm.maxAmount = parseFloat(data.maxLimit);
});
}
}
}
vm.isRequestingSpecificAmount = !passthroughParams.fromWalletId;
var config = configService.getSync().wallet.settings;
setAvailableUnits();
updateUnitUI();
var reNr = /^[1234567890\.]$/;
var reOp = /^[\*\+\-\/]$/;
if (!isAndroid && !isIos) {
var disableKeys = angular.element($window).on('keydown', function(e) {
if (!e.key) return;
if (e.which === 8) { // you can add others here inside brackets.
if (!altCurrencyModal) {
e.preventDefault();
vm.removeDigit();
}
}
if (e.key.match(reNr)) {
vm.pushDigit(e.key);
} else if (e.key.match(reOp)) {
pushOperator(e.key);
} else if (e.keyCode === 86) {
if (e.ctrlKey || e.metaKey) processClipboard();
} else if (e.keyCode === 13) vm.finish();
$timeout(function() {
$scope.$apply();
});
});
}
unitToSatoshi = config.unitToSatoshi;
satToUnit = 1 / unitToSatoshi;
unitDecimals = config.unitDecimals;
resetAmount();
processAmount();
$timeout(function() {
$ionicScrollDelegate.resize();
}, 10);
function setAvailableUnits() {
var defaults = configService.getDefaults();
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',
@ -70,11 +169,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
});
};
var hasBTCWallets = profileService.getWallets({
coin: 'btc'
}).length;
if (hasBTCWallets) {
if (coinFromWallet === 'btc') {
availableUnits.push({
name: 'Bitcoin',
id: 'btc',
@ -84,31 +179,11 @@ angular.module('copayApp.controllers').controller('amountController', function($
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) {
fiatCode = data.stateParams.currency;
if (passthroughParams.currency) {
fiatCode = passthroughParams.currency;
altUnitIndex = unitIndex
unitIndex = availableUnits.length;
} else {
@ -125,142 +200,53 @@ angular.module('copayApp.controllers').controller('amountController', function($
isFiat: true,
});
if (data.stateParams.fixedUnit) {
fixedUnit = true;
}
unitIndex = lodash.findIndex(availableUnits, {
isFiat: true
});
altUnitIndex = 0;
};
// Go to...
_id = data.stateParams.id; // Optional (BitPay Card ID or Wallet ID)
$scope.nextStep = data.stateParams.nextStep;
setAvailableUnits();
updateUnitUI();
$scope.hasMaxAmount = true;
if ($ionicHistory.backView().stateName == 'tabs.receive') {
$scope.hasMaxAmount = false;
}
$scope.showMenu = $ionicHistory.backView() && ($ionicHistory.backView().stateName == 'tabs.send' || $ionicHistory.backView().stateName == 'tabs.bitpayCard');
$scope.recipientType = data.stateParams.recipientType || null;
$scope.toAddress = data.stateParams.toAddress;
$scope.displayAddress = data.stateParams.displayAddress;
$scope.toName = data.stateParams.toName;
$scope.toEmail = data.stateParams.toEmail;
$scope.toColor = data.stateParams.toColor;
if (!$scope.nextStep && !data.stateParams.toAddress) {
$log.error('Bad params at amount')
throw ('bad params');
}
var reNr = /^[1234567890\.]$/;
var reOp = /^[\*\+\-\/]$/;
if (!$scope.isAndroid && !$scope.isIos) {
var disableKeys = angular.element($window).on('keydown', function(e) {
if (!e.key) return;
if (e.which === 8) { // you can add others here inside brackets.
if (!$scope.altCurrencyModal) {
e.preventDefault();
$scope.removeDigit();
}
}
if (e.key.match(reNr)) {
$scope.pushDigit(e.key);
} else if (e.key.match(reOp)) {
$scope.pushOperator(e.key);
} else if (e.keyCode === 86) {
if (e.ctrlKey || e.metaKey) processClipboard();
} else if (e.keyCode === 13) $scope.finish();
$timeout(function() {
$scope.$apply();
});
});
}
$scope.specificAmount = $scope.specificAlternativeAmount = '';
$scope.isCordova = platformInfo.isCordova;
unitToSatoshi = config.unitToSatoshi;
satToUnit = 1 / unitToSatoshi;
satToBtc = 1 / 100000000;
unitDecimals = config.unitDecimals;
$scope.resetAmount();
// in SAT ALWAYS
if ($stateParams.toAmount) {
$scope.amountModel.amount = (($stateParams.toAmount) * satToUnit).toFixed(unitDecimals);
}
$scope.processAmount();
$timeout(function() {
$ionicScrollDelegate.resize();
}, 10);
});
$scope.goBack = function() {
if ($scope.shapeshiftOrderId) {
$state.go('tabs.send').then(function() {
$ionicHistory.clearHistory();
$state.go('tabs.home').then(function() {
$state.transitionTo('tabs.shapeshift');
});
});
} else {
$ionicHistory.goBack();
if (passthroughParams.fromWalletId) {
var fromWallet = profileService.getWallet(passthroughParams.fromWalletId);
updateAvailableFundsFromWallet(fromWallet);
}
}
}
function goBack() {
$ionicHistory.goBack();
}
function paste(value) {
$scope.amountModel.amount = value;
$scope.processAmount();
vm.amount = value;
processAmount();
$timeout(function() {
$scope.$apply();
});
};
}
function processClipboard() {
if (!isNW) return;
var value = nodeWebkitService.readFromClipboard();
if (value && evaluate(value) > 0) paste(evaluate(value));
};
}
$scope.sendMax = function() {
$scope.useSendMax = true;
$scope.finish();
};
$scope.toggleAlternative = function() {
if ($scope.amountModel.amount && isExpression($scope.amountModel.amount)) {
var amount = evaluate(format($scope.amountModel.amount));
$scope.globalResult = '= ' + processResult(amount);
}
};
function sendMax() {
useSendMax = true;
finish();
}
function updateUnitUI() {
$scope.unit = availableUnits[unitIndex].shortName;
$scope.alternativeUnit = availableUnits[altUnitIndex].shortName;
vm.unit = availableUnits[unitIndex].shortName;
vm.alternativeUnit = availableUnits[altUnitIndex].shortName;
$scope.processAmount();
$log.debug('Update unit coin @amount unit:' + $scope.unit + " alternativeUnit:" + $scope.alternativeUnit);
};
processAmount();
$log.debug('Update unit coin @amount unit:' + vm.unit + " alternativeUnit:" + vm.alternativeUnit);
}
$scope.changeUnit = function() {
function changeUnit() {
$scope.amountModel.amount = '0';
if (fixedUnit) return;
vm.amount = '0';
if (!(availableUnits[unitIndex].isFiat && availableUnits.length > 2 && altUnitIndex == 0)) {
unitIndex++;
@ -275,153 +261,166 @@ angular.module('copayApp.controllers').controller('amountController', function($
});
}
updateAvailableFundsStringIfNeeded();
updateUnitUI();
};
}
$scope.changeAlternativeUnit = function() {
// Do nothing is fiat is not main unit
if (!availableUnits[unitIndex].isFiat) return;
var nextCoin = lodash.findIndex(availableUnits, function(x) {
if (x.isFiat) return false;
if (x.id == availableUnits[altUnitIndex].id) return false;
return true;
});
if (nextCoin >= 0) {
altUnitIndex = nextCoin;
updateUnitUI();
}
};
function checkFontSize() {
if ($scope.amountModel.amount && $scope.amountModel.amount.length >= SMALL_FONT_SIZE_LIMIT) $scope.smallFont = true;
else $scope.smallFont = false;
};
$scope.pushDigit = function(digit) {
if ($scope.amountModel.amount && digit != '.') {
var amountSplitByComma = $scope.amountModel.amount.split('.');
function pushDigit(digit) {
if (vm.amount && digit != '.') {
var amountSplitByComma = vm.amount.split('.');
if (amountSplitByComma.length > 1 && amountSplitByComma[1].length >= LENGTH_AFTER_COMMA_EXPRESSION_LIMIT) return;
if (amountSplitByComma.length == 1 && amountSplitByComma[0].length >= LENGTH_BEFORE_COMMA_EXPRESSION_LIMIT) return;
}
if ($scope.amountModel.amount && $scope.amountModel.amount.length >= LENGTH_EXPRESSION_LIMIT) return;
if ($scope.amountModel.amount.indexOf('.') > -1 && digit == '.') return;
if ($scope.amountModel.amount == '0' && digit == '0') return;
if (availableUnits[unitIndex].isFiat && $scope.amountModel.amount.indexOf('.') > -1 && $scope.amountModel.amount[$scope.amountModel.amount.indexOf('.') + 2]) return;
if (vm.amount && vm.amount.length >= LENGTH_EXPRESSION_LIMIT) return;
if (vm.amount.indexOf('.') > -1 && digit == '.') return;
if (vm.amount == '0' && digit == '0') return;
if (availableUnits[unitIndex].isFiat && vm.amount.indexOf('.') > -1 && vm.amount[vm.amount.indexOf('.') + 2]) return;
if ($scope.amountModel.amount == '0' && digit != '.') {
$scope.amountModel.amount = '';
if (vm.amount == '0' && digit != '.') {
vm.amount = '';
}
if ($scope.amountModel.amount == '' && digit == '.') {
$scope.amountModel.amount = '0';
if (vm.amount == '' && digit == '.') {
vm.amount = '0';
}
$scope.amountModel.amount = ($scope.amountModel.amount + digit).replace('..', '.');
checkFontSize();
$scope.processAmount();
};
vm.amount = (vm.amount + digit).replace('..', '.');
processAmount();
}
$scope.pushOperator = function(operator) {
if (!$scope.amountModel.amount || $scope.amountModel.amount.length == 0) return;
$scope.amountModel.amount = _pushOperator($scope.amountModel.amount);
function pushOperator(operator) {
if (!vm.amount || vm.amount.length == 0) return;
vm.amount = pushOperator(vm.amount);
function _pushOperator(val) {
function pushOperator(val) {
if (!isOperator(lodash.last(val))) {
return val + operator;
} else {
return val.slice(0, -1) + operator;
}
};
};
}
}
function isOperator(val) {
var regex = /[\/\-\+\x\*]/;
return regex.test(val);
};
}
function isExpression(val) {
var regex = /^\.?\d+(\.?\d+)?([\/\-\+\*x]\d?\.?\d+)+$/;
return regex.test(val);
};
}
$scope.removeDigit = function() {
$scope.amountModel.amount = ($scope.amountModel.amount).toString().slice(0, -1);
$scope.processAmount();
checkFontSize();
};
function removeDigit() {
vm.amount = (vm.amount).toString().slice(0, -1);
processAmount();
}
$scope.resetAmount = function() {
$scope.amountModel.amount = $scope.alternativeAmount = $scope.globalResult = '';
$scope.allowSend = false;
checkFontSize();
};
function resetAmount() {
vm.amount = vm.alternativeAmount = vm.globalResult = '0';
vm.allowSend = false;
}
$scope.openPopup = function() {
function openPopup() {
$ionicModal.fromTemplateUrl('views/modals/altCurrency.html', {
scope: $scope
}).then(function(modal) {
$scope.altCurrencyModal = modal;
$scope.altCurrencyModal.show();
altCurrencyModal = modal;
altCurrencyModal.show();
});
};
}
$scope.close = function() {
$scope.altCurrencyModal.remove();
$scope.altCurrencyModal = false;
};
function close() {
altCurrencyModal.remove();
altCurrencyModal = null;
}
$scope.processAmount = function() {
var formatedValue = format($scope.amountModel.amount);
function processAmount() {
var formatedValue = format(vm.amount);
var result = evaluate(formatedValue);
var amountInCrypto = 0;
if (lodash.isNumber(result)) {
$scope.globalResult = isExpression($scope.amountModel.amount) ? '= ' + processResult(result) : '';
vm.globalResult = isExpression(vm.amount) ? '= ' + processResult(result) : '';
if (availableUnits[unitIndex].isFiat) {
var a = fromFiat(result);
if (a) {
$scope.alternativeAmount = txFormatService.formatAmount(a * unitToSatoshi, true);
$scope.allowSend = lodash.isNumber(a) && a > 0
&& (!$scope.shapeshiftOrderId
|| (a >= $scope.minShapeshiftAmount && a <= $scope.maxShapeshiftAmount));
amountInCrypto = a;
var amountInSatoshis = a * unitToSatoshi;
vm.fundsAreInsufficient = !!passthroughParams.fromWalletId
&& availableSatoshis !== null
&& availableSatoshis < amountInSatoshis;
vm.alternativeAmount = txFormatService.formatAmount(amountInSatoshis, true);
vm.allowSend = lodash.isNumber(a)
&& a > 0
&& (!vm.minAmount || a >= vm.minAmount)
&& (!vm.maxAmount || a <= vm.maxAmount)
&& !vm.fundsAreInsufficient;
} else {
if (result) {
$scope.alternativeAmount = 'N/A';
vm.alternativeAmount = 'N/A';
} else {
$scope.alternativeAmount = null;
vm.alternativeAmount = null;
}
$scope.allowSend = false;
vm.fundsAreInsufficient = false;
vm.allowSend = false;
}
} else {
$scope.alternativeAmount = $filter('formatFiatAmount')(toFiat(result));
$scope.allowSend = lodash.isNumber(result) && result > 0
&& (!$scope.shapeshiftOrderId
|| (result >= $scope.minShapeshiftAmount && result <= $scope.maxShapeshiftAmount));
amountInCrypto = result;
vm.fundsAreInsufficient = passthroughParams.fromWalletId
&& availableSatoshis !== null
&& availableSatoshis < result * unitToSatoshi;
vm.alternativeAmount = $filter('formatFiatAmount')(toFiat(result));
vm.allowSend = lodash.isNumber(result)
&& result > 0
&& (!vm.minAmount || result >= vm.minAmount)
&& (!vm.maxAmount || result <= vm.maxAmount)
&& !vm.fundsAreInsufficient;
}
} else {
vm.fundsAreInsufficient = false;
}
};
if (vm.fundsAreInsufficient) {
vm.errorMessage = gettextCatalog.getString('Not enough available funds');
} else if (amountInCrypto && vm.thirdParty && vm.thirdParty.id === 'shapeshift') {
if (amountInCrypto < vm.minAmount) {
vm.errorMessage = gettextCatalog.getString('Amount is below minimum');
} else if (amountInCrypto > vm.maxAmount) {
vm.errorMessage = gettextCatalog.getString('Amount is above maximum');
} else {
vm.errorMessage = '';
}
} else {
vm.errorMessage = '';
}
}
function processResult(val) {
if (availableUnits[unitIndex].isFiat) return $filter('formatFiatAmount')(val);
else return txFormatService.formatAmount(val.toFixed(unitDecimals) * unitToSatoshi, true);
};
}
function fromFiat(val) {
return parseFloat((rateService.fromFiat(val, fiatCode, availableUnits[altUnitIndex].id) * satToUnit).toFixed(unitDecimals));
};
}
function toFiat(val) {
if (!rateService.getRate(fiatCode)) return;
return parseFloat((rateService.toFiat(val * unitToSatoshi, fiatCode, availableUnits[unitIndex].id)).toFixed(2));
};
}
function evaluate(val) {
var result;
@ -432,7 +431,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
}
if (!lodash.isFinite(result)) return 0;
return result;
};
}
function format(val) {
if (!val) return;
@ -442,92 +441,39 @@ angular.module('copayApp.controllers').controller('amountController', function($
if (isOperator(lodash.last(val))) result = result.slice(0, -1);
return result.replace('x', '*');
};
}
$scope.finish = function() {
function finish() {
var unit = availableUnits[unitIndex];
var uiAmount = evaluate(format(vm.amount));
function finish() {
var unit = availableUnits[unitIndex];
var _amount = evaluate(format($scope.amountModel.amount));
var coin = unit.id;
if (unit.isFiat) {
coin = availableUnits[altUnitIndex].id;
}
var satoshis = 0;
if (unit.isFiat) {
satoshis = (fromFiat(uiAmount) * unitToSatoshi).toFixed(0);
} else {
satoshis = (uiAmount * unitToSatoshi).toFixed(0);
}
if ($scope.nextStep) {
$state.transitionTo($scope.nextStep, {
id: _id,
amount: $scope.useSendMax ? null : _amount,
currency: unit.id.toUpperCase(),
coin: coin,
useSendMax: $scope.useSendMax,
fromWalletId: $scope.fromWalletId
});
} else {
var amount = _amount;
var confirmData = {
amount: useSendMax ? undefined : satoshis,
fromWalletId: passthroughParams.fromWalletId,
sendMax: useSendMax,
toAddress: passthroughParams.toAddress,
toWalletId: passthroughParams.toWalletId
};
if (unit.isFiat) {
amount = (fromFiat(amount) * unitToSatoshi).toFixed(0);
} else {
amount = (amount * unitToSatoshi).toFixed(0);
}
if (vm.thirdParty) {
confirmData['thirdParty'] = this.thirdParty;
}
var confirmData = {
recipientType: $scope.recipientType,
toAmount: amount,
toAddress: $scope.toAddress,
displayAddress: $scope.displayAddress || $scope.toAddress,
toName: $scope.toName,
toEmail: $scope.toEmail,
toColor: $scope.toColor,
coin: coin,
useSendMax: $scope.useSendMax,
fromWalletId: $scope.fromWalletId
};
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 }),
function(w) {
return w.id == $scope.fromWalletId;
});
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;
} else if (balance > $scope.maxShapeshiftAmount) {
confirmData.useSendMax = false;
confirmData.toAmount = $scope.maxShapeshiftAmount * unitToSatoshi * 0.99;
}
}
}
$state.transitionTo('tabs.send.confirm', confirmData);
}
sendFlowService.pushState(confirmData);
if (!confirmData.fromWalletId) {
$state.transitionTo('tabs.paymentRequest.confirm', confirmData);
} else {
$state.transitionTo('tabs.send.review', confirmData);
$scope.useSendMax = null;
}
if ($scope.showWarningMessage) {
var u = $scope.unit == 'BCH' || $scope.unit == 'BTC' ? $scope.unit : $scope.alternativeUnit;
var message = 'Are you sure you want to send ' + u.toUpperCase() + '?';
popupService.showConfirm(message, '', 'Yes', 'No', function(res) {
if (!res) {
$scope.useSendMax = null;
return;
};
finish();
});
} else {
finish();
}
};
}
// Currency
@ -546,7 +492,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
{isoCode: 'CNY', order: 7},
{isoCode: 'KRW', order: 8},
{isoCode: 'HKD', order: 9},
]
];
function initCurrencies() {
var unusedCurrencyList = [{
@ -562,10 +508,10 @@ angular.module('copayApp.controllers').controller('amountController', function($
}];
rateService.whenAvailable(function() {
$scope.listComplete = false;
vm.listComplete = false;
var idx = lodash.indexBy(unusedCurrencyList, 'isoCode');
var idx2 = lodash.indexBy($scope.lastUsedAltCurrencyList, 'isoCode');
var idx2 = lodash.indexBy(lastUsedAltCurrencyList, 'isoCode');
var idx3 = lodash.indexBy(popularCurrencyList, 'isoCode');
var alternatives = rateService.listAlternatives(true);
@ -578,8 +524,10 @@ angular.module('copayApp.controllers').controller('amountController', function($
}
});
$scope.altCurrencyList = completeAlternativeList.slice(0, 10);
$scope.lastUsedPopularList = lodash.unique(lodash.union($scope.lastUsedAltCurrencyList, popularCurrencyList), 'isoCode');
vm.altCurrencyList = completeAlternativeList.slice(0, 10);
vm.lastUsedPopularList = lodash.unique(lodash.union(lastUsedAltCurrencyList, popularCurrencyList), 'isoCode');
rateService.updateRates();
$timeout(function() {
$scope.$apply();
@ -587,19 +535,19 @@ angular.module('copayApp.controllers').controller('amountController', function($
});
}
$scope.loadMore = function() {
function loadMore() {
$timeout(function() {
$scope.altCurrencyList = completeAlternativeList.slice(0, next);
vm.altCurrencyList = completeAlternativeList.slice(0, next);
next += 10;
$scope.listComplete = $scope.altCurrencyList.length >= completeAlternativeList.length;
vm.listComplete = vm.altCurrencyList.length >= completeAlternativeList.length;
$scope.$broadcast('scroll.infiniteScrollComplete');
}, 100);
};
$scope.findCurrency = function(search) {
function findCurrency(search) {
if (!search) initCurrencies();
var list = lodash.unique(lodash.union(completeAlternativeList, lodash.union($scope.lastUsedAltCurrencyList, popularCurrencyList)), 'isoCode');
$scope.altCurrencyList = lodash.filter(list, function(item) {
var list = lodash.unique(lodash.union(completeAlternativeList, lodash.union(lastUsedAltCurrencyList, popularCurrencyList)), 'isoCode');
vm.altCurrencyList = lodash.filter(list, function(item) {
var val = item.name
var val2 = item.isoCode;
return lodash.includes(val.toLowerCase(), search.toLowerCase()) || lodash.includes(val2.toLowerCase(), search.toLowerCase());
@ -607,9 +555,9 @@ angular.module('copayApp.controllers').controller('amountController', function($
$timeout(function() {
$scope.$apply();
});
};
}
$scope.save = function(newAltCurrency) {
function save(newAltCurrency) {
var opts = {
wallet: {
settings: {
@ -629,8 +577,63 @@ angular.module('copayApp.controllers').controller('amountController', function($
availableUnits[altUnitIndex].name = newAltCurrency.isoCode;
availableUnits[altUnitIndex].shortName = newAltCurrency.isoCode;
fiatCode = newAltCurrency.isoCode;
updateAvailableFundsStringIfNeeded();
updateUnitUI();
$scope.close();
close();
});
};
});
}
function updateAvailableFundsStringIfNeeded() {
if (passthroughParams.fromWalletId && availableSatoshis !== null) {
availableFundsInFiat = '';
vm.availableFunds = availableFundsInCrypto;
if (availableUnits[unitIndex].isFiat) {
var coin = availableUnits[altUnitIndex].id;
txFormatService.formatAlternativeStr(coin, availableSatoshis, function formatCallback(formatted){
if (formatted) {
availableFundsInFiat = formatted;
$scope.$apply(function() {
vm.availableFunds = availableFundsInFiat;
});
}
});
}
}
}
function updateAvailableFundsFromWallet(wallet) {
if (wallet.status && wallet.status.isValid) {
availableFundsInCrypto = wallet.status.spendableBalanceStr;
availableSatoshis = wallet.status.spendableAmount;
if (wallet.status.alternativeBalanceAvailable) {
availableFundsInFiat = wallet.status.spendableBalanceAlternative + ' ' + wallet.status.alternativeIsoCode;
} else {
availableFundsInFiat = '';
}
} else if (wallet.cachedStatus && wallet.status.isValid) {
if (wallet.cachedStatus.alternativeBalanceAvailable) {
availableFundsInFiat = wallet.cachedStatus.spendableBalanceAlternative + ' ' + wallet.cachedStatus.alternativeIsoCode;
} else {
availableFundsInFiat = '';
}
availableFundsInCrypto = wallet.cachedStatus.spendableBalanceStr;
availableSatoshis = wallet.cachedStatus.spendableAmount;
} else {
availableFundsInFiat = '';
availableFundsInCrypto = '';
availableSatoshis = null;
}
if (availableUnits[unitIndex].isFiat) {
vm.availableFunds = availableFundsInFiat || availableFundsInCrypto;
} else {
vm.availableFunds = availableFundsInCrypto;
}
}
}

View file

@ -0,0 +1,101 @@
describe('amountController', function(){
var configCache,
configService,
$controller,
$ionicHistory,
$rootScope,
platformInfo,
profileService,
rateService,
$stateParams;
beforeEach(function(){
module('ngLodash');
module('copayApp.controllers');
configCache = {
wallet: {
settings: {
}
}
};
configService = jasmine.createSpyObj(['getDefaults','getSync']);
configService.getDefaults.and.returnValue({
bitcoinCashAlias: 'bch',
bitcoinAlias: 'btc'
});
configService.getSync.and.returnValue(configCache);
$ionicHistory = jasmine.createSpyObj(['backView']);
platformInfo = {
isChromeApp: false,
isAndroid: false,
isIos: true
};
profileService = jasmine.createSpyObj(['getWallets']);
rateService = jasmine.createSpyObj(['fromFiat', 'whenAvailable']);
$stateParams = {};
inject(function(_$controller_, _$rootScope_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
$rootScope = _$rootScope_;
});
});
it('receives fromWalletId and toAddress.', function() {
var backView = {
stateName: 'ignoreme'
};
$ionicHistory.backView.and.returnValue(backView);
profileService.getWallets.and.returnValue([{}]);
rateService.fromFiat.and.returnValue(12); // satoshis or coins?
var $scope = $rootScope.$new();
var amountController = $controller('amountController', {
configService: configService,
gettextCatalog: {},
$ionicHistory: $ionicHistory,
$ionicModal: {},
$ionicScrollDelegate: {},
nodeWebkitService: {},
ongoingProcess: {},
platformInfo: platformInfo,
profileService: profileService,
popupService: {},
rateService: rateService,
$scope: $scope,
$state: {},
$stateParams: $stateParams,
txFormatService: {},
walletService: {}
});
var data = {
stateParams: {
fromWalletId: 'fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b',
toAddress: 'qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s'
}
};
$scope.$emit('$ionicView.beforeEnter', data);
expect($scope.fromWalletId).toBe('fd56c1e7-e3ac-4fd9-8afc-27b9c1b3718b');
expect($scope.toAddress).toBe('qrup46avn8t466xxwlzs4qelht7cnwvesv2e29wf7s');
});
});

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('backupController',
function($scope, $timeout, $log, $state, $stateParams, $ionicHistory, lodash, profileService, bwcService, walletService, ongoingProcess, popupService, gettextCatalog, $ionicModal, firebaseEventsService) {
function($scope, $timeout, $log, $state, $stateParams, $ionicHistory, lodash, profileService, bwcService, walletService, ongoingProcess, popupService, gettextCatalog, $ionicModal) {
if ($state.current.name == 'onboarding.backup') {
$scope.onboarding = true;
@ -89,7 +89,8 @@ angular.module('copayApp.controllers').controller('backupController',
$scope.setFlow(2);
})
} else {
firebaseEventsService.logEvent('backed_up_wallet');
//firebaseEventsService.logEvent('backed_up_wallet');
openConfirmBackupModal();
}
};

View file

@ -1,25 +1,20 @@
'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, $timeout, $ionicScrollDelegate, $ionicLoading, ionicToast, addressbookService, gettextCatalog, walletService, platformInfo, lodash, configService, $state, $log, profileService, bitcore, bitcoreCash, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, feeService, bitcoinCashJsService, bwcError, txConfirmNotification, soundService, clipboardService) {
var countDown = null;
var FEE_TOO_HIGH_LIMIT_PER = 15;
var tx = {};
var lastTxId = "";
// 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,6 +26,16 @@ angular.module('copayApp.controllers').controller('confirmController', function(
}, 10);
}
$scope.shareTransaction = function() {
var explorerTxUrl = 'https://explorer.bitcoin.com/'+tx.coin+'/tx/'+lastTxId;
if (platformInfo.isCordova) {
var text = 'Take a look at this Bitcoin transaction here: '+explorerTxUrl;
window.plugins.socialsharing.share(text, null, null, null);
} else {
ionicToast.show(gettextCatalog.getString('Copied to clipboard'), 'bottom', false, 3000);
clipboardService.copyToClipboard(explorerTxUrl);
}
};
$scope.showWalletSelector = function() {
$scope.walletSelector = true;
@ -45,7 +50,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() {
@ -68,112 +72,108 @@ angular.module('copayApp.controllers').controller('confirmController', function(
});
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
var setWalletSelector = function(coin, network, minAmount, cb) {
function setWalletSelector(coin, network, minAmount, cb) {
// no min amount? (sendMax) => look for no empty wallets
minAmount = minAmount || 1;
// no min amount? (sendMax) => look for no empty wallets
minAmount = minAmount || 1;
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: network,
coin: coin
});
$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 (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;
try {
networkName = (new B.Address(data.stateParams.toAddress)).network.name;
} 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;
}
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.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 = {
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,
toName: data.stateParams.toName,
toEmail: data.stateParams.toEmail,
toColor: data.stateParams.toColor,
network: networkName,
coin: data.stateParams.coin,
recipientType: $scope.recipientType || null,
toName: null,
toEmail: null,
toColor: null,
network: false,
coin: $scope.fromWallet.coin,
txp: {},
};
@ -182,18 +182,71 @@ 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(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
$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 +260,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.displayBalanceAsFiat = walletConfig.settings.priceDisplay === 'fiat';
});
};
function getSendMaxInfo(tx, wallet, cb) {
@ -231,7 +284,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 +294,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
txp.outputs = [{
'toAddress': tx.toAddress,
'amount': tx.toAmount,
'amount': tx.amount,
'message': tx.description
}];
@ -280,13 +333,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 +395,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 +446,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
function useSelectedWallet() {
if (!$scope.useSendMax) {
showAmount(tx.toAmount);
showAmount(tx.amount);
}
$scope.onWalletSelect($scope.wallet);
@ -402,19 +455,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');
@ -422,7 +475,6 @@ angular.module('copayApp.controllers').controller('confirmController', function(
}
};
$scope.toggleAddress = function() {
$scope.showAddress = !$scope.showAddress;
};
@ -612,6 +664,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
txConfirmNotification.subscribe(wallet, {
txid: txp.txid
});
lastTxId = txp.txid;
}
}, onSendStatusChange);
};
@ -643,9 +696,9 @@ angular.module('copayApp.controllers').controller('confirmController', function(
soundService.play('misc/payment_sent.mp3');
}
var channel = "firebase";
if (platformInfo.isNW) {
channel = "ga";
var channel = "ga";
if (platformInfo.isCordova) {
channel = "firebase";
}
var log = new window.BitAnalytics.LogEvent("transfer_success", [{
"coin": $scope.wallet.coin,
@ -655,8 +708,6 @@ angular.module('copayApp.controllers').controller('confirmController', function(
}], [channel, "adjust"]);
window.BitAnalytics.LogEventHandlers.postEvent(log);
// Should be removed
firebaseEventsService.logEvent('sent_bitcoin', { coin: $scope.wallet.coin });
$timeout(function() {
$scope.$digest();
}, 100);

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('createController',
function($scope, $rootScope, $timeout, $log, lodash, $state, $ionicScrollDelegate, $ionicHistory, profileService, configService, gettextCatalog, ledger, trezor, intelTEE, derivationPathHelper, ongoingProcess, walletService, storageService, popupService, appConfigService, pushNotificationsService, firebaseEventsService, $ionicNavBarDelegate) {
function($scope, $timeout, $log, lodash, $state, $ionicScrollDelegate, $ionicHistory, profileService, configService, gettextCatalog, ledger, trezor, intelTEE, derivationPathHelper, ongoingProcess, walletService, popupService, appConfigService, pushNotificationsService, $ionicNavBarDelegate) {
/* For compressed keys, m*73 + n*34 <= 496 */
var COPAYER_PAIR_LIMITS = {
@ -268,7 +268,7 @@ angular.module('copayApp.controllers').controller('createController',
}, 100);
}
else {
firebaseEventsService.logEvent('wallet_created', { coin: opts.coin });
//firebaseEventsService.logEvent('wallet_created', { coin: opts.coin });
$state.go('tabs.home');
}
}

View file

@ -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,17 +53,26 @@ 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;
var currency = parsedAmount.currency;
$scope.amountUnitStr = parsedAmount.amountUnitStr;
configService.whenAvailable(function (config) {
$scope.selectedPriceDisplay = config.wallet.settings.priceDisplay;
$timeout(function () {
$scope.$apply();
});
});
if (currency != 'BTC' && currency != 'BCH') {
// Convert to BTC or BCH
var config = configService.getSync().wallet.settings;

View file

@ -76,9 +76,9 @@ angular.module('copayApp.controllers').controller('preferencesNotificationsContr
emailService.updateEmail(opts);
var channel = "firebase";
if (platformInfo.isNW) {
channel = "ga";
var channel = "ga";
if (platformInfo.isCordova) {
channel = "firebase";
}
var log = new window.BitAnalytics.LogEvent("settings_email_notification_toggle", [{
"toggle": $scope.emailNotifications.value

View file

@ -0,0 +1,900 @@
'use strict';
angular
.module('copayApp.controllers')
.controller('reviewController', reviewController);
function reviewController(addressbookService, bitcoinCashJsService, bitcore, bitcoreCash, bwcError, configService, feeService, gettextCatalog, $interval, $ionicHistory, $ionicModal, lodash, $log, ongoingProcess, platformInfo, popupService, profileService, $scope, sendFlowService, shapeshiftService, soundService, $state, $timeout, txConfirmNotification, txFormatService, walletService) {
var vm = this;
vm.buttonText = '';
vm.destination = {
address: '',
balanceAmount: '',
balanceCurrency: '',
coin: '',
color: '',
currency: '',
currencyColor: '',
kind: '', // 'address', 'contact', 'wallet'
name: ''
};
vm.feeCrypto = '';
vm.feeFiat = '';
vm.fiatCurrency = '';
vm.feeIsHigh = false;
vm.feeLessThanACent = false;
vm.isCordova = platformInfo.isCordova;
vm.notReadyMessage = '';
vm.origin = {
balanceAmount: '',
balanceCurrency: '',
currency: '',
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.goBack = goBack;
vm.onSuccessConfirm = onSuccessConfirm;
var sendFlowData;
var config = null;
var countDown = null;
var defaults = {};
var coin = '';
var countDown = null;
var usingCustomFee = false;
var usingMerchantFee = false;
var destinationWalletId = '';
var originWalletId = '';
var priceDisplayIsFiat = true;
var satoshis = null;
var toAddress = '';
var tx = {};
var txPayproData = null;
var unitFromSat = 0;
var FEE_TOO_HIGH_LIMIT_PERCENTAGE = 15;
$scope.$on("$ionicView.beforeEnter", onBeforeEnter);
function onBeforeEnter(event, data) {
console.log('walletSelector onBeforeEnter sendflow ', sendFlowService.getState());
defaults = configService.getDefaults();
sendFlowData = sendFlowService.getState();
originWalletId = sendFlowData.fromWalletId;
satoshis = parseInt(sendFlowData.amount, 10);
toAddress = sendFlowData.toAddress;
destinationWalletId = sendFlowData.toWalletId;
vm.originWallet = profileService.getWallet(originWalletId);
vm.origin.currency = vm.originWallet.coin.toUpperCase();
coin = vm.originWallet.coin;
if (sendFlowData.thirdParty) {
vm.thirdParty = sendFlowData.thirdParty;
handleThirdPartyInitIfBip70();
handleThirdPartyInitIfShapeshift();
}
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 = (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();
getOriginWalletBalance(vm.originWallet);
handleDestinationAsAddress(toAddress, coin);
handleDestinationAsWallet(sendFlowData.toWalletId);
createVanityTransaction(data);
});
}
vm.approve = function() {
if (!tx || !vm.originWallet) return;
if (vm.paymentExpired) {
popupService.showAlert(null, gettextCatalog.getString('This bitcoin payment request has expired.'));
vm.sendStatus = '';
$timeout(function() {
$scope.$apply();
});
return;
}
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(vm.originWallet))
return cb();
var amountUsd = parseFloat(txFormatService.formatToUSD(vm.originWallet.coin, txp.amount));
return cb();
};
function publishAndSign() {
if (!vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()) {
$log.info('No signing proposal: No private key');
return walletService.onlyPublish(vm.originWallet, txp, function(err) {
if (err) setSendError(err);
}, statusChangeHandler);
}
walletService.publishAndSign(vm.originWallet, txp, function(err, txp) {
if (err) return setSendError(err);
if (config.confirmedTxsNotifications && config.confirmedTxsNotifications.enabled) {
txConfirmNotification.subscribe(vm.originWallet, {
txid: txp.txid
});
}
}, statusChangeHandler);
};
confirmTx(function(nok) {
if (nok) {
vm.sendStatus = '';
$timeout(function() {
$scope.$apply();
});
return;
}
publishAndSign();
});
});
};
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 = vm.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, vm.originWallet, {
clearCache: true,
dryRun: true
}, function() {});
};
};
function createVanityTransaction(data) {
console.log('createVanityTransaction()');
var configFeeLevel = config.wallet.settings.feeLevel ? config.wallet.settings.feeLevel : 'normal';
// Grab stateParams
tx = {
amount: parseInt(sendFlowData.amount),
sendMax: sendFlowData.sendMax === 'true' ? true : false,
fromWalletId: sendFlowData.fromWalletId,
toAddress: sendFlowData.toAddress,
paypro: txPayproData,
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: vm.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 = tx.coin === 'bch' ? bitcoreCash : bitcore;
var networkName;
try {
if (vm.destination.kind === 'wallet') { // This is a wallet-to-wallet transfer
ongoingProcess.set('generatingNewAddress', true);
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...');
walletService.getAddress(toWallet, true, function onWalletAddress(err, addr) {
console.log('getAddress cb called', err);
ongoingProcess.set('generatingNewAddress', false);
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({
disableAnimate: true,
historyRoot: true
});
$state.go('tabs.send').then(function () {
$ionicHistory.clearHistory();
});
});
return;
}
}
function getOriginWalletBalance(originWallet) {
var balanceText = getWalletBalanceDisplayText(vm.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': vm.memo
}];
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 = vm.memo;
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 = '';
var balanceFiatAmount = '';
var balanceFiatCurrency = ''
var displayAmount = '';
var displayCurrency = '';
var walletStatus = null;
if (wallet.status.isValid) {
walletStatus = wallet.status;
} else if (wallet.cachedStatus.isValid) {
walletStatus = wallet.cachedStatus;
}
if (walletStatus) {
var cryptoBalanceParts = walletStatus.spendableBalanceStr.split(' ');
balanceCryptoAmount = cryptoBalanceParts[0];
balanceCryptoCurrencyCode = cryptoBalanceParts.length > 1 ? cryptoBalanceParts[1] : '';
if (walletStatus.alternativeBalanceAvailable) {
balanceFiatAmount = walletStatus.spendableBalanceAlternative;
balanceFiatCurrency = walletStatus.alternativeIsoCode;
}
}
if (priceDisplayIsFiat) {
displayAmount = balanceFiatAmount ? balanceFiatAmount : balanceCryptoAmount;
displayCurrency = balanceFiatAmount ? balanceFiatCurrency : balanceCryptoCurrencyCode;
} else {
displayAmount = balanceCryptoAmount;
displayCurrency = balanceCryptoCurrencyCode;
}
return {
amount: displayAmount,
currency: displayCurrency
};
}
function goBack() {
$ionicHistory.goBack();
}
function handleDestinationAsAddress(address, originCoin) {
if (!address) {
return;
}
// Check if the recipient is a contact
addressbookService.get(originCoin + address, function(err, contact) {
if (!err && contact) {
handleDestinationAsAddressOfContact(contact);
} else {
if (originCoin === 'bch') {
vm.destination.address = bitcoinCashJsService.readAddress(address).cashaddr;
} else {
vm.destination.address = address;
}
vm.destination.kind = 'address';
}
});
}
function handleDestinationAsAddressOfContact(contact) {
vm.destination.kind = 'contact';
vm.destination.name = contact.name;
vm.destination.email = contact.email;
vm.destination.color = contact.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor;
vm.destination.currency = contact.coin.toUpperCase();
vm.destination.currencyColor = vm.destination.color;
}
function handleDestinationAsWallet(walletId) {
destinationWalletId = walletId;
if (!destinationWalletId) {
return;
}
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 (defaults) {
vm.destination.currencyColor = vm.destination.coin === 'btc' ? defaults.bitcoinWalletColor : defaults.bitcoinCashWalletColor;
}
var balanceText = getWalletBalanceDisplayText(destinationWallet);
vm.destination.balanceAmount = balanceText.amount;
vm.destination.balanceCurrency = balanceText.currency;
}
function handleThirdPartyInitIfBip70() {
if (vm.thirdParty.id === 'bip70') {
vm.sendingTitle = gettextCatalog.getString('You are paying');
vm.memo = vm.thirdParty.memo;
vm.memoExpanded = !!vm.memo;
vm.destination.name = vm.thirdParty.name;
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(destinationWalletId);
vm.destination.name = toWallet.name;
vm.destination.color = toWallet.color;
vm.destination.currency = toWallet.coin.toUpperCase();
ongoingProcess.set('connectingShapeshift', true);
walletService.getAddress(vm.originWallet, false, function onReturnWalletAddress(err, returnAddr) {
if (err) {
ongoingProcess.set('connectingShapeshift', false);
popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString());
return;
}
walletService.getAddress(toWallet, false, function onWithdrawalWalletAddress(err, withdrawalAddr) {
if (err) {
ongoingProcess.set('connectingShapeshift', false);
popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString());
return;
}
shapeshiftService.shiftIt(vm.originWallet.coin, toWallet.coin, withdrawalAddr, returnAddr, function onShiftIt(err, shapeshiftData) {
if (err && err != null) {
ongoingProcess.set('connectingShapeshift', false);
popupService.showAlert(gettextCatalog.getString('Shapeshift Error'), err.toString());
} else {
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';
}
});
});
});
}
}
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');
vm.readyToSend = false;
if (countDown) $interval.cancel(countDown);
$timeout(function() {
$scope.$apply();
});
};
};
function updateSendAmounts() {
if (typeof satoshis !== 'number') {
return;
}
var cryptoAmount = '';
var cryptoCurrencyCode = '';
var amountStr = txFormatService.formatAmountStr(coin, satoshis);
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;
}
vm.secondaryAmount = vm.primaryAmount;
vm.secondaryCurrency = vm.primaryCurrency;
var fiatParts = v.split(' ');
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;
}
});
}
function onSuccessConfirm() {
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) {
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 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();
});
popupService.showAlert(gettextCatalog.getString('Error at confirm'), bwcError.msg(msg));
};
function setupTx(tx) {
if (tx.coin === 'bch') {
tx.displayAddress = bitcoinCashJsService.readAddress(tx.toAddress).cashaddr;
} else {
tx.displayAddress = tx.toAddress;
}
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';
}
});
vm.showAddress = false;
setButtonText(vm.originWallet.credentials.m > 1, !!tx.paypro);
if (tx.paypro)
startExpirationTimer(tx.paypro.expires);
updateTx(tx, vm.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 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 (
(
processName === 'broadcastingTx' ||
((processName === 'signingTx') && vm.originWallet.m > 1) ||
(processName == 'sendingTx' && !vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal())
) && !isOn) {
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');
}
var channel = "firebase";
if (platformInfo.isNW) {
channel = "ga";
}
// When displaying Fiat, if the formatting fails, the crypto will be the primary amount.
var amount = unitFromSat * satoshis;
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) {
vm.sendStatus = showName;
}
};
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();
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);
}
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);
setNotReady(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;
satoshis = tx.amount;
updateSendAmounts();
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);
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') {
setNotReady(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 = (unitFromSat * txp.fee).toFixed(8);
vm.feeIsHigh = txp.feeToHigh;
console.log("crypto", vm.feeCrypto);
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();
});
});
});
}
}

View file

@ -1,7 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('shapeshiftController', function($scope, $interval, profileService, walletService, popupService, lodash, $ionicNavBarDelegate) {
angular.module('copayApp.controllers').controller('shapeshiftController', function($scope, sendFlowService, $state, $timeout, $ionicHistory, profileService, walletService, popupService, lodash, $ionicNavBarDelegate) {
var walletsBtc = [];
var walletsBch = [];
@ -16,24 +15,9 @@ 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.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.singleToWallet = $scope.toWallets.length === 1;
}
$scope.$on("$ionicView.beforeEnter", function(event, data) {
@ -42,15 +26,15 @@ angular.module('copayApp.controllers').controller('shapeshiftController', functi
$scope.fromWallets = lodash.filter(walletsBtc.concat(walletsBch), function(w) {
return w.status.balance.availableAmount > 0;
});
if ($scope.fromWallets.length == 0) return;
$scope.onFromWalletSelect($scope.fromWallets[0]);
$scope.onToWalletSelect($scope.toWallets[0]);
$scope.singleFromWallet = $scope.fromWallets.length == 1;
$scope.singleToWallet = $scope.toWallets.length == 1;
$scope.singleFromWallet = $scope.fromWallets.length === 1;
$scope.fromWalletSelectorTitle = 'From';
$scope.toWalletSelectorTitle = 'To';
$scope.showFromWallets = false;
$scope.showToWallets = false;
$scope.walletsWithFunds = profileService.getWallets({onlyComplete: true, hasFunds: true});
$scope.wallets = profileService.getWallets({onlyComplete: true});
$scope.hasWallets = !lodash.isEmpty($scope.wallets);
});
$scope.$on("$ionicView.enter", function(event, data) {
@ -59,9 +43,42 @@ 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 stateParams = {
thirdParty: {
id: 'shapeshift'
}
};
// Starting new send flow, so ensure everything is reset
sendFlowService.clear();
$state.go('tabs.home').then(function() {
$ionicHistory.clearHistory();
$state.go('tabs.send').then(function() {
$timeout(function () {
sendFlowService.pushState(stateParams);
$state.transitionTo('tabs.send.origin');
}, 60);
});
});
}
});

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('tabHomeController',
function($rootScope, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, bannerService, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, storageService, txpModalService, appConfigService, startupService, addressbookService, bwcError, nextStepsService, buyAndSellService, homeIntegrationsService, bitpayCardService, pushNotificationsService, timeService, bitcoincomService, pricechartService, firebaseEventsService, servicesService, shapeshiftService, $ionicNavBarDelegate, signVerifyMessageService) {
function($rootScope, sendFlowService, $timeout, $scope, $state, $stateParams, $ionicScrollDelegate, $window, gettextCatalog, lodash, popupService, ongoingProcess, bannerService, externalLinkService, latestReleaseService, profileService, walletService, configService, $log, platformInfo, storageService, txpModalService, appConfigService, startupService, addressbookService, bwcError, nextStepsService, buyAndSellService, homeIntegrationsService, bitpayCardService, pushNotificationsService, timeService, $ionicNavBarDelegate) {
var wallet;
var listeners = [];
var notifications = [];
@ -20,7 +20,12 @@ angular.module('copayApp.controllers').controller('tabHomeController',
$scope.bannerUrl = '';
$scope.$on("$ionicView.afterEnter", function() {
$scope.$on("$ionicView.beforeEnter", onBeforeEnter);
$scope.$on("$ionicView.enter", onEnter);
$scope.$on("$ionicView.afterEnter", onAfterEnter);
$scope.$on("$ionicView.leave", onLeave);
function onAfterEnter () {
startupService.ready();
bannerService.getBanner(function (banner) {
@ -28,9 +33,10 @@ angular.module('copayApp.controllers').controller('tabHomeController',
$scope.bannerUrl = banner.url;
$scope.bannerIsLoading = false;
});
});
};
function onBeforeEnter (event, data) {
$scope.$on("$ionicView.beforeEnter", function(event, data) {
if (!$scope.homeTip) {
storageService.getHomeTipAccepted(function(error, value) {
$scope.homeTip = (value == 'accepted') ? false : true;
@ -51,9 +57,9 @@ angular.module('copayApp.controllers').controller('tabHomeController',
}
});
}
});
$scope.$on("$ionicView.enter", function(event, data) {
};
function onEnter(event, data) {
$ionicNavBarDelegate.showBar(true);
updateAllWallets();
@ -97,26 +103,29 @@ angular.module('copayApp.controllers').controller('tabHomeController',
}
$scope.showServices = true;
pushNotificationsService.init();
firebaseEventsService.init();
$timeout(function() {
$ionicScrollDelegate.resize();
$scope.$apply();
}, 10);
});
});
};
$scope.$on("$ionicView.leave", function(event, data) {
function onLeave (event, data) {
lodash.each(listeners, function(x) {
x();
});
});
};
$scope.createdWithinPastDay = function(time) {
return timeService.withinPastDay(time);
};
$scope.startFreshSend = function() {
sendFlowService.clear();
$state.go('tabs.send');
}
$scope.openExternalLink = function() {
var url = 'https://github.com/Bitcoin-com/Wallet/releases/latest';
var optIn = true;
@ -224,6 +233,7 @@ angular.module('copayApp.controllers').controller('tabHomeController',
cb();
}
}
$scope.walletsWithFunds = profileService.getWallets({hasFunds: true});
});
});
};

View file

@ -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
});
};
@ -145,9 +144,9 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
}
$scope.paymentReceivedCoin = $scope.wallet.coin;
var channel = "firebase";
if (platformInfo.isNW) {
channel = "ga";
var channel = "ga";
if (platformInfo.isCordova) {
channel = "firebase";
}
var log = new window.BitAnalytics.LogEvent("transfer_success", [{
"coin": $scope.wallet.coin,
@ -233,10 +232,14 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
if (!$scope.wallets[0]) return;
// select first wallet if no wallet selected previously
var selectedWallet = checkSelectedWallet($scope.wallet, $scope.wallets);
var selectedWallet = null;
if (data.stateParams.walletId) { // from walletDetails
selectedWallet = checkSelectedWallet(profileService.getWallet(data.stateParams.walletId), $scope.wallets);
} else {
// select first wallet if no wallet selected previously
selectedWallet = checkSelectedWallet($scope.wallet, $scope.wallets);
}
$scope.onWalletSelect(selectedWallet);
$scope.showShareButton = platformInfo.isCordova ? (platformInfo.isIOS ? 'iOS' : 'Android') : null;
listeners = [

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, $ionicLoading, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, bwcError, gettextCatalog, scannerService, configService, bitcoinCashJsService, $ionicPopup, $ionicNavBarDelegate, clipboardService) {
angular.module('copayApp.controllers').controller('tabSendController', function($scope, $rootScope, $log, $timeout, $ionicScrollDelegate, $ionicLoading, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, platformInfo, sendFlowService, bwcError, gettextCatalog, scannerService, configService, bitcoinCashJsService, $ionicPopup, $ionicNavBarDelegate, clipboardService) {
var clipboardHasAddress = false;
var clipboardHasContent = false;
var originalList;
@ -33,6 +33,9 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
text = text.substring(0, 200);
}
var stateParams = sendFlowService.getState();
$scope.fromWallet = profileService.getWallet(stateParams.fromWalletId);
$scope.clipboardHasAddress = false;
$scope.clipboardHasContent = false;
if ((text.indexOf('bitcoincash:') === 0 || text[0] === 'C' || text[0] === 'H' || text[0] === 'p' || text[0] === 'q') && text.replace('bitcoincash:', '').length === 42) { // CashAddr
@ -55,42 +58,6 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
});
});
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) {
if (incomingData.redir(search)) {
@ -133,7 +100,6 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
};
var updateHasFunds = function() {
$scope.hasFunds = false;
var index = 0;
lodash.each($scope.wallets, function(w) {
@ -179,10 +145,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;
@ -203,39 +166,46 @@ 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:' + toAddress + ' | ' + item.name);
var stateParams = sendFlowService.getState();
stateParams.toAddress = toAddress,
stateParams.coin = item.coin;
sendFlowService.pushState(stateParams);
if (!stateParams.fromWalletId) { // If we have no toAddress or fromWallet
$state.transitionTo('tabs.send.origin');
} else {
$state.transitionTo('tabs.send.amount');
}
$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
});
});
});
};
$scope.startWalletToWalletTransfer = function() {
console.log('startWalletToWalletTransfer()');
var params = sendFlowService.getState();
sendFlowService.pushState(params);
$state.transitionTo('tabs.send.wallet-to-wallet', {
fromWalletId: sendFlowService.fromWalletId
});
}
// This could probably be enhanced refactoring the routes abstract states
$scope.createWallet = function() {
$state.go('tabs.home').then(function() {
@ -250,6 +220,8 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
console.log(data);
console.log('tab-send onBeforeEnter sendflow ', sendFlowService.getState());
$scope.isIOS = platformInfo.isIOS && platformInfo.isCordova;
$scope.showWalletsBch = $scope.showWalletsBtc = $scope.showWallets = false;
@ -264,5 +236,9 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
$scope.displayBalanceAsFiat = _config.wallet.settings.priceDisplay === 'fiat';
});
if (data.direction == "back") {
sendFlowService.clear();
}
});
});
});

View file

@ -16,7 +16,7 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
isoCode: config.wallet.settings.alternativeIsoCode
};
$scope.selectedPriceDisplay = config.wallet.settings.priceDisplay;
$scope.selectedPriceDisplay = config.wallet.settings.priceDisplay === 'crypto' ? gettextCatalog.getString('Cryptocurrency') : gettextCatalog.getString('Fiat');
// TODO move this to a generic service
bitpayAccountService.getAccounts(function(err, data) {

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, platformInfo, incomingData, lodash, popupService, gettextCatalog, scannerService) {
angular.module('copayApp.controllers').controller('tabsController', function($rootScope, $log, $scope, $state, $stateParams, $timeout, platformInfo, incomingData, lodash, popupService, gettextCatalog, scannerService, sendFlowService) {
$scope.onScan = function(data) {
if (!incomingData.redir(data)) {
@ -15,6 +15,11 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
};
};
$scope.startFreshSend = function() {
sendFlowService.clear();
$state.go('tabs.send');
};
$scope.importInit = function() {
$scope.fromOnboarding = $stateParams.fromOnboarding;
$timeout(function() {
@ -23,7 +28,7 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
};
$scope.chooseScanner = function() {
sendFlowService.clear();
var isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
if (!isWindowsPhoneApp) {

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicHistory, profileService, lodash, configService, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService, appConfigService, rateService) {
angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicHistory, profileService, lodash, configService, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService, sendFlowService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService, appConfigService, rateService) {
var HISTORY_SHOW_LIMIT = 10;
var currentTxHistoryPage = 0;
@ -12,9 +12,9 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
$scope.isAndroid = platformInfo.isAndroid;
$scope.isIOS = platformInfo.isIOS;
var channel = "firebase";
if (platformInfo.isNW) {
channel = "ga";
var channel = "ga";
if (platformInfo.isCordova) {
channel = "firebase";
}
var log = new window.BitAnalytics.LogEvent("wallet_details_open", [], [channel]);
window.BitAnalytics.LogEventHandlers.postEvent(log);
@ -342,9 +342,9 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
top = TOP_BALANCE_BUTTON;
}
var amountTop = ((amountScale - 0.7) / 0.7) * top;
if (amountTop < -10) {
amountTop = -10;
var amountTop = ((amountScale - 0.80) / 0.80) * top;
if (amountTop < -2) {
amountTop = -2;
}
if (amountTop > top) {
amountTop = top;
@ -353,6 +353,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
var t = amountTop;
$scope.altAmountOpacity = (amountHeight - 100) / 80;
$scope.buttonsOpacity = (amountHeight - 140) / 70;
$window.requestAnimationFrame(function() {
$scope.amountHeight = amountHeight + 'px';
$scope.contentMargin = contentMargin + 'px';
@ -373,6 +374,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
sendFlowService.clear();
configService.whenAvailable(function (config) {
$scope.selectedPriceDisplay = config.wallet.settings.priceDisplay;
@ -469,4 +471,36 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
function rgbToHex(r, g, b) {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
$scope.goToSend = function() {
sendFlowService.startSend({
fromWalletId: $scope.wallet.id
});
// Go home first so that the Home tab works properly
$state.go('tabs.home').then(function () {
$ionicHistory.clearHistory();
$state.go('tabs.send');
});
};
$scope.goToReceive = function() {
$state.go('tabs.home', {
walletId: $scope.wallet.id
}).then(function () {
$ionicHistory.clearHistory();
$state.go('tabs.receive', {
walletId: $scope.wallet.id
});
});
};
$scope.goToBuy = function() {
$state.go('tabs.home', {
walletId: $scope.wallet.id
}).then(function () {
$ionicHistory.clearHistory();
$state.go('tabs.buyandsell');
});
};
});

View file

@ -0,0 +1,210 @@
'use strict';
angular.module('copayApp.controllers').controller('walletSelectorController', function($scope, $rootScope, $state, $log, $ionicHistory, sendFlowService, configService, gettextCatalog, profileService, txFormatService) {
var fromWalletId = '';
var priceDisplayAsFiat = false;
var unitDecimals = 0;
var unitsFromSatoshis = 0;
$scope.$on("$ionicView.beforeEnter", onBeforeEnter);
$scope.$on("$ionicView.enter", onEnter);
function onBeforeEnter(event, data) {
console.log('walletSelector onBeforeEnter sendflow', sendFlowService.getState());
if (data.direction == "back") {
sendFlowService.popState();
}
var stateParams = sendFlowService.getState();
var config = configService.getSync().wallet.settings;
priceDisplayAsFiat = config.priceDisplay === 'fiat';
unitDecimals = config.unitDecimals;
unitsFromSatoshis = 1 / config.unitToSatoshi;
switch($state.current.name) {
case 'tabs.send.wallet-to-wallet':
$scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer');
break;
case 'tabs.send.destination':
if (stateParams.fromWalletId) {
$scope.sendFlowTitle = gettextCatalog.getString('Wallet to Wallet Transfer');
}
break;
default:
// nop
}
$scope.params = sendFlowService;
$scope.coin = false; // Wallets to show (for destination screen or contacts)
$scope.type = $scope.params['fromWalletId'] ? 'destination' : 'origin'; // origin || destination
fromWalletId = $scope.params['fromWalletId'];
if ($scope.type === 'destination' && $scope.params.toAddress) {
$state.transitionTo(getNextStep());
}
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 is a payment request
$scope.sendFlowTitle = gettextCatalog.getString('Payment Request');
$scope.specificAmount = $scope.specificAlternativeAmount = '';
$scope.isPaymentRequest = true;
}
if ($scope.params.thirdParty) {
$scope.thirdParty = $scope.params.thirdParty;
}
};
function onEnter (event, data) {
configService.whenAvailable(function(config) {
$scope.selectedPriceDisplay = config.wallet.settings.priceDisplay;
});
if ($scope.thirdParty) {
// Third party services specific logic
handleThirdPartyIfShapeshift();
}
prepareWalletLists();
formatRequestedAmount();
};
function formatRequestedAmount() {
if ($scope.params.amount) {
var cryptoAmount = (unitsFromSatoshis * $scope.params.amount).toFixed(unitDecimals);
var cryptoCoin = $scope.coin.toUpperCase();
txFormatService.formatAlternativeStr($scope.coin, $scope.params.amount, 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 = $scope.thirdParty
}
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
return 'tabs.send.amount';
} else { // If we do have them
return 'tabs.send.review';
}
}
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') {
$scope.coin = 'btc';
} else {
$scope.coin = 'bch';
}
}
}
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') {
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
$scope.walletsBtc = profileService.getWallets({coin: $scope.coin});
} else {
$scope.walletsBch = profileService.getWallets({coin: $scope.coin});
}
}
}
$scope.useWallet = function(wallet) {
var params = sendFlowService.getState();
if ($scope.type === 'origin') { // we're on the origin screen, set wallet to send from
params.fromWalletId = wallet.id;
} else { // we're on the destination screen, set wallet to send to
params.toWalletId = wallet.id;
}
sendFlowService.pushState(params);
$state.transitionTo(getNextStep(), $scope.params);
};
$scope.goBack = function() {
$ionicHistory.goBack();
}
});

View file

@ -0,0 +1,177 @@
'use strict';
/**
* @desc amount directive that can be used to display formatted financial values
* size-equal attribute is optional, defaults to false.
* @example fee = {
* value: 12.49382901,
* currency: 'BCH'
* }
* @example <formatted-amount value="fee.value" currency="fee.currency"></formatted-amount>
* @example <formatted-amount value="fee.value" currency="fee.currency" size-equal="true"></formatted-amount>
*/
angular.module('bitcoincom.directives')
.directive('formattedAmount', function(uxLanguage) {
return {
restrict: 'E',
scope: {
value: '@',
currency: '@',
sizeEqual: '@'
},
templateUrl: 'views/includes/formatted-amount.html',
controller: function($scope, $timeout) {
$scope.canShow = false;
$scope.displaySizeEqual = !!$scope.sizeEqual;
$timeout(function onFormattedAmountTimeout() {
var decimalPlaces = {
'0': ['BIF', 'CLP', 'DJF', 'GNF', 'ILS', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'],
'3': ['BHD', 'IQD', 'JOD', 'KWD', 'OMR', 'TND'],
'8': ['BCH', 'BTC']
};
var localizeNumbers = function(x, minimumFractionDigits) {
var parsed = parseFloat(x);
var opts = {
minimumFractionDigits: minimumFractionDigits,
useGrouping: true
};
var lang = uxLanguage.getCurrentLanguage();
var localized = parsed.toLocaleString(lang, opts);
var corrected = ensureEnoughFractionalDigits(localized, x, minimumFractionDigits);
return corrected;
};
var buildAmount = function(start, middle, end) {
$scope.start = start;
$scope.middle = middle;
$scope.end = end;
};
var getDecimalPlaces = function(currency) {
if (decimalPlaces['0'].indexOf(currency.toUpperCase()) > -1) return '0';
if (decimalPlaces['3'].indexOf(currency.toUpperCase()) > -1) return '3';
if (decimalPlaces['8'].indexOf(currency.toUpperCase()) > -1) return '8';
return '2';
};
var getDecimalSeparator = function() {
var testNum = 1.5;
var testString = testNum.toLocaleString(uxLanguage.getCurrentLanguage());
// Some environments let you set decimal separators that are more than one character
var separator = /^1(.+)5$/.exec(testString)[1]
return separator;
};
var formatNumbers = function() {
// During watch, may be changed from having a separate currency value,
// to both being in value. Don't want to use previous currency value.
// Try to extract currency from value..
var currencySplit = $scope.value.split(" ");
if (currencySplit.length === 2) {
$scope.currency = currencySplit[1];
}
$scope.currency = $scope.currency || '';
var parsed = parseFloat($scope.value);
var valueFormatted = '';
var valueProcessing = '';
switch (getDecimalPlaces($scope.currency)) {
case '0':
if (isNaN(parsed)) {
buildAmount('-', '', '');
} else {
valueFormatted = localizeNumbers(Math.round(parsed), 0);
buildAmount(valueFormatted, '', '');
}
break;
case '3':
if (isNaN(parsed)) {
buildAmount('-' + getDecimalSeparator() + '---', '', '');
} else {
valueProcessing = parsed.toFixed(3);
valueFormatted = localizeNumbers(valueProcessing, 3);
buildAmount(valueFormatted, '', '');
}
break;
case '8':
if (isNaN(parsed)) {
buildAmount('-' + getDecimalSeparator() + '---', '', '');
} else if (parsed === 0) {
buildAmount('0', '', '');
} else {
valueFormatted = parsed.toFixed(8);
valueFormatted = localizeNumbers(valueFormatted, 8);
var start = valueFormatted.slice(0, -5);
var middle = valueFormatted.slice(-5, -2);
var end = valueFormatted.substr(valueFormatted.length - 2);
buildAmount(start, middle, end);
}
break;
default: // 2
if (isNaN(parsed)) {
buildAmount('-' + getDecimalSeparator() + '--', '', '');
} else {
valueProcessing = parseFloat(parsed.toFixed(2));
valueFormatted = localizeNumbers(valueProcessing, 2);
buildAmount(valueFormatted, '', '');
}
break;
}
$scope.canShow = true;
};
formatNumbers();
$scope.$watchGroup(['currency', 'value'], function onFormattedAmountWatch() {
formatNumbers();
});
/**
* On Android 4.4, toLocaleString() only returns 3 fractional digits when 8 is specified.
*/
function ensureEnoughFractionalDigits(localizedString, number, desiredFractionDigits) {
if (desiredFractionDigits === 0) {
// Assume it is OK
return localizedString;
}
var fractionalRe = /^(\d*\D)(\d+)$/;
var match = fractionalRe.exec(localizedString);
if (match.length !== 3) {
// Don't know what's happening, just return what we have
return localizedString;
}
var decimals = match[2];
var decimalCount = decimals.length;
if (decimalCount >= desiredFractionDigits) {
// Everything is OK.
return localizedString;
}
if (typeof number !== 'number') {
number = parseFloat(number);
}
var fixed = number.toFixed(desiredFractionDigits);
var fixedMatch = fractionalRe.exec(fixed);
if (fixedMatch.length !== 3) {
// Don't know what's happening, just return what we have
return localizedString;
}
// Keeps locale decimal separator.
var enough = match[1] + fixedMatch[2];
return enough;
}
});
}
};
}
);

View file

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

View file

@ -9,12 +9,12 @@ angular.module('copayApp.directives')
scope: {
isShown: '=slideSuccessShow',
onConfirm: '&slideSuccessOnConfirm',
hideOnConfirm: '=slideSuccessHideOnConfirm'
hideOnConfirm: '=slideSuccessHideOnConfirm',
onShare: '=slideSuccessOnShare',
},
link: function(scope, element, attrs) {
scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
scope.isCordova = platformInfo.isCordova;
scope.hasShareFunction = typeof scope.onShare === 'function';
var elm = element[0];
elm.style.display = 'none';
scope.$watch('isShown', function() {
@ -32,6 +32,9 @@ angular.module('copayApp.directives')
elm.style.display = 'none';
}
};
scope.onShareButtonClick = function() {
scope.onShare();
}
}
};
});

View file

@ -236,7 +236,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
})
.state('tabs.receive', {
url: '/receive',
url: '/receive/:walletId',
views: {
'tab-receive': {
controller: 'tabReceiveController',
@ -287,16 +287,44 @@ 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',
views: {
'tab-send@tabs': {
controller: 'amountController',
controllerAs: 'vm',
templateUrl: 'views/amount.html'
}
}
})
.state('tabs.send.wallet-to-wallet', {
url: '/wallet-to-wallet',
views: {
'tab-send@tabs': {
controller: 'walletSelectorController',
templateUrl: 'views/walletSelector.html'
}
}
})
.state('tabs.send.origin', {
url: '/origin',
views: {
'tab-send@tabs': {
controller: 'walletSelectorController',
templateUrl: 'views/walletSelector.html',
}
}
})
.state('tabs.send.destination', {
url: '/destination',
views: {
'tab-send@tabs': {
controller: 'walletSelectorController',
templateUrl: 'views/walletSelector.html',
}
}
})
.state('tabs.send.confirm', {
url: '/confirm/:recipientType/:toAddress/:toName/:toAmount/:toEmail/:toColor/:description/:coin/:useSendMax/:fromWalletId/:displayAddress/:requiredFeeRate',
url: '/confirm',
views: {
'tab-send@tabs': {
controller: 'confirmController',
@ -316,6 +344,19 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
}
})
.state('tabs.send.review', {
url: '/review/:thirdParty/:amount/:fromWalletId/:sendMax/:toAddress/:toWalletId',
views: {
'tab-send@tabs': {
controller: 'reviewController',
controllerAs: 'vm',
templateUrl: 'views/review.html'
}
},
params: {
paypro: null
}
})
/*
*
@ -695,16 +736,17 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
})
.state('tabs.paymentRequest.amount', {
url: '/amount/:coin',
url: '/amount/:toWalletId',
views: {
'tab-receive@tabs': {
controller: 'amountController',
controllerAs: 'vm',
templateUrl: 'views/amount.html'
}
}
})
.state('tabs.paymentRequest.confirm', {
url: '/confirm/:amount/:currency/:coin',
url: '/confirm/:amount/:toWalletId',
views: {
'tab-receive@tabs': {
controller: 'customAmountController',
@ -845,6 +887,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
views: {
'tab-home@tabs': {
controller: 'amountController',
controllerAs: 'vm',
templateUrl: 'views/amount.html'
}
}
@ -910,6 +953,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
views: {
'tab-home@tabs': {
controller: 'amountController',
controllerAs: 'vm',
templateUrl: 'views/amount.html'
}
}
@ -968,7 +1012,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',
@ -1029,6 +1073,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
views: {
'tab-home@tabs': {
controller: 'amountController',
controllerAs: 'vm',
templateUrl: 'views/amount.html'
}
},
@ -1081,6 +1126,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
views: {
'tab-home@tabs': {
controller: 'amountController',
controllerAs: 'vm',
templateUrl: 'views/amount.html'
}
},
@ -1137,6 +1183,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
views: {
'tab-home@tabs': {
controller: 'amountController',
controllerAs: 'vm',
templateUrl: 'views/amount.html'
}
}
@ -1160,7 +1207,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
});
})
.run(function($rootScope, $state, $location, $log, $timeout, startupService, ionicToast, fingerprintService, $ionicHistory, $ionicPlatform, $window, appConfigService, lodash, platformInfo, profileService, uxLanguage, gettextCatalog, openURLService, storageService, scannerService, configService, emailService, /* plugins START HERE => */ buydotbitcoindotcomService, glideraService, amazonService, bitpayCardService, applicationService, mercadoLibreService, rateService) {
.run(function($rootScope, $state, $location, $log, $timeout, startupService, ionicToast, fingerprintService, $ionicHistory, $ionicPlatform, $window, appConfigService, lodash, platformInfo, profileService, uxLanguage, gettextCatalog, openURLService, storageService, scannerService, configService, emailService, /* plugins START HERE => */ buydotbitcoindotcomService, pushNotificationsService, glideraService, amazonService, bitpayCardService, applicationService, mercadoLibreService, rateService) {
$ionicPlatform.ready(function() {
@ -1184,9 +1231,14 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
});
var channel = "firebase";
if (platformInfo.isNW) {
channel = "ga";
configService.whenAvailable(function(config) {
pushNotificationsService.init();
});
//firebaseEventsService.init();
var channel = "ga";
if (platformInfo.isCordova) {
channel = "firebase";
}
// Send a log to test
@ -1264,7 +1316,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');
}

View file

@ -1,5 +1,5 @@
'use strict';
angular.module('copayApp.services').factory('bitcoincomService', function(platformInfo, nextStepsService) {
angular.module('copayApp.services').factory('bitcoincomService', function(gettextCatalog, nextStepsService, platformInfo) {
var root = {};
var credentials = {};
@ -19,42 +19,42 @@ angular.module('copayApp.services').factory('bitcoincomService', function(platfo
var cashGamesItem = {
name: 'games',
title: 'Bitcoin Cash Games',
title: gettextCatalog.getString('Bitcoin Cash Games'),
icon: 'icon-games',
href: 'https://cashgames.bitcoin.com'
};
var newsItem = {
name: 'news',
title: 'News',
title: gettextCatalog.getString('News'),
icon: 'icon-news',
href: 'https://news.bitcoin.com/?utm_source=WalletApp&utm_medium=' + os + '&utm_campaign=News'
};
var poolItem = {
name: 'pool',
title: 'Mining Pool',
title: gettextCatalog.getString('Mining Pool'),
icon: 'icon-mining',
href: 'https://pool.bitcoin.com/?utm_source=WalletApp&utm_medium=' + os + '&utm_campaign=Pool'
};
var toolsItem = {
name: 'tools',
title: 'Tools',
title: gettextCatalog.getString('Tools'),
icon: 'icon-tools',
href: 'https://tools.bitcoin.com/?utm_source=WalletApp&utm_medium=' + os + '&utm_campaign=Tools'
};
var priceChartItem = {
name: 'pricechart',
title: 'Bitcoin Price Charts',
title: gettextCatalog.getString('Bitcoin Price Charts'),
icon: 'icon-chart',
sref: 'tabs.pricechart',
};
var faucetItem = {
name: 'faucet',
title: 'Free Bitcoin Cash',
title: gettextCatalog.getString('Free Bitcoin Cash'),
icon: 'icon-faucet',
href: 'https://free.bitcoin.com/?utm_source=WalletApp&utm_medium=' + os + '&utm_campaign=Faucet'
};

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.services').factory('buyAndSellService', function($log, servicesService, lodash, $ionicScrollDelegate, $timeout) {
angular.module('copayApp.services').factory('buyAndSellService', function(gettextCatalog, $log, servicesService, lodash, $ionicScrollDelegate, $timeout) {
var root = {};
var services = [];
var linkedServices = [];
@ -23,7 +23,7 @@ angular.module('copayApp.services').factory('buyAndSellService', function($log,
if (linkedServices.length == 0) {
servicesService.register({
title: 'Buy Bitcoin',
title: gettextCatalog.getString('Buy Bitcoin'),
name: 'buyandsell',
icon: 'icon-buy-bitcoin2',
sref: 'tabs.buyandsell',

View file

@ -11,10 +11,15 @@ angular.module('copayApp.services').factory('clipboardService', function ($http,
cordova.plugins.clipboard.copy(data);
} else if (platformInfo.isNW) {
nodeWebkitService.writeToClipboard(data);
} else if (navigator && navigator.clipboard) {
$log.debug("Use navigator clipboard.")
navigator.clipboard.writeText(data).catch(function onClipboardError(err) {
$log.debug("Clipboard writing is not supported in your browser..");
});
} else if (clipboard.supported) {
clipboard.copyText(data);
} else {
// No supported
// Not supported
return;
}
};

View file

@ -1,5 +1,5 @@
'use strict'
angular.module('copayApp.services').factory('communityService', function(configService, $log, lodash) {
angular.module('copayApp.services').factory('communityService', function(configService, gettextCatalog, $log, lodash) {
var root = {};
var services = [];
@ -37,14 +37,14 @@ angular.module('copayApp.services').factory('communityService', function(configS
var bchRedditItem = {
name: 'bchreddit',
title: 'Bitcoin Cash Reddit',
title: gettextCatalog.getString('Bitcoin Cash Reddit'),
icon: 'icon-reddit-white',
href: 'http://reddit.com/r/btc'
};
var bitcoincomTwitterItem = {
name: 'bitcoincomTwitter',
title: 'Bitcoin.com Twitter',
title: gettextCatalog.getString('Bitcoin.com Twitter'),
icon: 'icon-twitter-white',
href: 'https://twitter.com/BTCTN'
};

View file

@ -1,5 +1,5 @@
'use strict';
angular.module('copayApp.services').factory('firebaseEventsService', function firebaseEventsService($log, $state, $ionicHistory, sjcl, platformInfo, lodash, appConfigService, profileService, configService) {
angular.module('copayApp.services').factory('firebaseEventsService', function firebaseEventsService($log, platformInfo) {
var root = {};
var useEvents = platformInfo.isCordova && !platformInfo.isWP;

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, bitcoreCash, $rootScope, payproService, scannerService, appConfigService, popupService, gettextCatalog, bitcoinCashJsService) {
angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, bitcoreCash, $rootScope, payproService, scannerService, sendFlowService, appConfigService, popupService, gettextCatalog, bitcoinCashJsService) {
var root = {};
@ -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,35 +75,41 @@ 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
});
// Timeout is required to enable the "Back" button
$timeout(function() {
var params = sendFlowService.getState();
if (amount) {
$state.transitionTo('tabs.send.confirm', {
toAmount: amount,
toAddress: addr,
displayAddress: originalAddress ? originalAddress : addr,
description: message,
coin: coin
});
params.amount = amount;
}
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;
sendFlowService.pushState(params);
$state.transitionTo('tabs.send.amount');
} else {
var params = {
toAddress: addr,
coin: coin,
displayAddress: originalAddress ? originalAddress : addr,
noPrefix: noPrefixInAddress
};
if (shapeshiftData) {
params['fromWalletId'] = shapeshiftData.fromWalletId;
params['minShapeshiftAmount'] = shapeshiftData.minAmount;
params['maxShapeshiftAmount'] = shapeshiftData.maxAmount;
params['shapeshiftOrderId'] = shapeshiftData.orderId;
}
$state.transitionTo('tabs.send.amount', params);
sendFlowService.pushState(params);
$state.transitionTo('tabs.send.origin');
}
}, 100);
}
@ -112,15 +118,20 @@ 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);
handlePayPro(details, coin);
}
});
} 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 {
@ -146,12 +157,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
@ -169,14 +180,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;
@ -212,14 +223,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);
}
}
);
@ -377,46 +388,71 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
'notify': $state.current.name == 'tabs.send' ? false : true
});
$timeout(function() {
$state.transitionTo('tabs.send.amount', {
var stateParams = {
toAddress: toAddress,
coin: coin,
noPrefix: 1
});
};
sendFlowService.pushState(stateParams);
$state.transitionTo('tabs.send.origin');
}, 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) {
console.log(payProData);
var toAddr = payProData.toAddress;
var amount = payProData.amount;
var paymentUrl = payProData.url;
var expires = payProData.expires;
var time = payProData.time;
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)
time = Math.ceil(new Date(time).getTime() / 1000)
}
var name = payProData.domain;
if (payProData.memo.indexOf('eGifter') > -1) {
name = 'eGifter'
} else if (paymentUrl.indexOf('https://bitpay.com') > -1) {
name = 'BitPay';
}
var thirdPartyData = {
id: 'bip70',
amount: amount,
caTrusted: true,
domain: 'bitpay.com',
expires: Math.floor(new Date(payProData.expires).getTime() / 1000),
name: name,
domain: payProData.domain,
expires: expires,
memo: payProData.memo,
network: 'livenet',
requiredFeeRate: payProData.requiredFeeRate,
selfSigned: 0,
time: Math.ceil(new Date(payProData.time).getTime() / 1000),
time: time,
displayAddress: displayAddr,
toAddress: toAddr,
url: payProData.paymentUrl,
url: paymentUrl,
verified: true
};
}
function handlePayPro(payProDetails, coin) {
var stateParams = {
toAmount: payProDetails.amount,
toAddress: payProDetails.toAddress,
description: payProDetails.memo,
paypro: payProDetails,
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();
@ -425,7 +461,8 @@ 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);
sendFlowService.pushState(stateParams); // Need to do more here
$state.transitionTo('tabs.send.origin');
});
});
}

View file

@ -0,0 +1,78 @@
'use strict';
// For BIP70 Payment Protocol
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);
});
}
}

View file

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

View file

@ -427,9 +427,9 @@ angular.module('copayApp.services')
}, function(err, secret) {
if (err) return bwcError.cb(err, gettextCatalog.getString('Error creating wallet'), cb);
var channel = "firebase";
if (platformInfo.isNW) {
channel = "ga";
var channel = "ga";
if (platformInfo.isCordova) {
channel = "firebase";
}
var log = new window.BitAnalytics.LogEvent("wallet_created", [{
"coin": opts.coin
@ -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;

View file

@ -1,4 +1,4 @@
describe('secureStorageService in browser', function(){
xdescribe('secureStorageService in browser', function(){
var localStorage,
sss;
@ -100,7 +100,7 @@ describe('secureStorageService in browser', function(){
});
describe('secureStorageService on desktop', function(){
xdescribe('secureStorageService on desktop', function(){
var desktopSss,
sss;
@ -202,7 +202,7 @@ describe('secureStorageService on desktop', function(){
});
describe('secureStorageService on mobile', function(){
xdescribe('secureStorageService on mobile', function(){
var mobileSss,
sss;

View file

@ -0,0 +1,95 @@
'use strict';
(function(){
angular
.module('copayApp.services')
.factory('sendFlowService', sendFlowService);
function sendFlowService($log) {
var service = {
amount: '',
fromWalletId: '',
sendMax: false,
thirdParty: null,
toAddress: '',
toWalletId: '',
previousStates: [],
// Functions
clear: clear,
getState: getState,
map: map,
popState: popState,
pushState: pushState,
startSend: startSend
};
return service;
function clear() {
console.log("sendFlow clear()");
clearCurrent();
service.previousStates = [];
}
function clearCurrent() {
console.log("sendFlow clearCurrent()");
service.amount = '';
service.fromWalletId = '';
service.sendMax = false;
service.thirdParty = null;
service.toAddress = '';
service.toWalletId = '';
}
/**
* Handy for debugging
*/
function getState() {
var currentState = {};
Object.keys(service).forEach(function forCurrentParam(key) {
if (typeof service[key] !== 'function' && key !== 'previousStates') {
currentState[key] = service[key];
}
});
return currentState;
}
/**
* Clears all previous state
*/
function startSend(params) {
console.log('startSend()');
clear();
map(params);
}
function map(params) {
Object.keys(params).forEach(function forNewParam(key) {
service[key] = params[key];
});
};
function popState() {
console.log('sendFlow pop');
if (service.previousStates.length) {
var params = service.previousStates.pop();
clearCurrent();
map(params);
} else {
clear();
}
};
function pushState(params) {
console.log('sendFlow push');
var currentParams = getState();
service.previousStates.push(currentParams);
clearCurrent();
map(params);
};
};
})();

View file

@ -1,7 +1,141 @@
'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, $interval, $log, lodash, moment, ongoingProcess, shapeshiftApiService, storageService, configService, incomingData, platformInfo, servicesService) {
var root = {};
var credentials = {};
root.ShiftState = 'Shift';
root.coinIn = '';
root.coinOut = '';
root.withdrawalAddress = '';
root.returnAddress = '';
root.amount = '';
root.marketData = {};
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.err) return true;
return false;
}
root.shiftIt = function (coinIn, coinOut, withdrawalAddress, returnAddress, cb) {
ongoingProcess.set('connectingShapeshift', true);
root.withdrawalAddress = withdrawalAddress;
root.returnAddress = returnAddress;
root.coinIn = coinIn;
root.coinOut = coinOut;
shapeshiftApiService.ValidateAddress(withdrawalAddress, coinOut).then(function (valid) {
var tx = ShapeShift();
var coin;
console.log("Starting");
tx.then(function (txData) {
console.log("Got txData", txData);
if (txData['fixedTxData']) {
txData = txData.fixedTxData;
if (checkForError(txData)) return cb(txData.err);
//console.log(txData)
var coinPair = txData.pair.split('_');
txData.depositType = coinPair[0].toUpperCase();
txData.withdrawalType = coinPair[1].toUpperCase();
coin = root.coins[txData.depositType].name.toLowerCase();
txData.depositQR = coin + ":" + txData.deposit + "?amount=" + txData.depositAmount;
root.txFixedPending = true;
} else if (txData['normalTxData']) {
txData = txData.normalTxData;
if (checkForError(txData)) return cb(txData.err);
coin = root.coins[txData.depositType.toUpperCase()].name.toLowerCase();
txData.depositQR = coin + ":" + txData.deposit;
} else if (txData['cancelTxData']) {
txData = txData.cancelTxData;
if (checkForError(txData)) return cb(txData.err);
if (root.txFixedPending) {
root.txFixedPending = false;
}
root.ShiftState = 'Shift';
}
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');
ongoingProcess.set('connectingShapeshift', false);
root.ShiftState = 'Cancel';
//root.GetStatus();
//root.txInterval=$interval(root.GetStatus, 8000);
var shapeshiftData = {
coinIn: coinIn,
coinOut: coinOut,
toWalletId: root.toWalletId,
minAmount: root.marketData.minimum,
maxAmount: root.marketData.maxLimit,
orderId: root.depositInfo.orderId,
toAddress: txData.deposit
};
//
// if (incomingData.redir(sendAddress, 'shapeshift', shapeshiftData)) {
ongoingProcess.set('connectingShapeshift', false);
// return;
// }
cb(null, shapeshiftData);
});
})
};
function ShapeShift() {
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',

View file

@ -414,7 +414,7 @@ xdescribe('storageService on desktop', function(){
});
describe('storageService on desktop using local storage', function(){
xdescribe('storageService on desktop using local storage', function(){
var appConfig,
localStorageServiceMock,
log,
@ -614,7 +614,7 @@ describe('storageService on desktop using local storage', function(){
});
describe('storageService on mobile', function(){
xdescribe('storageService on mobile', function(){
var appConfig,
expectedOldProfileSavedToSecure,
expectedOldProfileMergedWithSecure,

View file

@ -201,7 +201,7 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
var alternativeIsoCode = config.alternativeIsoCode;
// If fiat currency
if (currency != 'BCH' && currency != 'BTC' && currency != 'sat') {
if (currency && currency.toUpperCase() != 'BCH' && currency.toUpperCase() != 'BTC' && currency != 'sat') {
amountUnitStr = $filter('formatFiatAmount')(amount) + ' ' + currency;
amountSat = rateService.fromFiat(amount, currency, coin).toFixed(0);
} else if (currency == 'sat') {

View file

@ -899,7 +899,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../))) {
@ -917,6 +917,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);
}

View file

@ -73,5 +73,17 @@
&.activated {
color: #FFF;
}
&-outline {
@include button-style(transparent, #FFFFFF, #FAFAFA, #FFF, #FFFFFF);
@include button-outline(#FFFFFF);
background: none;
box-shadow: none;
}
}
&-grey-outline {
@include button-style(transparent, #727272, #FAFAFA, #727272, #727272);
@include button-outline(#727272);
background: none;
box-shadow: none;
}
}

View file

@ -0,0 +1,24 @@
.action-minor {
margin: 20px 14px;
font-size: 14px;
&.mt-negative {
margin-top: 0;
}
&.text-right {
text-align: right;
}
> .action-icon {
width: 15px;
height: 15px;
vertical-align: middle;
margin-right: 3px;
}
> .action-text {
vertical-align: middle;
color: #444444;
}
}

View file

@ -0,0 +1,27 @@
.address-frame {
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;
}
}

View file

@ -0,0 +1,5 @@
.card {
&.card-gutter-compact {
margin: 10px 12px;
}
}

View file

@ -0,0 +1,11 @@
@import "item";
@import "ion-content";
@import "card";
@import "header";
@import "content-frame";
@import "address-frame";
@import "action-minor";
@import "expand-content";
@import "fee-summary";
@import "formatted-amount";

View file

@ -0,0 +1,11 @@
.content-frame {
&.negative-top {
margin-top: -40px;
.card {
&:first-child {
margin-top: 0;
}
}
}
}

View file

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

View file

@ -0,0 +1,40 @@
.fee-summary {
position: relative;
display: flex;
flex-direction: column;
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%);
}
.amount {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
.fee-fiat {
&.positive {
color: #70955F;
}
&.negative {
color: #C24633;
}
}
.fee-crypto {
color: #A7A7A7;
}
}
}

View file

@ -0,0 +1,37 @@
.formatted-amount {
display: inline-block;
.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;
}
&.size-equal {
.middle,
.end {
font-size: 1em;
}
}
.currency {
font-size: 1em;
margin-left: 5px;
text-transform: uppercase;
}
}

View file

@ -0,0 +1,39 @@
.header {
padding: 29px 12px 61px;
background-color: $v-bitcoin-orange;
&.btc {
background-color: $v-bitcoin-core;
}
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;
}
}
}
}

View file

@ -0,0 +1,17 @@
/*
* Extends Ionic v1 ion-content
*/
ion-content {
&.bg-neutral {
background-color: #F2F2F2;
}
&.padded-bottom-cta {
bottom: 92px;
}
&.padded-bottom-cta-with-summary {
bottom: 134px;
}
}

View file

@ -0,0 +1,48 @@
/*
* Extends Ionic v1 item
*/
.item {
&.item-compact {
padding: 11px 13px;
}
&.item-gutterless {
padding: 0;
}
.item-content {
&.item-content-avatar {
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%);
}
}
}
&.item-content-compact {
min-height: 0;
padding: 13px 11px;
}
.highlight {
color: #FAB915
}
+ .item-content {
padding-top: 0;
}
}
}

View file

@ -1 +1,2 @@
@import "gravatar";
@import "elastic";

View file

@ -0,0 +1,4 @@
.elastic {
width: 100%;
font-size: 14px;
}

View file

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

View file

@ -9,4 +9,5 @@
@import "mixins/mixins";
@import "views/views";
@import "directives/directives";
@import "components/components";
@import "shame";

View file

@ -18,3 +18,53 @@
.absolute-center{
@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;
.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;
}
}

View file

@ -5,8 +5,8 @@ qrcode {
content: "";
background-size: 100% 100%;
display: block;
left: 88px;
margin-top: 88px;
left: calc(50% - 22px);
margin-top: calc(50% - 22px);
width: 44px;
height: 44px;
position:absolute;

View file

@ -233,6 +233,10 @@ input[type=number] {
font-size: 24px;
}
.size-25 {
font-size: 25px;
}
.size-28 {
font-size: 28px;
}
@ -464,3 +468,7 @@ input[type=file] {
.white-space-initial {
white-space: initial;
}
.height-spacer {
height: 15px;
}

View file

@ -8,7 +8,9 @@ $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;
$v-mid-gray: #667 !default;
$v-light-gray: #9b9bab !default;
@ -24,8 +26,11 @@ $v-text-accent-color: #647ce8 !default;
$v-success-color: #13e5b6 !default;
$v-warning-color: #ffa500 !default;
$v-warning-color-2: #b7664d;
$v-error-color: #ef473a !default;
$v-background-under-card: #f2f2f2;
$v-wallet-color-map: (
0: (color: #dd4b39, name: 'Cinnabar'),
1: (color: #f38f12, name: 'Carrot Orange'),
@ -77,6 +82,7 @@ $v-button-primary-active-bg: darken($v-accent-color, 10%
$v-button-primary-active-border: transparent !default;
$v-button-primary-clear-bg: none !default;
$v-button-primary-clear-color: $v-accent-color !default;
$v-button-primary-disabled-bg: $v-mid-gray;
$v-button-primary-outline-bg: transparent !default;
$v-button-primary-outline-border: $v-accent-color !default;
$v-button-primary-outline-color: $v-accent-color !default;

View file

@ -244,6 +244,21 @@
flex-direction: column;
justify-content: center;
.send-amount-header-footer {
flex: 1 1 auto;
min-height: 20px;
.warning {
font-weight: bold;
font-size: 12px;
padding: 0 6px 6px 6px;
text-align: center;
}
&__max {
float: right;
}
}
.send-amount-tool {
flex: 0 1 auto;
@ -260,6 +275,8 @@
}
.primary-amount {
color: #333;
font-weight: bold;
input, .unit, .primary-amount-display {
font-size: 1.8em;
@ -329,16 +346,15 @@
line-height: 1em;
}
.unit {
font-weight: bold;
}
.primary-amount-display {
margin-right: 5px;
word-break: break-all;
}
}
.alternative-amount {
color: #6F6F70;
}
.switch-currencies {
position: absolute;
right: 0;
@ -351,27 +367,56 @@
}
}
}
}
}
.send-amount-actions {
margin-top: 15px;
.send-amount-extras {
display: flex;
flex: 0 0 auto;
/* So that if only one item is present, it appears on the right. */
flex-direction: row-reverse;
font-size: 12px;
align-items: center;
justify-content: space-between;
margin: 0 14px;
.available-funds {
color: #6F6F70;
}
.warning {
color: $v-warning-color-2;
}
.extra,
button.extra {
/*display: flex;*/
flex: 0 1 auto;
}
button.extra {
background: none;
border: none;
color: #000;
font-family: 'ProximaNova';
font-size: 14px;
line-height: normal;
min-height: auto;
min-width: auto;
padding: 0;
}
.button .icon:before {
font-size: 14px;
line-height: normal;
}
.button {
span {
display: flex;
align-items: center;
justify-content: center;
.button {
flex: 1 1 auto;
line-height: 1.2em;
+ .button {
margin-left: 10px;
}
span {
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
}
@ -394,37 +439,58 @@
.keypad-container {
position: relative;
font-size: 18px;
line-height: 2em;
//flex: 0 1 196px;
@media (min-height: 667px) {
font-size: 24px;
}
@media(max-height: 480px) {
font-size: 12px;
}
@media (min-height: 667px) {
//flex: 0 1 224px;
}
.sendmax {
background: $v-off-black;
.button {
color: white;
background: black;
border: 1px solid $v-off-black;
border-radius: 0;
font-size: 0.8em;
line-height: 2em;
width: 100%;
.available-funds-amount {
color: #C9C9C9;
}
&:active {
background-color: $v-dark-gray;
}
}
}
.keypad {
text-align: center;
font-size: 18px;
font-weight: lighter;
position: absolute;
bottom: 0;
width: 100%;
color: $v-mid-gray;
color: $v-text-primary-color;
@media (min-height: 667px) {
font-size: 24px;
}
.row {
padding: 0 !important;
margin: 0 !important;
}
.col {
line-height: 38px;
@media (min-height: 667px) {
line-height: 45px;
}
}
.row {
&:last-child {
@ -458,23 +524,34 @@
.digit{
cursor: pointer;
border-top: 1px solid $v-subtle-gray;
border-left: 1px solid $v-subtle-gray;
background-color: #000;
border: 1px solid $v-off-black;
transition: all 0.1s ease;
&:active {
background-color: $v-subtle-gray;
background-color: $v-dark-gray;
}
}
@media(max-height: 480px) {
font-size: 12px;
}
}
}
.button-primary {
background-color: $v-primary-color;
border-radius: 0;
font-weight: bold;
}
.button-primary[disabled] {
background-color: $v-button-primary-disabled-bg;
opacity: 1;
}
}
background: #494949;
.warning {
color: $v-warning-color-2;
}
background: $v-background-under-card;
ion-content {
margin-bottom: constant(safe-area-inset-bottom); /* iOS 11.0 */

View file

@ -26,16 +26,10 @@
height: 100%;
.qr-code {
text-align: center;
margin-top: 24vh;
margin-bottom: 7vh;
@media(max-height: 800px) {
margin-top: 18vh;
}
@media(max-height: 700px) {
margin-top: 14vh;
}
@media(max-height: 600px) {
margin-top: 8vh;
margin-top: 6px;
qrcode canvas {
height: 30vh;
max-height: 220px;
}
}
.info {
@ -91,5 +85,34 @@
.address-types {
text-align: center;
}
.amount {
margin-top: 20vh;
margin-bottom: 4vh;
@media(max-height: 800px) {
margin-top: 12vh;
margin-bottom: 6vh;
}
@media(max-height: 700px) {
margin-top: 10vh;
margin-bottom: 4vh;
}
@media(max-height: 600px) {
margin-top: 6vh;
margin-bottom: 2vh;
}
width: 100%;
text-align: center;
//padding-top: 30px;
display: block;
align-items: center;
justify-content: center;
&-alternative {
line-height: 36px;
}
}
}
}

View file

@ -12,12 +12,6 @@ slide-to-accept-success {
.slide-success {
$duration: 400ms;
&__windows-background {
background: $v-success-bg-color;
height: 100%;
width: 100%;
position: fixed;
}
&__background {
$start-radius: 5;
$scale-factor: 20;
@ -40,9 +34,11 @@ slide-to-accept-success {
&__content {
position: relative;
z-index: 1;
margin-top: -20vh;
margin-top: -10vh;
> img {
width: 45vw;
max-width: 166px;
margin-bottom: 1.8rem;
-webkit-transform: translateY(5rem);
transform: translateY(5rem);
@ -59,7 +55,7 @@ slide-to-accept-success {
&__header {
color: #FFFFFF;
font-size: 26px;
font-size: 29px;
-webkit-transform: translateY(5rem);
transform: translateY(5rem);
opacity: 0;
@ -72,6 +68,26 @@ slide-to-accept-success {
opacity: 1;
}
}
&__share {
transition: transform $duration ease, opacity $duration ease;
transition-delay: 600ms;
opacity: 0;
margin-top: 15vh;
span {
color: #FFF;
font-size: 22px;
height: 28px;
}
img {
height: 28px;
width: auto;
vertical-align: bottom;
margin-right: 4px;
}
&.reveal {
opacity: 0.79;
}
}
}
&__footer {
@ -98,11 +114,11 @@ slide-to-accept-success {
&__btn {
display: block;
color: #FFFFFF;
font-size: 18px;
font-size: 22px;
font-weight: 600;
letter-spacing: 2.86px;
padding: 1rem 0 1.1rem;
border-top: 1px solid rgba(255, 255, 255, .45);
padding: 2rem 0 2.1rem;
border-top: 1px solid rgba(255, 255, 255, 0.25);
cursor: pointer;
}
}

View file

@ -0,0 +1,21 @@
#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 */
}
.fee-summary {
position: absolute;
bottom: 92px;
}
.shapeshift-banner, .bitpay-banner, .egifter-banner {
box-shadow: none;
}
.warning {
color: $v-warning-color-2;
}
}

View file

@ -0,0 +1,23 @@
#shapeshift {
.swap-image {
width: auto;
max-width: 400px;
max-height: 25vh;
}
.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();
}
}
.header.shapeshift {
background: url(../img/shapeshiftbg.jpg) center center repeat #28394d;
opacity: 0.99;
}

View file

@ -69,6 +69,30 @@
}
}
}
.buttons {
display: flex;
flex-direction: row;
justify-content: space-evenly;
margin: 6px auto -12px;
text-align: center;
width: 100%;
>.col {
padding: 5px 10px;
margin-bottom: 0;
}
.button {
border: 2px solid;
border-radius: 47px;
padding: 0 15px 0 15px;
text-align: center;
width: 100%;
max-width: 300px;
font-size: 19px;
font-weight: bolder;
min-height: auto;
line-height: 36px;
}
}
.wallet-coin-logo {
vertical-align: middle;
margin-right: 5px;

View file

@ -2,15 +2,15 @@
@extend .deflash-blue;
&-header{
height: 300px;
//height: 300px;
width: 100%;
}
&-contacts {
height: calc(100vh - 300px - 50px - 44px); /* screen size - button container - bottom-tab-menu - header top */
//height: calc(100vh - 300px - 50px - 44px); /* screen size - button container - bottom-tab-menu - header top */
&.ios {
height: calc(100vh - 300px - 50px - 44px - 18px); // Remove the notification-bar height on iOS
//height: calc(100vh - 300px - 50px - 44px - 18px); // Remove the notification-bar height on iOS
}
overflow: scroll;
//overflow: scroll;
}
.input {
@ -88,6 +88,8 @@
&.contains-address {
.address {
display: inline;
border: none;
background-color: transparent;
}
.non-address {
display: none;
@ -133,42 +135,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;
@ -256,12 +223,12 @@
}
}
#tab-send-header {
height: 270px;
//height: 270px;
}
#tab-send-contacts {
height: calc(100vh - 270px - 50px - 44px); /* screen size - button container - bottom-tab-menu - header top */
//height: calc(100vh - 270px - 50px - 44px); /* screen size - button container - bottom-tab-menu - header top */
&.ios {
height: calc(100vh - 270px - 50px - 44px - 18px); // Remove the notification-bar height on iOS
//height: calc(100vh - 270px - 50px - 44px - 18px); // Remove the notification-bar height on iOS
}
}
}

View file

@ -8,11 +8,13 @@
@import "tab-receive";
@import "tab-scan";
@import "tab-send";
@import "wallet-origin-destination";
@import "tab-settings";
@import "wallet-colors";
@import "walletBalance";
@import "walletDetails";
@import "advancedSettings";
@import "shapeshift";
@import "bitpayCard";
@import "bitpayCardIntro";
@import "buyandsell";
@ -48,3 +50,4 @@
@import "includes/logOptions";
@import "includes/checkBar";
@import "cashScan";
@import "review";

View file

@ -0,0 +1,74 @@
#wallet-origin-destination {
.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 {
margin: 20px 14px 0px;
.title {
font-size: 16px;
font-weight: bold;
color: $v-dark-gray;
margin-bottom: -12px;
}
}
.card {
font-size: 12px;
margin: 20px 14px 0px;
.item-heading {
.subtitle {
font-size: 12px;
}
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;
}
}
}
}

View file

@ -135,11 +135,12 @@
&.status-bar {
margin-top: 20px;
margin-top: env(safe-area-inset-top);
}
}
.bar-header {
border: 0;
background: none;
background: rgb(238, 182, 64);
.title, .button {
color: #fff;
}
@ -153,7 +154,7 @@
ion-content {
&.collapsible {
margin-top: 210px;
margin-top: 230px;
}
padding-top: 0;
@ -190,12 +191,38 @@
transform: translateY(100px);
}
}
.send-receive-buttons {
display: flex;
flex-direction: row;
justify-content: space-evenly;
width: 100%;
position: absolute;
bottom: 20px;
>.col {
padding: 5px 10px;
margin-bottom: 0;
}
.button {
border: 2px solid;
border-radius: 47px;
padding: 0 15px 0 15px;
text-align: center;
width: 100%;
max-width: 300px;
font-size: 19px;
font-weight: bolder;
min-height: auto;
line-height: 36px;
}
}
}
.amount {
width: 100%;
text-align: center;
color: #fff;
height: 210px;
height: 230px;
padding-top: 40px;
display: block;
align-items: center;

View file

@ -17,6 +17,8 @@ module.exports = function(config) {
files: [
'node_modules/angular/angular.js',
'bitanalytics/bitanalytics-0.1.0.js',
// From Gruntfile.js
'bower_components/qrcode-generator/js/qrcode.js',
'bower_components/qrcode-generator/js/qrcode_UTF8.js',

View file

@ -318,5 +318,33 @@ div.slide-success__background.fill-screen {
display: block;
float: left;
max-height: 100%;
max-width: 100%;
max-width: 100%;
}
.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;
}
.egifter-banner {
background: #066EAA;
padding: 10px;
box-shadow: 0px 5px 10px 0px #cccccc;
height: 5em;
text-align: center;
}
.egifter-logo {
max-height: 100%;
max-width: 100%;
height: 4em;
}

File diff suppressed because it is too large Load diff

13
www/img/bitpay_banner.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
www/img/egifter_banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="17px" height="17px" viewBox="0 0 17 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: sketchtool 40.1 (33804) - http://www.bohemiancoding.com/sketch -->
<title>3A719124-019D-470F-908A-5D61F117A295</title>
<desc>Created with sketchtool.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Icons" transform="translate(-324.000000, -770.000000)" stroke="#000000" stroke-width="0.7">
<g id="icons/list-items/sync" transform="translate(324.000000, 770.000000)">
<g id="Group" transform="translate(0.365217, 0.365217)">
<polyline id="Shape" points="10.5913043 11.6869565 0 11.6869565 0 0 16.0695652 0 16.0695652 7.96173913"></polyline>
<ellipse id="Oval" cx="8.03478261" cy="5.84347826" rx="1.46086957" ry="1.46086957"></ellipse>
<path d="M13.8782609,6.57391304 L13.8782609,4.3826087 C12.6365217,4.3826087 11.6869565,3.43304348 11.6869565,2.19130435 L4.3826087,2.19130435 C4.3826087,3.43304348 3.43304348,4.3826087 2.19130435,4.3826087 L2.19130435,7.30434783 C3.43304348,7.30434783 4.3826087,8.25391304 4.3826087,9.49565217 L10.5913043,9.49565217" id="Shape"></path>
<path d="M15.0469565,13.5130435 C15.6313043,13.8052174 16.0695652,14.1704348 16.0695652,14.6086957 C16.0695652,15.4121739 14.7547826,16.0695652 13.1478261,16.0695652 C11.5408696,16.0695652 10.226087,15.4121739 10.226087,14.6086957 C10.226087,14.1704348 10.5913043,13.8052174 11.2486957,13.5130435" id="Shape"></path>
<path d="M15.0469565,11.3217391 C15.6313043,11.613913 16.0695652,11.9791304 16.0695652,12.4173913 C16.0695652,13.2208696 14.7547826,13.8782609 13.1478261,13.8782609 C11.5408696,13.8782609 10.226087,13.2208696 10.226087,12.4173913 C10.226087,11.9791304 10.5913043,11.613913 11.1756522,11.3217391" id="Shape"></path>
<path d="M15.0469565,9.13043478 C15.6313043,9.4226087 16.0695652,9.78782609 16.0695652,10.226087 C16.0695652,11.0295652 14.7547826,11.6869565 13.1478261,11.6869565 C11.5408696,11.6869565 10.226087,11.0295652 10.226087,10.226087 C10.226087,9.78782609 10.5913043,9.4226087 11.1756522,9.13043478" id="Shape"></path>
<ellipse id="Oval" cx="13.1478261" cy="8.03478261" rx="2.92173913" ry="1.46086957"></ellipse>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

11
www/img/icon-bookmark.svg Normal file
View file

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 11.25 15">
<defs>
<style>
.cls-1 {
fill: #444;
opacity: 0.564;
}
</style>
</defs>
<path id="_ionicons_svg_md-bookmark" class="cls-1" d="M121.688,64h-8.125A1.567,1.567,0,0,0,112,65.563V79l5.625-2.5L123.25,79V65.563A1.567,1.567,0,0,0,121.688,64Z" transform="translate(-112 -64)"/>
</svg>

After

Width:  |  Height:  |  Size: 381 B

BIN
www/img/icon-egifter.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View file

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 166 166">
<defs>
<style>
.cls-1 {
fill: #fff;
}
</style>
</defs>
<path id="_ionicons_svg_md-checkmark-circle-outline" class="cls-1" d="M96.969,115.231,85.35,126.85,122.7,164.2l83-83L194.081,69.581,122.7,140.544,96.969,115.231ZM197.4,131a66.116,66.116,0,1,1-48.138-63.91l12.863-12.865A77.206,77.206,0,0,0,131,48a83,83,0,1,0,83,83Z" transform="translate(-48 -48)"/>
</svg>

After

Width:  |  Height:  |  Size: 458 B

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="400px" height="400px" viewBox="56 56 400 400" enable-background="new 56 56 400 400" xml:space="preserve">
<g>
<g>
<path fill="#FFFFFF" d="M350.833,297.017c-17.517,0-32.967,8.346-43.062,21.124l-93.629-47.881
c1.226-4.571,2.106-9.296,2.106-14.267c0-5.412-1.046-10.507-2.506-15.465l93.216-47.661
c10.039,13.358,25.889,22.089,43.888,22.089c30.489,0,55.153-24.664,55.153-55.125c0-30.435-24.664-55.112-55.152-55.112
c-30.42,0-55.111,24.677-55.111,55.111c0,4.985,0.882,9.723,2.12,14.308l-93.615,47.882
c-10.108-12.793-25.587-21.166-43.131-21.166c-30.462,0-55.111,24.691-55.111,55.139c0,30.447,24.65,55.125,55.111,55.125
c18.026,0,33.863-8.758,43.943-22.129l93.174,47.66c-1.46,4.943-2.52,10.081-2.52,15.507c0,30.447,24.691,55.125,55.111,55.125
c30.489,0,55.152-24.678,55.152-55.125C405.985,321.681,381.322,297.017,350.833,297.017z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
www/img/shapeshift_swap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

View file

@ -1,82 +1,96 @@
<ion-view id="view-amount" hide-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>
{{'Enter amount' | translate}}
{{'Enter Amount' | translate}}
</ion-nav-title>
<ion-nav-back-button ng-click="goBack()"></ion-nav-back-button>
<ion-nav-back-button ng-click="vm.goBack()"></ion-nav-back-button>
</ion-nav-bar>
<ion-content scroll="false" style="background: #fff;">
<ion-content scroll="false">
<div style="order: 0; position: relative;">
<div class="item send-amount">
<div ng-if="shapeshiftOrderId">
Minimum amount: {{minShapeshiftAmount}} <br/>
Maximum amount: {{maxShapeshiftAmount}} <br/>
<div class="card item send-amount">
<div class="send-amount-header-footer">
<span class="send-amount-header-footer__min" ng-if="vm.minAmount">Min: {{vm.minAmount}}</span> <span class="send-amount-header-footer__max" ng-if="vm.maxAmount">Max: {{vm.maxAmount}}</span>
</div>
<div class="send-amount-tool">
<div class="send-amount-tool-input amount">
<div class="primary-amount"
ng-class="{long: amountModel.amount.length > 5, 'very-long': amountModel.amount.length > 10}">
<span class="primary-amount-display text-selectable">{{ amountModel.amount || 0 }}</span><span class="unit">{{unit}}</span>
ng-class="{long: vm.amount.length > 5, 'very-long': vm.amount.length > 10}">
<span class="primary-amount-display text-selectable">
<formatted-amount value="{{vm.amount || '0'}}" currency="{{vm.unit}}"></formatted-amount>
</span>
</div>
<span ng-show="globalResult">{{globalResult}} {{unit}}</span>
<span ng-show="vm.globalResult"><formatted-amount value="{{vm.globalResult}}" currency="{{vm.unit}}"></formatted-amount></span>
<div class="alternative-amount">
<span class="text-selectable">{{alternativeAmount || '0.00'}}</span> <span>{{alternativeUnit}}</span>
<span class="text-selectable"><formatted-amount value="{{vm.alternativeAmount || '0.00'}}" currency="{{vm.alternativeUnit}}"></formatted-amount></span>
</div>
<div class="switch-currencies" ng-click="changeUnit()"><img src="img/icon-convert.svg"></div>
<div class="switch-currencies" ng-click="vm.changeUnit()"><img src="img/icon-convert.svg"></div>
</div>
<div class="send-amount-actions text-center">
<button class="button button-sendmax" ng-click="sendMax()">
<span>
<i class="icon ion-ios-speedometer-outline"></i>&emsp;
<span translate>Send max amount</span>
</span>
</button>
<button class="button button-sendmax" ng-click="openPopup()">
<span>
<i class="icon ion-social-usd"></i>&emsp;
<span translate>Change currency</span>
</span>
</button>
<div class="send-amount-header-footer">
<div class="warning" ng-show="vm.errorMessage">
{{vm.errorMessage}}
</div>
</div>
</div>
</div>
</div>
<div class="send-amount-extras text-center">
<button class="extra button" ng-click="vm.openPopup()">
<span>
<img src="img/icon-alternative-currency-black.svg"/>
&ensp;
<span translate>Change Currency</span>
</span>
</button>
<div class="extra available-funds"
ng-class="{warning: vm.fundsAreInsufficient}"
ng-if="!vm.isRequestingSpecificAmount" translate>
<span>Available Funds:</span>&ensp;<span><formatted-amount value="{{vm.availableFunds}}" size-equal="true"></formatted-amount></span>
</div>
</div>
</div>
<div class="keypad-container" style="background: #fff; position: absolute; bottom: 0; margin-bottom: 57px; width: 100%;">
<div class="keypad" style="background: #f2f2f2; position: relative;">
<div class="sendmax" ng-if="vm.availableFunds && !vm.isRequestingSpecificAmount">
<button class="button button-sendmax" ng-click="vm.sendMax()">
<span>
<span translate>Use All Available Funds</span>&ensp;
<span class="available-funds-amount">(<formatted-amount value="{{vm.availableFunds}}"></formatted-amount>)</span>
</span>
</button>
</div>
<div class="keypad" style="position: relative;">
<div class="row">
<div class="col digit" ng-click="pushDigit('7')">7</div>
<div class="col digit" ng-click="pushDigit('8')">8</div>
<div class="col digit" ng-click="pushDigit('9')">9</div>
<div class="col digit" ng-click="vm.pushDigit('7')">7</div>
<div class="col digit" ng-click="vm.pushDigit('8')">8</div>
<div class="col digit" ng-click="vm.pushDigit('9')">9</div>
</div>
<div class="row">
<div class="col digit" ng-click="pushDigit('4')">4</div>
<div class="col digit" ng-click="pushDigit('5')">5</div>
<div class="col digit" ng-click="pushDigit('6')">6</div>
<div class="col digit" ng-click="vm.pushDigit('4')">4</div>
<div class="col digit" ng-click="vm.pushDigit('5')">5</div>
<div class="col digit" ng-click="vm.pushDigit('6')">6</div>
</div>
<div class="row">
<div class="col digit" ng-click="pushDigit('1')">1</div>
<div class="col digit" ng-click="pushDigit('2')">2</div>
<div class="col digit" ng-click="pushDigit('3')">3</div>
<div class="col digit" ng-click="vm.pushDigit('1')">1</div>
<div class="col digit" ng-click="vm.pushDigit('2')">2</div>
<div class="col digit" ng-click="vm.pushDigit('3')">3</div>
</div>
<div class="row">
<div class="col digit" ng-click="pushDigit('.')">.</div>
<div class="col digit" ng-click="pushDigit('0')">0</div>
<div class="col digit icon ion-backspace-outline" ng-click="removeDigit()"></div>
<div class="col digit" ng-click="vm.pushDigit('.')">.</div>
<div class="col digit" ng-click="vm.pushDigit('0')">0</div>
<div class="col digit icon ion-backspace-outline" ng-click="vm.removeDigit()"></div>
</div>
</div>
</div>
<button
class="button button-full button-primary no-margin"
ng-disabled="!allowSend"
ng-click="finish()"
ng-disabled="!vm.allowSend"
ng-click="vm.finish()"
style="position: absolute; bottom: 0;"
translate>
Next

View file

@ -105,13 +105,13 @@
</ion-content>
<click-to-accept
ng-click="approve(tx, wallet, statusChangeHandler)"
ng-if="(!isCordova || isWindowsPhoneApp) && wallet"
ng-if="(!isCordova) && wallet"
click-send-status="sendStatus"
is-disabled="!wallet">
{{buttonText}}
</click-to-accept>
<slide-to-accept
ng-if="isCordova && !isWindowsPhoneApp && wallet"
ng-if="isCordova && wallet"
slide-on-confirm="approve(tx, wallet, statusChangeHandler)"
slide-send-status="sendStatus"
is-disabled="!wallet">
@ -120,6 +120,7 @@
<slide-to-accept-success
slide-success-show="sendStatus === 'success'"
slide-success-on-confirm="onSuccessConfirm()"
slide-success-on-share="shareTransaction"
slide-success-hide-on-confirm="true">
<span ng-show="wallet.m == 1 && (wallet.canSign() || wallet.isPrivKeyExternal())" translate>Payment Sent</span>
<span ng-show="wallet.m > 1 && (wallet.canSign() || wallet.isPrivKeyExternal())" translate>Proposal Created</span>

View file

@ -30,8 +30,22 @@
<br/>Return To Address<br/>
</p>
</div>
<div ng-show="!showingPaymentReceived" class="amount">
<div ng-show="selectedPriceDisplay=='fiat'">
<span class="size-36">{{amountUnitStr}}</span>
<div class="size-14 amount-alternative">
{{altAmountStr | uppercase}}
</div>
</div>
<div ng-show="selectedPriceDisplay=='crypto'">
<span class="size-36">{{altAmountStr | uppercase}}</span>
<div class="size-14 amount-alternative">
{{amountUnitStr}}
</div>
</div>
</div>
<div ng-show="!showingPaymentReceived" class="qr-code" copy-to-clipboard="copyToClipboard()">
<qrcode size="220" data="{{ protocolHandler }}:{{address + '?amount=' + amountBtc}}" color="#334"></qrcode>
<qrcode class="qr-overlay qr-overlay--{{ wallet.coin }}" size="220" data="{{ protocolHandler }}:{{address + '?amount=' + amountBtc}}" color="#334"></qrcode>
</div>
<div ng-show="!showingPaymentReceived" ng-show="address && coin == 'bch'" class="address-types">
<div>
@ -57,12 +71,6 @@
{{address}}
</span>
</div>
<div class="item single-line">
<span class="label" translate>Amount</span>
<span class="item-note">
{{amountUnitStr}} - {{altAmountStr}}
</span>
</div>
<div class="item single-line">
<div class="wallet">
<i class="icon big-icon-svg" ng-include="'views/includes/walletIcon.html'"></i>

View file

@ -0,0 +1,4 @@
<div class="formatted-amount"
ng-class="{ 'size-equal': displaySizeEqual }" ng-show="canShow">
<span ng-if="start.length > 0" class="start">{{start}}</span><span ng-if="middle.length > 0" class="middle">{{middle}}</span><span ng-if="end.length > 0" class="end">{{end}}</span><span ng-if="currency.length > 0" class="currency">{{currency}}</span>
</div>

View file

@ -1,13 +1,16 @@
<div
class="slide-success__background"
ng-class="{'fill-screen': fillScreen, 'slide-success__windows-background': isWindowsPhoneApp}">
ng-class="{'fill-screen': fillScreen}">
</div>
<div ng-disabled="wallet" class="slide-success__content">
<img src="img/onboarding-success.svg" ng-class="{reveal: fillScreen}">
<img src="img/icon-sent-successful.svg" ng-class="{reveal: fillScreen}">
<div class="slide-success__content__header" ng-class="{reveal: fillScreen}">
<ng-transclude>Payment Sent</ng-transclude>
</div>
<div class="slide-success__content__share" ng-if="hasShareFunction" ng-class="{reveal: fillScreen}" ng-click="onShareButtonClick()">
<span><img src="img/icon-share-white.svg"></span><span translate>Share this transaction</span>
</div>
</div>
<div class="slide-success__footer" ng-class="{reveal: fillScreen}">

View file

@ -64,17 +64,16 @@
<span class="item-note text-right wallet-details__tx-amount">
<span class="wallet-details__tx-amount" ng-class="{'wallet-details__tx-amount--recent': btx.recent, 'wallet-details__tx-amount--received': btx.action == 'received', 'wallet-details__tx-amount--sent': btx.action == 'sent'}">
<span ng-if="btx.action == 'sent'"></span>
<span class="size-12" ng-if="btx.action == 'invalid'" translate>
(possible double spend)
</span>
<span ng-if="btx.action != 'invalid'">
{{btx.amountValueStr}} {{btx.amountUnitStr}}
<formatted-amount value="{{btx.action == 'sent'?'-':''}}{{btx.amountValueStr}}" currency="{{btx.amountUnitStr}}"></formatted-amount>
</span>
</span>
<div>
<span class="size-12 wallet-details__tx-amount" ng-class="{'wallet-details__tx-amount--recent': btx.recent, 'wallet-details__tx-amount--received': btx.action == 'received', 'wallet-details__tx-amount--sent': btx.action == 'sent'}">
{{btx.alternativeAmountStr}}
<formatted-amount value="{{btx.alternativeAmountStr}}"></formatted-amount>
</span>
</div>
</span>

View file

@ -7,8 +7,12 @@
Incomplete
</span>
<span ng-if="wallet.isComplete()">
<span ng-if="selectedPriceDisplay == 'crypto' && !wallet.balanceHidden && !wallet.scanning"> {{wallet.status.totalBalanceStr ? wallet.status.totalBalanceStr : ( wallet.cachedBalance ? wallet.cachedBalance + (wallet.cachedBalanceUpdatedOn ? ' &middot; ' + ( wallet.cachedBalanceUpdatedOn * 1000 | amTimeAgo) : '') : '' ) }} </span>
<span ng-if="selectedPriceDisplay == 'fiat' && !wallet.balanceHidden && !wallet.scanning"> {{wallet.status.totalBalanceAlternative ? wallet.status.totalBalanceAlternative : ( wallet.cachedBalance ? wallet.cachedBalance + (wallet.cachedBalanceUpdatedOn ? ' &middot; ' + ( wallet.cachedBalanceUpdatedOn * 1000 | amTimeAgo) : '') : '' ) }} {{wallet.status.alternativeIsoCode}}</span>
<span ng-if="selectedPriceDisplay == 'crypto' && !wallet.balanceHidden && !wallet.scanning">
<formatted-amount value="{{wallet.status.totalBalanceStr ? wallet.status.totalBalanceStr : ( wallet.cachedBalance ? wallet.cachedBalance + (wallet.cachedBalanceUpdatedOn ? ' &middot; ' + ( wallet.cachedBalanceUpdatedOn * 1000 | amTimeAgo) : '') : '' ) }}"></formatted-amount>
</span>
<span ng-if="selectedPriceDisplay == 'fiat' && !wallet.balanceHidden && !wallet.scanning">
<formatted-amount value="{{wallet.status.totalBalanceAlternative ? wallet.status.totalBalanceAlternative : ( wallet.cachedBalance ? wallet.cachedBalance + (wallet.cachedBalanceUpdatedOn ? ' &middot; ' + ( wallet.cachedBalanceUpdatedOn * 1000 | amTimeAgo) : '') : '' ) }}" currency="{{wallet.status.alternativeIsoCode}}"></formatted-amount>
</span>
<span ng-if="wallet.scanning" translate> Scanning funds... </span>
<span ng-if="wallet.balanceHidden && !wallet.scanning" translate>[Balance Hidden]</span>

View file

@ -27,8 +27,8 @@
Incomplete
</span>
<span ng-if="wallet.isComplete()">
<span ng-if="displayBalanceAsFiat && !wallet.balanceHidden">{{wallet.status.totalBalanceAlternative}} {{wallet.status.alternativeIsoCode}}</span>
<span ng-if="!displayBalanceAsFiat && !wallet.balanceHidden">{{wallet.status.availableBalanceStr}}</span>
<span ng-if="displayBalanceAsFiat && !wallet.balanceHidden"><formatted-amount value="{{wallet.status.totalBalanceAlternative}}" currency="{{wallet.status.alternativeIsoCode}}"></formatted-amount></span>
<span ng-if="!displayBalanceAsFiat && !wallet.balanceHidden"><formatted-amount value="{{wallet.status.availableBalanceStr}}"></formatted-amount></span>
<span ng-if="wallet.balanceHidden" translate>[Balance Hidden]</span>
</span>
</span>
@ -58,8 +58,8 @@
Incomplete
</span>
<span ng-if="wallet.isComplete()">
<span ng-if="displayBalanceAsFiat && !wallet.balanceHidden">{{wallet.status.totalBalanceAlternative}} {{wallet.status.alternativeIsoCode}}</span>
<span ng-if="!displayBalanceAsFiat && !wallet.balanceHidden">{{wallet.status.availableBalanceStr}}</span>
<span ng-if="displayBalanceAsFiat && !wallet.balanceHidden"><formatted-amount value="{{wallet.status.totalBalanceAlternative}}" currency="{{wallet.status.alternativeIsoCode}}"></formatted-amount></span>
<span ng-if="!displayBalanceAsFiat && !wallet.balanceHidden"><formatted-amount value="{{wallet.status.availableBalanceStr}}"></formatted-amount></span>
<span ng-if="wallet.balanceHidden" translate>[Balance Hidden]</span>
</span>
</span>

View file

@ -3,7 +3,7 @@
<div class="title">
{{'Alternative Currency'|translate}}
</div>
<button class="button button-clear" ng-click="close()" translate>
<button class="button button-clear" ng-click="vm.close()" translate>
{{'Close'|translate}}
</button>
</ion-header-bar>
@ -11,23 +11,23 @@
<div class="bar bar-header item-input-inset m20b">
<label class="item-input-wrapper">
<i class="icon ion-ios-search placeholder-icon"></i>
<input type="search" ng-init="searchedAltCurrency = ''" ng-model="searchedAltCurrency" ng-change="findCurrency(searchedAltCurrency)"
<input type="search" ng-init="searchedAltCurrency = ''" ng-model="searchedAltCurrency" ng-change="vm.findCurrency(searchedAltCurrency)"
placeholder="{{'Search your currency' | translate}}">
</label>
</div>
<div class="list" ng-if="lastUsedPopularList[0] && searchedAltCurrency.length == 0">
<ion-radio class="alt-currency-radio" ng-repeat="lastUsedAltCurrency in lastUsedPopularList" ng-value="lastUsedAltCurrency.isoCode" ng-model="currentCurrency"
ng-click="save(lastUsedAltCurrency)">{{lastUsedAltCurrency.name}} <span class="item-note">{{lastUsedAltCurrency.isoCode}}</span>
<div class="list" ng-if="vm.lastUsedPopularList[0] && searchedAltCurrency.length == 0">
<ion-radio class="alt-currency-radio" ng-repeat="lastUsedAltCurrency in vm.lastUsedPopularList" ng-value="lastUsedAltCurrency.isoCode" ng-model="currentCurrency"
ng-click="vm.save(lastUsedAltCurrency)">{{lastUsedAltCurrency.name}} <span class="item-note">{{lastUsedAltCurrency.isoCode}}</span>
</ion-radio>
</div>
<div class="list">
<div class="item" ng-repeat="altCurrency in altCurrencyList" ng-value="altCurrency.isoCode" ng-model="currentCurrency"
ng-click="save(altCurrency)">{{altCurrency.name}} <span class="item-note">{{altCurrency.isoCode}}</span>
<div class="item" ng-repeat="altCurrency in vm.altCurrencyList" ng-value="altCurrency.isoCode" ng-model="currentCurrency"
ng-click="vm.save(altCurrency)">{{altCurrency.name}} <span class="item-note">{{altCurrency.isoCode}}</span>
</div>
</div>
<ion-infinite-scroll
ng-if="!listComplete"
on-infinite="loadMore()"
ng-if="!vm.listComplete"
on-infinite="vm.loadMore()"
distance="50%">
</ion-infinite-scroll>
</ion-content>

View file

@ -1,18 +1,18 @@
<ion-view id="settings-pricedisplay" class="settings" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>
{{'Price display'|translate}}
{{'Price Display'|translate}}
</ion-nav-title>
<ion-nav-back-button>
</ion-nav-back-button>
</ion-nav-bar>
<ion-content>
<div class="price-display">
<ion-radio class="capitalize" ng-value="'fiat'" ng-model="selectedPriceDisplay" ng-click="save(selectedPriceDisplay)">
fiat
<ion-radio ng-value="'fiat'" ng-model="selectedPriceDisplay" ng-click="save(selectedPriceDisplay)">
<span translate>Fiat</span>
</ion-radio>
<ion-radio class="capitalize" ng-value="'crypto'" ng-model="selectedPriceDisplay" ng-click="save(selectedPriceDisplay)">
cryptocurrency
<ion-radio ng-value="'crypto'" ng-model="selectedPriceDisplay" ng-click="save(selectedPriceDisplay)">
<span translate>Cryptocurrency</span>
</ion-radio>
</div>
</ion-content>

119
www/views/review.html Normal file
View file

@ -0,0 +1,119 @@
<ion-view id="view-review" can-swipe-back="false" hide-tabs>
<ion-nav-bar class="bar-royal {{vm.origin.currency.toLowerCase()}}">
<ion-nav-title>
{{'Review Transaction' | translate}}
</ion-nav-title>
<ion-nav-back-button ng-click="vm.goBack()"></ion-nav-back-button>
</ion-nav-bar>
<ion-content class="padded-bottom-cta-with-summary bg-neutral">
<div ng-if="vm.thirdParty && vm.thirdParty.id === 'shapeshift'" ng-include="'views/thirdparty/shapeshift-header.html'"></div>
<div class="header {{vm.origin.currency.toLowerCase()}}" ng-class="vm.thirdParty.id">
<div class="content">
<p>{{vm.sendingTitle}}</p>
<p class="large">{{vm.primaryAmount}} {{vm.primaryCurrency}}</p>
<p ng-show="vm.secondaryAmount">{{vm.secondaryAmount}} {{vm.secondaryCurrency}}</p>
</div>
</div>
<div class="content-frame negative-top">
<div class="card card-gutter-compact">
<div class="item item-compact" translate>From:</div>
<div class="item item-gutterless item-complex item-avatar">
<div class="item-content item-content-avatar">
<i class="icon big-icon-svg theme-circle theme-circle-services">
<div class="bg icon-wallet"
style="background-color: {{vm.originWallet.color}}"
></div>
</i>
<h2>{{vm.originWallet.name}} <span class="highlight" style="color: {{vm.origin.currencyColor}}">({{vm.origin.currency}})</span></h2>
<p ng-show="vm.origin.balanceAmount">{{vm.origin.balanceAmount}} {{vm.origin.balanceCurrency}}</p>
</div>
</div>
</div>
<div class="card card-gutter-compact">
<div class="item item-compact" translate>To:</div>
<div class="item item-gutterless item-complex item-avatar">
<div class="item-content item-content-avatar"
ng-if="vm.destination.kind === 'contact' || vm.destination.kind === 'wallet' || vm.destination.kind == 'shapeshift'">
<img src="img/contact-placeholder.svg" class="bg" ng-if="vm.destination.kind === 'contact'">
<i class="icon big-icon-svg theme-circle theme-circle-services" ng-if="vm.destination.kind === 'wallet' || vm.destination.kind === 'shapeshift'">
<div class="bg icon-wallet"
style="background-color: {{vm.destination.color}}"
></div>
</i>
<h2>{{vm.destination.name}}<span class="highlight" style="color: {{vm.destination.currencyColor}}" ng-if="vm.destination.currency"> ({{vm.destination.currency}})</span></h2></h2>
<p ng-if="vm.destination.balanceAmount">{{vm.destination.balanceAmount}} {{vm.destination.balanceCurrency}}</p>
</div>
<div class="item-content item-content-avatar"
ng-if="vm.thirdParty && vm.thirdParty.id === 'bip70'
&& (vm.thirdParty.name === 'BitPay' || vm.thirdParty.name === 'eGifter')">
<img ng-if="vm.thirdParty.name === 'BitPay'" src="img/icon-bitpay.svg" class="bg">
<img ng-if="vm.thirdParty.name === 'eGifter'" src="img/icon-egifter.png" class="bg">
<h2>{{vm.destination.name}}</h2>
<p translate ng-if="!vm.paymentExpired">Payment expires: {{vm.remainingTimeStr}}</p>
<p class="warning" translate ng-if="vm.paymentExpired">Payment request has expired</p>
</div>
<div class="item-content item-content-compact" ng-init="addressExpanded = false" ng-if="vm.destination.kind === 'address' && !vm.thirdParty">
<div class="address-frame" ng-class="{ 'expanded': addressExpanded }" ng-click="addressExpanded = !addressExpanded">
<span class="prefix">{{vm.destination.address.substring(0,5)}}</span><span class="mid">{{vm.destination.address.substring(5,vm.destination.address.length-4)}}</span><span class="suffix">{{vm.destination.address.substring(vm.destination.address.length-4)}}</span>
</div>
</div>
</div>
</div>
<div class="expand-content-frame">
<div class="action-minor mt-negative text-right expand-content-trigger"
ng-class="{ 'expand-content-revealed': vm.memoExpanded }"
ng-click="vm.memoExpanded = !vm.memoExpanded">
<img src="img/icon-bookmark.svg" class="action-icon">
<span class="action-text">Add personal note</span>
</div>
<div class="card card-gutter-compact expand-content"
ng-class="{ 'expand-content-revealed': vm.memoExpanded }">
<div class="item item-compact" translate>Personal Note:</div>
<div class="item">
<div class="item-content">
<textarea elastic placeholder="{{btx.note.body || btx.message || 'Enter text here'}}" class="elastic" ng-model="vm.memo"></textarea>
</div>
</div>
</div>
</div>
<div class="height-spacer"></div>
</div>
</ion-content>
<div class="fee-summary">
<div ng-if="vm.thirdParty && vm.thirdParty.id === 'bip70'" translate="">Suggested by merchant:</div>
<div class="amount">
<div class="fee-fiat positive" ng-if="vm.feeLessThanACent">Fee: Less than 1 cent</div>
<div class="fee-fiat" ng-class="vm.feeIsHigh ? 'negative' : 'positive'" ng-if="!vm.feeLessThanACent">Fee: {{vm.feeFiat}} {{vm.feeCurrency}}</div>
<div class="fee-crypto" ng-if="vm.feeCrypto">
{{vm.feeCrypto}} {{vm.origin.currency}}
</div>
</div>
</div>
<click-to-accept
ng-click="vm.approve()"
ng-if="!vm.isCordova"
click-send-status="vm.sendStatus"
is-disabled="!vm.readyToSend">
{{vm.buttonText}}
</click-to-accept>
<slide-to-accept
ng-if="vm.isCordova"
slide-on-confirm="vm.approve()"
slide-send-status="vm.sendStatus"
is-disabled="!vm.readyToSend">
{{vm.buttonText}}
</slide-to-accept>
<slide-to-accept-success
slide-success-show="vm.sendStatus === 'success'"
slide-success-on-confirm="vm.onSuccessConfirm()"
slide-success-hide-on-confirm="true">
<span ng-show="vm.originWallet.m == 1 && (vm.originWallet.canSign() || vm.originWallet.isPrivKeyExternal())" translate>Payment Sent</span>
<span ng-show="vm.originWallet.m > 1 && (vm.originWallet.canSign() || vm.originWallet.isPrivKeyExternal())" translate>Proposal Created</span>
<span ng-show="!vm.originWallet.canSign() && !vm.originWallet.isPrivKeyExternal()" translate>Transaction Created</span>
</slide-to-accept-success>
</ion-view>

View file

@ -1,4 +1,4 @@
<ion-view class="settings" show-tabs>
<ion-view id="shapeshift" class="settings" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>
{{'Shapeshift'|translate}}
@ -7,14 +7,29 @@
</ion-nav-back-button>
</ion-nav-bar>
<ion-content>
<div class="send-header-wrapper shapeshift-banner">
<img class="shapeshift-logo" src="img/shapeshiftlogo.svg"/>
</div>
<div class="list card ng-hide" ng-show="fromWallets.length == 0 || toWallets.length == 0">
<div class="item item-heading">
<span translate>No available wallets to convert between.</span>
<div ng-include="'views/thirdparty/shapeshift-header.html'"></div>
<div class="list card empty-case">
<div>
<img class="swap-image" src="img/shapeshift_swap.png"/>
</div>
<div class="subtitle">
<p translate>Exchange your BTC to BCH in minutes.</p>
<div ng-show="!walletsWithFunds.length">
<p translate>To start the process you need to add funds to your wallet.</p>
</div>
<div ng-show="walletsWithFunds.length">
<p translate>The process is fast and you will receive the exchanged amount in your wallet.</p>
</div>
<div ng-show="!hasWallets" translate>To get started, you'll need to create a bitcoin wallet and get some bitcoin.</div>
<div class="padding buttons">
<button class="button button-standard button-green" ng-click="buyBitcoin()" ng-show="!walletsWithFunds.length" translate>Buy Bitcoin now</button>
<button class="button button-standard button-green" ng-click="createWallet()" ng-show="!hasWallets" translate>Create bitcoin wallet</button>
<button class="button button-standard button-shapeshift track_shapeshift_start_click" ng-click="shapeshift()" ng-show="walletsWithFunds.length" translate>Start ShapeShift</button>
</div>
</div>
</div>
<div class="third-party-notice" translate>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.</div>
<shapeshift-coin-trader class="ng-hide" ng-show="fromWallets.length > 0 && toWallets.length > 0">
<div class="list card">
<div class="item item-heading">
@ -94,21 +109,21 @@
</shapeshift-coin-trader>
</ion-content>
<wallet-selector
wallet-selector-title="fromWalletSelectorTitle"
wallet-selector-wallets="fromWallets"
wallet-selector-selected-wallet="fromWallet"
wallet-selector-show="showFromWallets"
wallet-selector-on-select="onFromWalletSelect"
wallet-selector-always-display-bitcoin-core="true">
</wallet-selector>
<!--<wallet-selector-->
<!--wallet-selector-title="fromWalletSelectorTitle"-->
<!--wallet-selector-wallets="fromWallets"-->
<!--wallet-selector-selected-wallet="fromWallet"-->
<!--wallet-selector-show="showFromWallets"-->
<!--wallet-selector-on-select="onFromWalletSelect"-->
<!--wallet-selector-always-display-bitcoin-core="true">-->
<!--</wallet-selector>-->
<wallet-selector
wallet-selector-title="toWalletSelectorTitle"
wallet-selector-wallets="toWallets"
wallet-selector-selected-wallet="toWallet"
wallet-selector-show="showToWallets"
wallet-selector-on-select="onToWalletSelect"
wallet-selector-always-display-bitcoin-core="true">
</wallet-selector>
</ion-view>
<!--<wallet-selector-->
<!--wallet-selector-title="toWalletSelectorTitle"-->
<!--wallet-selector-wallets="toWallets"-->
<!--wallet-selector-selected-wallet="toWallet"-->
<!--wallet-selector-show="showToWallets"-->
<!--wallet-selector-on-select="onToWalletSelect"-->
<!--wallet-selector-always-display-bitcoin-core="true">-->
<!--</wallet-selector>-->
<!--</ion-view>-->

View file

@ -22,6 +22,24 @@
</div>
</div>
<div class="buttons row">
<div class="col">
<div class="button button-outline button-grey-outline" ui-sref="tabs.receive">
<span translate>Receive</span>
</div>
</div>
<div class="col">
<div class="button button-outline button-grey-outline" ng-class="{'ng-hide': walletsWithFunds.length}"
ui-sref="tabs.buyandsell">
<span translate>Buy Bitcoin</span>
</div>
<div class="button button-outline button-grey-outline" ng-class="{'ng-hide': !walletsWithFunds.length}"
ng-click="startFreshSend()">
<span translate>Send</span>
</div>
</div>
</div>
<div class="list card homeTip" ng-if="homeTip">
<div class="item item-icon-right item-heading">
<div class="title" translate>

View file

@ -61,8 +61,8 @@
</svg>
<p class="success animated fadeIn">
<br/>Payment Received!
<span ng-if="!(displayBalanceAsFiat && paymentReceivedAlternativeAmount)" class="payment-received-amount">{{ paymentReceivedAmount }} <span class="payment-received-currency">{{ paymentReceivedCoin }}</span></span>
<span ng-if="displayBalanceAsFiat && paymentReceivedAlternativeAmount" class="payment-received-amount">{{ paymentReceivedAlternativeAmount }}</span></span>
<span ng-if="!(displayBalanceAsFiat && paymentReceivedAlternativeAmount)" class="payment-received-amount"><formatted-amount value="{{ paymentReceivedAmount }}" currency="{{paymentReceivedCoin}}"></formatted-amount></span>
<span ng-if="displayBalanceAsFiat && paymentReceivedAlternativeAmount" class="payment-received-amount"><formatted-amount value="{{ paymentReceivedAlternativeAmount }}"></formatted-amount></span></span>
Return To Address<br/>
</p>
</div>
@ -95,8 +95,8 @@
{{wallet.name || wallet.id}}
</span>
<p>
<span ng-if="displayBalanceAsFiat && !wallet.balanceHidden" translate> {{wallet.status.totalBalanceAlternative}} {{wallet.status.alternativeIsoCode}} </span>
<span ng-if="!displayBalanceAsFiat && !wallet.balanceHidden"> {{wallet.status.totalBalanceStr}} </span>
<span ng-if="displayBalanceAsFiat && !wallet.balanceHidden" translate> <formatted-amount value="{{wallet.status.totalBalanceAlternative}}" currency="{{wallet.status.alternativeIsoCode}}"></formatted-amount></span>
<span ng-if="!displayBalanceAsFiat && !wallet.balanceHidden"> <formatted-amount value="{{wallet.status.totalBalanceStr}}"></formatted-amount> </span>
<span ng-if="wallet.balanceHidden" translate>[Balance Hidden]</span>
<span class="tab-home__wallet__multisig-number" ng-if="wallet.n > 1">

View file

@ -4,6 +4,25 @@
</ion-nav-bar>
<ion-content>
<div id="tab-send-header" ng-if="hasFunds">
<div class="content-frame" ng-if="fromWallet">
<div class="card card-gutter-compact">
<div class="item item-compact" translate>From:</div>
<div class="item item-gutterless item-complex item-avatar">
<div class="item-content item-content-avatar">
<i class="icon big-icon-svg theme-circle theme-circle-services">
<div class="bg icon-wallet"
style="background-color: {{fromWallet.color}}"
></div>
</i>
<h2>{{fromWallet.name}}</h2>
<!--<p ng-show="vm.origin.balanceAmount">{{vm.origin.balanceAmount}} {{vm.origin.balanceCurrency}}</p>-->
<formatted-amount value="{{fromWallet.status.totalBalanceStr ? fromWallet.status.totalBalanceStr : ( fromWallet.cachedBalance ? fromWallet.cachedBalance + (fromWallet.cachedBalanceUpdatedOn ? ' &middot; ' + ( fromWallet.cachedBalanceUpdatedOn * 1000 | amTimeAgo) : '') : '' ) }}"></formatted-amount>
</div>
</div>
</div>
</div>
<div class="send-wrapper item">
<div class="row">
<div class="input" ng-class="{'focus': searchFocus}">
@ -15,14 +34,14 @@
<div class="buttons">
<div class="row">
<div class="col-40">
<button class="button button-standard button-primary button-outline button-clipboard-paste" ng-click="pasteClipboard()" ng-class="{'contains-address': clipboardHasAddress, 'contains-content': clipboardHasContent}">
<button class="button button-standard button-primary button-outline button-clipboard-paste" ng-click="pasteClipboard()" ng-class="{'contains-address': clipboardHasAddress}">
<span class="icon"></span><br/>
<span class="non-address" translate>Paste Clipboard</span>
<span class="address" translate>Paste Address</span>
</button>
</div>
<div class="col-60">
<button class="button button-standard button-primary button-outline" ng-click="showWalletSelector()">
<button class="button button-standard button-primary button-outline" ng-click="startWalletToWalletTransfer()">
<img src="img/icon-w2w.svg"/><br/>
<span translate>Wallet to Wallet Transfer</span>
</button>
@ -90,7 +109,7 @@
</div>
<div class="list">
<a class="item item-icon-left item-icon-right" ng-repeat="item in list"
ng-if="!item.isWallet && item.recipientType != 'wallet'" ng-click="goToAmount(item)">
ng-if="!item.isWallet && item.recipientType != 'wallet'" ng-click="sendToContact(item)">
<i class="icon big-icon-svg">
<gravatar class="send-gravatar" name="{{item.name}}" width="120" email="{{item.email}}"></gravatar>
</i>
@ -105,31 +124,31 @@
</div>
</div>
</ion-content>
<wallet-selector
wallet-selector-title="walletSelectorTitleFrom"
wallet-selector-force-title="walletSelectorTitleForce"
wallet-selector-wallets="walletsWithFunds"
wallet-selector-selected-wallet="wallet"
wallet-selector-show="showWallets"
wallet-selector-on-select="onWalletSelect"
wallet-selector-display-balance-as-fiat="displayBalanceAsFiat">
</wallet-selector>
<wallet-selector
wallet-selector-on-hide=""
wallet-selector-title="walletSelectorTitleTo"
wallet-selector-wallets="walletsBch"
wallet-selector-selected-wallet="wallet"
wallet-selector-show="showWalletsBch"
wallet-selector-on-select="onWalletSelect"
wallet-selector-display-balance-as-fiat="displayBalanceAsFiat">
</wallet-selector>
<wallet-selector
wallet-selector-on-hide=""
wallet-selector-title="walletSelectorTitleTo"
wallet-selector-wallets="walletsBtc"
wallet-selector-selected-wallet="wallet"
wallet-selector-show="showWalletsBtc"
wallet-selector-on-select="onWalletSelect"
wallet-selector-display-balance-as-fiat="displayBalanceAsFiat">
</wallet-selector>
<!--<wallet-selector-->
<!--wallet-selector-title="walletSelectorTitleFrom"-->
<!--wallet-selector-force-title="walletSelectorTitleForce"-->
<!--wallet-selector-wallets="walletsWithFunds"-->
<!--wallet-selector-selected-wallet="wallet"-->
<!--wallet-selector-show="showWallets"-->
<!--wallet-selector-on-select="onWalletSelect"-->
<!--wallet-selector-display-balance-as-fiat="displayBalanceAsFiat">-->
<!--</wallet-selector>-->
<!--<wallet-selector-->
<!--wallet-selector-on-hide=""-->
<!--wallet-selector-title="walletSelectorTitleTo"-->
<!--wallet-selector-wallets="walletsBch"-->
<!--wallet-selector-selected-wallet="wallet"-->
<!--wallet-selector-show="showWalletsBch"-->
<!--wallet-selector-on-select="onWalletSelect"-->
<!--wallet-selector-display-balance-as-fiat="displayBalanceAsFiat">-->
<!--</wallet-selector>-->
<!--<wallet-selector-->
<!--wallet-selector-on-hide=""-->
<!--wallet-selector-title="walletSelectorTitleTo"-->
<!--wallet-selector-wallets="walletsBtc"-->
<!--wallet-selector-selected-wallet="wallet"-->
<!--wallet-selector-show="showWalletsBtc"-->
<!--wallet-selector-on-select="onWalletSelect"-->
<!--wallet-selector-display-balance-as-fiat="displayBalanceAsFiat">-->
<!--</wallet-selector>-->
</ion-view>

View file

@ -11,7 +11,7 @@
<ion-nav-view name="tab-scan"></ion-nav-view>
</ion-tab>
<ion-tab class="track_tab_open" id="tab_open_send" title="{{'Send'|translate}}" icon-off="ico-send" icon-on="ico-send-selected" ui-sref="tabs.send">
<ion-tab class="track_tab_open" id="tab_open_send" title="{{'Send'|translate}}" icon-off="ico-send" icon-on="ico-send-selected" ng-click="startFreshSend()">
<ion-nav-view name="tab-send"></ion-nav-view>
</ion-tab>

View file

@ -0,0 +1,3 @@
<div class="send-header-wrapper bitpay-banner">
<img class="bitpay-logo" src="img/bitpay_banner.svg"/>
</div>

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