commit
d691cd595a
17 changed files with 286 additions and 205 deletions
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<widget id="com.bitpay.copay"
|
<widget id="com.bitpay.copay"
|
||||||
version="1.12.14"
|
version="2.0.0"
|
||||||
android-versionCode="104"
|
android-versionCode="105"
|
||||||
ios-CFBundleVersion="1.12.14">
|
ios-CFBundleVersion="2.0.0">
|
||||||
<name>Copay</name>
|
<name>Copay</name>
|
||||||
<description>
|
<description>
|
||||||
A secure bitcoin wallet for friends and companies.
|
A secure bitcoin wallet for friends and companies.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Package xmlns="http://schemas.microsoft.com/appx/2010/manifest" xmlns:m2="http://schemas.microsoft.com/appx/2013/manifest" xmlns:m3="http://schemas.microsoft.com/appx/2014/manifest" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest">
|
<Package xmlns="http://schemas.microsoft.com/appx/2010/manifest" xmlns:m2="http://schemas.microsoft.com/appx/2013/manifest" xmlns:m3="http://schemas.microsoft.com/appx/2014/manifest" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest">
|
||||||
<Identity Name="18C7659D.CopayWallet" Publisher="CN=F89609D1-EB3E-45FD-A58A-C2E3895FCE7B" Version="1.12.14.0" />
|
<Identity Name="18C7659D.CopayWallet" Publisher="CN=F89609D1-EB3E-45FD-A58A-C2E3895FCE7B" Version="2.0.0.0" />
|
||||||
<mp:PhoneIdentity PhoneProductId="5381aa50-9069-11e4-84cc-293caf9cbdc8" PhonePublisherId="F89609D1-EB3E-45FD-A58A-C2E3895FCE7B" />
|
<mp:PhoneIdentity PhoneProductId="5381aa50-9069-11e4-84cc-293caf9cbdc8" PhonePublisherId="F89609D1-EB3E-45FD-A58A-C2E3895FCE7B" />
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Copay Bitcoin Wallet</DisplayName>
|
<DisplayName>Copay Bitcoin Wallet</DisplayName>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<Language code="pl" />
|
<Language code="pl" />
|
||||||
<Language code="cs" />
|
<Language code="cs" />
|
||||||
</Languages>
|
</Languages>
|
||||||
<App Author="Bitpay Inc." BitsPerPixel="32" Description="A multisignature Bitcoin Wallet" Genre="apps.normal" ProductID="{5381aa50-9069-11e4-84cc-293caf9cbdc8}" Publisher="Copay Bitcoin Wallet" PublisherID="{31cdd08b-457c-413d-b440-f6665eec847d}" RuntimeType="Silverlight" Title="Copay Bitcoin Wallet" Version="1.12.14.0" xmlns="" NotificationService="MPN">
|
<App Author="Bitpay Inc." BitsPerPixel="32" Description="A multisignature Bitcoin Wallet" Genre="apps.normal" ProductID="{5381aa50-9069-11e4-84cc-293caf9cbdc8}" Publisher="Copay Bitcoin Wallet" PublisherID="{31cdd08b-457c-413d-b440-f6665eec847d}" RuntimeType="Silverlight" Title="Copay Bitcoin Wallet" Version="2.0.0.0" xmlns="" NotificationService="MPN">
|
||||||
<IconPath IsRelative="true" IsResource="false">Assets\icon@2.png</IconPath>
|
<IconPath IsRelative="true" IsResource="false">Assets\icon@2.png</IconPath>
|
||||||
<Capabilities>
|
<Capabilities>
|
||||||
<Capability Name="ID_CAP_WEBBROWSERCOMPONENT" />
|
<Capability Name="ID_CAP_WEBBROWSERCOMPONENT" />
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
"name": "copay",
|
"name": "copay",
|
||||||
"description": "A multisignature wallet",
|
"description": "A multisignature wallet",
|
||||||
"author": "BitPay",
|
"author": "BitPay",
|
||||||
"version": "1.12.14",
|
"version": "2.0.0",
|
||||||
"androidVersionCode": "104",
|
"androidVersionCode": "105",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"wallet",
|
"wallet",
|
||||||
"copay",
|
"copay",
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<span ng-show="!comment" translate>Enter a new comment</span>
|
<span ng-show="!comment" translate>Enter a new comment</span>
|
||||||
<span ng-show="comment" translate>Edit comment</span>
|
<span ng-show="comment" translate>Edit comment</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="text" ng-model="data.comment">
|
<input type="text" ng-model="data.comment" autofocus>
|
||||||
</div>
|
</div>
|
||||||
<div class="small-6 columns">
|
<div class="small-6 columns">
|
||||||
<button class="round outline dark-gray expand" ng-click="commentPopupClose()" translate>CANCEL</button>
|
<button class="round outline dark-gray expand" ng-click="commentPopupClose()" translate>CANCEL</button>
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,18 @@
|
||||||
<img src="img/icon-moved.svg" alt="sync" width="40" ng-show="btx.action == 'moved'">
|
<img src="img/icon-moved.svg" alt="sync" width="40" ng-show="btx.action == 'moved'">
|
||||||
</div>
|
</div>
|
||||||
<div class="m10t">
|
<div class="m10t">
|
||||||
<span ng-show="btx.action == 'received'" translate>Received</span>
|
<span ng-show="btx.action == 'received'">
|
||||||
<span ng-show="btx.action == 'sent'">
|
<span class="ellipsis">
|
||||||
{{index.addressbook[btx.addressTo]}}
|
<span ng-if="btx.note.body">{{btx.note.body}}</span>
|
||||||
<span ng-show="!index.addressbook[btx.addressTo] && btx.message">
|
<span ng-if="!btx.note.body" translate> Received</span>
|
||||||
<span class="ellipsis">{{btx.message}}</span>
|
|
||||||
</span>
|
</span>
|
||||||
<span ng-show="!index.addressbook[btx.addressTo] && !btx.message">
|
</span>
|
||||||
<span translate> Sent</span>
|
<span ng-show="btx.action == 'sent'">
|
||||||
|
<span class="ellipsis">
|
||||||
|
<span ng-if="btx.message">{{btx.message}}</span>
|
||||||
|
<span ng-if="!btx.message && btx.note.body">{{btx.note.body}}</span>
|
||||||
|
<span ng-if="!btx.message && !btx.note.body && index.addressbook[btx.addressTo]">{{index.addressbook[btx.addressTo]}}</span>
|
||||||
|
<span ng-if="!btx.message && !btx.note.body && !index.addressbook[btx.addressTo]" translate> Sent</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span ng-show="btx.action == 'moved'" translate>Moved</span>
|
<span ng-show="btx.action == 'moved'" translate>Moved</span>
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="size-36" ng-click="copyToClipboard(btx.amountStr)">
|
<div class="size-36" ng-click="copyToClipboard(btx.amountStr)">
|
||||||
<span ng-if="btx.action == 'received'">+</span>
|
|
||||||
<span ng-if="btx.action == 'sent'">-</span>
|
|
||||||
<span class="enable_text_select">{{btx.amountStr}}</span>
|
<span class="enable_text_select">{{btx.amountStr}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="alternative-amount" ng-click="showRate=!showRate" ng-init="showRate = false">
|
<div class="alternative-amount" ng-click="showRate=!showRate" ng-init="showRate = false">
|
||||||
|
|
@ -100,7 +98,7 @@
|
||||||
|
|
||||||
<li class="line-b p10 oh" ng-if="btx.message && btx.action != 'received'"
|
<li class="line-b p10 oh" ng-if="btx.message && btx.action != 'received'"
|
||||||
ng-click="copyToClipboard(btx.message)">
|
ng-click="copyToClipboard(btx.message)">
|
||||||
<span class="text-gray" translate>Note</span>
|
<span class="text-gray" translate>Description</span>
|
||||||
<span class="right enable_text_select">{{btx.message}}</span>
|
<span class="right enable_text_select">{{btx.message}}</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
@ -127,12 +125,12 @@
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="p10 oh" ng-show="comment">
|
<li class="p10 oh" ng-show="btx.note">
|
||||||
<span class="text-gray" translate>Comment</span>
|
<span class="text-gray" translate>Comment</span>
|
||||||
<span class="right enable_text_select">{{comment}}</span><br>
|
<span class="right enable_text_select">{{btx.note.body}}</span><br>
|
||||||
<span class="right text-italic text-gray size-12 m10t">
|
<span class="right text-italic text-gray size-12">
|
||||||
<span>{{editedBy}}</span>
|
<span translated>Edited by</span> <span>{{btx.note.editedByName}}</span>,
|
||||||
<time>{{createdOn * 1000 | amTimeAgo}}</time></span>
|
<time>{{btx.note.editedOn * 1000 | amTimeAgo}}</time></span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -158,8 +156,8 @@
|
||||||
<span class="text-gray" translate>See it on the blockchain</span>
|
<span class="text-gray" translate>See it on the blockchain</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="button outline round dark-gray tiny" ng-click="showCommentPopup()">
|
<button class="button outline round dark-gray tiny" ng-click="showCommentPopup()">
|
||||||
<span class="text-gray" translate ng-show="!comment">Add a comment</i></span>
|
<span class="text-gray" translate ng-show="!btx.note">Add a comment</i></span>
|
||||||
<span class="text-gray" translate ng-show="comment">Edit comment</span>
|
<span class="text-gray" translate ng-show="btx.note">Edit comment</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@
|
||||||
|
|
||||||
<ul class="no-bullet size-14 m0">
|
<ul class="no-bullet size-14 m0">
|
||||||
<li class="line-b p10 oh" ng-show="tx.message">
|
<li class="line-b p10 oh" ng-show="tx.message">
|
||||||
<span class="text-gray" translate>Note</span>
|
<span class="text-gray" translate>Description</span>
|
||||||
<span class="right">{{tx.message}}</span>
|
<span class="right">{{tx.message}}</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,22 @@
|
||||||
<div class="content preferences" ng-controller="preferencesHistory as history">
|
<div class="content preferences" ng-controller="preferencesHistory as history">
|
||||||
<h4></h4>
|
<h4></h4>
|
||||||
<ul class="no-bullet m0">
|
<ul class="no-bullet m0">
|
||||||
<li ng-if="!index.isCordova" ng-init="index.csvHistory()">
|
<li ng-if="!index.isCordova" ng-init="history.csvHistory()">
|
||||||
<a ng-style="{'color':index.backgroundColor}" ng-csv="index.csvContent"
|
<a style="color:#444"
|
||||||
csv-header="index.csvHeader"
|
ng-csv="history.csvContent"
|
||||||
filename="{{index.csvFilename }}" translate>
|
csv-header="history.csvHeader"
|
||||||
|
ng-show="history.csvReady"
|
||||||
|
filename="Copay-{{index.alias || index.walletName}}.csv" translate>
|
||||||
Export to file
|
Export to file
|
||||||
</a>
|
</a>
|
||||||
|
<a style="color:#777"
|
||||||
|
ng-show="!history.csvReady"
|
||||||
|
translate>
|
||||||
|
Export to file [preparing...]
|
||||||
|
</a>
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
<li ng-style="{'color':index.backgroundColor}" ng-click="history.clearTransactionHistory();" translate>
|
<li ng-click="history.clearTransactionHistory();" translate>
|
||||||
Clear cache
|
Clear cache
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,12 @@
|
||||||
<img src="img/icon-moved.svg" alt="sync" width="40" ng-show="btx.action == 'moved'">
|
<img src="img/icon-moved.svg" alt="sync" width="40" ng-show="btx.action == 'moved'">
|
||||||
</div>
|
</div>
|
||||||
<div class="m10t">
|
<div class="m10t">
|
||||||
<span ng-show="btx.action == 'received'" translate>Received</span>
|
<span ng-show="btx.action == 'received'">
|
||||||
|
<span class="ellipsis">
|
||||||
|
<span ng-if="btx.note.body">{{btx.note.body}}</span>
|
||||||
|
<span ng-if="!btx.note.body" translate> Received</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
<span ng-show="btx.action == 'sent'">
|
<span ng-show="btx.action == 'sent'">
|
||||||
<span class="ellipsis">
|
<span class="ellipsis">
|
||||||
<span ng-if="btx.message">{{btx.message}}</span>
|
<span ng-if="btx.message">{{btx.message}}</span>
|
||||||
|
|
@ -449,7 +454,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row" ng-hide="home.hideNote">
|
<div class="row" ng-hide="home.hideNote">
|
||||||
<div class="large-12 columns">
|
<div class="large-12 columns">
|
||||||
<label for="comment"><span translate>Note</span>
|
<label for="comment"><span translate>Description</span>
|
||||||
<small translate ng-hide="!sendForm.comment.$pristine">optional</small>
|
<small translate ng-hide="!sendForm.comment.$pristine">optional</small>
|
||||||
<small translate class="has-error" ng-show="sendForm.comment.$invalid && !sendForm.comment.$pristine">too long!</small>
|
<small translate class="has-error" ng-show="sendForm.comment.$invalid && !sendForm.comment.$pristine">too long!</small>
|
||||||
</label>
|
</label>
|
||||||
|
|
|
||||||
|
|
@ -590,6 +590,11 @@ angular.module('copayApp.controllers').controller('indexController', function($r
|
||||||
self.hasUnsafeConfirmed = true;
|
self.hasUnsafeConfirmed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tx.note) {
|
||||||
|
delete tx.note.encryptedEditedByName;
|
||||||
|
delete tx.note.encryptedBody;
|
||||||
|
}
|
||||||
|
|
||||||
if (!txHistoryUnique[tx.txid]) {
|
if (!txHistoryUnique[tx.txid]) {
|
||||||
ret.push(tx);
|
ret.push(tx);
|
||||||
txHistoryUnique[tx.txid] = true;
|
txHistoryUnique[tx.txid] = true;
|
||||||
|
|
@ -692,135 +697,6 @@ angular.module('copayApp.controllers').controller('indexController', function($r
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.csvHistory = function() {
|
|
||||||
|
|
||||||
function formatDate(date) {
|
|
||||||
var dateObj = new Date(date);
|
|
||||||
if (!dateObj) {
|
|
||||||
$log.debug('Error formating a date');
|
|
||||||
return 'DateError'
|
|
||||||
}
|
|
||||||
if (!dateObj.toJSON()) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return dateObj.toJSON();
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatString(str) {
|
|
||||||
if (!str) return '';
|
|
||||||
|
|
||||||
if (str.indexOf('"') !== -1) {
|
|
||||||
//replace all
|
|
||||||
str = str.replace(new RegExp('"', 'g'), '\'');
|
|
||||||
}
|
|
||||||
|
|
||||||
//escaping commas
|
|
||||||
str = '\"' + str + '\"';
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
var step = 6;
|
|
||||||
var unique = {};
|
|
||||||
|
|
||||||
function getHistory(cb) {
|
|
||||||
storageService.getTxHistory(c.walletId, function(err, txs) {
|
|
||||||
if (err) return cb(err);
|
|
||||||
|
|
||||||
var txsFromLocal = [];
|
|
||||||
try {
|
|
||||||
txsFromLocal = JSON.parse(txs);
|
|
||||||
} catch (ex) {
|
|
||||||
$log.warn(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
allTxs.push(txsFromLocal);
|
|
||||||
return cb(null, lodash.flatten(allTxs));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCordova) {
|
|
||||||
$log.info('CSV generation not available in mobile');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var fc = profileService.focusedClient;
|
|
||||||
var c = fc.credentials;
|
|
||||||
if (!fc.isComplete()) return;
|
|
||||||
var self = this;
|
|
||||||
var allTxs = [];
|
|
||||||
|
|
||||||
$log.debug('Generating CSV from History');
|
|
||||||
self.setOngoingProcess('generatingCSV', true);
|
|
||||||
|
|
||||||
getHistory(function(err, txs) {
|
|
||||||
self.setOngoingProcess('generatingCSV', false);
|
|
||||||
if (err) {
|
|
||||||
self.handleError(err);
|
|
||||||
} else {
|
|
||||||
$log.debug('Wallet Transaction History:', txs);
|
|
||||||
|
|
||||||
self.satToUnit = 1 / self.unitToSatoshi;
|
|
||||||
var data = txs;
|
|
||||||
var satToBtc = 1 / 100000000;
|
|
||||||
self.csvContent = [];
|
|
||||||
self.csvFilename = 'Copay-' + (self.alias || self.walletName) + '.csv';
|
|
||||||
self.csvHeader = ['Date', 'Destination', 'Note', 'Amount', 'Currency', 'Txid', 'Creator', 'Copayers'];
|
|
||||||
|
|
||||||
var _amount, _note, _copayers, _creator;
|
|
||||||
data.forEach(function(it, index) {
|
|
||||||
var amount = it.amount;
|
|
||||||
|
|
||||||
if (it.action == 'moved')
|
|
||||||
amount = 0;
|
|
||||||
|
|
||||||
_copayers = '';
|
|
||||||
_creator = '';
|
|
||||||
|
|
||||||
if (it.actions && it.actions.length > 1) {
|
|
||||||
for (var i = 0; i < it.actions.length; i++) {
|
|
||||||
_copayers += it.actions[i].copayerName + ':' + it.actions[i].type + ' - ';
|
|
||||||
}
|
|
||||||
_creator = (it.creatorName && it.creatorName != 'undefined') ? it.creatorName : '';
|
|
||||||
}
|
|
||||||
_copayers = formatString(_copayers);
|
|
||||||
_creator = formatString(_creator);
|
|
||||||
_amount = (it.action == 'sent' ? '-' : '') + (amount * satToBtc).toFixed(8);
|
|
||||||
_note = formatString((it.message ? it.message : ''));
|
|
||||||
|
|
||||||
if (it.action == 'moved')
|
|
||||||
_note += ' Moved:' + (it.amount * satToBtc).toFixed(8)
|
|
||||||
|
|
||||||
self.csvContent.push({
|
|
||||||
'Date': formatDate(it.time * 1000),
|
|
||||||
'Destination': formatString(it.addressTo),
|
|
||||||
'Note': _note,
|
|
||||||
'Amount': _amount,
|
|
||||||
'Currency': 'BTC',
|
|
||||||
'Txid': it.txid,
|
|
||||||
'Creator': _creator,
|
|
||||||
'Copayers': _copayers
|
|
||||||
});
|
|
||||||
|
|
||||||
if (it.fees && (it.action == 'moved' || it.action == 'sent')) {
|
|
||||||
var _fee = (it.fees * satToBtc).toFixed(8)
|
|
||||||
self.csvContent.push({
|
|
||||||
'Date': formatDate(it.time * 1000),
|
|
||||||
'Destination': 'Bitcoin Network Fees',
|
|
||||||
'Note': '',
|
|
||||||
'Amount': '-' + _fee,
|
|
||||||
'Currency': 'BTC',
|
|
||||||
'Txid': '',
|
|
||||||
'Creator': '',
|
|
||||||
'Copayers': ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.removeAndMarkSoftConfirmedTx = function(txs) {
|
self.removeAndMarkSoftConfirmedTx = function(txs) {
|
||||||
return lodash.filter(txs, function(tx) {
|
return lodash.filter(txs, function(tx) {
|
||||||
if (tx.confirmations >= SOFT_CONFIRMATION_LIMIT)
|
if (tx.confirmations >= SOFT_CONFIRMATION_LIMIT)
|
||||||
|
|
@ -881,6 +757,7 @@ angular.module('copayApp.controllers').controller('indexController', function($r
|
||||||
|
|
||||||
var confirmedTxs = self.removeAndMarkSoftConfirmedTx(txsFromLocal);
|
var confirmedTxs = self.removeAndMarkSoftConfirmedTx(txsFromLocal);
|
||||||
var endingTxid = confirmedTxs[0] ? confirmedTxs[0].txid : null;
|
var endingTxid = confirmedTxs[0] ? confirmedTxs[0].txid : null;
|
||||||
|
var endingTs = confirmedTxs[0] ? confirmedTxs[0].time : null;
|
||||||
|
|
||||||
|
|
||||||
// First update
|
// First update
|
||||||
|
|
@ -935,24 +812,52 @@ angular.module('copayApp.controllers').controller('indexController', function($r
|
||||||
var newHistory = lodash.uniq(lodash.compact(txs.concat(confirmedTxs)), function(x) {
|
var newHistory = lodash.uniq(lodash.compact(txs.concat(confirmedTxs)), function(x) {
|
||||||
return x.txid;
|
return x.txid;
|
||||||
});
|
});
|
||||||
var historyToSave = JSON.stringify(newHistory);
|
|
||||||
|
|
||||||
lodash.each(txs, function(tx) {
|
|
||||||
tx.recent = true;
|
|
||||||
})
|
|
||||||
|
|
||||||
$log.debug('Tx History synced. Total Txs: ' + newHistory.length);
|
function updateNotes(cb2) {
|
||||||
|
if (!endingTs) return cb2();
|
||||||
|
|
||||||
// Final update
|
$log.debug('Syncing notes from: ' + endingTs);
|
||||||
if (walletId == profileService.focusedClient.credentials.walletId) {
|
client.getTxNotes({
|
||||||
self.completeHistory = newHistory;
|
minTs: endingTs
|
||||||
self.setCompactTxHistory();
|
}, function(err, notes) {
|
||||||
|
if (err) {
|
||||||
|
$log.warn(err);
|
||||||
|
return cb2();
|
||||||
|
};
|
||||||
|
lodash.each(notes, function(note) {
|
||||||
|
$log.debug('Note for ' + note.txid);
|
||||||
|
lodash.each(newHistory, function(tx) {
|
||||||
|
if (tx.txid == note.txid) {
|
||||||
|
$log.debug('...updating note for ' + note.txid);
|
||||||
|
tx.note = note;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return cb2();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return storageService.setTxHistory(historyToSave, walletId, function() {
|
updateNotes(function() {
|
||||||
$log.debug('Tx History saved.');
|
var historyToSave = JSON.stringify(newHistory);
|
||||||
|
|
||||||
return cb();
|
lodash.each(txs, function(tx) {
|
||||||
|
tx.recent = true;
|
||||||
|
})
|
||||||
|
|
||||||
|
$log.debug('Tx History synced. Total Txs: ' + newHistory.length);
|
||||||
|
|
||||||
|
// Final update
|
||||||
|
if (walletId == profileService.focusedClient.credentials.walletId) {
|
||||||
|
self.completeHistory = newHistory;
|
||||||
|
self.setCompactTxHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
return storageService.setTxHistory(historyToSave, walletId, function() {
|
||||||
|
$log.debug('Tx History saved.');
|
||||||
|
|
||||||
|
return cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -1008,8 +913,9 @@ angular.module('copayApp.controllers').controller('indexController', function($r
|
||||||
if (tx.addressTo && self.addressbook && self.addressbook[tx.addressTo]) addrbook = self.addressbook[tx.addressTo] || '';
|
if (tx.addressTo && self.addressbook && self.addressbook[tx.addressTo]) addrbook = self.addressbook[tx.addressTo] || '';
|
||||||
var searchableDate = computeSearchableDate(new Date(tx.time * 1000));
|
var searchableDate = computeSearchableDate(new Date(tx.time * 1000));
|
||||||
var message = tx.message ? tx.message : '';
|
var message = tx.message ? tx.message : '';
|
||||||
|
var comment = tx.note ? tx.note.body : '';
|
||||||
var addressTo = tx.addressTo ? tx.addressTo : '';
|
var addressTo = tx.addressTo ? tx.addressTo : '';
|
||||||
return ((tx.amountStr + message + addressTo + addrbook + searchableDate).toString()).toLowerCase();
|
return ((tx.amountStr + message + addressTo + addrbook + searchableDate + comment).toString()).toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeSearchableDate(date) {
|
function computeSearchableDate(date) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('txDetailsController', function($rootScope, $log, $scope, $filter, $ionicPopup, gettextCatalog, profileService, configService) {
|
angular.module('copayApp.controllers').controller('txDetailsController', function($rootScope, $log, $scope, $filter, $ionicPopup, gettextCatalog, profileService, configService, lodash) {
|
||||||
|
|
||||||
var self = $scope.self;
|
var self = $scope.self;
|
||||||
var fc = profileService.focusedClient;
|
var fc = profileService.focusedClient;
|
||||||
|
|
@ -12,24 +12,9 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
|
||||||
$scope.color = fc.backgroundColor;
|
$scope.color = fc.backgroundColor;
|
||||||
$scope.copayerId = fc.credentials.copayerId;
|
$scope.copayerId = fc.credentials.copayerId;
|
||||||
$scope.isShared = fc.credentials.n > 1;
|
$scope.isShared = fc.credentials.n > 1;
|
||||||
|
|
||||||
if ($scope.btx.txid) {
|
|
||||||
fc.getTxNote({
|
|
||||||
txid: $scope.btx.txid
|
|
||||||
}, function(err, note) {
|
|
||||||
if (err || !note) {
|
|
||||||
$log.debug(gettextCatalog.getString('Could not fetch transaction note'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$scope.comment = note.body;
|
|
||||||
$scope.editedBy = gettextCatalog.getString('Edited by') + ' ' + note.editedByName;
|
|
||||||
$scope.createdOn = note.createdOn;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.showCommentPopup = function() {
|
$scope.showCommentPopup = function() {
|
||||||
$scope.data = {
|
$scope.data = {
|
||||||
comment: ''
|
comment: $scope.btx.note ? $scope.btx.note.body : '',
|
||||||
};
|
};
|
||||||
|
|
||||||
var commentPopup = $ionicPopup.show({
|
var commentPopup = $ionicPopup.show({
|
||||||
|
|
@ -42,14 +27,31 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.commentPopupSave = function() {
|
$scope.commentPopupSave = function() {
|
||||||
fc.editTxNote({
|
$log.debug('Saving note');
|
||||||
|
var args = {
|
||||||
txid: $scope.btx.txid,
|
txid: $scope.btx.txid,
|
||||||
body: $scope.data.comment
|
};
|
||||||
}, function() {});
|
|
||||||
$scope.comment = $scope.data.comment;
|
if (!lodash.isEmpty($scope.data.comment)) {
|
||||||
$scope.editedBy = gettextCatalog.getString('Edited by') + ' ' + fc.credentials.copayerName;
|
args.body = $scope.data.comment;
|
||||||
$scope.createdOn = Math.floor(Date.now() / 1000);
|
};
|
||||||
commentPopup.close();
|
|
||||||
|
fc.editTxNote(args, function(err) {
|
||||||
|
if (err) {
|
||||||
|
$log.debug('Could not save tx comment');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// This is only to refresh the current screen data
|
||||||
|
$scope.btx.note = null;
|
||||||
|
if (args.body) {
|
||||||
|
$scope.btx.note = {};
|
||||||
|
$scope.btx.note.body = $scope.data.comment;
|
||||||
|
$scope.btx.note.editedByName = fc.credentials.copayerName;
|
||||||
|
$scope.btx.note.editedOn = Math.floor(Date.now() / 1000);
|
||||||
|
}
|
||||||
|
$scope.btx.searcheableString = null;
|
||||||
|
commentPopup.close();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,129 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('preferencesHistory',
|
angular.module('copayApp.controllers').controller('preferencesHistory',
|
||||||
function($scope, $log, $timeout, storageService, go, profileService) {
|
function($scope, $log, $timeout, storageService, go, profileService, lodash) {
|
||||||
var fc = profileService.focusedClient;
|
var fc = profileService.focusedClient;
|
||||||
var c = fc.credentials;
|
var c = fc.credentials;
|
||||||
|
this.csvReady = false;
|
||||||
|
|
||||||
|
|
||||||
|
this.csvHistory = function(cb) {
|
||||||
|
|
||||||
|
function formatDate(date) {
|
||||||
|
var dateObj = new Date(date);
|
||||||
|
if (!dateObj) {
|
||||||
|
$log.debug('Error formating a date');
|
||||||
|
return 'DateError'
|
||||||
|
}
|
||||||
|
if (!dateObj.toJSON()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return dateObj.toJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
var step = 6;
|
||||||
|
var unique = {};
|
||||||
|
function getHistory(cb) {
|
||||||
|
storageService.getTxHistory(c.walletId, function(err, txs) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
var txsFromLocal = [];
|
||||||
|
try {
|
||||||
|
txsFromLocal = JSON.parse(txs);
|
||||||
|
} catch (ex) {
|
||||||
|
$log.warn(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
allTxs.push(txsFromLocal);
|
||||||
|
return cb(null, lodash.flatten(allTxs));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var fc = profileService.focusedClient;
|
||||||
|
var c = fc.credentials;
|
||||||
|
|
||||||
|
if (!fc.isComplete())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var allTxs = [];
|
||||||
|
|
||||||
|
$log.debug('Generating CSV from History');
|
||||||
|
getHistory(function(err, txs) {
|
||||||
|
if (err || !txs || !txs[0]) {
|
||||||
|
$log.warn('Failed to generate CSV:', err);
|
||||||
|
if (cb) return cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$log.debug('Wallet Transaction History Length:', txs.length);
|
||||||
|
|
||||||
|
self.satToUnit = 1 / self.unitToSatoshi;
|
||||||
|
var data = txs;
|
||||||
|
var satToBtc = 1 / 100000000;
|
||||||
|
self.csvContent = [];
|
||||||
|
self.csvFilename = 'Copay-' + (self.alias || self.walletName) + '.csv';
|
||||||
|
self.csvHeader = ['Date', 'Destination', 'Description', 'Amount', 'Currency', 'Txid', 'Creator', 'Copayers', 'Comment'];
|
||||||
|
|
||||||
|
var _amount, _note, _copayers, _creator, _comment;
|
||||||
|
data.forEach(function(it, index) {
|
||||||
|
var amount = it.amount;
|
||||||
|
|
||||||
|
if (it.action == 'moved')
|
||||||
|
amount = 0;
|
||||||
|
|
||||||
|
_copayers = '';
|
||||||
|
_creator = '';
|
||||||
|
|
||||||
|
if (it.actions && it.actions.length > 1) {
|
||||||
|
for (var i = 0; i < it.actions.length; i++) {
|
||||||
|
_copayers += it.actions[i].copayerName + ':' + it.actions[i].type + ' - ';
|
||||||
|
}
|
||||||
|
_creator = (it.creatorName && it.creatorName != 'undefined') ? it.creatorName : '';
|
||||||
|
}
|
||||||
|
_amount = (it.action == 'sent' ? '-' : '') + (amount * satToBtc).toFixed(8);
|
||||||
|
_note = it.message || '';
|
||||||
|
_comment = it.note ? it.note.body : '';
|
||||||
|
|
||||||
|
if (it.action == 'moved')
|
||||||
|
_note += ' Moved:' + (it.amount * satToBtc).toFixed(8)
|
||||||
|
|
||||||
|
self.csvContent.push({
|
||||||
|
'Date': formatDate(it.time * 1000),
|
||||||
|
'Destination': it.addressTo || '',
|
||||||
|
'Description': _note,
|
||||||
|
'Amount': _amount,
|
||||||
|
'Currency': 'BTC',
|
||||||
|
'Txid': it.txid,
|
||||||
|
'Creator': _creator,
|
||||||
|
'Copayers': _copayers,
|
||||||
|
'Comment': _comment
|
||||||
|
});
|
||||||
|
|
||||||
|
if (it.fees && (it.action == 'moved' || it.action == 'sent')) {
|
||||||
|
var _fee = (it.fees * satToBtc).toFixed(8)
|
||||||
|
self.csvContent.push({
|
||||||
|
'Date': formatDate(it.time * 1000),
|
||||||
|
'Destination': 'Bitcoin Network Fees',
|
||||||
|
'Description': '',
|
||||||
|
'Amount': '-' + _fee,
|
||||||
|
'Currency': 'BTC',
|
||||||
|
'Txid': '',
|
||||||
|
'Creator': '',
|
||||||
|
'Copayers': ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.csvReady = true;
|
||||||
|
if (cb)
|
||||||
|
return cb();
|
||||||
|
return;
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
this.clearTransactionHistory = function() {
|
this.clearTransactionHistory = function() {
|
||||||
storageService.removeTxHistory(c.walletId, function(err) {
|
storageService.removeTxHistory(c.walletId, function(err) {
|
||||||
|
|
|
||||||
31
test/controllers/preferencesHistory.test.js
Normal file
31
test/controllers/preferencesHistory.test.js
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
describe('Preferences History Controller', function() {
|
||||||
|
|
||||||
|
var walletService;
|
||||||
|
|
||||||
|
var txHistory = '[{"txid":"bf31ecaa8e10ce57f9a889fc4c893b40ff57b016dd763957d942e21ed55fc62c","action":"received","amount":120000,"fees":4862,"time":1464969291,"confirmations":8,"outputs":[{"amount":120000,"address":"2N4HgtF9cJSzxhVkj5gbKxwJSKWBmnb9FNJ","message":null}],"note":{"body":"just a comment","editedBy":"31a8c3c0be9ffbb9f257c95f3fd2f73a59cf81e40199ba5918417270db8c4cdb","editedByName":"2-2","editedOn":1464969101},"message":null,"creatorName":"","hasUnconfirmedInputs":false,"amountStr":"1,200 bits","alternativeAmountStr":"0.68 USD","feeStr":"49 bits","safeConfirmed":"6+"}]';
|
||||||
|
|
||||||
|
describe('Complete 1-1 wallet', function() {
|
||||||
|
beforeEach(function(done) {
|
||||||
|
mocks.init(FIXTURES, 'preferencesHistory', {
|
||||||
|
loadProfile: PROFILE.testnet1of1,
|
||||||
|
loadStorage: {
|
||||||
|
'txsHistory-66d3afc9-7d76-4b25-850e-aa62fcc53a7d': txHistory,
|
||||||
|
},
|
||||||
|
}, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function(done) {
|
||||||
|
mocks.clear({}, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export csv', function(done) {
|
||||||
|
ctrl.csvHistory(function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
ctrl.csvReady.should.equal(true);
|
||||||
|
JSON.stringify(ctrl.csvContent).should.equal('[{"Date":"2016-06-03T15:54:51.000Z","Destination":"","Description":"","Amount":"0.00120000","Currency":"BTC","Txid":"bf31ecaa8e10ce57f9a889fc4c893b40ff57b016dd763957d942e21ed55fc62c","Creator":"","Copayers":"","Comment":"just a comment"}]');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
@ -105,7 +105,7 @@ mocks.init = function(fixtures, controllerName, opts, done) {
|
||||||
var headers = JSON.stringify(bwc._getHeaders(method, url, args));
|
var headers = JSON.stringify(bwc._getHeaders(method, url, args));
|
||||||
|
|
||||||
// Fixes BWC version... TODO
|
// Fixes BWC version... TODO
|
||||||
headers=headers.replace(/bwc-\d\.\d\.\d/,'bwc-2.4.0')
|
headers = headers.replace(/bwc-\d\.\d\.\d/, 'bwc-2.4.0')
|
||||||
var x = method + url + JSON.stringify(args) + headers;
|
var x = method + url + JSON.stringify(args) + headers;
|
||||||
var sjcl = $delegate.getSJCL();
|
var sjcl = $delegate.getSJCL();
|
||||||
return sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(x));
|
return sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(x));
|
||||||
|
|
@ -163,7 +163,7 @@ mocks.init = function(fixtures, controllerName, opts, done) {
|
||||||
});
|
});
|
||||||
module('copayApp.controllers');
|
module('copayApp.controllers');
|
||||||
|
|
||||||
inject(function($rootScope, $controller, $injector, _configService_, _profileService_, _storageService_) {
|
inject(function($rootScope, $controller, $injector, lodash, _configService_, _profileService_, _storageService_) {
|
||||||
scope = $rootScope.$new();
|
scope = $rootScope.$new();
|
||||||
storageService = _storageService_;
|
storageService = _storageService_;
|
||||||
|
|
||||||
|
|
@ -198,6 +198,13 @@ mocks.init = function(fixtures, controllerName, opts, done) {
|
||||||
if (opts.initController)
|
if (opts.initController)
|
||||||
startController();
|
startController();
|
||||||
|
|
||||||
|
|
||||||
|
if (opts.loadStorage) {
|
||||||
|
lodash.each(opts.loadStorage, function(v, k) {
|
||||||
|
localStorage.setItem(k, v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.loadProfile) {
|
if (opts.loadProfile) {
|
||||||
|
|
||||||
localStorage.setItem('profile', JSON.stringify(opts.loadProfile));
|
localStorage.setItem('profile', JSON.stringify(opts.loadProfile));
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Version=1.12.14
|
Version=2.0.0
|
||||||
Name=Copay
|
Name=Copay
|
||||||
Comment=A multisignature wallet
|
Comment=A multisignature wallet
|
||||||
Exec=copay
|
Exec=copay
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
#define MyAppName "Copay"
|
#define MyAppName "Copay"
|
||||||
#define MyAppVersion "1.12.14"
|
#define MyAppVersion "2.0.0"
|
||||||
#define MyAppPublisher "BitPay"
|
#define MyAppPublisher "BitPay"
|
||||||
#define MyAppURL "https://copay.io"
|
#define MyAppURL "https://copay.io"
|
||||||
#define MyAppExeName "Copay.exe"
|
#define MyAppExeName "Copay.exe"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue