From 40ff5364f727a9c57d9e40e269124463d60ae643 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 17 Aug 2016 17:26:13 -0300 Subject: [PATCH 1/5] refresh tx wallet service --- old/index.js | 12 +- src/js/controllers/walletDetails.js | 5 +- src/js/services/walletService.js | 301 +++++++++++++++------------- 3 files changed, 164 insertions(+), 154 deletions(-) diff --git a/old/index.js b/old/index.js index 2cca1d93c..71cdeed98 100644 --- a/old/index.js +++ b/old/index.js @@ -535,14 +535,6 @@ angular.module('copayApp.controllers').controller('indexController', function($r } }; - self.removeAndMarkSoftConfirmedTx = function(txs) { - return lodash.filter(txs, function(tx) { - if (tx.confirmations >= SOFT_CONFIRMATION_LIMIT) - return tx; - tx.recent = true; - }); - } - self.showMore = function() { $timeout(function() { @@ -1188,9 +1180,9 @@ console.log('[index.js:1063] walletImported'); //TODO }); $rootScope.$on('Local/NewFocusedWallet', function() { -console.log('[index.js.1200:NewFocusedWallet:] TODO'); //TODO + console.log('[index.js.1200:NewFocusedWallet:] TODO'); //TODO -return; + return; uxLanguage.update(); diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 7728104a0..87a802166 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -85,7 +85,10 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.wallet = wallet; if (wallet) { - walletService.updateStatus(wallet, {}, function(err, status) { + walletService.updateStatus(wallet, { + triggerTxUpdate: true + }, function(err, status) { + console.log(status); if (err) {} // TODO }); } diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js index e8a082ace..d7ecc5709 100644 --- a/src/js/services/walletService.js +++ b/src/js/services/walletService.js @@ -9,6 +9,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim var root = {}; + root.SOFT_CONFIRMATION_LIMIT = 12; // UI Related root.openStatusModal = function(type, txp, cb) { @@ -78,16 +79,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }); }; - var _walletStatusHash = function(walletStatus) { - var bal; - if (walletStatus) { - bal = walletStatus.balance.totalAmount; - } else { - bal = self.totalBalanceSat; - } - return bal; - }; - // TODO // This handles errors from BWS/index which normally @@ -102,7 +93,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim console.log('[walletService.js.93] TODO NOT AUTH'); //TODO // TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO - self.notAuthorized = true; + wallet.notAuthorized = true; go.walletHome(); } else if (err instanceof errors.NOT_FOUND) { root.showErrorPopup(gettext('Could not access Wallet Service: Not found')); @@ -183,102 +174,87 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim wallet.copayers = status.wallet.copayers; }; - root.updateStatus = function(wallet, opts, cb, initStatusHash, tries) { - tries = tries || 0; + root.updateStatus = function(wallet, opts, cb) { opts = opts || {}; - if (wallet.isValid && !opts.force) - return; - - - var walletId = wallet.id; - - if (opts.untilItChanges && lodash.isUndefined(initStatusHash)) { - initStatusHash = _walletStatusHash(); - $log.debug('Updating status until it changes. initStatusHash:' + initStatusHash) - } - - var get = function(cb) { - if (opts.walletStatus) - return cb(null, opts.walletStatus); - else { - return wallet.getStatus({ - twoStep: true - }, function(err, ret) { - if (err) - return cb(bwcError.msg(err, gettext('Could not update Wallet'))); - // TODO?? - // self.isSingleAddress = !!ret.wallet.singleAddress; - // self.updating = ret.wallet.scanStatus == 'running'; - return cb(null, ret); - }); - } + function updateBalance(cb) { + if (wallet.isValid && !opts.force) + return cb(); }; - // If not untilItChanges...trigger history update now - if (opts.triggerTxUpdate && !opts.untilItChanges) { + function updateHistory() { + if (!opts.triggerTxUpdate) return; $timeout(function() { - root.debounceUpdateHistory(); - }, 1); - } - - $timeout(function() { - - // if (!opts.quiet) - // self.updating = true; - - $log.debug('Updating Status:', wallet.credentials.walletName, tries); - get(function(err, walletStatus) { - var currentStatusHash = _walletStatusHash(walletStatus); - $log.debug('Status update. hash:' + currentStatusHash + ' Try:' + tries); - if (!err && opts.untilItChanges && initStatusHash == currentStatusHash && tries < 7 && walletId == profileService.focusedClient.credentials.walletId) { - return $timeout(function() { - $log.debug('Retrying update... ' + walletId + ' Try:' + tries) - return root.updateStatus(wallet, { - walletStatus: null, - untilItChanges: true, - triggerTxUpdate: opts.triggerTxUpdate, - }, cb, initStatusHash, ++tries); - }, 1400 * tries); - } - - if (err) { - root.handleError(err); - return cb(err); - } - $log.debug('Got Wallet Status for:' + wallet.credentials.walletName); - - root.setStatus(wallet, walletStatus); - - // self.setPendingTxps(walletStatus.pendingTxps); - // - // // Status Shortcuts - // self.lastUpdate = Date.now(); - // self.walletName = walletStatus.wallet.name; - // self.walletSecret = walletStatus.wallet.secret; - // self.walletStatus = walletStatus.wallet.status; - // self.walletScanStatus = walletStatus.wallet.scanStatus; - // self.copayers = walletStatus.wallet.copayers; - // self.preferences = walletStatus.preferences; - // self.setBalance(walletStatus.balance); - // self.otherWallets = lodash.filter(profileService.getWallets(self.network), function(w) { - // return w.id != self.walletId; - // }); - // - // Notify external addons or plugins - - // TODO - if (opts.triggerTxUpdate && opts.untilItChanges) { - $timeout(function() { - root.debounceUpdateHistory(); - }, 1); - } - return cb(); - // } else { - // self.loadingWallet = false; - // } + root.updateHistory(wallet); }); - }); + }; + + function getStatus(cb) { + wallet.getStatus({ + twoStep: true + }, function(err, ret) { + if (err) { + return cb(bwcError.msg(err, gettext('Could not update Wallet'))); + } + return cb(null, ret); + }); + }; + + function walletStatusHash(walletStatus) { + var bal; + if (walletStatus) { + bal = walletStatus.balance.totalAmount; + } else { + bal = wallet.totalBalanceSat; + } + return bal; + }; + + function doUpdate(initStatusHash, tries) { + if (wallet.isValid && !opts.force) return cb(); + + tries = tries || 0; + + $timeout(function() { + $log.debug('Updating Status:', wallet.credentials.walletName, tries); + getStatus(function(err, walletStatus) { + if (err) return cb(err); + + var currentStatusHash = walletStatusHash(walletStatus); + $log.debug('Status update. hash:' + currentStatusHash + ' Try:' + tries); + if (opts.untilItChanges && + initStatusHash == currentStatusHash && + tries < 7 && + walletId == wallet.credentials.walletId) { + return $timeout(function() { + $log.debug('Retrying update... ' + walletId + ' Try:' + tries) + return root.doUpdate(initStatusHash, ++tries, cb); + }, 1400 * tries); + } + + $log.debug('Got Wallet Status for:' + wallet.credentials.walletName); + + root.setStatus(wallet, walletStatus); + + // wallet.setPendingTxps(walletStatus.pendingTxps); + return cb(); + }); + }); + }; + + // trigger history update now? + if (!opts.untilItChanges) updateHistory(); + + doUpdate(walletStatusHash(), 0, function(err) { + if (err) { + root.handleError(err); + return cb(err); + } + + if (opts.untilItChanges) updateHistory(); + return cb(); + }) + }; var getSavedTxs = function(walletId, cb) { @@ -321,6 +297,50 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }); }; + var removeAndMarkSoftConfirmedTx = function(txs) { + return lodash.filter(txs, function(tx) { + if (tx.confirmations >= root.SOFT_CONFIRMATION_LIMIT) + return tx; + tx.recent = true; + }); + } + + var processNewTxs = function(wallet, txs) { + var config = configService.getSync().wallet.settings; + var now = Math.floor(Date.now() / 1000); + var txHistoryUnique = {}; + var ret = []; + wallet.hasUnsafeConfirmed = false; + + lodash.each(txs, function(tx) { + tx = txFormatService.processTx(tx); + + // no future transactions... + if (tx.time > now) + tx.time = now; + + if (tx.confirmations >= SAFE_CONFIRMATIONS) { + tx.safeConfirmed = SAFE_CONFIRMATIONS + '+'; + } else { + tx.safeConfirmed = false; + wallet.hasUnsafeConfirmed = true; + } + + if (tx.note) { + delete tx.note.encryptedEditedByName; + delete tx.note.encryptedBody; + } + + if (!txHistoryUnique[tx.txid]) { + ret.push(tx); + txHistoryUnique[tx.txid] = true; + } else { + $log.debug('Ignoring duplicate TX in history: ' + tx.txid) + } + }); + + return ret; + }; var updateLocalTxHistory = function(wallet, cb) { var FIRST_LIMIT = 5; @@ -352,21 +372,21 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim fixTxsUnit(txsFromLocal); - var confirmedTxs = self.removeAndMarkSoftConfirmedTx(txsFromLocal); + var confirmedTxs = removeAndMarkSoftConfirmedTx(txsFromLocal); var endingTxid = confirmedTxs[0] ? confirmedTxs[0].txid : null; var endingTs = confirmedTxs[0] ? confirmedTxs[0].time : null; // First update - if (walletId == profileService.focusedClient.credentials.walletId) { - self.completeHistory = txsFromLocal; - setCompactTxHistory(); + if (walletId == wallet.credentials.walletId) { + wallet.completeHistory = txsFromLocal; + setCompactTxHistory(wallet); } - if (historyUpdateInProgress[walletId]) + if (wallet.historyUpdateInProgress) return; - historyUpdateInProgress[walletId] = true; + wallet.historyUpdateInProgress = true; function getNewTxs(newTxs, skip, i_cb) { getTxsFromServer(wallet, skip, endingTxid, requestLimit, function(err, res, shouldContinue) { @@ -378,7 +398,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim $log.debug('Syncing TXs. Got:' + newTxs.length + ' Skip:' + skip, ' EndingTxid:', endingTxid, ' Continue:', shouldContinue); if (!shouldContinue) { - newTxs = self.processNewTxs(newTxs); + newTxs = processNewTxs(wallet, newTxs); $log.debug('Finished Sync: New / soft confirmed Txs: ' + newTxs.length); return i_cb(null, newTxs); } @@ -387,18 +407,15 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim getNewTxs(newTxs, skip, i_cb); // Progress update - if (walletId == profileService.focusedClient.credentials.walletId) { - self.txProgress = newTxs.length; - if (self.completeHistory < FIRST_LIMIT && txsFromLocal.length == 0) { + if (walletId == wallet.credentials.walletId) { + wallet.txProgress = newTxs.length; + if (wallet.completeHistory < FIRST_LIMIT && txsFromLocal.length == 0) { $log.debug('Showing partial history'); - var newHistory = self.processNewTxs(newTxs); + var newHistory = processNewTxs(wallet, newTxs); newHistory = lodash.compact(newHistory.concat(confirmedTxs)); - self.completeHistory = newHistory; - setCompactTxHistory(); + wallet.completeHistory = newHistory; + setCompactTxHistory(wallet); } - $timeout(function() { - $rootScope.$apply(); - }); } }); }; @@ -445,9 +462,9 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim $log.debug('Tx History synced. Total Txs: ' + newHistory.length); // Final update - if (walletId == profileService.focusedClient.credentials.walletId) { - self.completeHistory = newHistory; - setCompactTxHistory(); + if (walletId == wallet.credentials.walletId) { + wallet.completeHistory = newHistory; + setCompactTxHistory(wallet); } return storageService.setTxHistory(historyToSave, walletId, function() { @@ -468,22 +485,21 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim $log.debug('Updating Transaction History'); - self.txHistoryError = false; - self.updatingTxHistory = true; + wallet.txHistoryError = false; + wallet.updatingTxHistory = true; $timeout(function() { updateLocalTxHistory(wallet, function(err) { - historyUpdateInProgress[walletId] = self.updatingTxHistory = false; - self.loadingWallet = false; - self.txProgress = 0; + wallet.historyUpdateInProgress = wallet.updatingTxHistory = false; + wallet.loadingWallet = false; + wallet.txProgress = 0; if (err) - self.txHistoryError = true; + wallet.txHistoryError = true; $timeout(function() { - self.newTx = false + wallet.newTx = false }, 1000); - $rootScope.$apply(); }); }); }; @@ -673,20 +689,20 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }); }; - var setCompactTxHistory = function() { + var setCompactTxHistory = function(wallet) { // TODO - self.isSearching = false; - self.nextTxHistory = self.historyShowMoreLimit; - self.txHistory = self.completeHistory ? self.completeHistory.slice(0, self.historyShowLimit) : null; - self.historyShowMore = self.completeHistory ? self.completeHistory.length > self.historyShowLimit : null; + wallet.isSearching = false; + wallet.nextTxHistory = wallet.historyShowMoreLimit; + wallet.txHistory = wallet.completeHistory ? wallet.completeHistory.slice(0, wallet.historyShowLimit) : null; + wallet.historyShowMore = wallet.completeHistory ? wallet.completeHistory.length > wallet.historyShowLimit : null; }; root.debounceUpdateHistory = lodash.debounce(function() { root.updateHistory(); }, 1000); - self.throttledUpdateHistory = lodash.throttle(function() { + root.throttledUpdateHistory = lodash.throttle(function() { root.updateHistory(); }, 5000); @@ -707,19 +723,18 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim root.recreate = function(wallet, cb) { ongoingProcess.set('recreating', true); wallet.recreateWallet(function(err) { - self.notAuthorized = false; + wallet.notAuthorized = false; ongoingProcess.set('recreating', false); if (err) { - self.handleError(err); - $rootScope.$apply(); + wallet.handleError(err); return; } profileService.bindWalletClient(wallet, { force: true }); - self.startScan(wallet); + wallet.startScan(wallet); }); }; @@ -727,16 +742,16 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim $log.debug('Scanning wallet ' + wallet.credentials.walletId); if (!wallet.isComplete()) return; - // self.updating = true; + // wallet.updating = true; wallet.startScan({ includeCopayerBranches: true, }, function(err) { // TODO - // if (err && self.walletId == walletId) { - // self.updating = false; - // self.handleError(err); + // if (err && wallet.walletId == walletId) { + // wallet.updating = false; + // wallet.handleError(err); // $rootScope.$apply(); // } }); @@ -918,7 +933,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim if (err) { // TODO? //$scope.$emit('Local/TxProposalAction'); - var msg = err.message ? + var msg = err.message ? err.message : gettext('The payment was created but could not be completed. Please try again from home screen'); return cb(err); From 9423de71798caef9f8556a388a65c247f70b27a6 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 17 Aug 2016 17:32:22 -0300 Subject: [PATCH 2/5] limit nb of txs --- src/js/services/walletService.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js index d7ecc5709..43c55ac1f 100644 --- a/src/js/services/walletService.js +++ b/src/js/services/walletService.js @@ -10,6 +10,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim var root = {}; root.SOFT_CONFIRMATION_LIMIT = 12; + root.HISTORY_SHOW_LIMIT = 10; // UI Related root.openStatusModal = function(type, txp, cb) { @@ -693,9 +694,9 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim // TODO wallet.isSearching = false; - wallet.nextTxHistory = wallet.historyShowMoreLimit; - wallet.txHistory = wallet.completeHistory ? wallet.completeHistory.slice(0, wallet.historyShowLimit) : null; - wallet.historyShowMore = wallet.completeHistory ? wallet.completeHistory.length > wallet.historyShowLimit : null; + wallet.nextTxHistory = root.HISTORY_SHOW_LIMIT; + wallet.txHistory = wallet.completeHistory ? wallet.completeHistory.slice(0, root.HISTORY_SHOW_LIMIT) : null; + wallet.historyShowMore = wallet.completeHistory ? wallet.completeHistory.length > root.HISTORY_SHOW_LIMIT : null; }; root.debounceUpdateHistory = lodash.debounce(function() { From 5656caaa5a292f74dd8ae1df4028432c72bb3aac Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 17 Aug 2016 18:02:47 -0300 Subject: [PATCH 3/5] updateStatus -> getStatus --- public/views/walletDetails.html | 2 +- src/js/controllers/index.js | 1334 +++++++++++++++++++++++++++ src/js/controllers/walletDetails.js | 4 +- src/js/services/walletService.js | 222 ++--- 4 files changed, 1433 insertions(+), 129 deletions(-) create mode 100644 src/js/controllers/index.js diff --git a/public/views/walletDetails.html b/public/views/walletDetails.html index 7e4558cad..6082966b2 100644 --- a/public/views/walletDetails.html +++ b/public/views/walletDetails.html @@ -226,7 +226,7 @@ diff --git a/src/js/controllers/index.js b/src/js/controllers/index.js new file mode 100644 index 000000000..825a7fac1 --- /dev/null +++ b/src/js/controllers/index.js @@ -0,0 +1,1334 @@ +'use strict'; + +angular.module('copayApp.controllers').controller('indexController', function($rootScope, $scope, $log, $filter, $timeout, $ionicScrollDelegate, $ionicPopup, $ionicSideMenuDelegate, $httpBackend, latestReleaseService, feeService, bwcService, pushNotificationsService, lodash, go, profileService, configService, rateService, storageService, addressService, gettext, gettextCatalog, amMoment, addonManager, bwcError, txFormatService, uxLanguage, glideraService, coinbaseService, platformInfo, addressbookService, openURLService, ongoingProcess) { + + var self = this; + var SOFT_CONFIRMATION_LIMIT = 12; + var errors = bwcService.getErrors(); + var historyUpdateInProgress = {}; + var isChromeApp = platformInfo.isChromeApp; + var isCordova = platformInfo.isCordova; + var isNW = platformInfo.isNW; + + var ret = {}; + ret.isCordova = isCordova; + ret.isChromeApp = isChromeApp; + ret.isSafari = platformInfo.isSafari; + ret.isWindowsPhoneApp = platformInfo.isWP; + ret.historyShowLimit = 10; + ret.historyShowMoreLimit = 10; + ret.isSearching = false; + ret.prevState = 'walletHome'; + ret.physicalScreenWidth = ((window.innerWidth > 0) ? window.innerWidth : screen.width); + + ret.appConfig = window.appConfig; + + // Only for testing + //storageService.checkQuota(); + + ret.menu = [{ + 'title': gettext('Receive'), + 'icon': { + false: 'icon-receive', + true: 'icon-receive-active' + }, + 'link': 'receive' + }, { + 'title': gettext('Activity'), + 'icon': { + false: 'icon-activity', + true: 'icon-activity-active' + }, + 'link': 'walletHome' + }, { + 'title': gettext('Send'), + 'icon': { + false: 'icon-send', + true: 'icon-send-active' + }, + 'link': 'send' + }]; + + ret.addonViews = addonManager.addonViews(); + ret.txTemplateUrl = addonManager.txTemplateUrl() || 'views/includes/transaction.html'; + + ret.tab = 'walletHome'; + var vanillaScope = ret; + + if (isNW) { + latestReleaseService.checkLatestRelease(function(err, newRelease) { + if (err) { + $log.warn(err); + return; + } + + if (newRelease) + $scope.newRelease = gettext('There is a new version of Copay. Please update'); + }); + } + + function strip(number) { + return (parseFloat(number.toPrecision(12))); + }; + + self.goHome = function() { + go.walletHome(); + }; + + self.allowRefresher = function() { + if ($ionicSideMenuDelegate.getOpenRatio() != 0) self.allowPullToRefresh = false; + } + + self.hideBalance = function() { + storageService.getHideBalanceFlag(self.walletId, function(err, shouldHideBalance) { + if (err) self.shouldHideBalance = false; + else self.shouldHideBalance = (shouldHideBalance == 'true') ? true : false; + }); + } + + self.onHold = function() { + self.shouldHideBalance = !self.shouldHideBalance; + storageService.setHideBalanceFlag(self.walletId, self.shouldHideBalance.toString(), function() {}); + } + + self.setWalletPreferencesTitle = function() { + return gettext("Wallet Preferences"); + } + + self.cleanInstance = function() { + $log.debug('Cleaning Index Instance'); + lodash.each(self, function(v, k) { + if (lodash.isFunction(v)) return; + // This are to prevent flicker in mobile: + if (k == 'hasProfile') return; + if (k == 'tab') return; + if (k == 'noFocusedWallet') return; + if (k == 'backgroundColor') return; + if (k == 'physicalScreenWidth') return; + if (k == 'loadingWallet') { + self.loadingWallet = true; + return; + } + if (!lodash.isUndefined(vanillaScope[k])) { + self[k] = vanillaScope[k]; + return; + } + + delete self[k]; + }); + }; + + self.setFocusedWallet = function() { + var fc = profileService.focusedClient; + if (!fc) return; + + self.cleanInstance(); + self.loadingWallet = true; + self.setSpendUnconfirmed(); + + $timeout(function() { + $rootScope.$apply(); + + self.hasProfile = true; + self.isSingleAddress = false; + self.noFocusedWallet = false; + self.updating = false; + + // Credentials Shortcuts + self.m = fc.credentials.m; + self.n = fc.credentials.n; + self.network = fc.credentials.network; + self.copayerId = fc.credentials.copayerId; + self.copayerName = fc.credentials.copayerName; + self.requiresMultipleSignatures = fc.credentials.m > 1; + self.isShared = fc.credentials.n > 1; + self.walletName = fc.credentials.walletName; + self.walletId = fc.credentials.walletId; + self.isComplete = fc.isComplete(); + self.canSign = fc.canSign(); + self.isPrivKeyExternal = fc.isPrivKeyExternal(); + self.isPrivKeyEncrypted = fc.isPrivKeyEncrypted(); + self.externalSource = fc.getPrivKeyExternalSourceName(); + self.account = fc.credentials.account; + self.incorrectDerivation = fc.keyDerivationOk === false; + + if (self.externalSource == 'trezor') + self.account++; + + self.txps = []; + self.copayers = []; + self.updateColor(); + self.updateAlias(); + self.setAddressbook(); + + self.initGlidera(); + self.initCoinbase(); + + self.hideBalance(); + + self.setCustomBWSFlag(); + + if (!self.isComplete) { + $log.debug('Wallet not complete BEFORE update... redirecting'); + go.path('copayers'); + } else { + if (go.is('copayers')) { + $log.debug('Wallet Complete BEFORE update... redirect to home'); + go.walletHome(); + } + } + + profileService.needsBackup(fc, function(needsBackup) { + self.needsBackup = needsBackup; + self.openWallet(function() { + if (!self.isComplete) { + $log.debug('Wallet not complete after update... redirecting'); + go.path('copayers'); + } else { + if (go.is('copayers')) { + $log.debug('Wallet Complete after update... redirect to home'); + go.walletHome(); + } + } + }); + }); + }); + }; + + self.setCustomBWSFlag = function() { + var defaults = configService.getDefaults(); + var config = configService.getSync(); + + self.usingCustomBWS = config.bwsFor && config.bwsFor[self.walletId] && (config.bwsFor[self.walletId] != defaults.bws.url); + }; + + + self.setTab = function(tab, reset, tries, switchState) { + tries = tries || 0; + + // check if the whole menu item passed + if (typeof tab == 'object') { + if (tab.open) { + if (tab.link) { + self.tab = tab.link; + } + tab.open(); + return; + } else { + return self.setTab(tab.link, reset, tries, switchState); + } + } + if (self.tab === tab && !reset) + return; + + if (!document.getElementById('menu-' + tab) && ++tries < 5) { + return $timeout(function() { + self.setTab(tab, reset, tries, switchState); + }, 300); + } + + if (!self.tab || !go.is('walletHome')) + self.tab = 'walletHome'; + + var changeTab = function() { + if (document.getElementById(self.tab)) { + document.getElementById(self.tab).className = 'tab-out tab-view ' + self.tab; + var old = document.getElementById('menu-' + self.tab); + if (old) { + old.className = ''; + } + } + + if (document.getElementById(tab)) { + document.getElementById(tab).className = 'tab-in tab-view ' + tab; + var newe = document.getElementById('menu-' + tab); + if (newe) { + newe.className = 'active'; + } + } + + self.tab = tab; + $rootScope.$emit('Local/TabChanged', tab); + }; + + if (switchState && !go.is('walletHome')) { + go.path('walletHome', function() { + changeTab(); + }); + return; + } + + changeTab(); + }; + + + self.setSpendUnconfirmed = function(spendUnconfirmed) { + self.spendUnconfirmed = spendUnconfirmed || configService.getSync().wallet.spendUnconfirmed; + }; + + self.updateBalance = function() { + var fc = profileService.focusedClient; + $timeout(function() { + ongoingProcess.set('updatingBalance', true); + $log.debug('Updating Balance'); + fc.getBalance(function(err, balance) { + ongoingProcess.set('updatingBalance', false); + if (err) { + self.handleError(err); + return; + } + $log.debug('Wallet Balance:', balance); + self.setBalance(balance); + }); + }); + }; + + self.updatePendingTxps = function() { + var fc = profileService.focusedClient; + $timeout(function() { + self.updating = true; + $log.debug('Updating PendingTxps'); + fc.getTxProposals({}, function(err, txps) { + self.updating = false; + if (err) { + self.handleError(err); + } else { + $log.debug('Wallet PendingTxps:', txps); + self.setPendingTxps(txps); + } + $rootScope.$apply(); + }); + }); + }; + + // This handles errors from BWS/index which normally + // trigger from async events (like updates). + // Debounce function avoids multiple popups + var _handleError = function(err) { + $log.warn('Client ERROR: ', err); + if (err instanceof errors.NOT_AUTHORIZED) { + self.notAuthorized = true; + go.walletHome(); + } else if (err instanceof errors.NOT_FOUND) { + self.showErrorPopup(gettext('Could not access Wallet Service: Not found')); + } else { + var msg = "" + $scope.$emit('Local/ClientError', (err.error ? err.error : err)); + var msg = bwcError.msg(err, gettext('Error at Wallet Service')); + self.showErrorPopup(msg); + } + }; + + self.handleError = lodash.debounce(_handleError, 1000); + + self.openWallet = function(cb) { + var fc = profileService.focusedClient; + $timeout(function() { + $rootScope.$apply(); + self.updating = true; + self.updateError = false; + fc.openWallet(function(err, walletStatus) { + self.updating = false; + if (err) { + self.updateError = true; + self.handleError(err); + return; + } + $log.debug('Wallet Opened'); + + self.updateAll(lodash.isObject(walletStatus) ? { + walletStatus: walletStatus, + cb: cb, + } : { + cb: cb + }); + $rootScope.$apply(); + }); + }); + }; + + self.setPendingTxps = function(txps) { + self.pendingTxProposalsCountForUs = 0; + var now = Math.floor(Date.now() / 1000); + + /* Uncomment to test multiple outputs */ + /* + var txp = { + message: 'test multi-output', + fee: 1000, + createdOn: new Date() / 1000, + outputs: [] + }; + function addOutput(n) { + txp.outputs.push({ + amount: 600, + toAddress: '2N8bhEwbKtMvR2jqMRcTCQqzHP6zXGToXcK', + message: 'output #' + (Number(n) + 1) + }); + }; + lodash.times(150, addOutput); + txps.push(txp); + */ + + lodash.each(txps, function(tx) { + + tx = txFormatService.processTx(tx); + + // no future transactions... + if (tx.createdOn > now) + tx.createdOn = now; + + var action = lodash.find(tx.actions, { + copayerId: self.copayerId + }); + + if (!action && tx.status == 'pending') { + tx.pendingForUs = true; + } + + if (action && action.type == 'accept') { + tx.statusForUs = 'accepted'; + } else if (action && action.type == 'reject') { + tx.statusForUs = 'rejected'; + } else { + tx.statusForUs = 'pending'; + } + + if (!tx.deleteLockTime) + tx.canBeRemoved = true; + + if (tx.creatorId != self.copayerId) { + self.pendingTxProposalsCountForUs = self.pendingTxProposalsCountForUs + 1; + } + addonManager.formatPendingTxp(tx); + }); + self.txps = txps; + }; + + var SAFE_CONFIRMATIONS = 6; + + self.processNewTxs = function(txs) { + var config = configService.getSync().wallet.settings; + var now = Math.floor(Date.now() / 1000); + var txHistoryUnique = {}; + var ret = []; + self.hasUnsafeConfirmed = false; + + lodash.each(txs, function(tx) { + tx = txFormatService.processTx(tx); + + // no future transactions... + if (tx.time > now) + tx.time = now; + + if (tx.confirmations >= SAFE_CONFIRMATIONS) { + tx.safeConfirmed = SAFE_CONFIRMATIONS + '+'; + } else { + tx.safeConfirmed = false; + self.hasUnsafeConfirmed = true; + } + + if (tx.note) { + delete tx.note.encryptedEditedByName; + delete tx.note.encryptedBody; + } + + if (!txHistoryUnique[tx.txid]) { + ret.push(tx); + txHistoryUnique[tx.txid] = true; + } else { + $log.debug('Ignoring duplicate TX in history: ' + tx.txid) + } + }); + + return ret; + }; + + self.updateAlias = function() { + var config = configService.getSync(); + config.aliasFor = config.aliasFor || {}; + self.alias = config.aliasFor[self.walletId]; + var fc = profileService.focusedClient; + fc.alias = self.alias; + }; + + self.updateColor = function() { + var config = configService.getSync(); + config.colorFor = config.colorFor || {}; + self.backgroundColor = config.colorFor[self.walletId] || '#4A90E2'; + var fc = profileService.focusedClient; + fc.backgroundColor = self.backgroundColor; + if (isCordova && StatusBar.isVisible) { + StatusBar.backgroundColorByHexString(fc.backgroundColor); + } + }; + + self.setBalance = function(balance) { + if (!balance) return; + var config = configService.getSync().wallet.settings; + var COIN = 1e8; + + + // Address with Balance + self.balanceByAddress = balance.byAddress; + + // Spend unconfirmed funds + if (self.spendUnconfirmed) { + self.totalBalanceSat = balance.totalAmount; + self.lockedBalanceSat = balance.lockedAmount; + self.availableBalanceSat = balance.availableAmount; + self.totalBytesToSendMax = balance.totalBytesToSendMax; + self.pendingAmount = null; + } else { + self.totalBalanceSat = balance.totalConfirmedAmount; + self.lockedBalanceSat = balance.lockedConfirmedAmount; + self.availableBalanceSat = balance.availableConfirmedAmount; + self.totalBytesToSendMax = balance.totalBytesToSendConfirmedMax; + self.pendingAmount = balance.totalAmount - balance.totalConfirmedAmount; + } + + // Selected unit + self.unitToSatoshi = config.unitToSatoshi; + self.satToUnit = 1 / self.unitToSatoshi; + self.unitName = config.unitName; + + //STR + self.totalBalanceStr = profileService.formatAmount(self.totalBalanceSat) + ' ' + self.unitName; + self.lockedBalanceStr = profileService.formatAmount(self.lockedBalanceSat) + ' ' + self.unitName; + self.availableBalanceStr = profileService.formatAmount(self.availableBalanceSat) + ' ' + self.unitName; + + if (self.pendingAmount) { + self.pendingAmountStr = profileService.formatAmount(self.pendingAmount) + ' ' + self.unitName; + } else { + self.pendingAmountStr = null; + } + + self.alternativeName = config.alternativeName; + self.alternativeIsoCode = config.alternativeIsoCode; + + // Check address + addressService.isUsed(self.walletId, balance.byAddress, function(err, used) { + if (used) { + $log.debug('Address used. Creating new'); + $rootScope.$emit('Local/AddressIsUsed'); + } + }); + + rateService.whenAvailable(function() { + + var totalBalanceAlternative = rateService.toFiat(self.totalBalanceSat, self.alternativeIsoCode); + var lockedBalanceAlternative = rateService.toFiat(self.lockedBalanceSat, self.alternativeIsoCode); + var alternativeConversionRate = rateService.toFiat(100000000, self.alternativeIsoCode); + + self.totalBalanceAlternative = $filter('formatFiatAmount')(totalBalanceAlternative); + self.lockedBalanceAlternative = $filter('formatFiatAmount')(lockedBalanceAlternative); + self.alternativeConversionRate = $filter('formatFiatAmount')(alternativeConversionRate); + + self.alternativeBalanceAvailable = true; + + self.isRateAvailable = true; + $rootScope.$apply(); + }); + + if (!rateService.isAvailable()) { + $rootScope.$apply(); + } + }; + + + self.showMore = function() { + $timeout(function() { + if (self.isSearching) { + self.txHistorySearchResults = self.result.slice(0, self.nextTxHistory); + $log.debug('Total txs: ', self.txHistorySearchResults.length + '/' + self.result.length); + if (self.txHistorySearchResults.length >= self.result.length) + self.historyShowMore = false; + } else { + self.txHistory = self.completeHistory.slice(0, self.nextTxHistory); + $log.debug('Total txs: ', self.txHistory.length + '/' + self.completeHistory.length); + if (self.txHistory.length >= self.completeHistory.length) + self.historyShowMore = false; + } + self.nextTxHistory += self.historyShowMoreLimit; + $scope.$broadcast('scroll.infiniteScrollComplete'); + }, 100); + }; + + + + + self.toggleLeftMenu = function() { + profileService.isDisclaimerAccepted(function(val) { + if (val) go.toggleLeftMenu(); + else + $log.debug('Disclaimer not accepted, cannot open menu'); + }); + }; + + self.initGlidera = function(accessToken) { + self.glideraEnabled = configService.getSync().glidera.enabled; + self.glideraTestnet = configService.getSync().glidera.testnet; + var network = self.glideraTestnet ? 'testnet' : 'livenet'; + + self.glideraToken = null; + self.glideraError = null; + self.glideraPermissions = null; + self.glideraEmail = null; + self.glideraPersonalInfo = null; + self.glideraTxs = null; + self.glideraStatus = null; + + if (!self.glideraEnabled) return; + + glideraService.setCredentials(network); + + var getToken = function(cb) { + if (accessToken) { + cb(null, accessToken); + } else { + storageService.getGlideraToken(network, cb); + } + }; + + getToken(function(err, accessToken) { + if (err || !accessToken) return; + else { + glideraService.getAccessTokenPermissions(accessToken, function(err, p) { + if (err) { + self.glideraError = err; + } else { + self.glideraToken = accessToken; + self.glideraPermissions = p; + self.updateGlidera({ + fullUpdate: true + }); + } + }); + } + }); + }; + + self.updateGlidera = function(opts) { + if (!self.glideraToken || !self.glideraPermissions) return; + var accessToken = self.glideraToken; + var permissions = self.glideraPermissions; + + opts = opts || {}; + + glideraService.getStatus(accessToken, function(err, data) { + self.glideraStatus = data; + }); + + glideraService.getLimits(accessToken, function(err, limits) { + self.glideraLimits = limits; + }); + + if (permissions.transaction_history) { + glideraService.getTransactions(accessToken, function(err, data) { + self.glideraTxs = data; + }); + } + + if (permissions.view_email_address && opts.fullUpdate) { + glideraService.getEmail(accessToken, function(err, data) { + self.glideraEmail = data.email; + }); + } + if (permissions.personal_info && opts.fullUpdate) { + glideraService.getPersonalInfo(accessToken, function(err, data) { + self.glideraPersonalInfo = data; + }); + } + + }; + + self.initCoinbase = function(accessToken) { + self.coinbaseEnabled = configService.getSync().coinbase.enabled; + self.coinbaseTestnet = configService.getSync().coinbase.testnet; + var network = self.coinbaseTestnet ? 'testnet' : 'livenet'; + + self.coinbaseToken = null; + self.coinbaseError = null; + self.coinbasePermissions = null; + self.coinbaseEmail = null; + self.coinbasePersonalInfo = null; + self.coinbaseTxs = null; + self.coinbaseStatus = null; + + if (!self.coinbaseEnabled) return; + + coinbaseService.setCredentials(network); + + var getToken = function(cb) { + if (accessToken) { + cb(null, accessToken); + } else { + storageService.getCoinbaseToken(network, cb); + } + }; + + getToken(function(err, accessToken) { + if (err || !accessToken) return; + else { + coinbaseService.getAccounts(accessToken, function(err, a) { + if (err) { + self.coinbaseError = err; + if (err.errors[0] && err.errors[0].id == 'expired_token') { + self.refreshCoinbaseToken(); + } + } else { + self.coinbaseToken = accessToken; + lodash.each(a.data, function(account) { + if (account.primary && account.type == 'wallet') { + self.coinbaseAccount = account; + self.updateCoinbase(); + } + }); + } + }); + } + }); + }; + + self.updateCoinbase = lodash.debounce(function(opts) { + if (!self.coinbaseToken || !self.coinbaseAccount) return; + var accessToken = self.coinbaseToken; + var accountId = self.coinbaseAccount.id; + + opts = opts || {}; + + if (opts.updateAccount) { + coinbaseService.getAccount(accessToken, accountId, function(err, a) { + if (err) { + self.coinbaseError = err; + if (err.errors[0] && err.errors[0].id == 'expired_token') { + self.refreshCoinbaseToken(); + } + return; + } + self.coinbaseAccount = a.data; + }); + } + + coinbaseService.getCurrentUser(accessToken, function(err, u) { + if (err) { + self.coinbaseError = err; + if (err.errors[0] && err.errors[0].id == 'expired_token') { + self.refreshCoinbaseToken(); + } + return; + } + self.coinbaseUser = u.data; + }); + + coinbaseService.getPendingTransactions(function(err, txs) { + self.coinbasePendingTransactions = lodash.isEmpty(txs) ? null : txs; + lodash.forEach(txs, function(dataFromStorage, txId) { + if ((dataFromStorage.type == 'sell' && dataFromStorage.status == 'completed') || + (dataFromStorage.type == 'buy' && dataFromStorage.status == 'completed') || + dataFromStorage.status == 'error' || + (dataFromStorage.type == 'send' && dataFromStorage.status == 'completed')) return; + coinbaseService.getTransaction(accessToken, accountId, txId, function(err, tx) { + if (err) { + if (err.errors[0] && err.errors[0].id == 'expired_token') { + self.refreshCoinbaseToken(); + return; + } + coinbaseService.savePendingTransaction(dataFromStorage, { + status: 'error', + error: err + }, function(err) { + if (err) $log.debug(err); + }); + return; + } + _updateCoinbasePendingTransactions(dataFromStorage, tx.data); + self.coinbasePendingTransactions[txId] = dataFromStorage; + if (tx.data.type == 'send' && tx.data.status == 'completed' && tx.data.from) { + coinbaseService.sellPrice(accessToken, dataFromStorage.sell_price_currency, function(err, s) { + if (err) { + if (err.errors[0] && err.errors[0].id == 'expired_token') { + self.refreshCoinbaseToken(); + return; + } + coinbaseService.savePendingTransaction(dataFromStorage, { + status: 'error', + error: err + }, function(err) { + if (err) $log.debug(err); + }); + return; + } + var newSellPrice = s.data.amount; + var variance = Math.abs((newSellPrice - dataFromStorage.sell_price_amount) / dataFromStorage.sell_price_amount * 100); + if (variance < dataFromStorage.price_sensitivity.value) { + self.sellPending(tx.data); + } else { + var error = { + errors: [{ + message: 'Price falls over the selected percentage' + }] + }; + coinbaseService.savePendingTransaction(dataFromStorage, { + status: 'error', + error: error + }, function(err) { + if (err) $log.debug(err); + }); + } + }); + } else if (tx.data.type == 'buy' && tx.data.status == 'completed' && tx.data.buy) { + self.sendToCopay(dataFromStorage); + } else { + coinbaseService.savePendingTransaction(dataFromStorage, {}, function(err) { + if (err) $log.debug(err); + }); + } + }); + }); + }); + + }, 1000); + + var _updateCoinbasePendingTransactions = function(obj /*, …*/ ) { + for (var i = 1; i < arguments.length; i++) { + for (var prop in arguments[i]) { + var val = arguments[i][prop]; + if (typeof val == "object") + _updateCoinbasePendingTransactions(obj[prop], val); + else + obj[prop] = val ? val : obj[prop]; + } + } + return obj; + }; + + self.refreshCoinbaseToken = function() { + var network = self.coinbaseTestnet ? 'testnet' : 'livenet'; + storageService.getCoinbaseRefreshToken(network, function(err, refreshToken) { + if (!refreshToken) return; + coinbaseService.refreshToken(refreshToken, function(err, data) { + if (err) { + self.coinbaseError = err; + } else if (data && data.access_token && data.refresh_token) { + storageService.setCoinbaseToken(network, data.access_token, function() { + storageService.setCoinbaseRefreshToken(network, data.refresh_token, function() { + $timeout(function() { + self.initCoinbase(data.access_token); + }, 100); + }); + }); + } + }); + }); + }; + + self.sendToCopay = function(tx) { + if (!tx) return; + var data = { + to: tx.toAddr, + amount: tx.amount.amount, + currency: tx.amount.currency, + description: 'To Copay Wallet' + }; + coinbaseService.sendTo(self.coinbaseToken, self.coinbaseAccount.id, data, function(err, res) { + if (err) { + if (err.errors[0] && err.errors[0].id == 'expired_token') { + self.refreshCoinbaseToken(); + return; + } + coinbaseService.savePendingTransaction(tx, { + status: 'error', + error: err + }, function(err) { + if (err) $log.debug(err); + }); + } else { + if (!res.data.id) { + coinbaseService.savePendingTransaction(tx, { + status: 'error', + error: err + }, function(err) { + if (err) $log.debug(err); + }); + return; + } + coinbaseService.getTransaction(self.coinbaseToken, self.coinbaseAccount.id, res.data.id, function(err, sendTx) { + coinbaseService.savePendingTransaction(tx, { + remove: true + }, function(err) { + coinbaseService.savePendingTransaction(sendTx.data, {}, function(err) { + $timeout(function() { + self.updateCoinbase({ + updateAccount: true + }); + }, 1000); + }); + }); + }); + } + }); + }; + + self.sellPending = function(tx) { + if (!tx) return; + var data = tx.amount; + data['commit'] = true; + coinbaseService.sellRequest(self.coinbaseToken, self.coinbaseAccount.id, data, function(err, res) { + if (err) { + if (err.errors[0] && err.errors[0].id == 'expired_token') { + self.refreshCoinbaseToken(); + return; + } + coinbaseService.savePendingTransaction(tx, { + status: 'error', + error: err + }, function(err) { + if (err) $log.debug(err); + }); + } else { + if (!res.data.transaction) { + coinbaseService.savePendingTransaction(tx, { + status: 'error', + error: err + }, function(err) { + if (err) $log.debug(err); + }); + return; + } + coinbaseService.savePendingTransaction(tx, { + remove: true + }, function(err) { + coinbaseService.getTransaction(self.coinbaseToken, self.coinbaseAccount.id, res.data.transaction.id, function(err, updatedTx) { + coinbaseService.savePendingTransaction(updatedTx.data, {}, function(err) { + if (err) $log.debug(err); + $timeout(function() { + self.updateCoinbase({ + updateAccount: true + }); + }, 1000); + }); + }); + }); + } + }); + }; + + self.isInFocus = function(walletId) { + var fc = profileService.focusedClient; + return fc && fc.credentials.walletId == walletId; + }; + + self.setAddressbook = function(ab) { + if (ab) { + self.addressbook = ab; + return; + } + + addressbookService.list(function(err, ab) { + if (err) { + $log.error('Error getting the addressbook'); + return; + } + self.addressbook = ab; + }); + }; + + $rootScope.$on('$stateChangeSuccess', function(ev, to, toParams, from, fromParams) { + self.prevState = from.name || 'walletHome'; + self.tab = 'walletHome'; + }); + + $rootScope.$on('Local/ValidatingWalletEnded', function(ev, walletId, isOK) { + + if (self.isInFocus(walletId)) { + // NOTE: If the user changed the wallet, the flag is already turn off. + self.incorrectDerivation = isOK === false; + } + }); + + $rootScope.$on('Local/ClearHistory', function(event) { + $log.debug('The wallet transaction history has been deleted'); + self.txHistory = self.completeHistory = self.txHistorySearchResults = []; + self.debounceUpdateHistory(); + }); + + $rootScope.$on('Local/AddressbookUpdated', function(event, ab) { + self.setAddressbook(ab); + }); + + // UX event handlers + $rootScope.$on('Local/ColorUpdated', function(event) { + self.updateColor(); + $timeout(function() { + $rootScope.$apply(); + }); + }); + + $rootScope.$on('Local/AliasUpdated', function(event) { + self.updateAlias(); + $timeout(function() { + $rootScope.$apply(); + }); + }); + + $rootScope.$on('Local/SpendUnconfirmedUpdated', function(event, spendUnconfirmed) { + self.setSpendUnconfirmed(spendUnconfirmed); + self.updateAll(); + }); + + $rootScope.$on('Local/GlideraUpdated', function(event, accessToken) { + self.initGlidera(accessToken); + }); + + $rootScope.$on('Local/CoinbaseUpdated', function(event, accessToken) { + self.initCoinbase(accessToken); + }); + + $rootScope.$on('Local/GlideraTx', function(event, accessToken, permissions) { + self.updateGlidera(); + }); + + $rootScope.$on('Local/CoinbaseTx', function(event) { + self.updateCoinbase({ + updateAccount: true + }); + }); + + $rootScope.$on('Local/GlideraError', function(event) { + self.debouncedUpdate(); + }); + + $rootScope.$on('Local/UnitSettingUpdated', function(event) { + self.updateAll({ + triggerTxUpdate: true, + }); + }); + + $rootScope.$on('Local/WalletCompleted', function(event, walletId) { + if (self.isInFocus(walletId)) { + // reset main wallet variables + self.setFocusedWallet(); + go.walletHome(); + } + }); + + self.debouncedUpdate = function() { + var now = Date.now(); + var oneHr = 1000 * 60 * 60; + + if (!self.lastUpdate || (now - self.lastUpdate) > oneHr) { + self.updateAll({ + quiet: true, + triggerTxUpdate: true + }); + } + }; + + $rootScope.$on('Local/Resume', function(event) { + $log.debug('### Resume event'); + profileService.isDisclaimerAccepted(function(v) { + if (!v) { + $log.debug('Disclaimer not accepted, resume to home'); + go.path('disclaimer'); + } + }); + self.debouncedUpdate(); + }); + + $rootScope.$on('Local/BackupDone', function(event, walletId) { + self.needsBackup = false; + $log.debug('Backup done'); + storageService.setBackupFlag(walletId || self.walletId, function(err) { + $log.debug('Backup stored'); + }); + }); + + $rootScope.$on('Local/DeviceError', function(event, err) { + self.showErrorPopup(err, function() { + if (isCordova && navigator && navigator.app) { + navigator.app.exitApp(); + } + }); + }); + + $rootScope.$on('Local/WalletImported', function(event, walletId) { + self.needsBackup = false; + storageService.setBackupFlag(walletId, function() { + $log.debug('Backup done stored'); + addressService.expireAddress(walletId, function(err) { + $timeout(function() { + self.txHistory = self.completeHistory = self.txHistorySearchResults = []; + storageService.removeTxHistory(walletId, function() { + self.startScan(walletId); + }); + }, 500); + }); + }); + }); + + $rootScope.$on('NewIncomingTx', function() { + self.newTx = true; + self.updateAll({ + walletStatus: null, + untilItChanges: true, + triggerTxUpdate: true, + }); + }); + + + $rootScope.$on('NewBlock', function() { + if (self.glideraEnabled) { + $timeout(function() { + self.updateGlidera(); + }); + } + if (self.coinbaseEnabled) { + $timeout(function() { + self.updateCoinbase(); + }); + } + if (self.pendingAmount) { + self.updateAll({ + walletStatus: null, + untilItChanges: null, + triggerTxUpdate: true, + }); + } else if (self.hasUnsafeConfirmed) { + $log.debug('Wallet has transactions with few confirmations. Updating.') + if (self.network == 'testnet') { + self.throttledUpdateHistory(); + } else { + self.debounceUpdateHistory(); + } + } + }); + + $rootScope.$on('BalanceUpdated', function(e, n) { + self.setBalance(n.data); + }); + + + //untilItChange TRUE + lodash.each(['NewOutgoingTx', 'NewOutgoingTxByThirdParty'], function(eventName) { + $rootScope.$on(eventName, function(event) { + self.newTx = true; + self.updateAll({ + walletStatus: null, + untilItChanges: true, + triggerTxUpdate: true, + }); + }); + }); + + //untilItChange FALSE + lodash.each(['NewTxProposal', 'TxProposalFinallyRejected', 'TxProposalRemoved', 'NewOutgoingTxByThirdParty', + 'Local/GlideraTx' + ], function(eventName) { + $rootScope.$on(eventName, function(event) { + self.updateAll({ + walletStatus: null, + untilItChanges: null, + triggerTxUpdate: true, + }); + }); + }); + + + //untilItChange Maybe + $rootScope.$on('Local/TxProposalAction', function(event, untilItChanges) { + self.newTx = untilItChanges; + self.updateAll({ + walletStatus: null, + untilItChanges: untilItChanges, + triggerTxUpdate: true, + }); + }); + + $rootScope.$on('ScanFinished', function() { + $log.debug('Scan Finished. Updating history'); + storageService.removeTxHistory(self.walletId, function() { + self.updateAll({ + walletStatus: null, + triggerTxUpdate: true, + }); + }); + }); + + lodash.each(['TxProposalRejectedBy', 'TxProposalAcceptedBy'], function(eventName) { + $rootScope.$on(eventName, function() { + var f = function() { + if (self.updating) { + return $timeout(f, 200); + }; + self.updatePendingTxps(); + }; + f(); + }); + }); + + $rootScope.$on('Local/NoWallets', function(event) { + $timeout(function() { + self.hasProfile = true; + self.noFocusedWallet = true; + self.isComplete = null; + self.walletName = null; + uxLanguage.update(); + }); + }); + + $rootScope.$on('Local/NewFocusedWallet', function() { + console.log('[index.js.1200:NewFocusedWallet:] TODO'); //TODO + + return; + + + uxLanguage.update(); + self.setFocusedWallet(); + self.updateHistory(); + storageService.getCleanAndScanAddresses(function(err, walletId) { + + if (walletId && profileService.walletClients[walletId]) { + $log.debug('Clear last address cache and Scan ', walletId); + addressService.expireAddress(walletId, function(err) { + self.startScan(walletId); + }); + storageService.removeCleanAndScanAddresses(function() { + $rootScope.$emit('Local/NewFocusedWalletReady'); + }); + } else { + $rootScope.$emit('Local/NewFocusedWalletReady'); + } + }); + }); + + $rootScope.$on('Local/SetTab', function(event, tab, reset) { + self.setTab(tab, reset); + }); + + $rootScope.$on('disclaimerAccepted', function(event) { + $scope.isDisclaimerAccepted = true; + }); + + $rootScope.$on('Local/WindowResize', function() { + self.physicalScreenWidth = ((window.innerWidth > 0) ? window.innerWidth : screen.width); + }); + + $rootScope.$on('Local/NeedsConfirmation', function(event, txp, cb) { + + function openConfirmationPopup(txp, cb) { + + var config = configService.getSync(); + + $scope.color = config.colorFor[txp.walletId] || '#4A90E2'; + $scope.tx = txFormatService.processTx(txp); + + self.confirmationPopup = $ionicPopup.show({ + templateUrl: 'views/includes/confirm-tx.html', + scope: $scope, + }); + + $scope.processFee = function(amount, fee) { + var walletSettings = configService.getSync().wallet.settings; + var feeAlternativeIsoCode = walletSettings.alternativeIsoCode; + + $scope.feeLevel = feeService.feeOpts[feeService.getCurrentFeeLevel()]; + $scope.feeAlternativeStr = parseFloat((rateService.toFiat(fee, feeAlternativeIsoCode)).toFixed(2), 10) + ' ' + feeAlternativeIsoCode; + $scope.feeRateStr = (fee / (amount + fee) * 100).toFixed(2) + '%'; + }; + + $scope.cancel = function() { + return cb(); + }; + + $scope.accept = function() { + return cb(true); + }; + } + + openConfirmationPopup(txp, function(accept) { + self.confirmationPopup.close(); + return cb(accept); + }); + }); + + $rootScope.$on('Local/NeedsPassword', function(event, isSetup, cb) { + + function openPasswordPopup(isSetup, cb) { + $scope.data = {}; + $scope.data.password = null; + $scope.isSetup = isSetup; + $scope.isVerification = false; + $scope.loading = false; + var pass = null; + + self.passwordPopup = $ionicPopup.show({ + templateUrl: 'views/includes/password.html', + scope: $scope, + }); + + $scope.cancel = function() { + return cb('No spending password given'); + }; + + $scope.keyPress = function(event) { + if (!$scope.data.password || $scope.loading) return; + if (event.keyCode == 13) $scope.set(); + } + + $scope.set = function() { + $scope.loading = true; + $scope.error = null; + + $timeout(function() { + if (isSetup && !$scope.isVerification) { + $scope.loading = false; + $scope.isVerification = true; + pass = $scope.data.password; + $scope.data.password = null; + return; + } + if (isSetup && pass != $scope.data.password) { + $scope.loading = false; + $scope.error = gettext('Spending Passwords do not match'); + $scope.isVerification = false; + $scope.data.password = null; + pass = null; + return; + } + return cb(null, $scope.data.password); + }, 100); + }; + }; + + openPasswordPopup(isSetup, function(err, pass) { + self.passwordPopup.close(); + return cb(err, pass); + }); + + }); + + $rootScope.$on('Local/EmailUpdated', function(event, email) { + self.preferences.email = email; + }); + + lodash.each(['NewCopayer', 'CopayerUpdated'], function(eventName) { + $rootScope.$on(eventName, function() { + // Re try to open wallet (will triggers) + self.setFocusedWallet(); + }); + }); + + $rootScope.$on('Local/NewEncryptionSetting', function() { + var fc = profileService.focusedClient; + self.isPrivKeyEncrypted = fc.isPrivKeyEncrypted(); + $timeout(function() { + $rootScope.$apply(); + }); + }); + + + /* Start setup */ + lodash.assign(self, vanillaScope); + openURLService.init(); +}); diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 87a802166..299c91cf6 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -85,9 +85,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.wallet = wallet; if (wallet) { - walletService.updateStatus(wallet, { - triggerTxUpdate: true - }, function(err, status) { + walletService.updateStatus(wallet, {}, function(err, status) { console.log(status); if (err) {} // TODO }); diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js index 43c55ac1f..76048874a 100644 --- a/src/js/services/walletService.js +++ b/src/js/services/walletService.js @@ -9,6 +9,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim var root = {}; + root.WALLET_STATUS_MAX_TRIES = 7; + root.WALLET_STATUS_DELAY_BETWEEN_TRIES = 1.4 * 1000; root.SOFT_CONFIRMATION_LIMIT = 12; root.HISTORY_SHOW_LIMIT = 10; @@ -107,90 +109,10 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }; root.handleError = lodash.debounce(_handleError, 1000); - - root.setBalance = function(wallet, balance) { - if (!balance) return; - - var config = configService.getSync().wallet.settings; - var COIN = 1e8; - - // Address with Balance - wallet.balanceByAddress = balance.byAddress; - - // Spend unconfirmed funds - if (wallet.spendUnconfirmed) { - wallet.totalBalanceSat = balance.totalAmount; - wallet.lockedBalanceSat = balance.lockedAmount; - wallet.availableBalanceSat = balance.availableAmount; - wallet.totalBytesToSendMax = balance.totalBytesToSendMax; - wallet.pendingAmount = null; - } else { - wallet.totalBalanceSat = balance.totalConfirmedAmount; - wallet.lockedBalanceSat = balance.lockedConfirmedAmount; - wallet.availableBalanceSat = balance.availableConfirmedAmount; - wallet.totalBytesToSendMax = balance.totalBytesToSendConfirmedMax; - wallet.pendingAmount = balance.totalAmount - balance.totalConfirmedAmount; - } - - // Selected unit - wallet.unitToSatoshi = config.unitToSatoshi; - wallet.satToUnit = 1 / wallet.unitToSatoshi; - wallet.unitName = config.unitName; - - //STR - wallet.totalBalanceStr = txFormatService.formatAmount(wallet.totalBalanceSat) + ' ' + wallet.unitName; - wallet.lockedBalanceStr = txFormatService.formatAmount(wallet.lockedBalanceSat) + ' ' + wallet.unitName; - wallet.availableBalanceStr = txFormatService.formatAmount(wallet.availableBalanceSat) + ' ' + wallet.unitName; - - if (wallet.pendingAmount) { - wallet.pendingAmountStr = txFormatService.formatAmount(wallet.pendingAmount) + ' ' + wallet.unitName; - } else { - wallet.pendingAmountStr = null; - } - - wallet.alternativeName = config.alternativeName; - wallet.alternativeIsoCode = config.alternativeIsoCode; - - rateService.whenAvailable(function() { - - var totalBalanceAlternative = rateService.toFiat(wallet.totalBalanceSat, wallet.alternativeIsoCode); - var lockedBalanceAlternative = rateService.toFiat(wallet.lockedBalanceSat, wallet.alternativeIsoCode); - var alternativeConversionRate = rateService.toFiat(100000000, wallet.alternativeIsoCode); - - wallet.totalBalanceAlternative = $filter('formatFiatAmount')(totalBalanceAlternative); - wallet.lockedBalanceAlternative = $filter('formatFiatAmount')(lockedBalanceAlternative); - wallet.alternativeConversionRate = $filter('formatFiatAmount')(alternativeConversionRate); - - wallet.alternativeBalanceAvailable = true; - wallet.isRateAvailable = true; - }); - }; - - root.setStatus = function(wallet, status) { - wallet.status = status; - wallet.statusUpdatedOn = Date.now(); - wallet.isValid = true; - root.setBalance(wallet, status.balance); - wallet.email = status.preferences.email; - wallet.copayers = status.wallet.copayers; - }; - - root.updateStatus = function(wallet, opts, cb) { + root.getStatus = function(wallet, opts, cb) { opts = opts || {}; - function updateBalance(cb) { - if (wallet.isValid && !opts.force) - return cb(); - }; - - function updateHistory() { - if (!opts.triggerTxUpdate) return; - $timeout(function() { - root.updateHistory(wallet); - }); - }; - - function getStatus(cb) { + function get(cb) { wallet.getStatus({ twoStep: true }, function(err, ret) { @@ -201,61 +123,112 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }); }; - function walletStatusHash(walletStatus) { - var bal; - if (walletStatus) { - bal = walletStatus.balance.totalAmount; + function cacheBalance(wallet, balance) { + if (!balance) return; + + var config = configService.getSync().wallet.settings; + + // Address with Balance + wallet.balanceByAddress = balance.byAddress; + + // Spend unconfirmed funds + if (wallet.spendUnconfirmed) { + wallet.totalBalanceSat = balance.totalAmount; + wallet.lockedBalanceSat = balance.lockedAmount; + wallet.availableBalanceSat = balance.availableAmount; + wallet.totalBytesToSendMax = balance.totalBytesToSendMax; + wallet.pendingAmount = null; } else { - bal = wallet.totalBalanceSat; + wallet.totalBalanceSat = balance.totalConfirmedAmount; + wallet.lockedBalanceSat = balance.lockedConfirmedAmount; + wallet.availableBalanceSat = balance.availableConfirmedAmount; + wallet.totalBytesToSendMax = balance.totalBytesToSendConfirmedMax; + wallet.pendingAmount = balance.totalAmount - balance.totalConfirmedAmount; } - return bal; - }; - function doUpdate(initStatusHash, tries) { - if (wallet.isValid && !opts.force) return cb(); + // Selected unit + wallet.unitToSatoshi = config.unitToSatoshi; + wallet.satToUnit = 1 / wallet.unitToSatoshi; + wallet.unitName = config.unitName; - tries = tries || 0; + //STR + wallet.totalBalanceStr = root.formatAmount(wallet.totalBalanceSat) + ' ' + wallet.unitName; + wallet.lockedBalanceStr = root.formatAmount(wallet.lockedBalanceSat) + ' ' + wallet.unitName; + wallet.availableBalanceStr = root.formatAmount(wallet.availableBalanceSat) + ' ' + wallet.unitName; - $timeout(function() { - $log.debug('Updating Status:', wallet.credentials.walletName, tries); - getStatus(function(err, walletStatus) { - if (err) return cb(err); + if (wallet.pendingAmount) { + wallet.pendingAmountStr = root.formatAmount(wallet.pendingAmount) + ' ' + wallet.unitName; + } else { + wallet.pendingAmountStr = null; + } - var currentStatusHash = walletStatusHash(walletStatus); - $log.debug('Status update. hash:' + currentStatusHash + ' Try:' + tries); - if (opts.untilItChanges && - initStatusHash == currentStatusHash && - tries < 7 && - walletId == wallet.credentials.walletId) { - return $timeout(function() { - $log.debug('Retrying update... ' + walletId + ' Try:' + tries) - return root.doUpdate(initStatusHash, ++tries, cb); - }, 1400 * tries); - } + wallet.alternativeName = config.alternativeName; + wallet.alternativeIsoCode = config.alternativeIsoCode; - $log.debug('Got Wallet Status for:' + wallet.credentials.walletName); + rateService.whenAvailable(function() { - root.setStatus(wallet, walletStatus); + var totalBalanceAlternative = rateService.toFiat(wallet.totalBalanceSat, wallet.alternativeIsoCode); + var lockedBalanceAlternative = rateService.toFiat(wallet.lockedBalanceSat, wallet.alternativeIsoCode); + var alternativeConversionRate = rateService.toFiat(100000000, wallet.alternativeIsoCode); - // wallet.setPendingTxps(walletStatus.pendingTxps); - return cb(); - }); + wallet.totalBalanceAlternative = $filter('formatFiatAmount')(totalBalanceAlternative); + wallet.lockedBalanceAlternative = $filter('formatFiatAmount')(lockedBalanceAlternative); + wallet.alternativeConversionRate = $filter('formatFiatAmount')(alternativeConversionRate); + + wallet.alternativeBalanceAvailable = true; + wallet.isRateAvailable = true; }); }; - // trigger history update now? - if (!opts.untilItChanges) updateHistory(); + function cacheStatus = (status) { + wallet.status = status; + wallet.statusUpdatedOn = Date.now(); + wallet.isValid = true; + cacheBalance(wallet, status.balance); + wallet.email = status.preferences.email; + }; - doUpdate(walletStatusHash(), 0, function(err) { + function walletStatusHash(status) { + return status ? status.balance.totalAmount : wallet.totalBalanceSat; + }; + + function _getStatus(initStatusHash, tries, cb) { + if (wallet.isValid && !opts.force) return cb(null, wallet.status); + + tries = tries || 0; + + $log.debug('Updating Status:', wallet.credentials.walletName, tries); + get(function(err, status) { + if (err) return cb(err); + + var currentStatusHash = walletStatusHash(status); + $log.debug('Status update. hash:' + currentStatusHash + ' Try:' + tries); + if (opts.untilItChanges && + initStatusHash == currentStatusHash && + tries < root.WALLET_STATUS_MAX_TRIES && + walletId == wallet.credentials.walletId) { + return $timeout(function() { + $log.debug('Retrying update... ' + walletId + ' Try:' + tries) + return _getStatus(initStatusHash, ++tries, cb); + }, root.WALLET_STATUS_DELAY_BETWEEN_TRIES * tries); + } + + $log.debug('Got Wallet Status for:' + wallet.credentials.walletName); + + root.cacheStatus(wallet, status); + + // wallet.setPendingTxps(status.pendingTxps); + return cb(null, status); + }); + }; + + _getStatus(walletStatusHash(), 0, function(err, status) { if (err) { root.handleError(err); return cb(err); } - - if (opts.untilItChanges) updateHistory(); - return cb(); + return cb(null, status); }) - }; var getSavedTxs = function(walletId, cb) { @@ -363,7 +336,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim $log.debug('Fixing Tx Cache Unit to:' + name) lodash.each(txs, function(tx) { - tx.amountStr = txFormatService.formatAmount(tx.amount) + name; + tx.amountStr = txFormatService.formatAmount(tx.amount) + name; tx.feeStr = txFormatService.formatAmount(tx.fees) + name; }); }; @@ -479,11 +452,10 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }; - root.updateHistory = function(wallet) { + root.updateHistory = function(wallet, cb) { var walletId = wallet.credentials.walletId; - if (!wallet.isComplete()) return; - + if (!wallet.isComplete()) return cb(); $log.debug('Updating Transaction History'); wallet.txHistoryError = false; @@ -499,7 +471,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim $timeout(function() { wallet.newTx = false - }, 1000); + }); }); }); @@ -927,7 +899,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim if (err) return cb(err); ongoingProcess.set('signingTx', true); - root.signTx(wallet, publishedTxp, function(err, signedTxp) { + root.signTx(wallet, publishedTxp, function(err, signedTxp) { ongoingProcess.set('signingTx', false); root.lock(wallet); From e3076d18ab7e800ed4f988e9dc088046c6d16a3b Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 17 Aug 2016 18:48:30 -0300 Subject: [PATCH 4/5] show status balance --- public/views/walletDetails.html | 22 ++--- src/js/controllers/tab-home.js | 7 +- src/js/controllers/walletDetails.js | 29 +++++-- src/js/services/walletService.js | 120 +++++++++++++--------------- 4 files changed, 90 insertions(+), 88 deletions(-) diff --git a/public/views/walletDetails.html b/public/views/walletDetails.html index 6082966b2..cb43b4795 100644 --- a/public/views/walletDetails.html +++ b/public/views/walletDetails.html @@ -33,7 +33,7 @@
-
+
{{wallet.updateError|translate}} @@ -46,11 +46,11 @@
-
- {{wallet.totalBalanceStr}} -
{{wallet.totalBalanceAlternative}} {{wallet.alternativeIsoCode}}
+
+ {{status.totalBalanceStr}} +
{{status.totalBalanceAlternative}} {{status.alternativeIsoCode}}
- Pending Confirmation: {{wallet.pendingAmountStr}} + Pending Confirmation: {{status.pendingAmountStr}}
@@ -61,7 +61,7 @@
-
+
...
@@ -82,7 +82,7 @@
-
+
@@ -128,11 +128,11 @@
No transactions yet ZZZZ {{wallet.totalBalanceStr}}
-
+
@@ -146,7 +146,7 @@
-
+
@@ -160,7 +160,7 @@
-
diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index c37f9557b..d897faecf 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -93,11 +93,10 @@ angular.module('copayApp.controllers').controller('tabHomeController', i = 0; lodash.each(wallets, function(wallet) { - walletService.updateStatus(wallet, {}, function(err) { - var status = wallet.status; + walletService.getStatus(wallet, {}, function(err, status) { if (err) { - console.log('[tab-home.js.35:err:]',$log.error(err)); //TODO - return; + console.log('[tab-home.js.35:err:]', $log.error(err)); //TODO + return; } // TODO if (status.pendingTxps && status.pendingTxps[0]) { txps = txps.concat(status.pendingTxps); diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 299c91cf6..09615af35 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -61,12 +61,21 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun }); }; + $scope.updateAll = function()  { + $scope.update(); + } + $scope.update = function() { - walletService.updateStatus(wallet, { - force: true - }, function(err, status) { - if (err) {} // TODO - }); + $scope.updating = true; + $timeout(function() { + walletService.getStatus(wallet, { + force: true + }, function(err, status) { + if (err) {} // TODO + $scope.status = status; + $scope.updating = false; + }); + }) }; $scope.hideToggle = function() { @@ -85,9 +94,15 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.wallet = wallet; if (wallet) { - walletService.updateStatus(wallet, {}, function(err, status) { - console.log(status); + walletService.getStatus(wallet, {}, function(err, status) { + console.log('*** [walletDetails.js ln89] status:', status); // TODO if (err) {} // TODO + $scope.status = status; + }); + walletService.getHistory(wallet, {}, function(err, txHistory) { + console.log('*** [walletDetails.js ln93] txHistory:', txHistory); // TODO + if (err) {} // TODO + $scope.txHistory = txHistory; }); } }); diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js index 76048874a..4ab83f1b2 100644 --- a/src/js/services/walletService.js +++ b/src/js/services/walletService.js @@ -128,64 +128,71 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim var config = configService.getSync().wallet.settings; + var cache = wallet.cachedStatus; + // Address with Balance - wallet.balanceByAddress = balance.byAddress; + cache.balanceByAddress = balance.byAddress; // Spend unconfirmed funds - if (wallet.spendUnconfirmed) { - wallet.totalBalanceSat = balance.totalAmount; - wallet.lockedBalanceSat = balance.lockedAmount; - wallet.availableBalanceSat = balance.availableAmount; - wallet.totalBytesToSendMax = balance.totalBytesToSendMax; - wallet.pendingAmount = null; + if (cache.spendUnconfirmed) { + cache.totalBalanceSat = balance.totalAmount; + cache.lockedBalanceSat = balance.lockedAmount; + cache.availableBalanceSat = balance.availableAmount; + cache.totalBytesToSendMax = balance.totalBytesToSendMax; + cache.pendingAmount = null; } else { - wallet.totalBalanceSat = balance.totalConfirmedAmount; - wallet.lockedBalanceSat = balance.lockedConfirmedAmount; - wallet.availableBalanceSat = balance.availableConfirmedAmount; - wallet.totalBytesToSendMax = balance.totalBytesToSendConfirmedMax; - wallet.pendingAmount = balance.totalAmount - balance.totalConfirmedAmount; + cache.totalBalanceSat = balance.totalConfirmedAmount; + cache.lockedBalanceSat = balance.lockedConfirmedAmount; + cache.availableBalanceSat = balance.availableConfirmedAmount; + cache.totalBytesToSendMax = balance.totalBytesToSendConfirmedMax; + cache.pendingAmount = balance.totalAmount - balance.totalConfirmedAmount; } // Selected unit - wallet.unitToSatoshi = config.unitToSatoshi; - wallet.satToUnit = 1 / wallet.unitToSatoshi; - wallet.unitName = config.unitName; + cache.unitToSatoshi = config.unitToSatoshi; + cache.satToUnit = 1 / cache.unitToSatoshi; + cache.unitName = config.unitName; //STR - wallet.totalBalanceStr = root.formatAmount(wallet.totalBalanceSat) + ' ' + wallet.unitName; - wallet.lockedBalanceStr = root.formatAmount(wallet.lockedBalanceSat) + ' ' + wallet.unitName; - wallet.availableBalanceStr = root.formatAmount(wallet.availableBalanceSat) + ' ' + wallet.unitName; + cache.totalBalanceStr = root.formatAmount(cache.totalBalanceSat) + ' ' + cache.unitName; + cache.lockedBalanceStr = root.formatAmount(cache.lockedBalanceSat) + ' ' + cache.unitName; + cache.availableBalanceStr = root.formatAmount(cache.availableBalanceSat) + ' ' + cache.unitName; - if (wallet.pendingAmount) { - wallet.pendingAmountStr = root.formatAmount(wallet.pendingAmount) + ' ' + wallet.unitName; + if (cache.pendingAmount) { + cache.pendingAmountStr = root.formatAmount(cache.pendingAmount) + ' ' + cache.unitName; } else { - wallet.pendingAmountStr = null; + cache.pendingAmountStr = null; } - wallet.alternativeName = config.alternativeName; - wallet.alternativeIsoCode = config.alternativeIsoCode; + cache.alternativeName = config.alternativeName; + cache.alternativeIsoCode = config.alternativeIsoCode; rateService.whenAvailable(function() { - var totalBalanceAlternative = rateService.toFiat(wallet.totalBalanceSat, wallet.alternativeIsoCode); - var lockedBalanceAlternative = rateService.toFiat(wallet.lockedBalanceSat, wallet.alternativeIsoCode); - var alternativeConversionRate = rateService.toFiat(100000000, wallet.alternativeIsoCode); + var totalBalanceAlternative = rateService.toFiat(cache.totalBalanceSat, cache.alternativeIsoCode); + var lockedBalanceAlternative = rateService.toFiat(cache.lockedBalanceSat, cache.alternativeIsoCode); + var alternativeConversionRate = rateService.toFiat(100000000, cache.alternativeIsoCode); - wallet.totalBalanceAlternative = $filter('formatFiatAmount')(totalBalanceAlternative); - wallet.lockedBalanceAlternative = $filter('formatFiatAmount')(lockedBalanceAlternative); - wallet.alternativeConversionRate = $filter('formatFiatAmount')(alternativeConversionRate); + cache.totalBalanceAlternative = $filter('formatFiatAmount')(totalBalanceAlternative); + cache.lockedBalanceAlternative = $filter('formatFiatAmount')(lockedBalanceAlternative); + cache.alternativeConversionRate = $filter('formatFiatAmount')(alternativeConversionRate); - wallet.alternativeBalanceAvailable = true; - wallet.isRateAvailable = true; + cache.alternativeBalanceAvailable = true; + cache.isRateAvailable = true; }); }; - function cacheStatus = (status) { - wallet.status = status; - wallet.statusUpdatedOn = Date.now(); - wallet.isValid = true; + function isStatusCached() { + return wallet.cachedStatus && wallet.cachedStatus.isValid; + }; + + function cacheStatus(status) { + wallet.cachedStatus = status ||  {}; + var cache = wallet.cachedStatus; + cache.statusUpdatedOn = Date.now(); + cache.isValid = true; + cache.email = status.preferences ? status.preferences.email : null; cacheBalance(wallet, status.balance); - wallet.email = status.preferences.email; }; function walletStatusHash(status) { @@ -193,7 +200,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }; function _getStatus(initStatusHash, tries, cb) { - if (wallet.isValid && !opts.force) return cb(null, wallet.status); + if (isStatusCached() && !opts.force) return cb(null, wallet.cachedStatus); tries = tries || 0; @@ -215,7 +222,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim $log.debug('Got Wallet Status for:' + wallet.credentials.walletName); - root.cacheStatus(wallet, status); + cacheStatus(status); // wallet.setPendingTxps(status.pendingTxps); return cb(null, status); @@ -354,7 +361,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim // First update if (walletId == wallet.credentials.walletId) { wallet.completeHistory = txsFromLocal; - setCompactTxHistory(wallet); } if (wallet.historyUpdateInProgress) @@ -388,7 +394,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim var newHistory = processNewTxs(wallet, newTxs); newHistory = lodash.compact(newHistory.concat(confirmedTxs)); wallet.completeHistory = newHistory; - setCompactTxHistory(wallet); } } }); @@ -438,7 +443,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim // Final update if (walletId == wallet.credentials.walletId) { wallet.completeHistory = newHistory; - setCompactTxHistory(wallet); } return storageService.setTxHistory(historyToSave, walletId, function() { @@ -452,28 +456,21 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }; - root.updateHistory = function(wallet, cb) { + root.getHistory = function(wallet, opts, cb) { + opts = opts || {}; + opts.skip = opts.skip || 0; + opts.limit = opts.limit || root.HISTORY_SHOW_LIMIT; + var walletId = wallet.credentials.walletId; if (!wallet.isComplete()) return cb(); $log.debug('Updating Transaction History'); - wallet.txHistoryError = false; - wallet.updatingTxHistory = true; - $timeout(function() { - updateLocalTxHistory(wallet, function(err) { - wallet.historyUpdateInProgress = wallet.updatingTxHistory = false; - wallet.loadingWallet = false; - wallet.txProgress = 0; - if (err) - wallet.txHistoryError = true; - - $timeout(function() { - wallet.newTx = false - }); - - }); + updateLocalTxHistory(wallet, function(err) { + if (err) return cb(err); + var txs = wallet.completeHistory ? wallet.completeHistory.slice(opts.skip, opts.limit) : null; + return cb(err, txs); }); }; @@ -662,15 +659,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }); }; - var setCompactTxHistory = function(wallet) { - - // TODO - wallet.isSearching = false; - wallet.nextTxHistory = root.HISTORY_SHOW_LIMIT; - wallet.txHistory = wallet.completeHistory ? wallet.completeHistory.slice(0, root.HISTORY_SHOW_LIMIT) : null; - wallet.historyShowMore = wallet.completeHistory ? wallet.completeHistory.length > root.HISTORY_SHOW_LIMIT : null; - }; - root.debounceUpdateHistory = lodash.debounce(function() { root.updateHistory(); }, 1000); From 8aefbe25b30b7639d08d882f3ef75b9ac746c76a Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 18 Aug 2016 10:37:08 -0300 Subject: [PATCH 5/5] wallet details refactor --- public/views/tab-home.html | 4 +- public/views/walletDetails.html | 64 +- src/js/controllers/preferences.js | 2 +- src/js/controllers/preferencesUnit.js | 29 +- src/js/controllers/tab-home.js | 9 +- src/js/controllers/walletDetails.js | 136 ++-- src/js/routes.js | 1072 ++++++++++++------------- src/js/services/txFormatService.js | 17 +- src/js/services/walletService.js | 82 +- 9 files changed, 706 insertions(+), 709 deletions(-) diff --git a/public/views/tab-home.html b/public/views/tab-home.html index 28cd452b1..f2c41021e 100644 --- a/public/views/tab-home.html +++ b/public/views/tab-home.html @@ -3,7 +3,7 @@ Home - +

Payment Proposals

@@ -64,7 +64,7 @@ Incomplete - {{item.availableBalanceStr}} + {{item.status.availableBalanceStr}} diff --git a/public/views/walletDetails.html b/public/views/walletDetails.html index cb43b4795..aa5ab94c6 100644 --- a/public/views/walletDetails.html +++ b/public/views/walletDetails.html @@ -1,6 +1,6 @@ - + {{wallet.name}} @@ -14,7 +14,7 @@ - +
No Wallet @@ -33,35 +33,35 @@
-
+
-
- {{wallet.updateError|translate}} +
+ {{updateStatusError|translate}}
-
+
Scan status finished with error
Tap to retry
-
+
{{status.totalBalanceStr}} -
{{status.totalBalanceAlternative}} {{status.alternativeIsoCode}}
-
+
{{status.totalBalanceAlternative}} {{status.alternativeIsoCode}}
+
Pending Confirmation: {{status.pendingAmountStr}}
-
+
[Balance Hidden]
Tap and hold to show
-
+
...
@@ -82,7 +82,7 @@
-
+
@@ -94,7 +94,7 @@
+ ng-click="recreate()"> Recreate
@@ -105,48 +105,46 @@
-

Payment Proposals

-

Unsent transactions

+

Payment Proposals

+

Unsent transactions

+ ng-show="status.lockedBalanceSat"> Total Locked Balance: - {{wallet.lockedBalanceStr}} - {{wallet.lockedBalanceAlternative}} - {{wallet.alternativeIsoCode}} + {{status.lockedBalanceStr}} + {{status.lockedBalanceAlternative}} + {{status.alternativeIsoCode}}
- - -

+ +

Activity

No transactions yet ZZZZ {{wallet.totalBalanceStr}} + ng-show="!txHistory[0] && !updatingTxHistory && !txHistoryError && !updateStatusError && !notAuthorized" + translate>No transactions yet {{status.totalBalanceStr}}
+
-
- -
+
-
{{wallet.txProgress}} transactions downloaded
+
{{updatingTxHistoryProgress}} transactions downloaded
Updating transaction history. Please stand by.
-
+
@@ -219,14 +217,12 @@
-
-
- -
+
+
diff --git a/src/js/controllers/preferences.js b/src/js/controllers/preferences.js index a23706cdc..5137bd1ac 100644 --- a/src/js/controllers/preferences.js +++ b/src/js/controllers/preferences.js @@ -11,7 +11,7 @@ angular.module('copayApp.controllers').controller('preferencesController', $scope.externalSource = null; if (wallet) { - walletService.updateStatus(wallet, {}, function(err, status) {}); + walletService.getStatus(wallet, {}, function(err, status) {}); var config = configService.getSync(); config.aliasFor = config.aliasFor || {}; $scope.alias = config.aliasFor[walletId] || wallet.credentials.walletName; diff --git a/src/js/controllers/preferencesUnit.js b/src/js/controllers/preferencesUnit.js index c74efc0de..abdee5625 100644 --- a/src/js/controllers/preferencesUnit.js +++ b/src/js/controllers/preferencesUnit.js @@ -7,22 +7,19 @@ angular.module('copayApp.controllers').controller('preferencesUnitController', f $scope.currentUnit = config.wallet.settings.unitCode; } - $scope.unitList = [ - { - name: 'bits (1,000,000 bits = 1BTC)', - shortName: 'bits', - value: 100, - decimals: 2, - code: 'bit', - }, - { - name: 'BTC', - shortName: 'BTC', - value: 100000000, - decimals: 8, - code: 'btc', - } - ]; + $scope.unitList = [{ + name: 'bits (1,000,000 bits = 1BTC)', + shortName: 'bits', + value: 100, + decimals: 2, + code: 'bit', + }, { + name: 'BTC', + shortName: 'BTC', + value: 100000000, + decimals: 8, + code: 'btc', + }]; $scope.save = function(newUnit) { var opts = { diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index d897faecf..579069381 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -88,11 +88,9 @@ angular.module('copayApp.controllers').controller('tabHomeController', self.updateAllClients = function() { var txps = []; - var wallets = profileService.getWallets(); - var l = wallets.length, - i = 0; + var i = $scope.wallets.length; - lodash.each(wallets, function(wallet) { + lodash.each($scope.wallets, function(wallet) { walletService.getStatus(wallet, {}, function(err, status) { if (err) { console.log('[tab-home.js.35:err:]', $log.error(err)); //TODO @@ -101,9 +99,10 @@ angular.module('copayApp.controllers').controller('tabHomeController', if (status.pendingTxps && status.pendingTxps[0]) { txps = txps.concat(status.pendingTxps); } - if (++i == l) { + if (--i == 0) { setPendingTxps(txps); } + wallet.status = status; }); }); } diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 09615af35..0e2a6927a 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -1,36 +1,16 @@ 'use strict'; -angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, profileService, lodash, configService, gettext, gettextCatalog, platformInfo, go, walletService) { - +angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, bwcError, profileService, lodash, configService, gettext, gettextCatalog, platformInfo, go, walletService) { var isCordova = platformInfo.isCordova; var isWP = platformInfo.isWP; var isAndroid = platformInfo.isAndroid; var isChromeApp = platformInfo.isChromeApp; - var self = this; - $rootScope.shouldHideMenuBar = false; - $rootScope.wpInputFocused = false; - var config = configService.getSync(); - var configWallet = config.wallet; - var walletSettings = configWallet.settings; - var ret = {}; + var errorPopup; + + var HISTORY_SHOW_LIMIT = 10; - // INIT. Global value - ret.unitToSatoshi = walletSettings.unitToSatoshi; - ret.satToUnit = 1 / ret.unitToSatoshi; - ret.unitName = walletSettings.unitName; - ret.alternativeIsoCode = walletSettings.alternativeIsoCode; - ret.alternativeName = walletSettings.alternativeName; - ret.alternativeAmount = 0; - ret.unitDecimals = walletSettings.unitDecimals; - ret.isCordova = isCordova; - ret.addresses = []; - ret.isMobile = platformInfo.isMobile; - ret.isWindowsPhoneApp = platformInfo.isWP; - ret.countDown = null; - ret.sendMaxInfo = {}; - ret.showAlternative = false; $scope.openSearchModal = function() { var fc = profileService.focusedClient; @@ -61,48 +41,100 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun }); }; - $scope.updateAll = function()  { - $scope.update(); - } + $scope.recreate = function() { + walletService.recreate(); + }; - $scope.update = function() { - $scope.updating = true; + $scope.updateStatus = function(force) { + $scope.updatingStatus = true; + $scope.updateStatusError = false; $timeout(function() { walletService.getStatus(wallet, { - force: true + force: !!force, }, function(err, status) { - if (err) {} // TODO + $scope.updatingStatus = false; + if (err) { + $scope.status = null; + $scope.updateStatusError = true; + return; + } $scope.status = status; - $scope.updating = false; }); }) }; + $scope.updateTxHistory = function() { + + if ($scope.updatingTxHistory) return; + + $scope.updatingTxHistory = true; + $scope.updateTxHistoryError = false; + $scope.updatingTxHistoryProgress = null; + + var progressFn = function(txs) { + $scope.updatingTxHistoryProgress = txs ? txs.length : 0; + completeTxHistory = txs; + $scope.showHistory(); + $scope.$digest(); + }; + + $timeout(function() { + walletService.getTxHistory(wallet, { + progressFn: progressFn, + }, function(err, txHistory) { + $scope.updatingTxHistory = false; + if (err) { + $scope.txHistory = null; + $scope.updateTxHistoryError = true; + return; + } + completeTxHistory = txHistory; + + $scope.showHistory(); + $scope.$apply(); + }); + }); + }; + + $scope.showHistory = function() { + if ($scope.isSearching) { + $scope.txHistorySearchResults = filteredTxHistory ? filteredTxHistory.slice(0, (currentTxHistoryPage + 1) * HISTORY_SHOW_LIMIT) : []; + $scope.txHistoryShowMore = filteredTxHistory.length > $scope.txHistorySearchResults.length; + } else { + $scope.txHistory = completeTxHistory ? completeTxHistory.slice(0, (currentTxHistoryPage + 1) * HISTORY_SHOW_LIMIT) : []; + $scope.txHistoryShowMore = completeTxHistory.length > $scope.txHistory.length; + } + }; + + $scope.showMore = function() { + currentTxHistoryPage++; + $scope.showHistory(); + $scope.$broadcast('scroll.infiniteScrollComplete'); + }; + + $scope.updateAll = function()  { + $scope.updateStatus(false); + $scope.updateTxHistory(); + } + $scope.hideToggle = function() { console.log('[walletDetails.js.70:hideToogle:] TODO'); //TODO }; - if (!$stateParams.walletId) { - $log.debug('No wallet provided... using the first one'); - $stateParams.walletId = profileService.getWallets({ - onlyComplete: true - })[0].id; - } + var currentTxHistoryPage; + var completeTxHistory; + var wallet; + $scope.init = function() { + currentTxHistoryPage = 0; + completeTxHistory = []; - var wallet = profileService.getWallet($stateParams.walletId); - $scope.wallet = wallet; + wallet = profileService.getWallet($stateParams.walletId); + $scope.wallet = wallet; + $scope.requiresMultipleSignatures = wallet.credentials.m > 1; + $scope.newTx = false; + + $scope.updateAll(); + }; - if (wallet) { - walletService.getStatus(wallet, {}, function(err, status) { - console.log('*** [walletDetails.js ln89] status:', status); // TODO - if (err) {} // TODO - $scope.status = status; - }); - walletService.getHistory(wallet, {}, function(err, txHistory) { - console.log('*** [walletDetails.js ln93] txHistory:', txHistory); // TODO - if (err) {} // TODO - $scope.txHistory = txHistory; - }); - } }); diff --git a/src/js/routes.js b/src/js/routes.js index 8f1edee76..632681450 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -14,555 +14,555 @@ if (window && window.navigator) { //Setting up route angular.module('copayApp').config(function(historicLogProvider, $provide, $logProvider, $stateProvider, $urlRouterProvider, $compileProvider) { - $urlRouterProvider.otherwise('/tabs.home'); + $urlRouterProvider.otherwise('/tabs.home'); - $logProvider.debugEnabled(true); - $provide.decorator('$log', ['$delegate', 'platformInfo', - function($delegate, platformInfo) { - var historicLog = historicLogProvider.$get(); + $logProvider.debugEnabled(true); + $provide.decorator('$log', ['$delegate', 'platformInfo', + function($delegate, platformInfo) { + var historicLog = historicLogProvider.$get(); - ['debug', 'info', 'warn', 'error', 'log'].forEach(function(level) { - if (platformInfo.isDevel && level == 'error') return; + ['debug', 'info', 'warn', 'error', 'log'].forEach(function(level) { + if (platformInfo.isDevel && level == 'error') return; - var orig = $delegate[level]; - $delegate[level] = function() { - if (level == 'error') - console.log(arguments); + var orig = $delegate[level]; + $delegate[level] = function() { + if (level == 'error') + console.log(arguments); - var args = Array.prototype.slice.call(arguments); - - args = args.map(function(v) { - try { - if (typeof v == 'undefined') v = 'undefined'; - if (!v) v = 'null'; - if (typeof v == 'object') { - if (v.message) - v = v.message; - else - v = JSON.stringify(v); - } - // Trim output in mobile - if (platformInfo.isCordova) { - v = v.toString(); - if (v.length > 3000) { - v = v.substr(0, 2997) + '...'; - } - } - } catch (e) { - console.log('Error at log decorator:', e); - v = 'undefined'; - } - return v; - }); + var args = Array.prototype.slice.call(arguments); + args = args.map(function(v) { try { - if (platformInfo.isCordova) - console.log(args.join(' ')); - - historicLog.add(level, args.join(' ')); - orig.apply(null, args); + if (typeof v == 'undefined') v = 'undefined'; + if (!v) v = 'null'; + if (typeof v == 'object') { + if (v.message) + v = v.message; + else + v = JSON.stringify(v); + } + // Trim output in mobile + if (platformInfo.isCordova) { + v = v.toString(); + if (v.length > 3000) { + v = v.substr(0, 2997) + '...'; + } + } } catch (e) { - console.log('ERROR (at log decorator):', e, args[0]); + console.log('Error at log decorator:', e); + v = 'undefined'; } - }; - }); - return $delegate; - } - ]); + return v; + }); - // whitelist 'chrome-extension:' for chromeApp to work with image URLs processed by Angular - // link: http://stackoverflow.com/questions/15606751/angular-changes-urls-to-unsafe-in-extension-page?lq=1 - $compileProvider.imgSrcSanitizationWhitelist(/^\s*((https?|ftp|file|blob|chrome-extension):|data:image\/)/); + try { + if (platformInfo.isCordova) + console.log(args.join(' ')); - $stateProvider - .state('translators', { - url: '/translators', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/translators.html' + historicLog.add(level, args.join(' ')); + orig.apply(null, args); + } catch (e) { + console.log('ERROR (at log decorator):', e, args[0]); } - } - }) - .state('disclaimer', { - url: '/disclaimer', - needProfile: false, - views: { - 'main': { - templateUrl: 'views/disclaimer.html', - } - } - }) - .state('wallet', { - url: '/wallet/{walletId}', - abstract: true, - needProfile: true, - views: { - 'main': { - template: '', - }, - }, - }) - .state('wallet.details', { - url: '/details', - templateUrl: 'views/walletDetails.html', - needProfile: true - }) - .state('wallet.preferences', { - url: '/preferences', - templateUrl: 'views/preferences.html', - needProfile: true - }) - .state('wallet.preferencesAlias', { - url: '/preferencesAlias', - templateUrl: 'views/preferencesAlias.html', - needProfile: true - }) - .state('wallet.preferencesColor', { - url: '/preferencesColor', - templateUrl: 'views/preferencesColor.html', - needProfile: true - }) - .state('wallet.preferencesEmail', { - url: '/preferencesEmail', - templateUrl: 'views/preferencesEmail.html', - needProfile: true - }) - .state('wallet.backup', { - url: '/backup', - templateUrl: 'views/backup.html', - needProfile: true - }) - .state('wallet.preferencesAdvanced', { - url: '/preferencesAdvanced', - templateUrl: 'views/preferencesAdvanced.html', - needProfile: true - }) - .state('wallet.information', { - url: '/information', - templateUrl: 'views/preferencesInformation.html', - needProfile: true - }) - .state('wallet.export', { - url: '/export', - templateUrl: 'views/export.html', - needProfile: true - }) - .state('wallet.preferencesBwsUrl', { - url: '/preferencesBwsUrl', - templateUrl: 'views/preferencesBwsUrl.html', - needProfile: true - }) - .state('wallet.preferencesHistory', { - url: '/preferencesHistory', - templateUrl: 'views/preferencesHistory.html', - needProfile: true - }) - .state('wallet.deleteWords', { - url: '/deleteWords', - templateUrl: 'views/preferencesDeleteWords.html', - needProfile: true - }) - .state('wallet.delete', { - url: '/delete', - templateUrl: 'views/preferencesDeleteWallet.html', - needProfile: true - }) - .state('wallet.copayers', { - url: '/copayers', - needProfile: true, - cache: false, - templateUrl: 'views/copayers.html' - }) - -// OLD - // .state('walletHome', { - // url: '/old', - // needProfile: true, - // views: { - // 'main': { - // templateUrl: 'views/walletHome.html', - // }, - // } - // }) - .state('tabs', { - url: '/tabs', - cache: false, - needProfile: true, - abstract: true, - views: { - 'main': { - templateUrl: 'views/tabs.html', - }, - } - }) - .state('tabs.home', { - url: '/home', - cache: false, - needProfile: true, - views: { - 'tab-home': { - templateUrl: 'views/tab-home.html', - }, - } - }) - .state('tabs.receive', { - url: '/receive', - cache: false, - needProfile: true, - views: { - 'tab-receive': { - templateUrl: 'views/tab-receive.html', - }, - } - }) - .state('tabs.scan', { - url: '/scan', - needProfile: true, - views: { - 'tab-scan': { - templateUrl: 'views/tab-scan.html', - }, - } - }) - .state('tabs.send', { - url: '/send', - cache: false, - needProfile: true, - views: { - 'tab-send': { - templateUrl: 'views/tab-send.html', - }, - } - }) - .state('tabs.settings', { - url: '/settings', - needProfile: true, - views: { - 'tab-settings': { - templateUrl: 'views/tab-settings.html', - }, - } - }) - .state('amount', { - cache: false, - url: '/amount:/:toAddress/:toName', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/amount.html', - }, - }, - }) - .state('confirm', { - cache: false, - url: '/confirm/:toAddress/:toName/:toAmount', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/confirm.html', - }, - }, - }) - - .state('unsupported', { - url: '/unsupported', - needProfile: false, - views: { - 'main': { - templateUrl: 'views/unsupported.html' - } - } - }) - .state('uri', { - url: '/uri/:url', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/uri.html' - } - } - }) - .state('uripayment', { - url: '/uri-payment/:url', - templateUrl: 'views/paymentUri.html', - views: { - 'main': { - templateUrl: 'views/paymentUri.html', - }, - }, - needProfile: true - }) - .state('join', { - url: '/join', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/join.html' - }, - } - }) - .state('import', { - url: '/import', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/import.html' - }, - } - }) - .state('create', { - url: '/create', - templateUrl: 'views/create.html', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/create.html' - }, - } - }) - .state('preferencesLanguage', { - url: '/preferencesLanguage', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/preferencesLanguage.html' - }, - } - }) - .state('preferencesUnit', { - url: '/preferencesUnit', - templateUrl: 'views/preferencesUnit.html', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/preferencesUnit.html' - }, - } - }) - .state('preferencesFee', { - url: '/preferencesFee', - templateUrl: 'views/preferencesFee.html', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/preferencesFee.html' - }, - } - }) - .state('uriglidera', { - url: '/uri-glidera/:url', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/glideraUri.html' - }, - } - }) - .state('glidera', { - url: '/glidera', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/glidera.html' - }, - } - }) - .state('buyGlidera', { - url: '/buy', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/buyGlidera.html' - }, - } - }) - .state('sellGlidera', { - url: '/sell', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/sellGlidera.html' - }, - } - }) - .state('preferencesGlidera', { - url: '/preferencesGlidera', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/preferencesGlidera.html' - }, - } - }) - .state('bitpayCard', { - url: '/bitpay-card', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/bitpayCard.html' - }, - } - }) - .state('preferencesBitpayCard', { - url: '/preferences-bitpay-card', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/preferencesBitpayCard.html' - }, - } - }) - .state('coinbase', { - url: '/coinbase', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/coinbase.html' - }, - } - }) - .state('preferencesCoinbase', { - url: '/preferencesCoinbase', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/preferencesCoinbase.html' - }, - } - }) - .state('uricoinbase', { - url: '/uri-coinbase/:url', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/coinbaseUri.html' - }, - } - }) - .state('buyCoinbase', { - url: '/buycoinbase', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/buyCoinbase.html' - }, - } - }) - .state('sellCoinbase', { - url: '/sellcoinbase', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/sellCoinbase.html' - }, - } - }) - .state('buyandsell', { - url: '/buyandsell', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/buyAndSell.html', - controller: function(platformInfo) { - if (platformInfo.isCordova && StatusBar.isVisible) { - StatusBar.backgroundColorByHexString("#4B6178"); - } - } - } - } - }) - .state('amazon', { - url: '/amazon', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/amazon.html' - }, - } - }) - .state('buyAmazon', { - url: '/buyamazon', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/buyAmazon.html' - }, - } - }) - .state('preferencesAltCurrency', { - url: '/preferencesAltCurrency', - templateUrl: 'views/preferencesAltCurrency.html', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/preferencesAltCurrency.html' - }, - } - }) - .state('about', { - url: '/about', - templateUrl: 'views/preferencesAbout.html', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/preferencesAbout.html' - }, - } - }) - .state('logs', { - url: '/logs', - templateUrl: 'views/preferencesLogs.html', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/preferencesLogs.html' - }, - } - }) - .state('paperWallet', { - url: '/paperWallet', - templateUrl: 'views/paperWallet.html', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/paperWallet.html' - }, - } - }) - .state('preferencesGlobal', { - url: '/preferencesGlobal', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/preferencesGlobal.html', - }, - } - }) - .state('termOfUse', { - url: '/termOfUse', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/termOfUse.html', - }, - } - }) - .state('add', { - url: '/add', - needProfile: true, - views: { - 'main': { - templateUrl: 'views/add.html', - controller: function(platformInfo) { - if (platformInfo.isCordova && StatusBar.isVisible) { - StatusBar.backgroundColorByHexString("#4B6178"); - } - } - } - } + }; }); + return $delegate; + } + ]); + + // whitelist 'chrome-extension:' for chromeApp to work with image URLs processed by Angular + // link: http://stackoverflow.com/questions/15606751/angular-changes-urls-to-unsafe-in-extension-page?lq=1 + $compileProvider.imgSrcSanitizationWhitelist(/^\s*((https?|ftp|file|blob|chrome-extension):|data:image\/)/); + + $stateProvider + .state('translators', { + url: '/translators', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/translators.html' + } + } + }) + .state('disclaimer', { + url: '/disclaimer', + needProfile: false, + views: { + 'main': { + templateUrl: 'views/disclaimer.html', + } + } + }) + .state('wallet', { + url: '/wallet/{walletId}', + abstract: true, + needProfile: true, + views: { + 'main': { + template: '', + }, + }, + }) + .state('wallet.details', { + url: '/details', + templateUrl: 'views/walletDetails.html', + needProfile: true + }) + .state('wallet.preferences', { + url: '/preferences', + templateUrl: 'views/preferences.html', + needProfile: true + }) + .state('wallet.preferencesAlias', { + url: '/preferencesAlias', + templateUrl: 'views/preferencesAlias.html', + needProfile: true + }) + .state('wallet.preferencesColor', { + url: '/preferencesColor', + templateUrl: 'views/preferencesColor.html', + needProfile: true + }) + .state('wallet.preferencesEmail', { + url: '/preferencesEmail', + templateUrl: 'views/preferencesEmail.html', + needProfile: true + }) + .state('wallet.backup', { + url: '/backup', + templateUrl: 'views/backup.html', + needProfile: true + }) + .state('wallet.preferencesAdvanced', { + url: '/preferencesAdvanced', + templateUrl: 'views/preferencesAdvanced.html', + needProfile: true + }) + .state('wallet.information', { + url: '/information', + templateUrl: 'views/preferencesInformation.html', + needProfile: true + }) + .state('wallet.export', { + url: '/export', + templateUrl: 'views/export.html', + needProfile: true + }) + .state('wallet.preferencesBwsUrl', { + url: '/preferencesBwsUrl', + templateUrl: 'views/preferencesBwsUrl.html', + needProfile: true + }) + .state('wallet.preferencesHistory', { + url: '/preferencesHistory', + templateUrl: 'views/preferencesHistory.html', + needProfile: true + }) + .state('wallet.deleteWords', { + url: '/deleteWords', + templateUrl: 'views/preferencesDeleteWords.html', + needProfile: true + }) + .state('wallet.delete', { + url: '/delete', + templateUrl: 'views/preferencesDeleteWallet.html', + needProfile: true + }) + .state('wallet.copayers', { + url: '/copayers', + needProfile: true, + cache: false, + templateUrl: 'views/copayers.html' + }) + + // OLD + // .state('walletHome', { + // url: '/old', + // needProfile: true, + // views: { + // 'main': { + // templateUrl: 'views/walletHome.html', + // }, + // } + // }) + .state('tabs', { + url: '/tabs', + cache: false, + needProfile: true, + abstract: true, + views: { + 'main': { + templateUrl: 'views/tabs.html', + }, + } }) + .state('tabs.home', { + url: '/home', + cache: false, + needProfile: true, + views: { + 'tab-home': { + templateUrl: 'views/tab-home.html', + }, + } + }) + .state('tabs.receive', { + url: '/receive', + cache: false, + needProfile: true, + views: { + 'tab-receive': { + templateUrl: 'views/tab-receive.html', + }, + } + }) + .state('tabs.scan', { + url: '/scan', + needProfile: true, + views: { + 'tab-scan': { + templateUrl: 'views/tab-scan.html', + }, + } + }) + .state('tabs.send', { + url: '/send', + cache: false, + needProfile: true, + views: { + 'tab-send': { + templateUrl: 'views/tab-send.html', + }, + } + }) + .state('tabs.settings', { + url: '/settings', + needProfile: true, + views: { + 'tab-settings': { + templateUrl: 'views/tab-settings.html', + }, + } + }) + .state('amount', { + cache: false, + url: '/amount:/:toAddress/:toName', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/amount.html', + }, + }, + }) + .state('confirm', { + cache: false, + url: '/confirm/:toAddress/:toName/:toAmount', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/confirm.html', + }, + }, + }) + + .state('unsupported', { + url: '/unsupported', + needProfile: false, + views: { + 'main': { + templateUrl: 'views/unsupported.html' + } + } + }) + .state('uri', { + url: '/uri/:url', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/uri.html' + } + } + }) + .state('uripayment', { + url: '/uri-payment/:url', + templateUrl: 'views/paymentUri.html', + views: { + 'main': { + templateUrl: 'views/paymentUri.html', + }, + }, + needProfile: true + }) + .state('join', { + url: '/join', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/join.html' + }, + } + }) + .state('import', { + url: '/import', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/import.html' + }, + } + }) + .state('create', { + url: '/create', + templateUrl: 'views/create.html', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/create.html' + }, + } + }) + .state('preferencesLanguage', { + url: '/preferencesLanguage', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/preferencesLanguage.html' + }, + } + }) + .state('preferencesUnit', { + url: '/preferencesUnit', + templateUrl: 'views/preferencesUnit.html', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/preferencesUnit.html' + }, + } + }) + .state('preferencesFee', { + url: '/preferencesFee', + templateUrl: 'views/preferencesFee.html', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/preferencesFee.html' + }, + } + }) + .state('uriglidera', { + url: '/uri-glidera/:url', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/glideraUri.html' + }, + } + }) + .state('glidera', { + url: '/glidera', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/glidera.html' + }, + } + }) + .state('buyGlidera', { + url: '/buy', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/buyGlidera.html' + }, + } + }) + .state('sellGlidera', { + url: '/sell', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/sellGlidera.html' + }, + } + }) + .state('preferencesGlidera', { + url: '/preferencesGlidera', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/preferencesGlidera.html' + }, + } + }) + .state('bitpayCard', { + url: '/bitpay-card', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/bitpayCard.html' + }, + } + }) + .state('preferencesBitpayCard', { + url: '/preferences-bitpay-card', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/preferencesBitpayCard.html' + }, + } + }) + .state('coinbase', { + url: '/coinbase', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/coinbase.html' + }, + } + }) + .state('preferencesCoinbase', { + url: '/preferencesCoinbase', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/preferencesCoinbase.html' + }, + } + }) + .state('uricoinbase', { + url: '/uri-coinbase/:url', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/coinbaseUri.html' + }, + } + }) + .state('buyCoinbase', { + url: '/buycoinbase', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/buyCoinbase.html' + }, + } + }) + .state('sellCoinbase', { + url: '/sellcoinbase', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/sellCoinbase.html' + }, + } + }) + .state('buyandsell', { + url: '/buyandsell', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/buyAndSell.html', + controller: function(platformInfo) { + if (platformInfo.isCordova && StatusBar.isVisible) { + StatusBar.backgroundColorByHexString("#4B6178"); + } + } + } + } + }) + .state('amazon', { + url: '/amazon', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/amazon.html' + }, + } + }) + .state('buyAmazon', { + url: '/buyamazon', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/buyAmazon.html' + }, + } + }) + .state('preferencesAltCurrency', { + url: '/preferencesAltCurrency', + templateUrl: 'views/preferencesAltCurrency.html', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/preferencesAltCurrency.html' + }, + } + }) + .state('about', { + url: '/about', + templateUrl: 'views/preferencesAbout.html', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/preferencesAbout.html' + }, + } + }) + .state('logs', { + url: '/logs', + templateUrl: 'views/preferencesLogs.html', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/preferencesLogs.html' + }, + } + }) + .state('paperWallet', { + url: '/paperWallet', + templateUrl: 'views/paperWallet.html', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/paperWallet.html' + }, + } + }) + .state('preferencesGlobal', { + url: '/preferencesGlobal', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/preferencesGlobal.html', + }, + } + }) + .state('termOfUse', { + url: '/termOfUse', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/termOfUse.html', + }, + } + }) + .state('add', { + url: '/add', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/add.html', + controller: function(platformInfo) { + if (platformInfo.isCordova && StatusBar.isVisible) { + StatusBar.backgroundColorByHexString("#4B6178"); + } + } + } + } + }); +}) .run(function($rootScope, $state, $location, $log, $timeout, $ionicPlatform, lodash, platformInfo, profileService, uxLanguage, go, gettextCatalog) { if (platformInfo.isCordova) { diff --git a/src/js/services/txFormatService.js b/src/js/services/txFormatService.js index d4a75873a..bfcaeb1c8 100644 --- a/src/js/services/txFormatService.js +++ b/src/js/services/txFormatService.js @@ -3,17 +3,6 @@ angular.module('copayApp.services').factory('txFormatService', function(bwcService, rateService, configService, lodash) { var root = {}; - - // // RECEIVE - // // Check address - // root.isUsed(wallet.walletId, balance.byAddress, function(err, used) { - // if (used) { - // $log.debug('Address used. Creating new'); - // $rootScope.$emit('Local/AddressIsUsed'); - // } - // }); - // - root.Utils = bwcService.getUtils(); @@ -57,8 +46,8 @@ angular.module('copayApp.services').factory('txFormatService', function(bwcServi }; root.processTx = function(tx) { - if (!tx || tx.action == 'invalid') - return tx; + if (!tx || tx.action == 'invalid') + return tx; // New transaction output format if (tx.outputs && tx.outputs.length) { @@ -77,7 +66,7 @@ angular.module('copayApp.services').factory('txFormatService', function(bwcServi }, 0); } tx.toAddress = tx.outputs[0].toAddress; - } + } tx.amountStr = root.formatAmountStr(tx.amount); tx.alternativeAmountStr = root.formatAlternativeStr(tx.amount); diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js index 4ab83f1b2..00fd0c854 100644 --- a/src/js/services/walletService.js +++ b/src/js/services/walletService.js @@ -1,7 +1,7 @@ 'use strict'; // DO NOT INCLUDE STORAGE HERE \/ \/ -angular.module('copayApp.services').factory('walletService', function($log, $timeout, lodash, trezor, ledger, storageService, configService, rateService, uxLanguage, bwcService, $filter, gettextCatalog, bwcError, $ionicPopup, fingerprintService, ongoingProcess, gettext, $rootScope, txStatus, txFormatService, $ionicModal) { +angular.module('copayApp.services').factory('walletService', function($log, $timeout, lodash, trezor, ledger, storageService, configService, rateService, uxLanguage, $filter, gettextCatalog, bwcError, $ionicPopup, fingerprintService, ongoingProcess, gettext, $rootScope, txStatus, txFormatService, $ionicModal) { // DO NOT INCLUDE STORAGE HERE ^^ // // @@ -12,7 +12,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim root.WALLET_STATUS_MAX_TRIES = 7; root.WALLET_STATUS_DELAY_BETWEEN_TRIES = 1.4 * 1000; root.SOFT_CONFIRMATION_LIMIT = 12; - root.HISTORY_SHOW_LIMIT = 10; + root.SAFE_CONFIRMATIONS = 6; // UI Related root.openStatusModal = function(type, txp, cb) { @@ -31,8 +31,15 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }); }; - - + // // RECEIVE + // // Check address + // root.isUsed(wallet.walletId, balance.byAddress, function(err, used) { + // if (used) { + // $log.debug('Address used. Creating new'); + // $rootScope.$emit('Local/AddressIsUsed'); + // } + // }); + // var _signWithLedger = function(wallet, txp, cb) { $log.info('Requesting Ledger Chrome app to sign the transaction'); @@ -154,12 +161,12 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim cache.unitName = config.unitName; //STR - cache.totalBalanceStr = root.formatAmount(cache.totalBalanceSat) + ' ' + cache.unitName; - cache.lockedBalanceStr = root.formatAmount(cache.lockedBalanceSat) + ' ' + cache.unitName; - cache.availableBalanceStr = root.formatAmount(cache.availableBalanceSat) + ' ' + cache.unitName; + cache.totalBalanceStr = txFormatService.formatAmount(cache.totalBalanceSat) + ' ' + cache.unitName; + cache.lockedBalanceStr = txFormatService.formatAmount(cache.lockedBalanceSat) + ' ' + cache.unitName; + cache.availableBalanceStr = txFormatService.formatAmount(cache.availableBalanceSat) + ' ' + cache.unitName; if (cache.pendingAmount) { - cache.pendingAmountStr = root.formatAmount(cache.pendingAmount) + ' ' + cache.unitName; + cache.pendingAmountStr = txFormatService.formatAmount(cache.pendingAmount) + ' ' + cache.unitName; } else { cache.pendingAmountStr = null; } @@ -229,13 +236,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }); }; - _getStatus(walletStatusHash(), 0, function(err, status) { - if (err) { - root.handleError(err); - return cb(err); - } - return cb(null, status); - }) + _getStatus(walletStatusHash(), 0, cb); }; var getSavedTxs = function(walletId, cb) { @@ -300,8 +301,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim if (tx.time > now) tx.time = now; - if (tx.confirmations >= SAFE_CONFIRMATIONS) { - tx.safeConfirmed = SAFE_CONFIRMATIONS + '+'; + if (tx.confirmations >= root.SAFE_CONFIRMATIONS) { + tx.safeConfirmed = root.SAFE_CONFIRMATIONS + '+'; } else { tx.safeConfirmed = false; wallet.hasUnsafeConfirmed = true; @@ -323,13 +324,15 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim return ret; }; - var updateLocalTxHistory = function(wallet, cb) { + var updateLocalTxHistory = function(wallet, progressFn, cb) { var FIRST_LIMIT = 5; var LIMIT = 50; var requestLimit = FIRST_LIMIT; var walletId = wallet.credentials.walletId; var config = configService.getSync().wallet.settings; + progressFn = progressFn || function() {}; + var fixTxsUnit = function(txs) { if (!txs || !txs[0] || !txs[0].amountStr) return; @@ -359,43 +362,27 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim // First update - if (walletId == wallet.credentials.walletId) { - wallet.completeHistory = txsFromLocal; - } + wallet.completeHistory = txsFromLocal; - if (wallet.historyUpdateInProgress) - return; - - wallet.historyUpdateInProgress = true; - - function getNewTxs(newTxs, skip, i_cb) { + function getNewTxs(newTxs, skip, cb) { getTxsFromServer(wallet, skip, endingTxid, requestLimit, function(err, res, shouldContinue) { - if (err) return i_cb(err); + if (err) return cb(err); + + newTxs = newTxs.concat(processNewTxs(wallet, lodash.compact(res))); + + progressFn(newTxs); - newTxs = newTxs.concat(lodash.compact(res)); skip = skip + requestLimit; $log.debug('Syncing TXs. Got:' + newTxs.length + ' Skip:' + skip, ' EndingTxid:', endingTxid, ' Continue:', shouldContinue); if (!shouldContinue) { - newTxs = processNewTxs(wallet, newTxs); $log.debug('Finished Sync: New / soft confirmed Txs: ' + newTxs.length); - return i_cb(null, newTxs); + return cb(null, newTxs); } requestLimit = LIMIT; - getNewTxs(newTxs, skip, i_cb); - - // Progress update - if (walletId == wallet.credentials.walletId) { - wallet.txProgress = newTxs.length; - if (wallet.completeHistory < FIRST_LIMIT && txsFromLocal.length == 0) { - $log.debug('Showing partial history'); - var newHistory = processNewTxs(wallet, newTxs); - newHistory = lodash.compact(newHistory.concat(confirmedTxs)); - wallet.completeHistory = newHistory; - } - } + getNewTxs(newTxs, skip, cb); }); }; @@ -456,10 +443,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }; - root.getHistory = function(wallet, opts, cb) { + root.getTxHistory = function(wallet, opts, cb) { opts = opts || {}; - opts.skip = opts.skip || 0; - opts.limit = opts.limit || root.HISTORY_SHOW_LIMIT; var walletId = wallet.credentials.walletId; @@ -467,10 +452,9 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim $log.debug('Updating Transaction History'); - updateLocalTxHistory(wallet, function(err) { + updateLocalTxHistory(wallet, opts.progressFn, function(err) { if (err) return cb(err); - var txs = wallet.completeHistory ? wallet.completeHistory.slice(opts.skip, opts.limit) : null; - return cb(err, txs); + return cb(err, wallet.completeHistory); }); };