Merge pull request #4247 from JDonadio/ionic/integration

Ionic/integration
This commit is contained in:
Matias Alejo Garcia 2016-06-07 11:20:39 -03:00
commit 13c91f4278
104 changed files with 3248 additions and 3223 deletions

3
.gitignore vendored
View file

@ -24,7 +24,8 @@ webkitbuilds/*
!webkitbuilds/build-osx.sh !webkitbuilds/build-osx.sh
!webkitbuilds/Background.png !webkitbuilds/Background.png
#fonts
public/fonts
# chrome extensions # chrome extensions
browser-extensions/chrome/copay-chrome-extension browser-extensions/chrome/copay-chrome-extension

View file

@ -61,7 +61,7 @@ module.exports = function(grunt) {
'src/js/routes.js', 'src/js/routes.js',
'src/js/services/*.js', 'src/js/services/*.js',
'src/js/models/*.js', 'src/js/models/*.js',
'src/js/controllers/*.js' 'src/js/controllers/**/*.js'
], ],
tasks: ['concat:js'] tasks: ['concat:js']
} }
@ -93,17 +93,13 @@ module.exports = function(grunt) {
'bower_components/angular/angular.js', 'bower_components/angular/angular.js',
'bower_components/angular-ui-router/release/angular-ui-router.js', 'bower_components/angular-ui-router/release/angular-ui-router.js',
'bower_components/angular-foundation/mm-foundation-tpls.js', 'bower_components/angular-foundation/mm-foundation-tpls.js',
'bower_components/angular-touch/angular-touch.js',
'bower_components/fastclick/lib/fastclick.js',
'bower_components/stateful-fastclick/dist/stateful-fastclick.js',
'bower_components/angular-stateful-fastclick/lib/angular-stateful-fastclick.js',
'bower_components/angular-moment/angular-moment.js', 'bower_components/angular-moment/angular-moment.js',
'bower_components/ng-lodash/build/ng-lodash.js', 'bower_components/ng-lodash/build/ng-lodash.js',
'bower_components/angular-qrcode/angular-qrcode.js', 'bower_components/angular-qrcode/angular-qrcode.js',
'bower_components/angular-gettext/dist/angular-gettext.js', 'bower_components/angular-gettext/dist/angular-gettext.js',
'bower_components/angular-ui-switch/angular-ui-switch.js',
'bower_components/angular-sanitize/angular-sanitize.js', 'bower_components/angular-sanitize/angular-sanitize.js',
'bower_components/ng-csv/build/ng-csv.js', 'bower_components/ng-csv/build/ng-csv.js',
'bower_components/angular-mocks/angular-mocks.js',
'angular-bitcore-wallet-client/angular-bitcore-wallet-client.js' 'angular-bitcore-wallet-client/angular-bitcore-wallet-client.js'
], ],
dest: 'public/lib/angular.js' dest: 'public/lib/angular.js'
@ -116,7 +112,7 @@ module.exports = function(grunt) {
'src/js/filters/*.js', 'src/js/filters/*.js',
'src/js/models/*.js', 'src/js/models/*.js',
'src/js/services/*.js', 'src/js/services/*.js',
'src/js/controllers/*.js', 'src/js/controllers/**/*.js',
'src/js/translations.js', 'src/js/translations.js',
'src/js/version.js', 'src/js/version.js',
'src/js/coinbase.js', 'src/js/coinbase.js',
@ -134,11 +130,38 @@ module.exports = function(grunt) {
src: [ src: [
'bower_components/angular/angular-csp.css', 'bower_components/angular/angular-csp.css',
'bower_components/foundation/css/foundation.css', 'bower_components/foundation/css/foundation.css',
'bower_components/animate.css/animate.css', 'bower_components/animate.css/animate.css'
'bower_components/angular-ui-switch/angular-ui-switch.css'
], ],
dest: 'public/css/foundation.css', dest: 'public/css/foundation.css',
} },
ionic_js: {
src: [
'bower_components/ionic/release/js/ionic.bundle.min.js'
],
dest: 'public/lib/ionic.bundle.js'
},
ionic_css: {
src: [
'bower_components/ionic/release/css/ionic.min.css'
],
dest: 'public/css/ionic.css',
},
ui_components_js: {
src: [
'bower_components/jquery/dist/jquery.js',
'bower_components/roundSlider/dist/roundslider.min.js',
'bower_components/angular-gridster/dist/angular-gridster.min.js',
'bower_components/javascript-detect-element-resize/detect-element-resize.js'
],
dest: 'public/lib/ui-components.js'
},
ui_components_css: {
src: [
'bower_components/roundSlider/dist/roundslider.min.css',
'bower_components/angular-gridster/dist/angular-gridster.min.css'
],
dest: 'public/css/ui-components.css',
},
}, },
uglify: { uglify: {
options: { options: {
@ -156,11 +179,10 @@ module.exports = function(grunt) {
files: { files: {
'i18n/po/template.pot': [ 'i18n/po/template.pot': [
'public/index.html', 'public/index.html',
'public/views/*.html',
'public/views/**/*.html', 'public/views/**/*.html',
'src/js/routes.js', 'src/js/routes.js',
'src/js/services/*.js', 'src/js/services/*.js',
'src/js/controllers/*.js' 'src/js/controllers/**/*.js'
] ]
} }
}, },
@ -182,6 +204,12 @@ module.exports = function(grunt) {
src: 'bower_components/foundation-icon-fonts/foundation-icons.*', src: 'bower_components/foundation-icon-fonts/foundation-icons.*',
dest: 'public/icons/' dest: 'public/icons/'
}, },
ionic_fonts: {
expand: true,
flatten: true,
src: 'bower_components/ionic/release/fonts/ionicons.*',
dest: 'public/fonts/'
},
linux: { linux: {
files: [{ files: [{
expand: true, expand: true,
@ -242,7 +270,7 @@ module.exports = function(grunt) {
} }
}); });
grunt.registerTask('default', ['nggettext_compile', 'exec:version', 'exec:coinbase', 'browserify', 'sass', 'concat', 'copy:icons']); grunt.registerTask('default', ['nggettext_compile', 'exec:version', 'exec:coinbase', 'browserify', 'sass', 'concat', 'copy:icons', 'copy:ionic_fonts']);
grunt.registerTask('prod', ['default', 'uglify']); grunt.registerTask('prod', ['default', 'uglify']);
grunt.registerTask('translate', ['nggettext_extract']); grunt.registerTask('translate', ['nggettext_extract']);
grunt.registerTask('test', ['karma:unit']); grunt.registerTask('test', ['karma:unit']);

View file

@ -30,6 +30,7 @@ bwcModule.provider("bwcService", function() {
}; };
service.getClient = function(walletData, opts) { service.getClient = function(walletData, opts) {
opts = opts || {};
//note opts use `baseurl` all lowercase; //note opts use `baseurl` all lowercase;
var bwc = new Client({ var bwc = new Client({

View file

@ -14,23 +14,19 @@
"angular-moment": "0.10.1", "angular-moment": "0.10.1",
"angular-qrcode": "monospaced/angular-qrcode#~6.2.1", "angular-qrcode": "monospaced/angular-qrcode#~6.2.1",
"angular-ui-router": "0.2.18", "angular-ui-router": "0.2.18",
"angular-ui-switch": "0.1.1",
"animate.css": "3.5.1", "animate.css": "3.5.1",
"foundation": "5.5.3", "foundation": "5.5.3",
"foundation-icon-fonts": "*", "foundation-icon-fonts": "*",
"ionic": "1.3.1",
"moment": "2.10.3", "moment": "2.10.3",
"ng-lodash": "0.2.3", "ng-lodash": "0.2.3",
"qrcode-decoder-js": "*", "qrcode-decoder-js": "*",
"trezor-connect": "~1.0.1", "trezor-connect": "~1.0.1",
"ng-csv": "~0.3.6", "ng-csv": "~0.3.6"
"angular-touch": "^1.5.5",
"fastclick": "^1.0.6",
"stateful-fastclick": "^1.0.2",
"angular-stateful-fastclick": "^1.0.2"
}, },
"resolutions": { "resolutions": {
"angular": "1.4.6", "angular": "1.4.6",
"qrcode-generator": "0.1.0", "qrcode-generator": "0.1.0",
"fastclick": "1.0.6" "angular-ui-router": "0.2.18"
} }
} }

View file

@ -1,6 +1,7 @@
./index.html ./index.html
./css/*.css ./css/*.css
./font/* ./font/*
./fonts/*
./icons/* ./icons/*
./img/* ./img/*
./img/**/* ./img/**/*

View file

@ -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.

View file

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

View file

@ -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" />

View file

@ -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",
@ -42,7 +42,7 @@
"url": "https://github.com/bitpay/copay/issues" "url": "https://github.com/bitpay/copay/issues"
}, },
"dependencies": { "dependencies": {
"bitcore-wallet-client": "2.5.0", "bitcore-wallet-client": "~2.5.0",
"express": "^4.11.2", "express": "^4.11.2",
"fs": "0.0.2", "fs": "0.0.2",
"grunt": "^0.4.5", "grunt": "^0.4.5",
@ -91,6 +91,7 @@
"load-grunt-tasks": "^3.5.0", "load-grunt-tasks": "^3.5.0",
"mocha": "^2.4.5", "mocha": "^2.4.5",
"phantomjs-prebuilt": "^2.1.7", "phantomjs-prebuilt": "^2.1.7",
"ionic": "^1.7.14",
"xcode": "^0.8.2" "xcode": "^0.8.2"
} }
} }

View file

@ -6,46 +6,45 @@
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
<meta name="msapplication-tap-highlight" content="no"> <meta name="msapplication-tap-highlight" content="no">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">
<link rel="stylesheet" type="text/css" href="css/ionic.css">
<link rel="stylesheet" type="text/css" href="css/foundation.css"> <link rel="stylesheet" type="text/css" href="css/foundation.css">
<link rel="stylesheet" type="text/css" href="icons/foundation-icons.css"> <link rel="stylesheet" type="text/css" href="icons/foundation-icons.css">
<link rel="stylesheet" type="text/css" href="css/ui-components.css">
<link rel="stylesheet" type="text/css" href="css/copay.css"> <link rel="stylesheet" type="text/css" href="css/copay.css">
<title>Copay - Multisignature Wallet</title> <title>Copay - Multisignature Wallet</title>
<link rel="shortcut icon" href="img/favicon.ico"> <link rel="shortcut icon" href="img/favicon.ico">
</head> </head>
<body ng-cloak class="ng-cloak"> <body ng-cloak class="ng-cloak">
<div class="page" ng-controller="indexController as index"> <ion-side-menus class="page" ng-controller="indexController as index">
<div notifications="right top"></div>
<div ng-include="'views/includes/tx-details.html'" ng-if="index.showTx"></div>
<div ng-include="'views/includes/password.html'" ng-if="index.askPassword"></div>
<div ng-include="'views/includes/alert.html'" ng-if="index.showAlert"></div>
<div ng-include="'views/includes/confirm-tx.html'" ng-if="index.confirmTx"></div>
<div class="off-canvas-wrap" id="off-canvas-wrap"
ng-swipe-disable-mouse
ng-swipe-left="index.closeMenu()"
ng-swipe-right="index.openMenu()">
<div class="inner-wrap">
<div ng-include="'views/includes/sidebar.html'" ng-if="index.hasProfile"></div>
<div id="sectionContainer">
<div id="mainSection">
<section ui-view="main"
ng-class="{
'main': index.hasProfile
}"></section>
</div>
</div>
<a class="close-menu" ng-click="index.closeMenu()"></a>
<!-- Left menu -->
<ion-side-menu side="left">
<div ng-include="'views/includes/sidebar.html'" ng-if="index.hasProfile"></div>
</ion-side-menu>
<!-- Main content -->
<ion-side-menu-content on-drag="index.onDrag()">
<div notifications="right top"></div>
<div id="sectionContainer">
<div id="mainSection">
<section ui-view="main"
ng-class="{
'main': index.hasProfile
}"></section>
</div>
</div> </div>
</div> </ion-side-menu-content>
</div> </ion-side-menus>
<script src="lib/ionic.bundle.js"></script>
<script src="lib/angular.js"></script> <script src="lib/angular.js"></script>
<!-- DO NOT DELETE THIS COMMET --> <!-- DO NOT DELETE THIS COMMET -->
<!-- PLACEHOLDER: CORDOVA SRIPT --> <!-- PLACEHOLDER: CORDOVA SRIPT -->
<script src="lib/ui-components.js"></script>
<script src="js/copay.js"></script> <script src="js/copay.js"></script>
</body> </body>

View file

@ -19,17 +19,17 @@
</section> </section>
</nav> </nav>
<div class="content preferences"> <div class="box-notification" ng-show="wordsC.error">
<div class="box-notification" ng-show="wordsC.error"> <span class="text-warning">
<span class="text-warning"> {{wordsC.error|translate}}
{{wordsC.error|translate}} </span>
</span> </div>
</div>
<!-- <!--
## STEP 1 ## STEP 1
--> -->
<div class="content preferences text-center">
<div ng-show="wordsC.step == 1"> <div ng-show="wordsC.step == 1">
<div ng-show="wordsC.mnemonicWords || (wordsC.credentialsEncrypted && !wordsC.deleted)" class="row"> <div ng-show="wordsC.mnemonicWords || (wordsC.credentialsEncrypted && !wordsC.deleted)" class="row">
<h5 class="text-center" translate>Write your wallet recovery phrase</h5> <h5 class="text-center" translate>Write your wallet recovery phrase</h5>
@ -37,21 +37,21 @@
<span translate> <span translate>
To restore this {{index.m}}-{{index.n}} <b>shared</b> wallet you will need To restore this {{index.m}}-{{index.n}} <b>shared</b> wallet you will need
</span>: </span>:
<ol class="m10t columns size-14 text-gray"> <div class="m10t columns size-14 text-gray">
<li translate>Your wallet recovery phrase and access to the server that coordinated the initial wallet creation. You still need {{index.m}} keys to spend.</li> <span translate>Your wallet recovery phrase and access to the server that coordinated the initial wallet creation. You still need {{index.m}} keys to spend.</span>
<li translate><b>OR</b> the wallet recovery phrase of <b>all</b> copayers in the wallet</li> <span translate><b>OR</b> the wallet recovery phrase of <b>all</b> copayers in the wallet</span>
<li translate><b>OR</b> 1 wallet export file and the remaining quorum of wallet recovery phrases (e.g. in a 3-5 wallet: 1 wallet export file + 2 wallet recovery phrases of any of the other copayers).</li> <span translate><b>OR</b> 1 wallet export file and the remaining quorum of wallet recovery phrases (e.g. in a 3-5 wallet: 1 wallet export file + 2 wallet recovery phrases of any of the other copayers).</span>
</ol> </div>
</span> </span>
</div> </div>
<div class="size-14 text-gray columns" ng-show="(index.n>1 && index.m == index.n )"> <div class="size-14 text-gray columns" ng-show="(index.n>1 && index.m == index.n )">
<span translate> <span translate>
To restore this {{index.m}}-{{index.n}} <b>shared</b> wallet you will need To restore this {{index.m}}-{{index.n}} <b>shared</b> wallet you will need
</span>: </span>:
<ol class="m10t columns size-14 text-gray"> <div class="m10t columns size-14 text-gray">
<li translate>Your wallet recovery phrase and access to the server that coordinated the initial wallet creation. You still need {{index.m}} keys to spend.</li> <span translate>Your wallet recovery phrase and access to the server that coordinated the initial wallet creation. You still need {{index.m}} keys to spend.</span>
<li translate><b>OR</b> the wallet recovery phrases of <b>all</b> copayers in the wallet</li> <span translate><b>OR</b> the wallet recovery phrases of <b>all</b> copayers in the wallet</span>
</ol> </div>
</span> </span>
</div> </div>
</div> </div>
@ -97,7 +97,7 @@
<div class="button-box"> <div class="button-box">
<button <button
ng-show="!wordsC.deleted" ng-show="!wordsC.deleted"
ng-disabled="wordsC.credentialsEncrypted" ng-disabled="wordsC.credentialsEncrypted || wordsC.error"
class="round expand m0" class="round expand m0"
ng-style="{'background-color':index.backgroundColor}" ng-style="{'background-color':index.backgroundColor}"
ng-click="wordsC.goToStep(2);" ng-click="wordsC.goToStep(2);"

View file

@ -1,5 +1,5 @@
<div <div
class="topbar-container" class="topbar-container"
ng-include="'views/includes/topbar.html'" ng-include="'views/includes/topbar.html'"
ng-init="titleSection='Buy'; goBackToState = 'coinbase'; noColor = true"> ng-init="titleSection='Buy'; goBackToState = 'coinbase'; noColor = true">
</div> </div>
@ -9,13 +9,7 @@
<div class="onGoingProcess" ng-show="buy.loading"> <div class="onGoingProcess" ng-show="buy.loading">
<div class="onGoingProcess-content" ng-style="{'background-color': '#2b71b1'}"> <div class="onGoingProcess-content" ng-style="{'background-color': '#2b71b1'}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span>{{buy.loading}}</span> <span>{{buy.loading}}</span>
</div> </div>
</div> </div>
@ -32,42 +26,42 @@
<div class="row m20t" <div class="row m20t"
ng-show="index.coinbaseAccount && !buy.buyInfo && !buy.receiveInfo"> ng-show="index.coinbaseAccount && !buy.buyInfo && !buy.receiveInfo">
<div class="columns"> <div class="columns">
<form name="buyCoinbaseForm" <form name="buyCoinbaseForm"
ng-submit="buy.buyRequest(index.coinbaseToken, index.coinbaseAccount)" novalidate> ng-submit="buy.buyRequest(index.coinbaseToken, index.coinbaseAccount)" novalidate>
<div ng-if="index.coinbaseToken" ng-init="buy.getPaymentMethods(index.coinbaseToken)"> <div ng-if="index.coinbaseToken" ng-init="buy.getPaymentMethods(index.coinbaseToken)">
<label>Payment method</label> <label>Payment method</label>
<select <select
ng-model="selectedPaymentMethod.id" ng-model="selectedPaymentMethod.id"
ng-options="item.id as item.name for item in buy.paymentMethods"> ng-options="item.id as item.name for item in buy.paymentMethods">
</select> </select>
</div> </div>
<label>Amount <label>Amount
<span <span
ng-if="index.coinbaseToken" ng-if="index.coinbaseToken"
ng-init="buy.getPrice(index.coinbaseToken)" ng-init="buy.getPrice(index.coinbaseToken)"
ng-show="buy.buyPrice" ng-show="buy.buyPrice"
class="size-11 text-light right"> class="size-11 text-light right">
1 BTC <i class="icon-arrow-right"></i> {{buy.buyPrice.amount}} {{buy.buyPrice.currency}} 1 BTC <i class="icon-arrow-right"></i> {{buy.buyPrice.amount}} {{buy.buyPrice.currency}}
</span> </span>
</label> </label>
<div class="input"> <div class="input">
<input ng-show="!showAlternative" type="number" id="amount" <input ng-show="!showAlternative" type="number" id="amount"
name="amount" ng-attr-placeholder="{{'Amount in ' + (showAlternative ? 'USD' : 'BTC')}}" name="amount" ng-attr-placeholder="{{'Amount in ' + (showAlternative ? 'USD' : 'BTC')}}"
ng-minlength="0.00000001" ng-maxlength="10000000000" ng-minlength="0.00000001" ng-maxlength="10000000000"
ng-model="amount" autocomplete="off" ng-disabled="buy.loading"> ng-model="amount" autocomplete="off" ng-disabled="buy.loading">
<input ng-show="showAlternative" type="number" id="fiat" <input ng-show="showAlternative" type="number" id="fiat"
name="fiat" ng-attr-placeholder="{{'Amount in ' + (showAlternative ? 'USD' : 'BTC')}}" name="fiat" ng-attr-placeholder="{{'Amount in ' + (showAlternative ? 'USD' : 'BTC')}}"
ng-model="fiat" autocomplete="off" ng-disabled="buy.loading"> ng-model="fiat" autocomplete="off" ng-disabled="buy.loading">
<a ng-show="!showAlternative" class="postfix button" <a ng-show="!showAlternative" class="postfix button"
ng-click="showAlternative = true; amount = null">BTC</a> ng-click="showAlternative = true; amount = null">BTC</a>
<a ng-show="showAlternative" class="postfix button black" <a ng-show="showAlternative" class="postfix button black"
ng-click="showAlternative = false; fiat = null">USD</a> ng-click="showAlternative = false; fiat = null">USD</a>
</div> </div>
@ -87,7 +81,7 @@
<i class="db fi-arrow-down size-24 m10v"></i> <i class="db fi-arrow-down size-24 m10v"></i>
</div> </div>
<div <div
ng-if="index.coinbaseToken" ng-if="index.coinbaseToken"
ng-init="buy.init(index.coinbaseTestnet)" ng-init="buy.init(index.coinbaseTestnet)"
ng-click="openWalletsModal(buy.otherWallets)"> ng-click="openWalletsModal(buy.otherWallets)">
@ -102,7 +96,7 @@
</div> </div>
<div class="input m20t"> <div class="input m20t">
<input class="button black expand round" <input class="button black expand round"
ng-disabled="buy.loading || (!amount && !fiat) || !selectedPaymentMethod" ng-disabled="buy.loading || (!amount && !fiat) || !selectedPaymentMethod"
ng-style="{'background-color': '#2b71b1'}" ng-style="{'background-color': '#2b71b1'}"
type="submit" value="{{'Continue'}}"> type="submit" value="{{'Continue'}}">
@ -146,11 +140,11 @@
<li class="line-b p15"> <li class="line-b p15">
<span class="m10 text-normal text-bold">Total</span> <span class="m10 text-normal text-bold">Total</span>
<span class="right text-gray">{{buy.buyInfo.total.amount}} {{buy.buyInfo.total.currency}}</span> <span class="right text-gray">{{buy.buyInfo.total.amount}} {{buy.buyInfo.total.currency}}</span>
</li> </li>
<li class="line-b p15"> <li class="line-b p15">
<span class="m10 text-normal text-bold">Payout at</span> <span class="m10 text-normal text-bold">Payout at</span>
<span class="right text-gray">{{buy.buyInfo.payout_at | amCalendar}}</span> <span class="right text-gray">{{buy.buyInfo.payout_at | amCalendar}}</span>
</li> </li>
<li class="line-b p15"> <li class="line-b p15">
<span class="m10 text-normal text-bold">Deposit into Copay Wallet</span> <span class="m10 text-normal text-bold">Deposit into Copay Wallet</span>
<span class="right text-gray">{{buy.selectedWalletName}}</span> <span class="right text-gray">{{buy.selectedWalletName}}</span>
@ -158,7 +152,7 @@
</ul> </ul>
<div class="row"> <div class="row">
<div class="columns"> <div class="columns">
<button class="button black round expand" <button class="button black round expand"
ng-style="{'background-color': '#2b71b1'}" ng-style="{'background-color': '#2b71b1'}"
ng-click="buy.confirmBuy(index.coinbaseToken, index.coinbaseAccount, buy.buyInfo)" ng-click="buy.confirmBuy(index.coinbaseToken, index.coinbaseAccount, buy.buyInfo)"
ng-disabled="buy.loading"> ng-disabled="buy.loading">

View file

@ -1,5 +1,5 @@
<div <div
class="topbar-container" class="topbar-container"
ng-include="'views/includes/topbar.html'" ng-include="'views/includes/topbar.html'"
ng-init="titleSection='Buy'; goBackToState = 'glidera'; noColor = true"> ng-init="titleSection='Buy'; goBackToState = 'glidera'; noColor = true">
</div> </div>
@ -9,13 +9,7 @@
<div class="onGoingProcess" ng-show="buy.loading"> <div class="onGoingProcess" ng-show="buy.loading">
<div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}"> <div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span>{{buy.loading}}</span> <span>{{buy.loading}}</span>
</div> </div>
</div> </div>
@ -24,7 +18,7 @@
<h4 class="title m0 text-left"> <h4 class="title m0 text-left">
<span class="text-light">Daily buy limit</span>: <span class="text-light">Daily buy limit</span>:
{{index.glideraLimits.dailyBuy|currency:'':2}} {{index.glideraLimits.currency}} {{index.glideraLimits.dailyBuy|currency:'':2}} {{index.glideraLimits.currency}}
(remaining {{index.glideraLimits.dailyBuyRemaining|currency:'':2}} {{index.glideraLimits.currency}}) (remaining {{index.glideraLimits.dailyBuyRemaining|currency:'':2}} {{index.glideraLimits.currency}})
<br> <br>
<span class="text-light">Monthly buy limit</span>: <span class="text-light">Monthly buy limit</span>:
{{index.glideraLimits.monthlyBuy|currency:'':2}} {{index.glideraLimits.currency}} {{index.glideraLimits.monthlyBuy|currency:'':2}} {{index.glideraLimits.currency}}
@ -43,16 +37,16 @@
<div ng-show="!buy.show2faCodeInput && !buy.success"> <div ng-show="!buy.show2faCodeInput && !buy.success">
<form name="buyPriceForm" <form name="buyPriceForm"
ng-submit="buy.get2faCode(index.glideraToken)" novalidate> ng-submit="buy.get2faCode(index.glideraToken)" novalidate>
<div ng-if="index.glideraToken" <div ng-if="index.glideraToken"
ng-init="buy.init(index.glideraTestnet)" ng-init="buy.init(index.glideraTestnet)"
ng-click="openWalletsModal(buy.otherWallets)"> ng-click="openWalletsModal(buy.otherWallets)">
<label>Wallet</label> <label>Wallet</label>
<div class="input"> <div class="input">
<input type="text" id="address" name="address" ng-disabled="buy.selectedWalletId" <input type="text" id="address" name="address" ng-disabled="buy.selectedWalletId"
ng-attr-placeholder="{{'Choose your destination wallet'}}" ng-attr-placeholder="{{'Choose your destination wallet'}}"
ng-model="buy.selectedWalletName" required> ng-model="buy.selectedWalletName" required>
<a class="postfix size-12 m0 text-gray"> <a class="postfix size-12 m0 text-gray">
<i class="icon-wallet size-18"></i> <i class="icon-wallet size-18"></i>
@ -62,41 +56,41 @@
<label>Amount in {{showAlternative ? 'USD' : 'BTC'}}</label> <label>Amount in {{showAlternative ? 'USD' : 'BTC'}}</label>
<div class="input"> <div class="input">
<input ng-show="!showAlternative" type="number" id="qty" <input ng-show="!showAlternative" type="number" id="qty"
name="qty" ng-attr-placeholder="{{'Amount'}}" name="qty" ng-attr-placeholder="{{'Amount'}}"
ng-minlength="0.00000001" ng-maxlength="10000000000" ng-minlength="0.00000001" ng-maxlength="10000000000"
ng-model="qty" autocomplete="off" ng-change="buy.getBuyPrice(index.glideraToken, {'qty': qty})"> ng-model="qty" autocomplete="off" ng-change="buy.getBuyPrice(index.glideraToken, {'qty': qty})">
<input ng-show="showAlternative" type="number" id="fiat" <input ng-show="showAlternative" type="number" id="fiat"
name="fiat" ng-attr-placeholder="{{'Amount'}}" name="fiat" ng-attr-placeholder="{{'Amount'}}"
ng-model="fiat" autocomplete="off" ng-change="buy.getBuyPrice(index.glideraToken, {'fiat': fiat})"> ng-model="fiat" autocomplete="off" ng-change="buy.getBuyPrice(index.glideraToken, {'fiat': fiat})">
<a ng-show="!showAlternative" class="postfix" <a ng-show="!showAlternative" class="postfix"
ng-click="showAlternative = true; qty = null; buy.buyPrice = null">BTC</a> ng-click="showAlternative = true; qty = null; buy.buyPrice = null">BTC</a>
<a ng-show="showAlternative" class="postfix" <a ng-show="showAlternative" class="postfix"
ng-click="showAlternative = false; fiat = null; buy.buyPrice = null">USD</a> ng-click="showAlternative = false; fiat = null; buy.buyPrice = null">USD</a>
<div class="text-center text-gray size-12 m20b" ng-show="!buy.gettingBuyPrice && buy.buyPrice.qty"> <div class="text-center text-gray size-12 m20b" ng-show="!buy.gettingBuyPrice && buy.buyPrice.qty">
Buy Buy
<span ng-show="qty">{{buy.buyPrice.subtotal|currency:'':2}} {{buy.buyPrice.currency}} in Bitcoin</span> <span ng-show="qty">{{buy.buyPrice.subtotal|currency:'':2}} {{buy.buyPrice.currency}} in Bitcoin</span>
<span ng-show="fiat">{{buy.buyPrice.qty}} BTC</span> <span ng-show="fiat">{{buy.buyPrice.qty}} BTC</span>
at {{buy.buyPrice.price}} {{buy.buyPrice.currency}}/BTC at {{buy.buyPrice.price}} {{buy.buyPrice.currency}}/BTC
</div> </div>
<div class="text-center text-gray size-12 m20b" ng-show="!buy.gettingBuyPrice && !buy.buyPrice.qty"> <div class="text-center text-gray size-12 m20b" ng-show="!buy.gettingBuyPrice && !buy.buyPrice.qty">
(Enter the amount to get the exchange rate) (Enter the amount to get the exchange rate)
</div> </div>
<div class="text-center text-gray size-12 m20b" ng-show="buy.gettingBuyPrice"> <div class="text-center text-gray size-12 m20b" ng-show="buy.gettingBuyPrice">
... ...
</div> </div>
<input class="button black expand round" <input class="button black expand round"
ng-style="{'background-color':index.backgroundColor}" ng-style="{'background-color':index.backgroundColor}"
type="submit" value="{{'Continue'}}" type="submit" value="{{'Continue'}}"
ng-disabled="index.glideraLimits.transactDisabledPendingFirstTransaction || !buy.buyPrice.qty || ng-disabled="index.glideraLimits.transactDisabledPendingFirstTransaction || !buy.buyPrice.qty ||
!buy.selectedWalletId || buy.loading"> !buy.selectedWalletId || buy.loading">
</div> </div>
</form> </form>
</div> </div>
<div ng-show="buy.show2faCodeInput && !buy.success"> <div ng-show="buy.show2faCodeInput && !buy.success">
<div class="m10t text-center"> <div class="m10t text-center">
@ -105,10 +99,10 @@
A SMS containing a confirmation code was sent to your phone. <br> A SMS containing a confirmation code was sent to your phone. <br>
Please, enter the code below Please, enter the code below
</p> </p>
<form name="buyForm" <form name="buyForm"
ng-submit="buy.sendRequest(index.glideraToken, index.glideraPermissions, twoFaCode)" novalidate> ng-submit="buy.sendRequest(index.glideraToken, index.glideraPermissions, twoFaCode)" novalidate>
<input type="number" ng-model="twoFaCode" required> <input type="number" ng-model="twoFaCode" required>
<input class="button black expand round" <input class="button black expand round"
ng-style="{'background-color':index.backgroundColor}" ng-style="{'background-color':index.backgroundColor}"
type="submit" value="{{'Buy'}}" ng-disabled="buyForm.$invalid || buy.loading"> type="submit" value="{{'Buy'}}" ng-disabled="buyForm.$invalid || buy.loading">
</form> </form>
@ -134,6 +128,6 @@
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="extra-margin-bottom"></div> <div class="extra-margin-bottom"></div>

View file

@ -28,13 +28,7 @@
<div class="onGoingProcess" ng-show="coinbase.loading || index.coinbaseLoading"> <div class="onGoingProcess" ng-show="coinbase.loading || index.coinbaseLoading">
<div class="onGoingProcess-content" ng-style="{'background-color': '#2b71b1'}"> <div class="onGoingProcess-content" ng-style="{'background-color': '#2b71b1'}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span ng-show="coinbase.loading">Connecting to Coinbase...</span> <span ng-show="coinbase.loading">Connecting to Coinbase...</span>
<span ng-show="index.coinbaseLoading">{{index.coinbaseLoading}}</span> <span ng-show="index.coinbaseLoading">{{index.coinbaseLoading}}</span>
</div> </div>
@ -156,7 +150,7 @@
<span ng-show="tx.type == 'sell' && tx.status == 'completed'">Sold</span> <span ng-show="tx.type == 'sell' && tx.status == 'completed'">Sold</span>
<span ng-show="tx.type == 'buy' && tx.status == 'completed'">Bought</span> <span ng-show="tx.type == 'buy' && tx.status == 'completed'">Bought</span>
<span class="text-bold"> <span class="text-bold">
<span ng-if="tx.type == 'sell' || (tx.type == 'send' && tx.from)">-</span>{{tx.amount.amount.replace('-','')}} <span ng-if="tx.type == 'sell' || (tx.type == 'send' && tx.from)">-</span>{{tx.amount.amount.replace('-','')}}
{{tx.amount.currency}} {{tx.amount.currency}}
</span> </span>
</div> </div>

View file

@ -1,5 +1,5 @@
<div <div
class="topbar-container" class="topbar-container"
ng-include="'views/includes/topbar.html'" ng-include="'views/includes/topbar.html'"
ng-init="titleSection='Coinbase'; closeToHome = true"> ng-init="titleSection='Coinbase'; closeToHome = true">
</div> </div>
@ -8,13 +8,7 @@
<div class="onGoingProcess" ng-show="coinbase.loading"> <div class="onGoingProcess" ng-show="coinbase.loading">
<div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}"> <div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span ng-show="coinbase.loading">Connecting to Coinbase...</span> <span ng-show="coinbase.loading">Connecting to Coinbase...</span>
</div> </div>
</div> </div>
@ -22,10 +16,10 @@
<div class="row m20t"> <div class="row m20t">
<div class="large-12 columns"> <div class="large-12 columns">
<div class="text-center"> <div class="text-center">
<img src="img/coinbase-logo.png" <img src="img/coinbase-logo.png"
ng-click="index.updateCoinbase()" width="100"> ng-click="index.updateCoinbase()" width="100">
</div> </div>
<div class="m10t text-center" ng-show="coinbase.error"> <div class="m10t text-center" ng-show="coinbase.error">
<div class="notification m10b size-12 text-warning">{{coinbase.error}}</div> <div class="notification m10b size-12 text-warning">{{coinbase.error}}</div>
<button class="outline dark-gray tiny round" ng-click="coinbase.submitOauthCode(coinbase.code)">Try again</button> <button class="outline dark-gray tiny round" ng-click="coinbase.submitOauthCode(coinbase.code)">Try again</button>

View file

@ -13,13 +13,7 @@
<qrcode size="220" error-correction-level="L" data="{{index.walletSecret}}"></qrcode> <qrcode size="220" error-correction-level="L" data="{{index.walletSecret}}"></qrcode>
<div ng-show="!index.walletSecret" style="position:relative; top:-226px; height:0px"> <div ng-show="!index.walletSecret" style="position:relative; top:-226px; height:0px">
<div style="height:220px; width:220px; margin:auto; background: white"> <div style="height:220px; width:220px; margin:auto; background: white">
<div class="spinner" style="margin-top:85px"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div> </div>
</div> </div>
<div class="secret"> <div class="secret">

View file

@ -6,30 +6,18 @@
<div class="content p20b" ng-controller="createController as create" ng-init="create.setTotalCopayers(1)"> <ion-content overflow-scroll="true" class="content p20b" ng-controller="createController as create" ng-init="create.setTotalCopayers(1)">
<div class="onGoingProcess" ng-show="create.loading && !create.hwWallet"> <div class="onGoingProcess" ng-show="create.loading && !create.hwWallet">
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}"> <div class="onGoingProcess-content" ng-style="{'background-color':'#222'}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span translate>Creating Wallet...</span> <span translate>Creating Wallet...</span>
</div> </div>
</div> </div>
<div class="onGoingProcess" ng-show="create.hwWallet"> <div class="onGoingProcess" ng-show="create.hwWallet">
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}" style="max-height:6.5em" > <div class="onGoingProcess-content" ng-style="{'background-color':'#222'}" style="max-height:6.5em" >
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span translate>Connecting to {{create.hwWallet}} Wallet...</span> <span translate>Connecting to {{create.hwWallet}} Wallet...</span>
<span ng-show="create.hwWallet=='Trezor'" translate>Please accept to export two public keys from the Trezor app</span> <span ng-show="create.hwWallet=='Trezor'" translate>Please accept to export two public keys from the Trezor app</span>
</div> </div>
@ -164,10 +152,11 @@
<input type="text" class="form-control" name="derivationPath" ng-model="derivationPath"> <input type="text" class="form-control" name="derivationPath" ng-model="derivationPath">
</label> </label>
</div> </div>
<div class="oh" ng-show="create.seedSourceId == 'new'"> <div class="oh" ng-show="create.seedSourceId == 'new'">
<label for="network-name" > <label for="network-name" class="dbi">
<span>Testnet</span> <span>Testnet</span>
<switch id="network-name" name="isTestnet" ng-model="isTestnet" class="green right m5t m10b"></switch> <ion-toggle ng-model="testnetEnabled" toggle-class="toggle-balanced" class="bct">
</ion-toggle>
</label> </label>
</div> </div>
</div> <!-- columns --> </div> <!-- columns -->
@ -184,5 +173,5 @@
</div> <!-- large-12 columns --> </div> <!-- large-12 columns -->
</div> <!-- row --> </div> <!-- row -->
</form> </form>
</div> </ion-content>
<div class="extra-margin-bottom"></div> <div class="extra-margin-bottom"></div>

View file

@ -25,20 +25,14 @@
</div> </div>
<div class="row"> <div class="row">
<div class="onGoingProcess" ng-show="creatingProfile"> <div class="onGoingProcess" ng-show="disclaimer.creatingProfile">
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}"> <div class="onGoingProcess-content" ng-style="{'background-color':'#222'}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span translate>Creating Wallet...</span> <span translate>Creating Wallet...</span>
</div> </div>
</div> </div>
<div class="start-button columns button-box"> <div class="start-button columns button-box">
<button ng-disabled="creatingProfile" ng-click="disclaimer.accept()" class="button black expand round size-12 text-spacing m0" translate> <button ng-disabled="disclaimer.creatingProfile" ng-click="disclaimer.accept()" class="button black expand round size-12 text-spacing m0" translate>
I AGREE. GET STARTED I AGREE. GET STARTED
</button> </button>
</div> </div>

View file

@ -4,8 +4,6 @@
ng-init="titleSection='Export'; goBackToState = 'preferencesAdvanced'"> ng-init="titleSection='Export'; goBackToState = 'preferencesAdvanced'">
</div> </div>
<div class="content preferences" ng-controller="exportController as exportC"> <div class="content preferences" ng-controller="exportController as exportC">
<h4></h4> <h4></h4>
<div ng-show="!exportC.backupWalletPlainText"> <div ng-show="!exportC.backupWalletPlainText">
@ -19,7 +17,6 @@
<div class="text-warning size-14 m20b" ng-show="exportC.isEncrypted"> <div class="text-warning size-14 m20b" ng-show="exportC.isEncrypted">
<i class="fi-alert size-12"></i> <i class="fi-alert size-12"></i>
<span translate> A spending password is set for this wallet. Exporting keeps the spending password in the export archive.</span> <span translate> A spending password is set for this wallet. Exporting keeps the spending password in the export archive.</span>
</div> </div>
</div> </div>
</div> </div>
@ -29,16 +26,12 @@
<div class="columns"> <div class="columns">
<label for="password" translate>Set up a password </label> <label for="password" translate>Set up a password </label>
<div class="input"> <div class="input">
<input type="password" class="form-control" <input type="password" class="form-control" placeholder="{{'Your password'|translate}}" name="password" ng-model="exportC.password">
placeholder="{{'Your password'|translate}}"
name="password" ng-model="exportC.password">
</div> </div>
<label for="password" translate>Repeat the password</label> <label for="password" translate>Repeat the password</label>
<div class="input"> <div class="input">
<input type="password" class="form-control" <input type="password" class="form-control" placeholder="{{'Repeat password'|translate}}" name="password" ng-model="exportC.repeatpassword">
placeholder="{{'Repeat password'|translate}}"
name="password" ng-model="exportC.repeatpassword">
</div> </div>
</div> </div>
</div> </div>
@ -46,20 +39,13 @@
<h4></h4> <h4></h4>
<ul class="no-bullet m0"> <ion-toggle ng-model="metaDataEnabled" toggle-class="toggle-balanced" class="r0">
<li> <span class="toggle-label" translate>Include address book and history cache</span>
<switch id="no-metaData" name="metaData" ng-model="metaData" class="green right"></switch> </ion-toggle>
<span translate>Include address book and history cache</span>
</li>
</ul>
<ul class="no-bullet m0" ng-show="index.canSign">
<li>
<switch id="no-sign" name="noSign" ng-model="noSign" class="green right"></switch>
<span translate>Do not include private key</span>
</li>
</ul>
<ion-toggle ng-model="noSignEnabled" toggle-class="toggle-balanced" class="r0">
<span class="toggle-label" translate>Do not include private key</span>
</ion-toggle>
<div class="box-notification" ng-show="!index.canSign"> <div class="box-notification" ng-show="!index.canSign">
<span class="text-warning size-14"> <span class="text-warning size-14">
@ -80,7 +66,6 @@
</span> </span>
</div> </div>
<div class="row"> <div class="row">
<div class="columns"> <div class="columns">
<button class="black round expand m20t" ng-click="exportC.downloadWalletBackup()" <button class="black round expand m20t" ng-click="exportC.downloadWalletBackup()"
@ -106,7 +91,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="row" ng-show="exportC.backupWalletPlainText"> <div class="row" ng-show="exportC.backupWalletPlainText">

View file

@ -10,13 +10,7 @@
<div class="onGoingProcess" ng-show="glidera.loading || index.glideraLoading"> <div class="onGoingProcess" ng-show="glidera.loading || index.glideraLoading">
<div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}"> <div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span ng-show="glidera.loading">Connecting to Glidera...</span> <span ng-show="glidera.loading">Connecting to Glidera...</span>
<span ng-show="index.glideraLoading">{{index.glideraLoading}}</span> <span ng-show="index.glideraLoading">{{index.glideraLoading}}</span>
</div> </div>
@ -146,9 +140,9 @@
</div> </div>
</div> </div>
<ul class="no-bullet m0 size-14" <ul class="no-bullet m0 size-14"
ng-show="index.glideraStatus && index.glideraStatus.userCanTransact"> ng-show="index.glideraStatus && index.glideraStatus.userCanTransact">
<li ng-show="index.glideraStatus.userCanBuy" <li ng-show="index.glideraStatus.userCanBuy"
class="line-b line-t p20 pointer" class="line-b line-t p20 pointer"
ng-click="$root.go('buyGlidera')"> ng-click="$root.go('buyGlidera')">
<img src="img/buy-bitcoin.svg" alt="buy bitcoin" width="40"> <img src="img/buy-bitcoin.svg" alt="buy bitcoin" width="40">

View file

@ -1,5 +1,5 @@
<div <div
class="topbar-container" class="topbar-container"
ng-include="'views/includes/topbar.html'" ng-include="'views/includes/topbar.html'"
ng-init="titleSection='Glidera'; closeToHome = true"> ng-init="titleSection='Glidera'; closeToHome = true">
</div> </div>
@ -8,13 +8,7 @@
<div class="onGoingProcess" ng-show="glidera.loading"> <div class="onGoingProcess" ng-show="glidera.loading">
<div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}"> <div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span ng-show="glidera.loading">Connecting to Glidera...</span> <span ng-show="glidera.loading">Connecting to Glidera...</span>
</div> </div>
</div> </div>
@ -22,10 +16,10 @@
<div class="row m20t"> <div class="row m20t">
<div class="large-12 columns"> <div class="large-12 columns">
<div class="text-center"> <div class="text-center">
<img src="img/glidera-logo.png" <img src="img/glidera-logo.png"
ng-click="index.updateGlidera()" width="100"> ng-click="index.updateGlidera()" width="100">
</div> </div>
<div class="m10t text-center" ng-show="glidera.error"> <div class="m10t text-center" ng-show="glidera.error">
<div class="notification m10b size-12 text-warning">{{glidera.error}}</div> <div class="notification m10b size-12 text-warning">{{glidera.error}}</div>
<button class="outline dark-gray tiny round" ng-click="glidera.submitOauthCode(glidera.code)">Try again</button> <button class="outline dark-gray tiny round" ng-click="glidera.submitOauthCode(glidera.code)">Try again</button>

View file

@ -4,28 +4,16 @@
ng-init="titleSection='Import wallet'; goBackToState = 'add'; noColor = true"> ng-init="titleSection='Import wallet'; goBackToState = 'add'; noColor = true">
</div> </div>
<div class="content p20b" ng-controller="importController as import" ng-init="type='12'"> <ion-content overflow-scroll="true" class="content p20b" ng-controller="importController as import" ng-init="type='12'">
<div class="onGoingProcess" ng-show="import.loading && !import.hwWallet"> <div class="onGoingProcess" ng-show="import.loading && !import.hwWallet">
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}"> <div class="onGoingProcess-content" ng-style="{'background-color':'#222'}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span translate>Importing wallet...</span> <span translate>Importing wallet...</span>
</div> </div>
</div> </div>
<div class="onGoingProcess" ng-show="import.hwWallet"> <div class="onGoingProcess" ng-show="import.hwWallet">
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}"> <div class="onGoingProcess-content" ng-style="{'background-color':'#222'}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span translate>Connecting to {{import.hwWallet}} Wallet...</span> <span translate>Connecting to {{import.hwWallet}} Wallet...</span>
</div> </div>
</div> </div>
@ -51,7 +39,7 @@
</span> </span>
</div> </div>
<div class="row large-12 columns"> <div class="row large-12 columns">
<form name="importForm12" ng-submit="import.importMnemonic(importForm12)" novalidate> <form name="importForm12" ng-submit="import.importMnemonic(importForm12)" novalidate>
<div > <div >
<label for="words"> <label for="words">
@ -84,7 +72,7 @@
<input type="text" class="form-control" name="derivationPath" ng-model="derivationPath"> <input type="text" class="form-control" name="derivationPath" ng-model="derivationPath">
</label> </label>
</div> </div>
<label for="bws" class="oh"> <label for="bws" class="oh">
<span>Wallet Service URL</span> <span>Wallet Service URL</span>
<input type="text" id="bwsurl" name="bwsurl" ng-model="bwsurl"> <input type="text" id="bwsurl" name="bwsurl" ng-model="bwsurl">
@ -107,7 +95,7 @@
</span> </span>
</div> </div>
<div class="row large-12 columns"> <div class="row large-12 columns">
<form name="importForm" ng-submit="import.importBlob(importForm)" novalidate> <form name="importForm" ng-submit="import.importBlob(importForm)" novalidate>
<div ng-show="!index.isSafari && !index.isCordova" class="line-b m10b"> <div ng-show="!index.isSafari && !index.isCordova" class="line-b m10b">
<label for="backupFile"> <label for="backupFile">
@ -172,7 +160,7 @@
{{import.error|translate}} {{import.error|translate}}
</span> </span>
</div> </div>
<div class="row large-12 columns"> <div class="row large-12 columns">
<form name="importForm3" ng-submit="import.importHW(importForm3)" novalidate> <form name="importForm3" ng-submit="import.importHW(importForm3)" novalidate>
<div class="large-12 columns"> <div class="large-12 columns">
@ -232,6 +220,6 @@
</form> </form>
</div> </div>
</div> </div>
</div> </ion-content>
<div class="extra-margin-bottom"></div> <div class="extra-margin-bottom"></div>

View file

@ -1,14 +1,9 @@
<div class="modalMask"> <div class="columns m20t">
</div> <div class="m20t size-14 text-center">
<i class="fi-alert"></i>
<div class="alertModal"> {{msg|translate}}
<div class="columns m20t">
<div class="m20t size-14 text-center">
<i class="fi-alert"></i>
{{index.showAlert.msg|translate}}
</div>
<div class="text-center m20t" ng-click="index.showAlert.close()">
<a class="button outline light-gray round tiny small-4">OK</a>
</div>
</div> </div>
</div> <div class="text-center m20t" ng-click="close()">
<a class="button outline light-gray round tiny small-4">OK</a>
</div>
</div>

View file

@ -1,47 +1,39 @@
<div class="modalMask"></div> <div class="m20t">
<label class="size-14 text-center">
<div class="confirmTxModal" ng-controller="confirmTxController as confirm" ng-init="tx = index.confirmTx.txp"> <span translate>Send bitcoin</span>
</label>
<div class="confirmHead" ng-style="{'background-color':index.backgroundColor}"> </div>
<h1 class="m0 text-center text-white size-18" translate>Send bitcoin</h1> <div class="text-center">
<div class="size-36">{{tx.amountStr}}</div>
<div class="size-12 label gray radius" ng-show="tx.alternativeAmountStr">{{tx.alternativeAmountStr}}</div>
<i class="db fi-arrow-down size-24 m10v"></i>
<div class="payment-proposal-to" ng-click="copyToClipboard(tx.toAddress)">
<i class="fi-bitcoin left m10l"></i>
<contact ng-if="!tx.hasMultiplesOutputs" class="dib enable_text_select ellipsis m5t m5b m15l size-14" address="{{tx.toAddress}}"></contact>
<span ng-if="tx.hasMultiplesOutputs" translate>
Multiple recipients
</span>
</div> </div>
<div class="p10"> <div class="m10t size-12" ng-init="processFee(tx.amount, tx.fee)">
<div class="size-36">{{tx.amountStr}}</div> <div ng-show="!showPercentage" ng-click="showPercentage = true">
<div class="size-12 label gray radius" ng-show="tx.alternativeAmountStr">{{tx.alternativeAmountStr}}</div> <span translate>Fee</span> <span class="tl">({{feeLevel|translate}})</span>:
<i class="db fi-arrow-down size-24 m10v"></i> <span class="text-bold">{{tx.feeStr}}</span>
<div class="payment-proposal-to" ng-click="copyToClipboard(tx.toAddress)"> <span class="label gray radius">{{feeAlternativeStr}}</span>
<i class="fi-bitcoin left m10l"></i>
<contact ng-if="!tx.hasMultiplesOutputs" class="dib enable_text_select ellipsis m5t m5b m15l size-14" address="{{tx.toAddress}}"></contact>
<span ng-if="tx.hasMultiplesOutputs" translate>
Multiple recipients
</span>
</div> </div>
<div class="m10t size-12" ng-init="confirm.processFee(tx.amount, tx.fee)"> <div ng-show="showPercentage" ng-click="showPercentage = false" translate>
<div ng-show="!showPercentage" ng-click="showPercentage = true"> {{feeRateStr}} of the transaction
<span translate>Fee</span> <span class="tl">({{confirm.feeLevel|translate}})</span>:
<span class="text-bold">{{tx.feeStr}}</span>
<span class="label gray radius">{{confirm.feeAlternativeStr}}</span>
</div>
<div ng-show="showPercentage" ng-click="showPercentage = false" translate>
{{confirm.feeRateStr}} of the transaction
</div>
</div> </div>
<div class="row m20t"> </div>
<div class="large-6 medium-6 small-6 columns"> <div class="row m20t dib">
<button <div class="half-row left">
ng-click="confirm.close(index.confirmTx.callback)" <button ng-click="cancel()" class="round outline dark-gray expand">
class="small m10b round outline dark-gray expand" translate> <span class="size-12" translate>Cancel</span>
Cancel </button>
</button> </div>
</div> <div class="half-row left">
<div class="large-6 medium-6 small-6 columns"> <button ng-click="accept()" class="round expand" ng-style="{'background-color':index.backgroundColor}">
<button <span class="size-12" translate>Confirm</span>
ng-click="confirm.accept(index.confirmTx.callback)" </button>
class="small m10b round expand"
ng-style="{'background-color':index.backgroundColor}" translate>
Confirm
</button>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,6 +1,6 @@
<div class="bottom-bar row collapse" ng-show="!index.notAuthorized"> <div class="bottom-bar row collapse p0i" ng-show="!index.notAuthorized">
<div> <div>
<div class="row collapse"> <div class="row collapse p0i">
<div class="medium-4 small-4 columns text-center bottombar-item" <div class="medium-4 small-4 columns text-center bottombar-item"
ng-repeat="item in index.menu"> ng-repeat="item in index.menu">
<span ng-include="'views/includes/menu-item.html'"></span> <span ng-include="'views/includes/menu-item.html'"></span>

View file

@ -0,0 +1,13 @@
<div class="columns m20t">
<label class="size-14 text-center">
<span ng-show="!comment" translate>Enter a new comment</span>
<span ng-show="comment" translate>Edit comment</span>
</label>
<input type="text" ng-model="data.comment" autofocus>
</div>
<div class="small-6 columns">
<button class="round outline dark-gray expand" ng-click="commentPopupClose()" translate>CANCEL</button>
</div>
<div class="small-6 columns">
<button ng-style="{'background-color': index.backgroundColor}" class="round outline expand" ng-click="commentPopupSave()" translate>SAVE</button>
</div>

View file

@ -1,49 +1,42 @@
<div class="modalMask">
</div>
<div ng-controller="passwordController as pass" class="passModal"
ng-class="{'animated bounceInDown':index.askPassword}"
>
<div class="columns m20t"> <div class="columns m20t">
<label class="size-14 text-center" for="password" ng-if="index.askPassword.isSetup"> <label class="size-14 text-center" for="password" ng-if="isSetup">
<span ng-show="!pass.isVerification" translate>Set up a spending password</span> <span ng-show="!isVerification" translate>Set up a spending password</span>
<span ng-show="pass.isVerification" translate>Repeat the spending password</span> <span ng-show="isVerification" translate>Repeat the spending password</span>
</label> </label>
<label class="size-14 text-center" for="password" ng-if="!index.askPassword.isSetup"> <label class="size-14 text-center" for="password" ng-if="!isSetup">
<span translate>Enter your spending password</span> <span translate>Enter your spending password</span>
</label> </label>
<div class="input m20t"> <div class="input m20t">
<input type="password" placeholder="{{'Your spending password'|translate}}" <input type="password" placeholder="{{'Your spending password'|translate}}"
id="passwordInput" name="password" ng-model="password"> id="passwordInput" name="password" ng-model="data.password" autofocus>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="small-6 columns"> <div class="small-6 columns">
<button <button
class="round small-6 columns outline dark-gray expand" class="round small-6 columns outline dark-gray expand"
ng-click="pass.close(index.askPassword.callback)" ng-click="cancel()"
ng-disabled="pass.loading" translate> ng-disabled="loading">
CANCEL <span class="size-12" translate>CANCEL</span>
</button> </button>
</div> </div>
<div class="small-6 columns"> <div class="small-6 columns">
<button class="round expand" <button class="round expand"
ng-click="pass.set(index.askPassword.isSetup, index.askPassword.callback)" ng-click="set()"
ng-disabled="!password || pass.loading" ng-disabled="!data.password || loading"
ng-style="{'background-color':index.backgroundColor}"> ng-style="{'background-color':index.backgroundColor}">
<span ng-if="index.askPassword.isSetup" translate>SET</span> <span ng-if="isSetup" class="size-12" translate>SET</span>
<span ng-if="!index.askPassword.isSetup">OK</span> <span ng-if="!isSetup" class="size-12">OK</span>
</button> </button>
</div> </div>
</div> </div>
<p class="text-warning size-12 columns m20t text-center" ng-show="index.askPassword.isSetup"> <p class="text-warning size-12 columns m20t text-center" ng-show="isSetup">
<i class="fi-alert"></i> <i class="fi-alert"></i>
<span translate ng-show="!pass.error"> Your wallet key will be encrypted. The Spending Password cannot be recovered. Be sure to write it down</span> <span ng-show="!error" translate> Your wallet key will be encrypted. The Spending Password cannot be recovered. Be sure to write it down</span>
<span ng-show="pass.error">{{pass.error|translate}}</span> <span ng-show="error">{{error|translate}}</span>
</p> </p>
</div>

View file

@ -1,51 +1,53 @@
<nav class="sidebar left-off-canvas-menu" ng-controller="sidebarController as sidebar"> <nav class="sidebar" ng-controller="sidebarController as sidebar">
<header> <header>
<logo ng-if="!sidebar.isWindowsPhoneApp" negative="true" width="80"></logo> <logo ng-if="!sidebar.isWindowsPhoneApp" negative="true" width="80"></logo>
<img ng-if="sidebar.isWindowsPhoneApp" src="img/logo-negative.png" alt="Copay" width="80"> <img ng-if="sidebar.isWindowsPhoneApp" src="img/logo-negative.png" alt="Copay" width="80">
<div ng-include="'views/includes/version.html'"></div> <div ng-include="'views/includes/version.html'"></div>
</header> </header>
<ul class="off-canvas-list"> <ion-content overflow-scroll="true">
<li ng-show="sidebar.wallets[0]" <ul class="pr">
ng-repeat="item in sidebar.wallets track by $index" <li ng-show="sidebar.wallets[0]"
ng-class="{'selected': item.id == index.walletId}" ng-repeat="item in sidebar.wallets track by $index"
class="nav-item"> ng-class="{'selected': item.id == index.walletId}"
<a ng-click="sidebar.closeMenu(); sidebar.switchWallet(item.id, index.walletId)" class="oh"> class="nav-item">
<div class="avatar-wallet" <a menu-toggle ng-click="$root.go('walletHome'); sidebar.switchWallet(item.id, index.walletId)" class="oh">
ng-style="{'background-color':item.color}"> <div class="avatar-wallet"
<i class="icon-wallet size-21"></i> ng-style="{'background-color':item.color}">
</div> <i class="icon-wallet size-21"></i>
<div class="name-wallet" ng-class="{'m8t':item.n == 1}">{{item.name || item.id}}</div>
<div class="size-12" ng-show="item.n > 1" translate>{{item.m}}-of-{{item.n}}</div>
</a>
</li>
<li>
<a ng-click="sidebar.closeMenu(); $root.go('add')" class="oh">
<i class="icon-arrow-right3 size-18 right m10t vm"></i>
<i class="fi-plus size-24 icon vm"></i>
<div class="tu text-bold">
<span class="size-12" translate>Add wallet</span>
</div> </div>
<div translate>Create, join or import</div> <div class="name-wallet" ng-class="{'m8t':item.n == 1}">{{item.name || item.id}}</div>
<div class="size-12" ng-show="item.n > 1" translate>{{item.m}}-of-{{item.n}}</div>
</a> </a>
</li> </li>
<li ng-show="!index.isWindowsPhoneApp && index.isComplete && (index.glideraEnabled || index.coinbaseEnabled)"> <li>
<a ng-click="sidebar.closeMenu(); $root.go('buyandsell')" class="oh"> <a menu-toggle ng-click="$root.go('add')" class="oh">
<i class="icon-arrow-right3 size-18 right m10t vm"></i> <i class="icon-arrow-right3 size-18 right m10t vm"></i>
<i class="icon-bank size-24 icon vm"></i> <i class="fi-plus size-24 icon vm"></i>
<div class="tu text-bold m5t"> <div class="tu text-bold">
<span class="size-12" translate>Buy &amp; Sell</span> <span class="size-12" translate>Add wallet</span>
</div> </div>
</a> <div translate>Create, join or import</div>
</li> </a>
<li> </li>
<a ng-click="sidebar.closeMenu(); $root.go('preferencesGlobal')" class="oh"> <li ng-show="!index.isWindowsPhoneApp && index.isComplete && (index.glideraEnabled || index.coinbaseEnabled)">
<i class="icon-arrow-right3 size-18 right m10t vm"></i> <a menu-toggle ng-click="$root.go('buyandsell')" class="oh">
<i class="fi-widget size-24 icon vm"></i> <i class="icon-arrow-right3 size-18 right m10t vm"></i>
<div class="tu text-bold"> <i class="icon-bank size-24 icon vm"></i>
<span class="size-12" translate>Settings</span> <div class="tu text-bold m5t">
</div> <span class="size-12" translate>Buy &amp; Sell</span>
<div translate>Global preferences</div> </div>
</a> </a>
</li> </li>
</ul> <li>
<a menu-toggle ng-click="$root.go('preferencesGlobal')" class="oh">
<i class="icon-arrow-right3 size-18 right m10t vm"></i>
<i class="fi-widget size-24 icon vm"></i>
<div class="tu text-bold">
<span class="size-12" translate>Settings</span>
</div>
<div translate>Global preferences</div>
</a>
</li>
</ul>
</ion-content>
</nav> </nav>

View file

@ -1,9 +1,8 @@
<nav ng-controller="topbarController as topbar" <nav ng-controller="topbarController as topbar"
class="tab-bar" class="tab-bar" ng-style="{'background-color': noColor ? '#4B6178' : index.backgroundColor}">
ng-style="{'background-color': noColor ? '#4B6178' : index.backgroundColor}">
<section class="left-small"> <section class="left-small">
<a id="hamburger" class="p10" ng-show="!goBackToState && !closeToHome && !index.noFocusedWallet" <a id="hamburger" class="p10" ng-show="!goBackToState && !closeToHome && !index.noFocusedWallet"
ng-click="index.openMenu()"><i class="fi-list size-24"></i> on-tap="index.toggleLeftMenu()"><i class="fi-list size-24"></i>
</a> </a>
<a ng-show="goBackToState" <a ng-show="goBackToState"
ng-click="$root.go(goBackToState); goBackToState = null"><i class="icon-arrow-left3 icon-back"></i> ng-click="$root.go(goBackToState); goBackToState = null"><i class="icon-arrow-left3 icon-back"></i>

View file

@ -1,159 +0,0 @@
<div
ng-controller="txController as txc"
class="txModal"
ng-swipe-disable-mouse
ng-swipe-right="txc.cancel()">
<nav class="tab-bar" ng-style="{'background-color':txc.color}">
<section class="left-small">
<a ng-click="txc.cancel()">
<i class="icon-arrow-left3 icon-back"></i>
<span class="text-back" translate>Back</span>
</a>
</section>
<section class="middle tab-bar-section">
<h1 class="title ellipsis" translate>
Transaction
</h1>
</section>
</nav>
<div class="txModal-content">
<div class="header-modal text-center" ng-init="txc.getAlternativeAmount(index.showTx)">
<div ng-show="index.showTx.action != 'invalid'">
<div ng-show="index.showTx.action == 'received'">
<img src="img/icon-receive-history.svg" alt="sync" width="50">
<p class="m0 text-gray size-14" translate>Received</p>
</div>
<div ng-show="index.showTx.action == 'sent'">
<img src="img/icon-sent-history.svg" alt="sync" width="50">
<p class="m0 text-gray size-14" translate>Sent</p>
</div>
<div ng-show="index.showTx.action == 'moved'">
<img src="img/icon-moved.svg" alt="sync" width="50">
<p class="m0 text-gray size-14" translate>Moved</p>
</div>
<div class="size-36" ng-click="txc.copyToClipboard(index.showTx.amountStr)">
<span ng-if="index.showTx.action == 'received'">+</span>
<span ng-if="index.showTx.action == 'sent'">-</span>
<span class="enable_text_select">{{index.showTx.amountStr}}</span>
</div>
<div class="alternative-amount" ng-click="showRate=!showRate" ng-init="showRate = false">
<span class="label gray radius" ng-show="!showRate && alternativeAmountStr">
{{alternativeAmountStr}}
</span>
<span class="size-12" ng-show="showRate && alternativeAmountStr">
{{rateStr}} ({{rateDate | amDateFormat:'MM/DD/YYYY HH:mm a'}})
</span>
</div>
</div>
<div ng-show="index.showTx.action == 'invalid'">
-
</div>
</div>
<h4 class="title m0" translate>Details</h4>
<ul class="no-bullet size-14 m0">
<li ng-if="!index.showTx.hasMultiplesOutputs && index.showTx.addressTo && index.showTx.addressTo != 'N/A'" class="line-b p10 oh"
ng-click="txc.copyToClipboard(index.showTx.addressTo)">
<span class="text-gray" translate>To</span>
<span class="right">
<span ng-if="index.showTx.merchant">
<span ng-show="index.showTx.merchant.pr.ca"><i class="fi-lock color-greeni"></i> {{index.showTx.merchant.domain}}</span>
<span ng-show="!index.showTx.merchant.pr.ca"><i class="fi-unlock color-yellowi"></i> {{index.showTx.merchant.domain}}</span>
</span>
<span ng-if="!index.showTx.merchant">
<span ng-show="index.showTx.labelTo">{{index.showTx.labelTo}}</span>
<contact ng-show="!index.showTx.labelTo" class="enable_text_select" address="{{index.showTx.addressTo}}"></contact>
</span>
</span>
</li>
<li ng-show="index.showTx.hasMultiplesOutputs" class="line-b p10 oh"
ng-click="showMultiplesOutputs = !showMultiplesOutputs">
<span class="text-gray" translate>Recipients</span>
<span class="right">{{index.showTx.recipientCount}}
<i ng-show="showMultiplesOutputs" class="icon-arrow-up3 size-24"></i>
<i ng-show="!showMultiplesOutputs" class="icon-arrow-down3 size-24"></i>
</span>
</li>
<div class="line-b" ng-show="index.showTx.hasMultiplesOutputs && showMultiplesOutputs"
ng-repeat="output in index.showTx.outputs"
ng-include="'views/includes/output.html'">
</div>
<li ng-if="index.showTx.action == 'invalid'" class="line-b p10 oh">
<span class="right" translate>
This transaction has become invalid; possibly due to a double spend attempt.
</span>
<li ng-if="index.showTx.time" class="line-b p10 oh">
<span class="text-gray" translate>Date</span>
<span class="right enable_text_select">
<time>{{ index.showTx.time * 1000 | amDateFormat:'MM/DD/YYYY HH:mm a'}}</time>
<time>({{ index.showTx.time * 1000 | amTimeAgo}})</time>
</span>
</li>
<li class="line-b p10" ng-show="index.showTx.action != 'received'"
ng-click="txc.copyToClipboard(index.showTx.feeStr)">
<span class="text-gray" translate>Fee</span>
<span class="right enable_text_select">{{index.showTx.feeStr}}</span>
</li>
<li class="line-b p10 oh" ng-if="index.showTx.message && index.showTx.action != 'received'"
ng-click="txc.copyToClipboard(index.showTx.message)">
<span class="text-gray" translate>Note</span>
<span class="right enable_text_select">{{index.showTx.message}}</span>
</li>
<li ng-if="index.showTx.merchant" class="line-b p10 oh"
ng-click="txc.copyToClipboard(index.showTx.merchant.pr.pd.memo)">
<span class="text-gray" translate>Merchant message</span>
<span class="right enable_text_select">
{{index.showTx.merchant.pr.pd.memo}}
</span>
</li>
<li ng-if="index.showTx.time" class="line-b p10 oh">
<span class="text-gray" translate>Confirmations</span>
<span class="right" >
<span class="text-warning" ng-show="!index.showTx.confirmations || index.showTx.confirmations == 0" translate>
Unconfirmed
</span>
<span class="label gray radius" ng-show="index.showTx.confirmations>0 && !index.showTx.safeConfirmed">
{{index.showTx.confirmations}}
</span>
<span class="label gray radius" ng-show="index.showTx.safeConfirmed">
{{index.showTx.safeConfirmed}}
</span>
</span>
</li>
</ul>
<div ng-if="index.showTx.actions[0] && txc.isShared">
<h4 class="title m0" translate>Participants</h4>
<ul class="no-bullet size-14 m0">
<li class="line-b p10 text-gray" ng-repeat="c in index.showTx.actions">
<i class="icon-contact size-24"></i>
<span class="right">
<i ng-if="c.type == 'reject'" class="fi-x icon-sign x db"></i>
<i ng-if="c.type == 'accept'" class="fi-check icon-sign check db"></i>
</span>
{{c.copayerName}} <span ng-if="c.copayerId == txc.copayerId">({{'Me'|translate}})</span>
</li>
</ul>
</div>
<div ng-show="index.showTx.txid" class="tx-details-blockchain">
<div class="text-center m20t">
<button class="button outline round dark-gray tiny" ng-click="$root.openExternalLink('https://' +
(txc.getShortNetworkName() == 'test' ? 'test-' : '') + 'insight.bitpay.com/tx/' + index.showTx.txid)">
<span class="text-gray" translate>See it on the blockchain</span>
</button>
</div>
</div>
<div class="extra-margin-bottom"></div>
</div>
</div>

View file

@ -17,4 +17,3 @@ src="img/icon-ledger-white.svg">
<img style="height:0.6em" class="animated flash infinite" ng-show="index.loadingWallet || <img style="height:0.6em" class="animated flash infinite" ng-show="index.loadingWallet ||
index.updatingTxHistory" src="img/icon-sync-white.svg"> index.updatingTxHistory" src="img/icon-sync-white.svg">

View file

@ -5,42 +5,30 @@
</div> </div>
<div class="content p20v" ng-controller="joinController as join"> <ion-content overflow-scroll="true" class="content p20v" ng-controller="joinController as join">
<div class="onGoingProcess" ng-show="join.loading && !join.hwWallet"> <div class="onGoingProcess" ng-show="join.loading && !join.hwWallet">
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}"> <div class="onGoingProcess-content" ng-style="{'background-color':'#222'}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span translate>Joining Wallet...</span> <span translate>Joining Wallet...</span>
</div> </div>
</div> </div>
<div class="onGoingProcess" ng-show="join.hwWallet"> <div class="onGoingProcess" ng-show="join.hwWallet">
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}" style="max-height:6.5em"> <div class="onGoingProcess-content" ng-style="{'background-color':'#222'}" style="max-height:6.5em">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span translate>Connecting to {{join.hwWallet}} Wallet...</span> <span translate>Connecting to {{join.hwWallet}} Wallet...</span>
<span ng-show="join.hwWallet=='Trezor'" translate>Please accept to export two public keys from the Trezor app</span> <span ng-show="join.hwWallet=='Trezor'" translate>Please accept to export two public keys from the Trezor app</span>
</div> </div>
</div> </div>
<form name="joinForm" ng-submit="join.join(joinForm)" novalidate> <form name="joinForm" ng-submit="join.join(joinForm)" novalidate>
<div class="box-notification m20b" ng-show="join.error "> <div class="box-notification m20b" ng-show="join.error">
<span class="text-warning"> <span class="text-warning">
{{join.error|translate}} {{join.error|translate}}
</span> </span>
</div> </div>
<div class="row"> <div class="row">
<div class="large-12 columns"> <div class="large-12 columns">
<div> <div>
<label><span translate>Your nickname</span> <label><span translate>Your nickname</span>
@ -153,6 +141,6 @@
</div> <!-- large-12 columns --> </div> <!-- large-12 columns -->
</div> <!-- row --> </div> <!-- row -->
</form> </form>
</div> </ion-content>
<div class="extra-margin-bottom"></div> <div class="extra-margin-bottom"></div>

View file

@ -0,0 +1,135 @@
<ion-modal-view ng-controller="addressbookController as addressbookC">
<div ng-init="wallets[0] ? selectedWalletsOpt = true : selectedWalletsOpt = false; checkClipboard()">
<ion-content ng-style="{'background-color': '#f6f7f9'}">
<nav class="tab-bar" ng-style="{'background-color':color}">
<section class="left-small">
<a ng-show="!editAddressbook && !addAddressbookEntry" ng-click="cancel()" class="p10">
<span class="text-close" translate>Close</span>
</a>
</section>
<section class="middle tab-bar-section">
<h1 class="title ellipsis">
{{walletName}}
</h1>
</section>
<section class="right-small" ng-show="!selectedWalletsOpt" ng-click="toggleEditAddressbook()">
<a ng-show="!editAddressbook && !addAddressbookEntry" href class="p10">
<span class="text-close" translate>Edit</span>
</a>
<a ng-show="editAddressbook && !addAddressbookEntry" href class="p10">
<span class="text-close" translate>Done</span>
</a>
</section>
</nav>
<div class="modal-content p20b">
<div class="create-tab small-only-text-center" ng-show="!editAddressbook && !addAddressbookEntry">
<div class="row">
<div class="tab-container small-6 medium-3 large-2"
ng-class="{'selected':selectedWalletsOpt}"
ng-style="{'border-color':selectedWalletsOpt ? color : 'inherit'}"
ng-click="selectedWalletsOpt = true">
<a href ng-style="{'color':selectedWalletsOpt ? color : 'inherit'}" translate> My wallets</a>
</div>
<div class="tab-container small-6 medium-3 large-2"
ng-class="{'selected':!selectedWalletsOpt}"
ng-style="{'border-color':!selectedWalletsOpt ? color : 'inherit'}"
ng-click="selectedWalletsOpt = false">
<a href ng-style="{'color':!selectedWalletsOpt ? color : 'inherit'}" translate>My contacts</a>
</div>
</div>
</div>
<div ng-show="selectedWalletsOpt">
<div class="onGoingProcess" ng-if="gettingAddress">
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}">
<ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<span translate> Getting address for wallet {{selectedWalletName}} ...</span>
</div>
</div>
<div ng-if="!gettingAddress">
<ul class="no-bullet">
<li class="line-b" ng-repeat="w in wallets">
<a ng-click="selectWallet(w.id, w.name)" class="db oh">
<div class="avatar-wallet"
ng-style="{'background-color':w.color}">
<i class="icon-wallet size-21"></i>
</div>
<div class="ellipsis name-wallet text-bold">{{w.name || w.id}}
<span class="has-error right text-light size-12" ng-show="errorSelectedWallet[w.id]">
<i class="icon-close-circle size-14"></i>
<span class="vm">{{errorSelectedWallet[w.id] }}</span>
</span>
</div>
<div class="size-12">{{w.m}} of {{w.n}}
<span ng-show="w.network=='testnet'">[Testnet]</span>
</div>
</a>
</li>
</ul>
</div>
</div>
<div ng-show="!selectedWalletsOpt" class="m20b">
<ul ng-show="!addAddressbookEntry" class="no-bullet m0" ng-init="list()">
<li class="p10 line-b" ng-repeat="(addr, label) in list">
<a ng-show="selectedAddressbook[addr]" class="removeAddressbook" ng-click="remove(addr)" translate>Remove</a>
<a ng-show="editAddressbook" class="selectAddressbook" ng-click="toggleSelectAddressbook(addr)">
<i class="fi-trash"></i></a>
<div ng-click="selectAddressbook(addr)">
<i class="icon-contact left size-42 m10r text-gray"></i>
<div class="">
<span>{{label}}</span>
<div class="size-12 text-gray ellipsis">{{addr}}</div>
</div>
</div>
</li>
<li class="p10" ng-show="!editAddressbook">
<a ng-click="toggleAddAddressbookEntry()" class="p0i">
<i class="fi-plus size-24 m20r lh icon"></i>
<span class="size-12 tu text-bold" translate>Add a new entry</span>
</a>
</li>
</ul>
<div ng-show="addAddressbookEntry">
<h4 translate>Add a new entry</h4>
<form name="addressbookForm" class="p10" no-validate>
<div class="text-warning size-12 m10b" ng-show="error">{{error|translate}}</div>
<span ng-hide="addressbookForm.address.$pristine">
<span class="has-error right size-12" ng-show="addressbookForm.address.$invalid && addressbook.address">
<i class="icon-close-circle size-14"></i>
<span class="vm" translate>Not valid</span>
</span>
<small class="right text-primary" ng-show="!addressbookForm.address.$invalid">
<i class="icon-checkmark-circle size-14"></i>
</small>
</span>
<label translate>Address</label>
<div class="input">
<input type="text" id="address" name="address" ng-model="addressbook.address" valid-address required>
<div class="qr-scanner-input">
<qr-scanner on-scan="onQrCodeScanned(data, addressbookForm)" before-scan="beforeQrCodeScann()"></qr-scanner>
</div>
</div>
<label translate>Label</label>
<input type="text" id="label" name="label" ng-model="addressbook.label" required>
<div class="row">
<div class="columns large-6 medium-6 small-6">
<input type="button" class="button expand outline dark-gray round" ng-click="toggleAddAddressbookEntry()" value="{{'Cancel'|translate}}">
</div>
<div class="columns large-6 medium-6 small-6">
<input type="submit" class="button expand round black" ng-click="add(addressbook)" value="{{'Save'|translate}}" ng-disabled="!addressbookForm.$valid">
</div>
</div>
</form>
</div>
</div>
</div>
</ion-content>
</div>
</ion-modal-view>

View file

@ -1,87 +1,91 @@
<nav class="tab-bar" ng-style="{'background-color':color}"> <ion-modal-view ng-controller="customAmountController">
<section class="left-small"> <ion-content>
<a ng-click="cancel()" class="p10"> <nav class="tab-bar" ng-style="{'background-color':color}">
<span class="text-close" translate>Close</span> <section class="left-small">
</a> <a ng-click="cancel()" class="p10">
</section> <span class="text-close" translate>Close</span>
<section class="middle tab-bar-section"> </a>
<h1 class="title ellipsis" translate> </section>
Request a specific amount
</h1>
</section>
</nav>
<div class="modal-content fix-modals-touch"> <section class="middle tab-bar-section">
<div class="m20b" ng-show="customizedAmountBtc"> <h1 class="title ellipsis" translate>Request a specific amount</h1>
<h4 class="title m0" translate>QR Code</h4> </section>
<ul class="no-bullet size-14 m0"> </nav>
<li class="line-b p10 oh text-center">
<qrcode size="220" data="bitcoin:{{addr + '?amount=' + customizedAmountBtc}}"></qrcode> <div class="modal-content fix-modals-touch">
<div class="m10t text-center" ng-show="isCordova"> <div class="m20b" ng-show="customizedAmountBtc">
<span class="button outline dark-gray tiny round" <h4 class="title m0" translate>QR Code</h4>
ng-click="shareAddress('bitcoin:' + addr + '?amount=' + customizedAmountBtc)"> <ul class="no-bullet size-14 m0">
<i class="fi-share"></i> <li class="line-b p10 oh text-center">
<span translate>Share address</span> <qrcode size="220" data="bitcoin:{{addr + '?amount=' + customizedAmountBtc}}"></qrcode>
</span> <div class="m10t text-center" ng-show="isCordova">
<span class="button outline dark-gray tiny round"
ng-click="shareAddress('bitcoin:' + addr + '?amount=' + customizedAmountBtc)">
<i class="fi-share"></i>
<span translate>Share address</span>
</span>
</div>
</li>
</ul>
<h4 class="title m0" translate>Details</h4>
<ul class="no-bullet size-14 m0">
<li class="line-b p10 oh">
<span class="text-gray" translate>Address</span>:
<span class="right">
<span class="text-gray enable_text_select">{{addr}}</span>
</span>
</li>
<li class="line-b p10 oh">
<span class="text-gray" translate>Amount</span>:
<span class="right">
{{customizedAmountUnit}}
<span class="label gray radius">{{customizedAlternativeUnit}}</span>
</span>
</li>
</ul>
</div> </div>
</li>
</ul>
<h4 class="title m0" translate>Details</h4>
<ul class="no-bullet size-14 m0">
<li class="line-b p10 oh">
<span class="text-gray" translate>Address</span>:
<span class="right">
<span class="text-gray enable_text_select">{{addr}}</span>
</span>
</li>
<li class="line-b p10 oh">
<span class="text-gray" translate>Amount</span>:
<span class="right">
{{customizedAmountUnit}}
<span class="label gray radius">{{customizedAlternativeUnit}}</span>
</span>
</li>
</ul>
</div>
<div ng-show="!customizedAmountBtc" class="row m20t">
<div class="large-12 large-centered columns">
<form name="amountForm" ng-submit="submitForm(amountForm)" novalidate>
<div class="right" ng-hide="amountForm.amount.$pristine && !amountForm.amount.$modelValue ">
<span class="has-error right size-12" ng-if="amountForm.amount.$invalid">
<i class="icon-close-circle size-14"></i>
<span clas="vm" translate>Not valid</span>
</span>
<small class="text-primary right" ng-if="!amountForm.amount.$invalid">
<i class="icon-checkmark-circle size-14"></i>
</small>
</div>
<div ng-if="!showAlternative">
<label for="amount">
<span translate>Amount</span>
</label>
<div class="input">
<input type="number" id="amount" name="amount" ng-attr-placeholder="{{'Amount in'|translate}} {{unitName}}"
ng-model="_customAmount" ng-minlength="0.00000001" ng-maxlength="10000000000" valid-amount required autocomplete="off">
<input type="number" id="alternative" name="alternative" ng-model="_customAlternative" style="display:none">
<a class="postfix button" ng-style="{'background-color':color}" ng-click="toggleAlternative()">{{unitName}}</a>
</div>
</div>
<div ng-if="showAlternative">
<label for="alternative"><span translate>Amount</span> [{{ alternativeIsoCode }}]
</label>
<div class="input">
<input type="number" id="alternative" name="alternative" ng-attr-placeholder="{{'Amount in'|translate}} {{alternativeName}}"
ng-model="_customAlternative" required autocomplete="off" required>
<input type="number" id="amount" name="amount" ng-model="_customAmount" style="display:none">
<a class="postfix button black" ng-click="toggleAlternative()"> {{ alternativeIsoCode }}</a>
</div>
</div>
<button type="submit" class="button black round expand" ng-disabled="amountForm.$invalid" ng-style="{'background-color':color}" translate>
Generate QR Code
</button>
</form>
</div>
</div>
<div class="extra-margin-bottom"></div> <div ng-show="!customizedAmountBtc" class="row m20t">
</div> <div class="large-12 large-centered columns">
<form name="amountForm" ng-submit="submitForm(amountForm)" novalidate>
<div class="right" ng-hide="amountForm.amount.$pristine && !amountForm.amount.$modelValue ">
<span class="has-error right size-12" ng-if="amountForm.amount.$invalid">
<i class="icon-close-circle size-14"></i>
<span clas="vm" translate>Not valid</span>
</span>
<small class="text-primary right" ng-if="!amountForm.amount.$invalid">
<i class="icon-checkmark-circle size-14"></i>
</small>
</div>
<div ng-if="!showAlternative">
<label for="amount">
<span translate>Amount</span>
</label>
<div class="input">
<input type="number" id="amount" name="amount" ng-attr-placeholder="{{'Amount in'|translate}} {{unitName}}"
ng-model="_customAmount" ng-minlength="0.00000001" ng-maxlength="10000000000" valid-amount required autocomplete="off">
<input type="number" id="alternative" name="alternative" ng-model="_customAlternative" style="display:none">
<a class="postfix button" ng-style="{'background-color':color}" ng-click="toggleAlternative()">{{unitName}}</a>
</div>
</div>
<div ng-if="showAlternative">
<label for="alternative"><span translate>Amount</span> [{{ alternativeIsoCode }}]
</label>
<div class="input">
<input type="number" id="alternative" name="alternative" ng-attr-placeholder="{{'Amount in'|translate}} {{alternativeName}}"
ng-model="_customAlternative" required autocomplete="off" required>
<input type="number" id="amount" name="amount" ng-model="_customAmount" style="display:none">
<a class="postfix button black" ng-click="toggleAlternative()"> {{ alternativeIsoCode }}</a>
</div>
</div>
<button type="submit" class="button black round expand" ng-disabled="amountForm.$invalid" ng-style="{'background-color':color}" translate>
Generate QR Code
</button>
</form>
</div>
</div>
</div>
</ion-content>
</ion-view-modal>

View file

@ -1,156 +0,0 @@
<div ng-init="wallets[0] ? selectedWalletsOpt = true : selectedWalletsOpt = false; checkClipboard()">
<nav class="tab-bar" ng-style="{'background-color':color}">
<section class="left-small">
<a ng-show="!editAddressbook && !addAddressbookEntry" ng-click="cancel()" class="p10">
<span class="text-close" translate>Close</span>
</a>
</section>
<section class="middle tab-bar-section">
<h1 class="title ellipsis">
{{walletName}}
</h1>
</section>
<section class="right-small" ng-show="!selectedWalletsOpt" ng-click="toggleEditAddressbook()">
<a ng-show="!editAddressbook && !addAddressbookEntry" href class="p10">
<span class="text-close" translate>Edit</span>
</a>
<a ng-show="editAddressbook && !addAddressbookEntry" href class="p10">
<span class="text-close" translate>Done</span>
</a>
</section>
</nav>
<div class="modal-content fix-modals-touch p20b">
<div class="create-tab small-only-text-center" ng-show="!editAddressbook && !addAddressbookEntry">
<div class="row">
<div class="tab-container small-6 medium-3 large-2"
ng-class="{'selected':selectedWalletsOpt}"
ng-style="{'border-color':selectedWalletsOpt ? color : 'inherit'}"
ng-click="selectedWalletsOpt = true">
<a href
ng-style="{'color':selectedWalletsOpt ? color : 'inherit'}" translate> My wallets</a>
</div>
<div class="tab-container small-6 medium-3 large-2"
ng-class="{'selected':!selectedWalletsOpt}"
ng-style="{'border-color':!selectedWalletsOpt ? color : 'inherit'}"
ng-click="selectedWalletsOpt = false">
<a href
ng-style="{'color':!selectedWalletsOpt ? color : 'inherit'}" translate>My contacts</a>
</div>
</div>
</div>
<div ng-show="selectedWalletsOpt">
<div class="onGoingProcess" ng-if="gettingAddress">
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}">
<div class="spinner">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span translate> Getting address for wallet {{selectedWalletName}} ...</span>
</div>
</div>
<div ng-if="!gettingAddress">
<ul class="no-bullet">
<li class="line-b" ng-repeat="w in wallets">
<a ng-click="selectWallet(w.id, w.name)" class="db oh">
<div class="avatar-wallet"
ng-style="{'background-color':w.color}">
<i class="icon-wallet size-21"></i>
</div>
<div class="ellipsis name-wallet text-bold">{{w.name || w.id}}
<span class="has-error right text-light size-12" ng-show="errorSelectedWallet[w.id]">
<i class="icon-close-circle size-14"></i>
<span class="vm">{{errorSelectedWallet[w.id] }}</span>
</span>
</div>
<div class="size-12">{{w.m}} of {{w.n}}
<span ng-show="w.network=='testnet'">[Testnet]</span>
</div>
</a>
</li>
</ul>
</div>
</div>
<div ng-show="!selectedWalletsOpt" class="m20b">
<ul ng-show="!addAddressbookEntry" class="no-bullet m0" ng-init="list()">
<li class="p10 line-b" ng-repeat="(addr, label) in list">
<a ng-show="selectedAddressbook[addr]"
class="removeAddressbook"
ng-click="remove(addr)" translate>Remove</a>
<a class="selectAddressbook"
ng-show="editAddressbook"
ng-click="toggleSelectAddressbook(addr)">
<i class="fi-trash"></i></a>
<div ng-click="selectAddressbook(addr)">
<i class="icon-contact left size-42 m10r text-gray"></i>
<div class="">
<span>{{label}}</span>
<div class="size-12 text-gray ellipsis">{{addr}}</div>
</div>
</div>
</li>
<li class="p10" ng-show="!editAddressbook">
<a ng-click="toggleAddAddressbookEntry()" class="p0i">
<i class="fi-plus size-24 m20r lh icon"></i>
<span class="size-12 tu text-bold" translate>Add a new entry</span>
<span class="size-10" ng-show="!list[newAddress] && newAddress">({{newAddress}})</span>
</a>
</li>
</ul>
<div ng-show="addAddressbookEntry">
<h4 translate>Add a new entry</h4>
<form name="addressbookForm" class="p10" no-validate>
<div class="text-warning size-12 m10b" ng-show="error">{{error|translate}}</div>
<span ng-hide="addressbookForm.address.$pristine">
<span class="has-error right size-12" ng-show="addressbookForm.address.$invalid && addressbook.address">
<i class="icon-close-circle size-14"></i>
<span class="vm" translate>Not valid</span>
</span>
<small class="right text-primary" ng-show="!addressbookForm.address.$invalid">
<i class="icon-checkmark-circle size-14"></i>
</small>
</span>
<label translate>Address</label>
<div class="input">
<input type="text" id="address" name="address" ng-model="addressbook.address" valid-address required>
<div class="qr-scanner-input">
<qr-scanner on-scan="onQrCodeScanned(data, addressbookForm)" before-scan="beforeQrCodeScann()"></qr-scanner>
</div>
</div>
<label translate>Label</label>
<input type="text" id="label" name="label" ng-model="addressbook.label" required>
<div class="row">
<div class="columns large-6 medium-6 small-6">
<input type="button"
class="button expand outline dark-gray round"
ng-click="newAddress = ''; toggleAddAddressbookEntry()"
value="{{'Cancel'|translate}}">
</div>
<div class="columns large-6 medium-6 small-6">
<input type="submit"
class="button expand round black"
value="{{'Save'|translate}}"
ng-disabled="!addressbookForm.$valid"
ng-click="newAddress = ''; add(addressbook)">
</div>
</div>
</form>
</div>
</div>
<div class="extra-margin-bottom"></div>
</div>
</div>

View file

@ -1,63 +1,66 @@
<nav class="tab-bar" ng-style="{'background-color':color}"> <ion-modal-view ng-controller="payproController as payproC">
<section class="left-small"> <nav class="tab-bar" ng-style="{'background-color':color}">
<a ng-click="cancel()" class="p10"> <section class="left-small">
<span class="text-close" translate>Close</span> <a ng-click="cancel()" class="p10">
</a> <span class="text-close" translate>Close</span>
</section> </a>
<section class="middle tab-bar-section"> </section>
<h1 class="title ellipsis" translate>
Payment request
</h1>
</section>
</nav>
<div class="modal-content fix-modals-touch"> <section class="middle tab-bar-section">
<div class="header-modal text-center"> <h1 class="title" translate>Payment request</h1>
<div class="size-42"> </section>
{{unitTotal}} {{unitName}} </nav>
</div>
<div class="size-18 m5t text-gray" ng-show="alternative">
{{ alternative }} {{ alternativeIsoCode }}
</div>
</div>
<h4 class="title m0" translate>Details</h4> <ion-content ng-style="{'background-color': '#F6F7F9'}">
<ul class="no-bullet size-14 m0"> <div class="modal-content">
<li class="line-b p10 oh"> <div class="header-modal text-center p50t">
<span class="text-gray" translate>Pay To</span> <div class="size-42">
<span class="right enable_text_select">{{paypro.domain}}</span> {{unitTotal}} {{unitName}}
</li> </div>
<li class="line-b p10 oh" ng-if="paypro.toAddress"> <div class="size-18 m5t text-gray" ng-show="alternative">
<span class="text-gray" translate>Address</span> {{ alternative }} {{ alternativeIsoCode }}
<span class="right enable_text_select">{{paypro.toAddress}}</span> </div>
</li> </div>
<li class="line-b p10 oh">
<span class="text-gray" translate>Certified by</span> <h4 class="title m10l" translate>Details</h4>
<span class="right text-right"> <ul class="no-bullet size-14 m10t">
<span ng-show="paypro.caTrusted"> <li class="line-b p10 oh">
<i class="fi-lock color-greeni"></i> <span class="text-gray" translate>Pay To</span>
{{paypro.caName}}<br> <span class="right enable_text_select">{{paypro.domain}}</span>
<span translate>(Trusted)</span> </li>
</span> <li class="line-b p10 oh" ng-if="paypro.toAddress">
<span ng-show="!paypro.caTrusted"> <span class="text-gray" translate>Address</span>
<span ng-show="paypro.selfSigned"> <span class="right enable_text_select">{{paypro.toAddress}}</span>
<i class="fi-unlock color-yellowi"></i> <span translate>Self-signed Certificate</span> </li>
<li class="line-b p10 oh">
<span class="text-gray" translate>Certified by</span>
<span class="right text-right">
<span ng-show="paypro.caTrusted">
<i class="fi-lock color-greeni"></i>
{{paypro.caName}}<br>
<span translate>(Trusted)</span>
</span>
<span ng-show="!paypro.caTrusted">
<span ng-show="paypro.selfSigned">
<i class="fi-unlock color-yellowi"></i> <span translate>Self-signed Certificate</span>
</span>
<span ng-show="!paypro.selfSigned">
<i class="fi-unlock color-yellowi"></i>{{paypro.caName}}<br>
<span translate>WARNING: UNTRUSTED CERTIFICATE</span>
</span>
</span>
</span> </span>
<span ng-show="!paypro.selfSigned"> </li>
<i class="fi-unlock color-yellowi"></i>{{paypro.caName}}<br> <li class="line-b p10 oh" ng-if="paypro.memo">
<span translate>WARNING: UNTRUSTED CERTIFICATE</span> <span class="text-gray" translate>Memo</span>
</span> <span class="right">{{paypro.memo}}</span>
</span> </li>
</span> <li class="line-b p10 oh" ng-if="paypro.expires">
</li> <span class="text-gray" translate>Expires</span>
<li class="line-b p10 oh" ng-if="paypro.memo"> <span class="right">{{paypro.expires * 1000 | amTimeAgo }}</span>
<span class="text-gray" translate>Memo</span> </li>
<span class="right">{{paypro.memo}}</span> </ul>
</li> </div>
<li class="line-b p10 oh" ng-if="paypro.expires"> <div class="extra-margin-bottom"></div>
<span class="text-gray" translate>Expires</span> </ion-content>
<span class="right">{{paypro.expires * 1000 | amTimeAgo }}</span> </ion-modal-view>
</li>
</ul>
<div class="extra-margin-bottom"></div>
</div>

View file

@ -0,0 +1,82 @@
<ion-modal-view ng-controller="searchController">
<ion-content>
<nav class="tab-bar" ng-style="{'background-color':color}">
<section class="left-small">
<a ng-click="cancel(); index.cancelSearch()" class="p10">
<span class="text-close" translate>Close</span>
</a>
</section>
<section class="middle tab-bar-section">
<h1 class="title ellipsis" translate>Search Transactions</h1>
</section>
</nav>
<div class="row searchBar searchLabel">
<i class="fi-magnifying-glass size-14"></i>
<form>
<input name="search" type="search" ng-model="search" ng-init="search = ''" ng-change="index.updateSearchInput(search)"
placeholder="{{'Search transactions' | translate}}">
</input>
</form>
</div>
<div ng-repeat="btx in index.txHistorySearchResults track by btx.txid"
ng-click="home.openTxModal(btx)"
class="row collapse last-transactions-content">
<div class="large-6 medium-6 small-6 columns size-14">
<div class="m10r left">
<img src="img/icon-receive-history.svg" alt="sync" width="40" ng-show="btx.action == 'received'">
<img src="img/icon-sent-history.svg" alt="sync" width="40" ng-show="btx.action == 'sent'">
<img src="img/icon-moved.svg" alt="sync" width="40" ng-show="btx.action == 'moved'">
</div>
<div class="m10t">
<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 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 ng-show="btx.action == 'moved'" translate>Moved</span>
<span class="label tu warning radius" ng-show="btx.action == 'invalid'" translate>Invalid</span>
</div>
</div>
<div class="large-5 medium-5 small-5 columns text-right" >
<span class="size-16" ng-class="{'text-bold': btx.recent}">
<span ng-if="btx.action == 'received'">+</span>
<span ng-if="btx.action == 'sent'">-</span>
<span class="size-12" ng-if="btx.action == 'invalid'" translate>
(possible double spend)
</span>
<span ng-if="btx.action != 'invalid'">
{{btx.amountStr}}
</span>
</span>
<div class="size-12 text-gray">
<time ng-if="btx.time">{{btx.time * 1000 | amTimeAgo}}</time>
<span translate class="text-warning"
ng-show="!btx.time && (!btx.confirmations || btx.confirmations == 0)">
Unconfirmed
</span>
</div>
</div>
<div class="large-1 medium-1 small-1 columns text-right m10t">
<i class="icon-arrow-right3 size-18"></i>
</div>
</div>
<ion-infinite-scroll
ng-if="index.historyShowMore && index.isSearching"
on-infinite="index.showMore()"
distance="1%">
</ion-infinite-scroll>
</ion-content>
</ion-modal-view>

View file

@ -1,148 +1,166 @@
<nav class="tab-bar" ng-style="{'background-color':color}"> <ion-modal-view ng-controller="txDetailsController">
<section class="left-small"> <nav class="tab-bar" ng-style="{'background-color':color}">
<a ng-click="cancel()"> <section class="left-small">
<i class="icon-arrow-left3 icon-back"></i> <a ng-click="cancel()" class="p10">
<span class="text-back" translate>Back</span> <span class="text-close" translate>Close</span>
</a> </a>
</section> </section>
<section class="middle tab-bar-section"> <section class="middle tab-bar-section">
<h1 class="title ellipsis" translate> <h1 class="title ellipsis" translate>Transaction</h1>
Transaction </section>
</h1> </nav>
</section>
</nav>
<div class="modal-content fix-modals-touch" <ion-content ng-style="{'background-color': '#F6F7F9'}">
ng-swipe-disable-mouse <div class="modal-content">
ng-swipe-right="cancel()"> <div class="header-modal text-center" ng-init="getAlternativeAmount(btx)">
<div class="header-modal text-center" ng-init="getAlternativeAmount()"> <div ng-show="btx.action != 'invalid'">
<div ng-show="btx.action != 'invalid'"> <div ng-show="btx.action == 'received'">
<div ng-show="btx.action == 'received'"> <img src="img/icon-receive-history.svg" alt="sync" width="50">
<img src="img/icon-receive-history.svg" alt="sync" width="50"> <p class="m0 text-gray size-14" translate>Received</p>
<p class="m0 text-gray size-14" translate>Received</p> </div>
<div ng-show="btx.action == 'sent'">
<img src="img/icon-sent-history.svg" alt="sync" width="50">
<p class="m0 text-gray size-14" translate>Sent</p>
</div>
<div ng-show="btx.action == 'moved'">
<img src="img/icon-moved.svg" alt="sync" width="50">
<p class="m0 text-gray size-14" translate>Moved</p>
</div>
<div class="size-36" ng-click="copyToClipboard(btx.amountStr)">
<span class="enable_text_select">{{btx.amountStr}}</span>
</div>
<div class="alternative-amount" ng-click="showRate=!showRate" ng-init="showRate = false">
<span class="label gray radius" ng-show="!showRate && alternativeAmountStr">
{{alternativeAmountStr}}
</span>
<span class="size-12" ng-show="showRate && alternativeAmountStr">
{{rateStr}} ({{rateDate | amDateFormat:'MM/DD/YYYY HH:mm a'}})
</span>
</div>
</div>
<div ng-show="btx.action == 'invalid'">
-
</div>
</div> </div>
<div ng-show="btx.action == 'sent'">
<img src="img/icon-sent-history.svg" alt="sync" width="50"> <h4 class="title m0" translate>Details</h4>
<p class="m0 text-gray size-14" translate>Sent</p>
<ul class="no-bullet size-14 m0">
<li ng-if="!btx.hasMultiplesOutputs && btx.addressTo && btx.addressTo != 'N/A'" class="line-b p10 oh"
ng-click="copyToClipboard(btx.addressTo)">
<span class="text-gray" translate>To</span>
<span class="right">
<span ng-if="btx.merchant">
<span ng-show="btx.merchant.pr.ca"><i class="fi-lock color-greeni"></i> {{btx.merchant.domain}}</span>
<span ng-show="!btx.merchant.pr.ca"><i class="fi-unlock color-yellowi"></i> {{btx.merchant.domain}}</span>
</span>
<span ng-if="!btx.merchant">
<span ng-show="btx.labelTo">{{btx.labelTo}}</span>
<contact ng-show="!btx.labelTo" class="enable_text_select" address="{{btx.addressTo}}"></contact>
</span>
</span>
</li>
<li ng-show="btx.hasMultiplesOutputs" class="line-b p10 oh"
ng-click="showMultiplesOutputs = !showMultiplesOutputs">
<span class="text-gray" translate>Recipients</span>
<span class="right">{{btx.recipientCount}}
<i ng-show="showMultiplesOutputs" class="icon-arrow-up3 size-24"></i>
<i ng-show="!showMultiplesOutputs" class="icon-arrow-down3 size-24"></i>
</span>
</li>
<div class="line-b" ng-show="btx.hasMultiplesOutputs && showMultiplesOutputs"
ng-repeat="output in btx.outputs"
ng-include="'views/includes/output.html'">
</div>
<li ng-if="btx.action == 'invalid'" class="line-b p10 oh">
<span class="right" translate>
This transaction has become invalid; possibly due to a double spend attempt.
</span>
</li>
<li ng-if="btx.time" class="line-b p10 oh">
<span class="text-gray" translate>Date</span>
<span class="right enable_text_select">
<time>{{ btx.time * 1000 | amDateFormat:'MM/DD/YYYY HH:mm a'}}</time>
<time>({{ btx.time * 1000 | amTimeAgo}})</time>
</span>
</li>
<li class="line-b p10" ng-show="btx.action != 'received'"
ng-click="copyToClipboard(btx.feeStr)">
<span class="text-gray" translate>Fee</span>
<span class="right enable_text_select">{{btx.feeStr}}</span>
</li>
<li class="line-b p10 oh" ng-if="btx.message && btx.action != 'received'"
ng-click="copyToClipboard(btx.message)">
<span class="text-gray" translate>Description</span>
<span class="right enable_text_select">{{btx.message}}</span>
</li>
<li ng-if="btx.merchant" class="line-b p10 oh"
ng-click="copyToClipboard(btx.merchant.pr.pd.memo)">
<span class="text-gray" translate>Merchant message</span>
<span class="right enable_text_select">
{{btx.merchant.pr.pd.memo}}
</span>
</li>
<li ng-if="btx.time" class="line-b p10 oh">
<span class="text-gray" translate>Confirmations</span>
<span class="right" >
<span class="text-warning" ng-show="!btx.confirmations || btx.confirmations == 0" translate>
Unconfirmed
</span>
<span class="label gray radius" ng-show="btx.confirmations>0 && !btx.safeConfirmed">
{{btx.confirmations}}
</span>
<span class="label gray radius" ng-show="btx.safeConfirmed">
{{btx.safeConfirmed}}
</span>
</span>
</li>
<li class="p10 oh" ng-show="btx.note">
<span class="text-gray" translate>Comment</span>
<span class="right enable_text_select">{{btx.note.body}}</span><br>
<span class="right text-italic text-gray size-12">
<span translated>Edited by</span> <span>{{btx.note.editedByName}}</span>,
<time>{{btx.note.editedOn * 1000 | amTimeAgo}}</time></span>
</span>
</li>
</ul>
<div ng-if="btx.actions[0] && isShared">
<h4 class="title m0" translate>Participants</h4>
<ul class="no-bullet size-14 m0">
<li class="line-b p10 text-gray" ng-repeat="c in btx.actions">
<i class="icon-contact size-24"></i>
<span class="right">
<i ng-if="c.type == 'reject'" class="fi-x icon-sign x db"></i>
<i ng-if="c.type == 'accept'" class="fi-check icon-sign check db"></i>
</span>
{{c.copayerName}} <span ng-if="c.copayerId == copayerId">({{'Me'|translate}})</span>
</li>
</ul>
</div> </div>
<div ng-show="btx.action == 'moved'">
<img src="img/icon-moved.svg" alt="sync" width="50"> <div ng-show="btx.txid" class="tx-details-blockchain">
<p class="m0 text-gray size-14" translate>Moved</p> <div class="text-center m20t">
</div> <button class="button outline round dark-gray tiny" ng-click="$root.openExternalLink('https://' +
(getShortNetworkName() == 'test' ? 'test-' : '') + 'insight.bitpay.com/tx/' + btx.txid)">
<div class="size-36"> <span class="text-gray" translate>See it on the blockchain</span>
<span ng-if="btx.action == 'received'">+</span><span ng-if="btx.action == 'sent'">-</span>{{btx.amountStr}} </button>
</div> <button class="button outline round dark-gray tiny" ng-click="showCommentPopup()">
<div class="alternative-amount" ng-click="showRate=!showRate" ng-init="showRate = false"> <span class="text-gray" translate ng-show="!btx.note">Add a comment</i></span>
<span class="label gray radius" ng-show="!showRate && alternativeAmountStr"> <span class="text-gray" translate ng-show="btx.note">Edit comment</span>
{{alternativeAmountStr}} </button>
</span> </div>
<span class="size-12" ng-show="showRate && alternativeAmountStr">
{{rateStr}} ({{rateDate | amDateFormat:'MM/DD/YYYY HH:mm a'}})
</span>
</div> </div>
</div> </div>
<div ng-show="btx.action == 'invalid'"> </ion-content>
- </ion-modal-view>
</div>
</div>
<h4 class="title m0" translate>Details</h4>
<ul class="no-bullet size-14 m0">
<li ng-if="!btx.hasMultiplesOutputs && btx.addressTo && btx.addressTo != 'N/A'" class="line-b p10 oh" ng-click="copyToClipboard(btx.addressTo)">
<span class="text-gray" translate>To</span>
<span class="right">
<span ng-if="btx.merchant">
<span ng-show="btx.merchant.pr.ca"><i class="fi-lock color-greeni"></i> {{btx.merchant.domain}}</span>
<span ng-show="!btx.merchant.pr.ca"><i class="fi-unlock color-yellowi"></i> {{btx.merchant.domain}}</span>
</span>
<span ng-if="!btx.merchant">
<span ng-show="btx.labelTo">{{btx.labelTo}}</span>
<contact ng-show="!btx.labelTo" class="enable_text_select" address="{{btx.addressTo}}"></contact>
</span>
</span>
</li>
<li ng-show="btx.hasMultiplesOutputs" class="line-b p10 oh"
ng-click="showMultiplesOutputs = !showMultiplesOutputs">
<span class="text-gray" translate>Recipients</span>
<span class="right">{{btx.recipientCount}}
<i ng-show="showMultiplesOutputs" class="icon-arrow-up3 size-24"></i>
<i ng-show="!showMultiplesOutputs" class="icon-arrow-down3 size-24"></i>
</span>
</li>
<div class="line-b" ng-show="btx.hasMultiplesOutputs && showMultiplesOutputs"
ng-repeat="output in btx.outputs"
ng-include="'views/includes/output.html'">
</div>
<li ng-if="btx.action == 'invalid'" class="line-b p10 oh">
<span class="right" translate>
This transaction has become invalid; possibly due to a double spend attempt.
</span>
<li ng-if="btx.time" class="line-b p10 oh">
<span class="text-gray" translate>Date</span>
<span class="right enable_text_select">
<time>{{ btx.time * 1000 | amDateFormat:'MM/DD/YYYY HH:mm a'}}</time>
<time>({{ btx.time * 1000 | amTimeAgo}})</time>
</span>
</li>
<li class="line-b p10" ng-show="btx.action != 'received'" ng-click="copyToClipboard(btx.feeStr)">
<span class="text-gray" translate>Fee</span>
<span class="right enable_text_select">{{btx.feeStr}}</span>
</li>
<li class="line-b p10 oh" ng-show="btx.message && btx.action != 'received'" ng-click="copyToClipboard(btx.message)">
<span class="text-gray" translate>Note</span>
<span class="right enable_text_select">{{btx.message}}</span>
</li>
<li ng-if="btx.merchant" class="line-b p10 oh" ng-click="copyToClipboard(btx.merchant.pr.pd.memo)">
<span class="text-gray" translate>Merchant message</span>
<span class="right enable_text_select">
{{btx.merchant.pr.pd.memo}}
</span>
</li>
<li ng-if="btx.time" class="line-b p10 oh">
<span class="text-gray" translate>Confirmations</span>
<span class="right" >
<span class="text-warning" ng-show="!btx.confirmations || btx.confirmations == 0" translate>
Unconfirmed
</span>
<span class="label gray radius" ng-show="btx.confirmations>0 && !btx.safeConfirmed">
{{btx.confirmations}}
</span>
<span class="label gray radius" ng-show="btx.safeConfirmed">
{{btx.safeConfirmed}}
</span>
</span>
</li>
</ul>
<div ng-if="btx.actions[0] && isShared">
<h4 class="title m0" translate>Participants</h4>
<ul class="no-bullet size-14 m0">
<li class="line-b p10 text-gray" ng-repeat="c in btx.actions">
<i class="icon-contact size-24"></i>
<span class="right">
<i ng-if="c.type == 'reject'" class="fi-x icon-sign x db"></i>
<i ng-if="c.type == 'accept'" class="fi-check icon-sign check db"></i>
</span>
{{c.copayerName}} <span ng-if="c.copayerId == copayerId">({{'Me'|translate}})</span>
</li>
</ul>
</div>
<div ng-show="btx.txid" class="tx-details-blockchain">
<div class="text-center m20t">
<button class="button outline round dark-gray tiny" ng-click="$root.openExternalLink('https://' +
(getShortNetworkName() == 'test' ? 'test-' : '') + 'insight.bitpay.com/tx/' + btx.txid)">
<span class="text-gray" translate>See it on the blockchain</span>
</button>
</div>
</div>
<div class="extra-margin-bottom"></div>
</div>

View file

@ -1,193 +1,181 @@
<nav class="tab-bar" ng-style="{'background-color':color}"> <ion-modal-view ng-controller="txpDetailsController">
<section class="left-small"> <nav class="tab-bar" ng-style="{'background-color':color}">
<a ng-click="cancel()"> <section class="left-small">
<i class="icon-arrow-left3 icon-back"></i> <a ng-click="cancel()" class="p10">
<span class="text-back" translate>Back</span> <span class="text-close" translate>Close</span>
</a> </a>
</section> </section>
<section class="middle tab-bar-section">
<h1 class="title ellipsis" translate>
Payment Proposal
</h1>
</section>
</nav>
<div class="modal-content fix-modals-touch"
ng-swipe-disable-mouse
ng-swipe-right="cancel()"
ng-init="updateCopayerList()">
<div class="payment-proposal-head"
ng-style="{'background-color':color}">
<div class="size-36">{{tx.amountStr}}</div>
<div class="size-14 text-light" ng-show="tx.alternativeAmountStr">{{tx.alternativeAmountStr}}</div>
<i class="db fi-arrow-down size-24 m10v"></i>
<span class="payment-proposal-to"
ng-click="copyToClipboard(tx.toAddress)">
<i class="fi-bitcoin left"></i>
<contact ng-if="!tx.hasMultiplesOutputs" class="dib enable_text_select ellipsis m5t m5b size-14" address="{{tx.toAddress}}"></contact>
<span ng-if="tx.hasMultiplesOutputs" translate>
Multiple recipients
</span>
</span>
</div>
<div class="oh"> <section class="middle tab-bar-section">
<div class="box-notification" ng-show="error"> <h1 class="title ellipsis" translate>Payment Proposal</h1>
<span class="text-warning size-14"> </section>
{{error|translate}} </nav>
</span>
</div>
<div class="row" ng-if="tx.removed"> <div class="onGoingProcess" ng-show="loading">
<div class="column m20t text-center text-warning size-12" translate> <div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}">
The payment was removed by creator <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
</div> <span>{{loading|translate}}</span>
</div>
<div class="row p20t white" ng-if="tx.pendingForUs">
<div class="large-6 medium-6 small-6 columns" ng-show="isShared">
<button class="button outline round dark-gray expand" ng-click="reject(tx);"
ng-disabled="loading">
<i class="fi-x"></i>
<span translate>Reject</span>
</button>
</div>
<div class="large-6 medium-6 small-6 columns text-right" ng-show="canSign">
<button class="button primary round expand" ng-click="sign(tx)"
ng-style="{'background-color':color}"
ng-disabled="loading || paymentExpired">
<i class="fi-check"></i>
<span translate>Accept</span>
</button>
</div>
</div>
<div class="text-center text-gray size-12 m20t" ng-show="tx.status != 'pending'">
<div ng-show="tx.status=='accepted' && !tx.isGlidera">
<div class="m10b" translate>Payment accepted, but not yet broadcasted</div>
<button class="primary round m0"
ng-style="{'background-color':color}"
ng-click="broadcast(tx)"
ng-disabled="loading"
> <i class="fi-upload-cloud"></i>
<span translate>Broadcast Payment</span>
</button>
</div>
<div ng-show="tx.status=='accepted' && tx.isGlidera" >
<div class="m10h" translate>Payment accepted. It will be broadcasted by Glidera. In case there is a problem, it can be deleted 6 hours after it was created.</div>
</div>
<div class="text-success"
ng-show="tx.status=='broadcasted'" translate>
Payment Sent
</div>
<div class="text-warning"
ng-show="tx.status=='rejected'" translate>
Payment Rejected
</div>
</div> </div>
</div> </div>
<h4 class="title m0" translate>Details</h4> <ion-content ng-style="{'background-color': '#F6F7F9'}">
<ul class="no-bullet size-14 m0"> <div class="modal-content fix-modals-touch" ng-init="updateCopayerList()">
<li class="line-b p10 oh" ng-show="tx.message"> <div class="payment-proposal-head" ng-style="{'background-color':color}">
<span class="text-gray" translate>Note</span> <div class="size-36">{{tx.amountStr}}</div>
<span class="right">{{tx.message}}</span> <div class="size-14 text-light" ng-show="tx.alternativeAmountStr">{{tx.alternativeAmountStr}}</div>
</li> <i class="db fi-arrow-down size-24 m10v"></i>
<span class="payment-proposal-to" ng-click="copyToClipboard(tx.toAddress)">
<i class="fi-bitcoin left"></i>
<contact ng-if="!tx.hasMultiplesOutputs" class="dib enable_text_select ellipsis m5t m5b size-14" address="{{tx.toAddress}}"></contact>
<span ng-if="tx.hasMultiplesOutputs" translate>Multiple recipients</span>
</span>
</div>
<li ng-show="tx.hasMultiplesOutputs" class="line-b p10 oh" <div class="oh">
ng-click="showMultiplesOutputs = !showMultiplesOutputs"> <div class="box-notification" ng-show="error">
<span class="text-gray" translate>Recipients</span> <span class="text-warning size-14">{{error|translate}}</span>
<span class="right">{{tx.recipientCount}} </div>
<i ng-show="showMultiplesOutputs" class="icon-arrow-up3 size-24"></i>
<i ng-show="!showMultiplesOutputs" class="icon-arrow-down3 size-24"></i>
</span>
</li>
<div class="line-b" ng-show="tx.hasMultiplesOutputs && showMultiplesOutputs" <div class="row" ng-if="tx.removed">
ng-repeat="output in tx.outputs" <div class="column m20t text-center text-warning size-12" translate>
ng-include="'views/includes/output.html'"> The payment was removed by creator
</div> </div>
</div>
<li class="line-b p10"> <div class="row p20t white" ng-if="tx.pendingForUs">
<span class="text-gray" translate>Fee</span> <div class="large-6 medium-6 small-6 columns" ng-show="isShared">
<span class="right">{{tx.feeStr}}</span> <button class="button outline round dark-gray expand" ng-click="reject(tx)" ng-disabled="loading">
</li> <i class="fi-x"></i>
<li class="line-b p10"> <span translate>Reject</span>
<span class="text-gray" translate>Time</span> </button>
<span class="right"> </div>
<time>{{ (tx.ts || tx.createdOn ) * 1000 | amTimeAgo}}</time> <div class="large-6 medium-6 small-6 columns text-right" ng-show="canSign">
</span> <button class="button primary round expand" ng-click="sign(tx)" ng-style="{'background-color':color}" ng-disabled="loading || paymentExpired">
</li> <i class="fi-check"></i>
<li class="line-b p10 oh"> <span translate>Accept</span>
<span class="text-gray" translate>Created by</span> </button>
<span class="right">{{tx.creatorName}}</span> </div>
</li> </div>
</ul>
<div class="p10 text-center size-12" ng-show="!currentSpendUnconfirmed && tx.hasUnconfirmedInputs">
<span class="text-warning" translate>Warning: this transaction has unconfirmed inputs</span>
</div>
<div ng-if="tx.paypro"> <div class="text-center text-gray size-12 m20t" ng-show="tx.status != 'pending'">
<h4 class="title m0" translate>Payment details</h4> <div ng-show="tx.status=='accepted' && !tx.isGlidera">
<ul class="no-bullet size-14 m0"> <div class="m10b" translate>Payment accepted, but not yet broadcasted</div>
<li class="line-b p10">
<span class="text-gray" translate>To</span> <button class="primary round m0" ng-style="{'background-color':color}" ng-click="broadcast(tx)" ng-disabled="loading">
<span class="right"> <i class="fi-upload-cloud"></i>
<span> <span translate>Broadcast Payment</span>
<span ng-show="tx.merchant.pr.ca"><i class="fi-lock"></i> {{tx.paypro.domain}}</span> </button>
<span ng-show="!tx.merchant.pr.ca"><i class="fi-unlock"></i> {{tx.paypro.domain}}</span> </div>
<div ng-show="tx.status=='accepted' && tx.isGlidera" >
<div class="m10h" translate>Payment accepted. It will be broadcasted by Glidera. In case there is a problem, it can be deleted 6 hours after it was created.</div>
</div>
<div class="text-success" ng-show="tx.status == 'broadcasted'" translate>Payment Sent</div>
<div class="text-warning" ng-show="tx.status=='rejected'" translate>Payment Rejected</div>
</div>
</div>
<h4 class="title m0" translate>Details</h4>
<ul class="no-bullet size-14 m0">
<li class="line-b p10 oh" ng-show="tx.message">
<span class="text-gray" translate>Description</span>
<span class="right">{{tx.message}}</span>
</li>
<li ng-show="tx.hasMultiplesOutputs" class="line-b p10 oh" ng-click="showMultiplesOutputs = !showMultiplesOutputs">
<span class="text-gray" translate>Recipients</span>
<span class="right">{{tx.recipientCount}}
<i ng-show="showMultiplesOutputs" class="icon-arrow-up3 size-24"></i>
<i ng-show="!showMultiplesOutputs" class="icon-arrow-down3 size-24"></i>
</span> </span>
<contact address="{{tx.toAddress}}" ng-hide="tx.merchant"></contact> </li>
</span>
</li>
<li class="line-b p10" ng-if="paymentExpired">
<span class="text-gray" translate>Expired</span>
<span class="right text-alert">
<time>{{tx.paypro.expires * 1000 | amTimeAgo }}</time>
</span>
</li>
<li class="line-b p10" ng-if="!paymentExpired">
<span class="text-gray" translate>Expires</span>
<span class="right">
<time>{{expires}}</time>
</span>
</li>
<li class="line-b p10">
<span class="text-gray">Merchant Message</span>
<span class="db">{{tx.paypro.pr.pd.memo}}</span>
</li>
</ul>
</div>
<div ng-if="tx.actions[0] && !txRejected && !txBroadcasted"> <div class="line-b" ng-show="tx.hasMultiplesOutputs && showMultiplesOutputs"
<h4 class="title m0"> ng-repeat="output in tx.outputs" ng-include="'views/includes/output.html'">
<div class="right size-12 text-gray m10r"> </div>
{{tx.requiredSignatures}}/{{tx.walletN}}
<li class="line-b p10">
<span class="text-gray" translate>Fee</span>
<span class="right">{{tx.feeStr}}</span>
</li>
<li class="line-b p10">
<span class="text-gray" translate>Time</span>
<span class="right">
<time>{{ (tx.ts || tx.createdOn ) * 1000 | amTimeAgo}}</time>
</span>
</li>
<li class="line-b p10 oh">
<span class="text-gray" translate>Created by</span>
<span class="right">{{tx.creatorName}}</span>
</li>
</ul>
<div class="p10 text-center size-12" ng-show="!currentSpendUnconfirmed && tx.hasUnconfirmedInputs">
<span class="text-warning" translate>Warning: this transaction has unconfirmed inputs</span>
</div> </div>
<span translate>Participants</span>
</h4>
<ul class="no-bullet size-14 m0">
<li class="line-b p10 text-gray" ng-repeat="ac in tx.actions">
<i class="icon-contact size-24"></i>
<span class="right">
<i ng-if="ac.type == 'reject'" class="fi-x icon-sign x db"></i>
<i ng-if="ac.type == 'accept'" class="fi-check icon-sign check db"></i>
</span>
{{ac.copayerName}} <span ng-if="ac.copayerId == copayerId">({{'Me'|translate}})</span>
</li>
</ul>
</div>
<div class="columns text-center m20t" ng-if="tx.canBeRemoved || (tx.status == 'accepted' && !tx.broadcastedOn)"> <div ng-if="tx.paypro">
<div class="text-gray size-12 m20b" ng-show="!tx.isGlidera && isShared" translate> <h4 class="title m0" translate>Payment details</h4>
* A payment proposal can be deleted if 1) you are the creator, and no other copayer has signed, or 2) 24 hours have passed since the proposal was created. <ul class="no-bullet size-14 m0">
<li class="line-b p10">
<span class="text-gray" translate>To</span>
<span class="right">
<span>
<span ng-show="tx.merchant.pr.ca"><i class="fi-lock"></i> {{tx.paypro.domain}}</span>
<span ng-show="!tx.merchant.pr.ca"><i class="fi-unlock"></i> {{tx.paypro.domain}}</span>
</span>
<contact address="{{tx.toAddress}}" ng-hide="tx.merchant"></contact>
</span>
</li>
<li class="line-b p10" ng-if="paymentExpired">
<span class="text-gray" translate>Expired</span>
<span class="right text-alert">
<time>{{tx.paypro.expires * 1000 | amTimeAgo }}</time>
</span>
</li>
<li class="line-b p10" ng-if="!paymentExpired">
<span class="text-gray" translate>Expires</span>
<span class="right">
<time>{{expires}}</time>
</span>
</li>
<li class="line-b p10">
<span class="text-gray">Merchant Message</span>
<span class="db">{{tx.paypro.pr.pd.memo}}</span>
</li>
</ul>
</div>
<div ng-if="tx.actions[0] && !txRejected && !txBroadcasted">
<h4 class="title m0">
<div class="right size-12 text-gray m10r">
{{tx.requiredSignatures}}/{{tx.walletN}}
</div>
<span translate>Participants</span>
</h4>
<ul class="no-bullet size-14 m0">
<li class="line-b p10 text-gray" ng-repeat="ac in tx.actions">
<i class="icon-contact size-24"></i>
<span class="right">
<i ng-if="ac.type == 'reject'" class="fi-x icon-sign x db"></i>
<i ng-if="ac.type == 'accept'" class="fi-check icon-sign check db"></i>
</span>
{{ac.copayerName}} <span ng-if="ac.copayerId == copayerId">({{'Me'|translate}})</span>
</li>
</ul>
</div>
<div class="columns text-center m20t" ng-if="tx.canBeRemoved || (tx.status == 'accepted' && !tx.broadcastedOn)">
<div class="text-gray size-12 m20b" ng-show="!tx.isGlidera && isShared" translate>
* A payment proposal can be deleted if 1) you are the creator, and no other copayer has signed, or 2) 24 hours have passed since the proposal was created.
</div>
<button class="tiny round outline dark-gray warning" ng-click="remove(tx)" ng-disabled="loading">
<i class="fi-trash size-14 m5r"></i>
<span translate>Delete Payment Proposal</span>
</button>
</div>
</div> </div>
<button class="tiny round outline dark-gray warning" ng-click="remove(tx)" </ion-content>
ng-disabled="loading"> </ion-modal-view>
<i class="fi-trash size-14 m5r"></i>
<span translate>Delete Payment Proposal</span>
</button>
</div>
<div class="extra-margin-bottom"></div>
</div>

View file

@ -8,13 +8,7 @@
<h4 ng-show="!paperWallet.error"></h4> <h4 ng-show="!paperWallet.error"></h4>
<div class="onGoingProcess" ng-show="paperWallet.scanning || paperWallet.sending"> <div class="onGoingProcess" ng-show="paperWallet.scanning || paperWallet.sending">
<div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}"> <div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span ng-show="paperWallet.scanning" translate>Scanning wallet funds...</span> <span ng-show="paperWallet.scanning" translate>Scanning wallet funds...</span>
<span ng-show="paperWallet.sending" translate>Sending funds...</span> <span ng-show="paperWallet.sending" translate>Sending funds...</span>
</div> </div>
@ -24,7 +18,7 @@
</div> </div>
<form ng-show="!paperWallet.balance" class="oh"> <form ng-show="!paperWallet.balance" class="oh">
<div class="row"> <div class="row">
<div class="large-12 medium-12 columns"> <div class="large-12 medium-12 columns">
<div class="input"> <div class="input">
<label for="inputData" translate>Paper Wallet Private Key</label> <label for="inputData" translate>Paper Wallet Private Key</label>
<input type="text" placeholder="{{'Paste your paper wallet private key here'|translate}}" ng-model="inputData" id="inputData" ng-change="paperWallet.onData(inputData)"> <input type="text" placeholder="{{'Paste your paper wallet private key here'|translate}}" ng-model="inputData" id="inputData" ng-change="paperWallet.onData(inputData)">
@ -42,12 +36,12 @@
</div> </div>
<button <button
ng-disabled="paperWallet.scanning || !paperWallet.scannedKey" ng-disabled="paperWallet.scanning || !paperWallet.scannedKey"
ng-style="{'background-color':index.backgroundColor}" ng-style="{'background-color':index.backgroundColor}"
class="button black round expand" class="button black round expand"
ng-click="paperWallet.scanFunds()" ng-click="paperWallet.scanFunds()"
translate>Scan Wallet Funds translate>Scan Wallet Funds
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</form> </form>
@ -60,11 +54,11 @@
</div> </div>
</div> </div>
<button <button
ng-disabled="paperWallet.sending || paperWallet.balanceSat <= 0" ng-disabled="paperWallet.sending || paperWallet.balanceSat <= 0"
ng-style="{'background-color':index.backgroundColor}" ng-style="{'background-color':index.backgroundColor}"
class="button black round expand" class="button black round expand"
ng-click="paperWallet.sweepWallet()" ng-click="paperWallet.sweepWallet()"
translate>Sweep Wallet translate>Sweep Wallet
</button> </button>
</div> </div>

View file

@ -1,6 +1,6 @@
<div <div
class="topbar-container" class="topbar-container"
ng-include="'views/includes/topbar.html'" ng-include="'views/includes/topbar.html'"
ng-init="titleSection='Wallet Preferences'; closeToHome = true"> ng-init="titleSection='Wallet Preferences'; closeToHome = true">
</div> </div>
@ -33,17 +33,17 @@
<div class="right text-gray"> <div class="right text-gray">
<span ng-style="{'color':index.backgroundColor}">&block;</span> <span ng-style="{'color':index.backgroundColor}">&block;</span>
<i class="icon-arrow-right3 size-24"></i> <i class="icon-arrow-right3 size-24"></i>
</div> </div>
<div translate>Color</div> <div translate>Color</div>
</li> </li>
<li ng-show="index.isPrivKeyExternal"> <li ng-show="index.isPrivKeyExternal">
<div class="right text-gray"> <div class="right text-gray">
{{preferences.externalSource}} {{preferences.externalSource}}
<!-- (Accont {{preferences.externalAccount}}) --> <!-- (Accont {{preferences.externalAccount}}) -->
</div> </div>
<div translate>Hardware wallet</div> <div translate>Hardware wallet</div>
</li> </li>
<li ng-click="$root.go('backup')" ng-hide="index.isPrivKeyExternal"> <li ng-click="$root.go('backup')" ng-hide="index.isPrivKeyExternal">
<div class="right text-gray"> <div class="right text-gray">
@ -56,7 +56,7 @@
</li> </li>
<li ng-click="$root.go('preferencesAdvanced')"> <li ng-click="$root.go('preferencesAdvanced')">
<i class="icon-arrow-right3 size-24 right text-gray"></i> <i class="icon-arrow-right3 size-24 right text-gray"></i>
<div translate>Advanced</div> <div translate>Advanced</div>
</li> </li>
@ -67,20 +67,13 @@
</h4> </h4>
<div ng-show="!index.noFocusedWallet && index.canSign"> <div ng-show="!index.noFocusedWallet && index.canSign">
<ion-toggle ng-model="encryptEnabled" toggle-class="toggle-balanced" ng-change="encryptChange()">
<span class="toggle-label" translate>Request Spending Password</span>
</ion-toggle>
<ul class="no-bullet m0"> <ion-toggle ng-model="touchidEnabled" toggle-class="toggle-balanced" ng-change="touchidChange()" ng-show="preferences.touchidAvailable">
<span class="toggle-label" translate>Scan Fingerprint</span>
<li> </ion-toggle>
<switch id="network-name" name="encrypt" ng-model="encrypt" class="green right"></switch>
<div translate>Request Spending Password</div>
</li>
<li ng-show="preferences.touchidAvailable">
<switch id="touchid" name="touchid" ng-model="touchid" class="green right"></switch>
<div translate>Scan Fingerprint</div>
</li>
</ul>
</div> </div>
<div ng-show ="!deleted"> <div ng-show ="!deleted">

View file

@ -8,13 +8,7 @@
<div class="content preferences" ng-controller="preferencesDeleteWalletController as preferences"> <div class="content preferences" ng-controller="preferencesDeleteWalletController as preferences">
<div class="onGoingProcess" ng-show="isDeletingWallet"> <div class="onGoingProcess" ng-show="isDeletingWallet">
<div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}"> <div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span translate>Deleting Wallet...</span> <span translate>Deleting Wallet...</span>
</div> </div>
</div> </div>

View file

@ -1,5 +1,5 @@
<div <div
class="topbar-container" class="topbar-container"
ng-include="'views/includes/topbar.html'" ng-include="'views/includes/topbar.html'"
ng-init="titleSection='Email Notifications'; goBackToState = 'preferences'"> ng-init="titleSection='Email Notifications'; goBackToState = 'preferences'">
</div> </div>
@ -11,13 +11,7 @@
<h4></h4> <h4></h4>
<div class="onGoingProcess" ng-show="prefEmail.saving && !index.isOffline"> <div class="onGoingProcess" ng-show="prefEmail.saving && !index.isOffline">
<div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}"> <div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span translate>Saving preferences...</span> <span translate>Saving preferences...</span>
</div> </div>
</div> </div>

View file

@ -8,13 +8,7 @@
<div class="preferences-fee" ng-show="prefFee.loading"> <div class="preferences-fee" ng-show="prefFee.loading">
<div class="row p20 text-center"> <div class="row p20 text-center">
<div class="columns large-12 medium-12 small-12 m10b"> <div class="columns large-12 medium-12 small-12 m10b">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div> </div>
<div class="size-12 text-gray m20t" translate> <div class="size-12 text-gray m20t" translate>
Loading... Loading...

View file

@ -1,14 +1,11 @@
<div <div class="topbar-container" ng-include="'views/includes/topbar.html'"
class="topbar-container"
ng-include="'views/includes/topbar.html'"
ng-init="titleSection='Global preferences'; closeToHome = true; noColor = true"> ng-init="titleSection='Global preferences'; closeToHome = true; noColor = true">
</div> </div>
<div class="content preferences" ng-controller="preferencesGlobalController as prefGlobal" ng-init="prefGlobal.init()"> <div class="content preferences" ng-controller="preferencesGlobalController as prefGlobal" ng-init="prefGlobal.init()">
<h4></h4> <h4></h4>
<ul class="no-bullet m0 "> <ul class="no-bullet m0">
<li ng-click="$root.go('preferencesLanguage')"> <li ng-click="$root.go('preferencesLanguage')">
<div class="right text-gray"> <div class="right text-gray">
{{prefGlobal.currentLanguageName|translate}} {{prefGlobal.currentLanguageName|translate}}
@ -18,7 +15,8 @@
</li> </li>
</ul> </ul>
<h4></h4> <h4></h4>
<ul class="no-bullet m0 ">
<ul class="no-bullet m0">
<li ng-click="$root.go('preferencesUnit')"> <li ng-click="$root.go('preferencesUnit')">
<div class="right text-gray"> <div class="right text-gray">
{{prefGlobal.unitName}} {{prefGlobal.unitName}}
@ -26,6 +24,7 @@
</div> </div>
<div translate>Unit</div> <div translate>Unit</div>
</li> </li>
<li ng-click="$root.go('preferencesAltCurrency')"> <li ng-click="$root.go('preferencesAltCurrency')">
<div class="right text-gray"> <div class="right text-gray">
{{prefGlobal.selectedAlternative.name}} {{prefGlobal.selectedAlternative.name}}
@ -35,7 +34,8 @@
</li> </li>
</ul> </ul>
<h4></h4> <h4></h4>
<ul class="no-bullet m0 ">
<ul class="no-bullet m0">
<li ng-click="$root.go('preferencesFee')"> <li ng-click="$root.go('preferencesFee')">
<div class="right text-gray"> <div class="right text-gray">
{{prefGlobal.feeOpts[prefGlobal.currentFeeLevel]|translate}} {{prefGlobal.feeOpts[prefGlobal.currentFeeLevel]|translate}}
@ -43,41 +43,37 @@
</div> </div>
<div translate>Bitcoin Network Fee Policy</div> <div translate>Bitcoin Network Fee Policy</div>
</li> </li>
<li>
<switch id="spend-unconfirmed" name="spendUnconfirmed" ng-model="spendUnconfirmed" class="green right"></switch>
<div translate>Use Unconfirmed Funds</div>
</li>
</ul> </ul>
<ion-toggle ng-model="spendUnconfirmed" toggle-class="toggle-balanced" ng-change="spendUnconfirmedChange()">
<span class="toggle-label" translate>Use Unconfirmed Funds</span>
</ion-toggle>
<div ng-show="prefGlobal.usePushNotifications && PNEnabledByUser"> <div ng-show="prefGlobal.usePushNotifications && PNEnabledByUser">
<h4></h4> <h4></h4>
<ul class="no-bullet m0"> <ion-toggle ng-model="pushNotifications" toggle-class="toggle-balanced" ng-change="pushNotificationsChange()">
<li> <span class="toggle-label" translate>Enable push notifications</span>
<switch id="push-notifications" name="pushNotifications" ng-model="pushNotifications" class="green right"></switch> </ion-toggle>
<div translate>Enable push notifications</div>
</li>
</ul>
</div> </div>
<h4></h4> <h4></h4>
<ul class="no-bullet m0 ">
<li> <ion-toggle ng-model="glideraEnabled" toggle-class="toggle-balanced" ng-change="glideraChange()">
<switch id="glidera-enabled" name="glideraEnabled" ng-model="glideraEnabled" class="green right"></switch> <span class="toggle-label" translate>Enable Glidera Service</span>
<div translate>Enable Glidera Service</div> </ion-toggle>
</li>
</ul>
<h4></h4> <h4></h4>
<ul class="no-bullet m0">
<li> <ion-toggle ng-model="coinbaseEnabled" toggle-class="toggle-balanced" ng-change="coinbaseChange()">
<switch id="coinbase-enabled" name="coinbaseEnabled" ng-model="coinbaseEnabled" class="green right"></switch> <span class="toggle-label" translate>Enable Coinbase Service</span>
<div translate>Enable Coinbase Service</div> </ion-toggle>
</li>
</ul>
<h4></h4> <h4></h4>
<ul class="no-bullet m0"> <ul class="no-bullet m0">
<li ng-click="$root.go('about')"> <li ng-click="$root.go('about')">
<i class="icon-arrow-right3 size-24 right text-gray"></i> <i class="icon-arrow-right3 size-24 right text-gray"></i>
<div translate>About Copay</div> <div translate>About Copay</div>
</li> </li>
</ul> </ul>
<div ng-show="prefGlobal.usePushNotifications && !PNEnabledByUser && isIOSApp"> <div ng-show="prefGlobal.usePushNotifications && !PNEnabledByUser && isIOSApp">
<div class="text-centered text-gray size-12 m10" translate>Push notifications for Copay are currently disabled. Enable them in the Settings app.</div> <div class="text-centered text-gray size-12 m10" translate>Push notifications for Copay are currently disabled. Enable them in the Settings app.</div>
<ul class="no-bullet m0" ng-click="prefGlobal.openSettings()"> <ul class="no-bullet m0" ng-click="prefGlobal.openSettings()">

View file

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

View file

@ -1,5 +1,5 @@
<div <div
class="topbar-container" class="topbar-container"
ng-include="'views/includes/topbar.html'" ng-include="'views/includes/topbar.html'"
ng-init="titleSection='Sell'; goBackToState = 'coinbase'; noColor = true"> ng-init="titleSection='Sell'; goBackToState = 'coinbase'; noColor = true">
</div> </div>
@ -9,13 +9,7 @@
<div class="onGoingProcess" ng-show="sell.loading"> <div class="onGoingProcess" ng-show="sell.loading">
<div class="onGoingProcess-content" ng-style="{'background-color': '#2b71b1'}"> <div class="onGoingProcess-content" ng-style="{'background-color': '#2b71b1'}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span>{{sell.loading}}</span> <span>{{sell.loading}}</span>
</div> </div>
</div> </div>
@ -30,25 +24,25 @@
</div> </div>
</div> </div>
<div class="row m20t" <div class="row m20t"
ng-show="index.coinbaseAccount && !sell.sellInfo && !sell.sendInfo"> ng-show="index.coinbaseAccount && !sell.sellInfo && !sell.sendInfo">
<div class="columns"> <div class="columns">
<form <form
name="sellCoinbaseForm" name="sellCoinbaseForm"
ng-submit="sell.depositFunds(index.coinbaseToken, index.coinbaseAccount)" ng-submit="sell.depositFunds(index.coinbaseToken, index.coinbaseAccount)"
novalidate> novalidate>
<div ng-show="!showPriceSensitivity"> <div ng-show="!showPriceSensitivity">
<div <div
ng-if="index.coinbaseToken" ng-if="index.coinbaseToken"
ng-init="sell.init(index.coinbaseTestnet)" ng-init="sell.init(index.coinbaseTestnet)"
ng-click="openWalletsModal(sell.otherWallets)"> ng-click="openWalletsModal(sell.otherWallets)">
<label>Copay Wallet</label> <label>Copay Wallet</label>
<div class="input"> <div class="input">
<input type="text" id="address" name="address" ng-disabled="sell.selectedWalletId" <input type="text" id="address" name="address" ng-disabled="sell.selectedWalletId"
ng-attr-placeholder="{{'Choose your source wallet'}}" ng-attr-placeholder="{{'Choose your source wallet'}}"
ng-model="sell.selectedWalletName" required> ng-model="sell.selectedWalletName" required>
<a class="postfix size-12 m0 text-gray"> <a class="postfix size-12 m0 text-gray">
<i class="icon-wallet size-18"></i> <i class="icon-wallet size-18"></i>
@ -57,28 +51,28 @@
</div> </div>
<label> <label>
Amount Amount
<span <span
ng-if="index.coinbaseToken" ng-if="index.coinbaseToken"
ng-init="sell.getPrice(index.coinbaseToken)" ng-init="sell.getPrice(index.coinbaseToken)"
ng-show="sell.sellPrice" ng-show="sell.sellPrice"
class="size-11 text-light right"> class="size-11 text-light right">
1 BTC <i class="icon-arrow-right"></i> {{sell.sellPrice.amount}} {{sell.sellPrice.currency}} 1 BTC <i class="icon-arrow-right"></i> {{sell.sellPrice.amount}} {{sell.sellPrice.currency}}
</span> </span>
</label> </label>
<div class="input"> <div class="input">
<input ng-show="!showAlternative" type="number" id="amount" <input ng-show="!showAlternative" type="number" id="amount"
name="amount" ng-attr-placeholder="{{'Amount in ' + (showAlternative ? 'USD' : 'BTC')}}" name="amount" ng-attr-placeholder="{{'Amount in ' + (showAlternative ? 'USD' : 'BTC')}}"
ng-minlength="0.00000001" ng-maxlength="10000000000" ng-minlength="0.00000001" ng-maxlength="10000000000"
ng-model="amount" autocomplete="off" ng-disabled="sell.loading"> ng-model="amount" autocomplete="off" ng-disabled="sell.loading">
<input ng-show="showAlternative" type="number" id="fiat" <input ng-show="showAlternative" type="number" id="fiat"
name="fiat" ng-attr-placeholder="{{'Amount in ' + (showAlternative ? 'USD' : 'BTC')}}" name="fiat" ng-attr-placeholder="{{'Amount in ' + (showAlternative ? 'USD' : 'BTC')}}"
ng-model="fiat" autocomplete="off" ng-disabled="sell.loading"> ng-model="fiat" autocomplete="off" ng-disabled="sell.loading">
<a ng-show="!showAlternative" class="postfix button" <a ng-show="!showAlternative" class="postfix button"
ng-click="showAlternative = true; amount = null">BTC</a> ng-click="showAlternative = true; amount = null">BTC</a>
<a ng-show="showAlternative" class="postfix button black" <a ng-show="showAlternative" class="postfix button black"
ng-click="showAlternative = false; fiat = null">USD</a> ng-click="showAlternative = false; fiat = null">USD</a>
</div> </div>
@ -100,13 +94,13 @@
<div ng-if="index.coinbaseToken" ng-init="sell.getPaymentMethods(index.coinbaseToken)"> <div ng-if="index.coinbaseToken" ng-init="sell.getPaymentMethods(index.coinbaseToken)">
<label>Deposit into</label> <label>Deposit into</label>
<select <select
ng-model="selectedPaymentMethod.id" ng-model="selectedPaymentMethod.id"
ng-options="item.id as item.name for item in sell.paymentMethods"> ng-options="item.id as item.name for item in sell.paymentMethods">
</select> </select>
</div> </div>
<div class="input m20t"> <div class="input m20t">
<a href class="button black expand round" <a href class="button black expand round"
ng-disabled="sell.loading || (!amount && !fiat)" ng-disabled="sell.loading || (!amount && !fiat)"
ng-style="{'background-color': '#2b71b1'}" ng-style="{'background-color': '#2b71b1'}"
ng-click="showPriceSensitivity = true">Continue</a> ng-click="showPriceSensitivity = true">Continue</a>
@ -114,20 +108,20 @@
</div> </div>
<div ng-show="showPriceSensitivity"> <div ng-show="showPriceSensitivity">
<h1>Price Sensitivity</h1> <h1>Price Sensitivity</h1>
<p class="size-14 text-gray"> <p class="size-14 text-gray">
Coinbase has not yet implemented an immediate method to sell bitcoin from a wallet. To make this sale, funds Coinbase has not yet implemented an immediate method to sell bitcoin from a wallet. To make this sale, funds
will be sent to your Coinbase account, and sold when Coinbase accepts the transaction (usually one will be sent to your Coinbase account, and sold when Coinbase accepts the transaction (usually one
hour). hour).
</p> </p>
<label>At what percentage lower price would you accept to sell?</label> <label>At what percentage lower price would you accept to sell?</label>
<select <select
ng-model="selectedPriceSensitivity" ng-model="selectedPriceSensitivity"
ng-options="item as item.name for item in priceSensitivity track by item.value"> ng-options="item as item.name for item in priceSensitivity track by item.value">
</select> </select>
<p class="size-12 text-gray"> <p class="size-12 text-gray">
Estimated sale value: {{sell.sellPrice.amount * amount | currency : 'USD ' : 2}} <br> Estimated sale value: {{sell.sellPrice.amount * amount | currency : 'USD ' : 2}} <br>
Still sell if price fall until: Still sell if price fall until:
{{(sell.sellPrice.amount - (selectedPriceSensitivity.value / 100) * sell.sellPrice.amount) * amount | currency : 'USD ' : 2}} {{(sell.sellPrice.amount - (selectedPriceSensitivity.value / 100) * sell.sellPrice.amount) * amount | currency : 'USD ' : 2}}
</p> </p>
@ -136,7 +130,7 @@
<a href class="button outline dark-gray expand round" ng-click="showPriceSensitivity = false">Back</a> <a href class="button outline dark-gray expand round" ng-click="showPriceSensitivity = false">Back</a>
</div> </div>
<div class="columns large-6 medium-6 small-6"> <div class="columns large-6 medium-6 small-6">
<input class="button black expand round" <input class="button black expand round"
ng-disabled="sell.loading || (!amount && !fiat)" ng-disabled="sell.loading || (!amount && !fiat)"
ng-style="{'background-color': '#2b71b1'}" ng-style="{'background-color': '#2b71b1'}"
type="submit" value="Confirm"> type="submit" value="Confirm">
@ -182,15 +176,15 @@
<li class="line-b p15"> <li class="line-b p15">
<span class="m10 text-normal text-bold">Total</span> <span class="m10 text-normal text-bold">Total</span>
<span class="right text-gray">{{sell.sellInfo.total.amount}} {{sell.sellInfo.total.currency}}</span> <span class="right text-gray">{{sell.sellInfo.total.amount}} {{sell.sellInfo.total.currency}}</span>
</li> </li>
<li class="line-b p15"> <li class="line-b p15">
<span class="m10 text-normal text-bold">Payout at</span> <span class="m10 text-normal text-bold">Payout at</span>
<span class="right text-gray">{{sell.sellInfo.payout_at | amCalendar}}</span> <span class="right text-gray">{{sell.sellInfo.payout_at | amCalendar}}</span>
</li> </li>
</ul> </ul>
<div class="row"> <div class="row">
<div class="columns"> <div class="columns">
<button class="button black round expand" <button class="button black round expand"
ng-style="{'background-color': '#2b71b1'}" ng-style="{'background-color': '#2b71b1'}"
ng-click="sell.confirmSell(index.coinbaseToken, index.coinbaseAccount, sell.sellInfo)" ng-click="sell.confirmSell(index.coinbaseToken, index.coinbaseAccount, sell.sellInfo)"
ng-disabled="sell.loading"> ng-disabled="sell.loading">
@ -210,6 +204,6 @@
ng-click="$root.go('coinbase')">OK</button> ng-click="$root.go('coinbase')">OK</button>
</div> </div>
</div> </div>
</div> </div>
<div class="extra-margin-bottom"></div> <div class="extra-margin-bottom"></div>

View file

@ -1,5 +1,5 @@
<div <div
class="topbar-container" class="topbar-container"
ng-include="'views/includes/topbar.html'" ng-include="'views/includes/topbar.html'"
ng-init="titleSection='Sell'; goBackToState = 'glidera'; noColor = true"> ng-init="titleSection='Sell'; goBackToState = 'glidera'; noColor = true">
</div> </div>
@ -9,13 +9,7 @@
<div class="onGoingProcess" ng-show="sell.loading"> <div class="onGoingProcess" ng-show="sell.loading">
<div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}"> <div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span>{{sell.loading}}</span> <span>{{sell.loading}}</span>
</div> </div>
</div> </div>
@ -24,7 +18,7 @@
<h4 class="title m0 text-left"> <h4 class="title m0 text-left">
<span class="text-light">Daily sell limit</span>: <span class="text-light">Daily sell limit</span>:
{{index.glideraLimits.dailySell|currency:'':2}} {{index.glideraLimits.currency}} {{index.glideraLimits.dailySell|currency:'':2}} {{index.glideraLimits.currency}}
(remaining {{index.glideraLimits.dailySellRemaining|currency:'':2}} {{index.glideraLimits.currency}}) (remaining {{index.glideraLimits.dailySellRemaining|currency:'':2}} {{index.glideraLimits.currency}})
<br> <br>
<span class="text-light">Monthly sell limit</span>: <span class="text-light">Monthly sell limit</span>:
{{index.glideraLimits.monthlySell|currency:'':2}} {{index.glideraLimits.currency}} {{index.glideraLimits.monthlySell|currency:'':2}} {{index.glideraLimits.currency}}
@ -42,16 +36,16 @@
</div> </div>
<div ng-show="!sell.show2faCodeInput && !sell.success"> <div ng-show="!sell.show2faCodeInput && !sell.success">
<form name="sellPriceForm" <form name="sellPriceForm"
ng-submit="sell.get2faCode(index.glideraToken)" novalidate> ng-submit="sell.get2faCode(index.glideraToken)" novalidate>
<div ng-if="index.glideraToken" <div ng-if="index.glideraToken"
ng-init="sell.init(index.glideraTestnet)" ng-init="sell.init(index.glideraTestnet)"
ng-click="openWalletsModal(sell.otherWallets)"> ng-click="openWalletsModal(sell.otherWallets)">
<label>Wallet</label> <label>Wallet</label>
<div class="input"> <div class="input">
<input type="text" id="address" name="address" ng-disabled="sell.selectedWalletId" <input type="text" id="address" name="address" ng-disabled="sell.selectedWalletId"
ng-attr-placeholder="{{'Choose your source wallet'}}" ng-attr-placeholder="{{'Choose your source wallet'}}"
ng-model="sell.selectedWalletName" required> ng-model="sell.selectedWalletName" required>
<a class="postfix size-12 m0 text-gray"> <a class="postfix size-12 m0 text-gray">
<i class="icon-wallet size-18"></i> <i class="icon-wallet size-18"></i>
@ -61,24 +55,24 @@
<label><span>Amount in</span> {{showAlternative ? 'USD' : 'BTC'}}</label> <label><span>Amount in</span> {{showAlternative ? 'USD' : 'BTC'}}</label>
<div class="input"> <div class="input">
<input ng-show="!showAlternative" type="number" id="qty" <input ng-show="!showAlternative" type="number" id="qty"
name="qty" ng-attr-placeholder="{{'Amount'}}" name="qty" ng-attr-placeholder="{{'Amount'}}"
ng-minlength="0.00000001" ng-maxlength="10000000000" ng-minlength="0.00000001" ng-maxlength="10000000000"
ng-model="qty" autocomplete="off" ng-change="sell.getSellPrice(index.glideraToken, {'qty': qty})"> ng-model="qty" autocomplete="off" ng-change="sell.getSellPrice(index.glideraToken, {'qty': qty})">
<input ng-show="showAlternative" type="number" id="fiat" <input ng-show="showAlternative" type="number" id="fiat"
name="fiat" ng-attr-placeholder="{{'Amount'}}" name="fiat" ng-attr-placeholder="{{'Amount'}}"
ng-model="fiat" autocomplete="off" ng-change="sell.getSellPrice(index.glideraToken, {'fiat': fiat})"> ng-model="fiat" autocomplete="off" ng-change="sell.getSellPrice(index.glideraToken, {'fiat': fiat})">
<a ng-show="!showAlternative" class="postfix" <a ng-show="!showAlternative" class="postfix"
ng-click="showAlternative = true; qty = null; sell.sellPrice = null">BTC</a> ng-click="showAlternative = true; qty = null; sell.sellPrice = null">BTC</a>
<a ng-show="showAlternative" class="postfix" <a ng-show="showAlternative" class="postfix"
ng-click="showAlternative = false; fiat = null; sell.sellPrice = null">USD</a> ng-click="showAlternative = false; fiat = null; sell.sellPrice = null">USD</a>
<div class="text-center text-gray size-12 m20b" ng-show="!sell.gettingSellPrice && sell.sellPrice.qty"> <div class="text-center text-gray size-12 m20b" ng-show="!sell.gettingSellPrice && sell.sellPrice.qty">
Sell Sell
<span ng-show="qty">{{sell.sellPrice.subtotal|currency:'':2}} {{sell.sellPrice.currency}} in Bitcoin</span> <span ng-show="qty">{{sell.sellPrice.subtotal|currency:'':2}} {{sell.sellPrice.currency}} in Bitcoin</span>
<span ng-show="fiat">{{sell.sellPrice.qty}} BTC</span> <span ng-show="fiat">{{sell.sellPrice.qty}} BTC</span>
at {{sell.sellPrice.price|currency:'':2}} {{sell.sellPrice.currency}}/BTC at {{sell.sellPrice.price|currency:'':2}} {{sell.sellPrice.currency}}/BTC
</div> </div>
@ -90,13 +84,13 @@
... ...
</div> </div>
<input class="button black expand round" <input class="button black expand round"
ng-style="{'background-color':index.backgroundColor}" ng-style="{'background-color':index.backgroundColor}"
type="submit" value="{{'Continue'}}" type="submit" value="{{'Continue'}}"
ng-disabled="index.glideraLimits.transactDisabledPendingFirstTransaction || !sell.sellPrice.qty || ng-disabled="index.glideraLimits.transactDisabledPendingFirstTransaction || !sell.sellPrice.qty ||
!sell.selectedWalletId || sell.loading"> !sell.selectedWalletId || sell.loading">
</div> </div>
</form> </form>
</div> </div>
<div ng-show="sell.show2faCodeInput && !sell.success"> <div ng-show="sell.show2faCodeInput && !sell.success">
<div class="m10t text-center"> <div class="m10t text-center">
@ -105,10 +99,10 @@
A SMS containing a confirmation code was sent to your phone. <br> A SMS containing a confirmation code was sent to your phone. <br>
Please, enter the code below Please, enter the code below
</p> </p>
<form name="sellForm" <form name="sellForm"
ng-submit="sell.createTx(index.glideraToken, index.glideraPermissions, twoFaCode)" novalidate> ng-submit="sell.createTx(index.glideraToken, index.glideraPermissions, twoFaCode)" novalidate>
<input type="number" ng-model="twoFaCode" required> <input type="number" ng-model="twoFaCode" required>
<input class="button black expand round" <input class="button black expand round"
ng-style="{'background-color':index.backgroundColor}" ng-style="{'background-color':index.backgroundColor}"
type="submit" value="{{'Sell'}}" ng-disabled="sellForm.$invalid || sell.loading"> type="submit" value="{{'Sell'}}" ng-disabled="sellForm.$invalid || sell.loading">
</form> </form>

View file

@ -18,26 +18,14 @@
<div class="onGoingProcess" ng-show="index.isOffline"> <div class="onGoingProcess" ng-show="index.isOffline">
<div class="onGoingProcess-content" ng-style="{'background-color':'#222'}"> <div class="onGoingProcess-content" ng-style="{'background-color':'#222'}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span translate>Reconnecting to Wallet Service...</span> <span translate>Reconnecting to Wallet Service...</span>
</div> </div>
</div> </div>
<div class="onGoingProcess" ng-show="index.anyOnGoingProcess && !index.isOffline"> <div class="onGoingProcess" ng-show="index.anyOnGoingProcess && !index.isOffline">
<div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}"> <div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
<span translate ng-show=" <span translate ng-show="
index.onGoingProcessName == 'openingWallet' index.onGoingProcessName == 'openingWallet'
|| index.onGoingProcessName == 'updatingStatus' || index.onGoingProcessName == 'updatingStatus'
@ -52,13 +40,7 @@
<div class="onGoingProcess" ng-show="home.onGoingProcess && !index.anyOnGoingProces && !index.isOffline"> <div class="onGoingProcess" ng-show="home.onGoingProcess && !index.anyOnGoingProces && !index.isOffline">
<div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}"> <div class="onGoingProcess-content" ng-style="{'background-color':index.backgroundColor}">
<div class="spinner"> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
{{home.onGoingProcess}}... {{home.onGoingProcess}}...
</div> </div>
</div> </div>
@ -71,242 +53,221 @@
--> -->
<div id="walletHome" class="walletHome tab-view tab-in"> <ion-content id="walletHome" class="walletHome tab-view tab-in" scroll="false" >
<div class="oh pr" ng-show="!index.isSearching"> <ion-content overflow-scroll="true">
<div class="amount" ng-style="{'background-color':index.backgroundColor}"> <ion-refresher
<div ng-if="!index.anyOnGoingProcess && !index.notAuthorized"> ng-if="index.isCordova"
<div class="m15t" ng-show="index.updateError" ng-click='index.updateAll({triggerTxUpdate: true})'> pulling-icon="ion-ios-refresh"
<span class="size-12 db m10b">{{index.updateError|translate}}</span> spinner="ios-small"
<button class="outline white tiny round" translate>Tap to retry</button> on-refresh="index.updateAll({triggerTxUpdate: true})">
</div> </ion-refresher>
<div class="oh pr">
<div ng-show="index.walletScanStatus == 'error'" ng-click='index.retryScan()'> <div ng-style="{'background-color':index.backgroundColor}" class="amount">
<span translate>Scan status finished with error</span> <div ng-if="!index.anyOnGoingProcess && !index.notAuthorized">
<br><span translate>Tap to retry</span> <div class="m15t" ng-show="index.updateError" ng-click='index.updateAll({triggerTxUpdate: true})'>
</div> <span class="size-12 db m10b">{{index.updateError|translate}}</span>
<button class="outline white tiny round" translate>Tap to retry</button>
<div ng-click='index.updateAll({triggerTxUpdate: true})' ng-show="!index.updateError && index.walletScanStatus != 'error'">
<strong class="size-36">{{index.totalBalanceStr}}</strong>
<div class="size-14"
ng-if="index.totalBalanceAlternative">
{{index.totalBalanceAlternative}} {{index.alternativeIsoCode}}
</div>
<div class="size-14"
ng-if="index.pendingAmount">
<span translate>Pending Confirmation</span>:
{{index.pendingAmountStr}}
</div> </div>
</div> <div ng-show="index.walletScanStatus == 'error'" ng-click='index.retryScan()'>
</div> <span translate>Scan status finished with error</span>
<br><span translate>Tap to retry</span>
</div>
<div ng-if="index.anyOnGoingProcess"> <div ng-click='index.updateAll({triggerTxUpdate: true})' ng-show="!index.updateError && index.walletScanStatus != 'error' && !index.shouldHideBalance" on-hold="index.onHold()">
<div class="size-36"> <strong class="size-36">{{index.totalBalanceStr}}</strong>
<strong>...</strong> <div class="size-14" ng-if="index.totalBalanceAlternative">{{index.totalBalanceAlternative}} {{index.alternativeIsoCode}}</div>
</div> <div class="size-14" ng-if="index.pendingAmount">
</div> <span translate>Pending Confirmation</span>: {{index.pendingAmountStr}}
</div> <!-- amount --> </div>
</div>
<div class="wallet-info"> <div ng-show="!index.updateError && index.walletScanStatus != 'error' && index.shouldHideBalance" on-hold="index.onHold()">
<span ng-include="'views/includes/walletInfo.html'"></span> <strong class="size-24" translate>[Balance Hidden]</strong>
</div> <div class="size-14" transalate>
<div class="camera-icon" ng-show="index.isComplete"> Tap and hold to show
<qr-scanner on-scan="home.onQrCodeScanned(data)"></qr-scanner> </div>
</div>
</div> <!-- oh -->
<div class="oh pr m20t" ng-show="index.incorrectDerivation">
<div class="text-center text-warning">
<i class="fi-alert"></i>
<span translate>
WARNING: Key derivation is not working on this device/wallet. Actions cannot be performed on this wallet.
</span>
</div>
</div>
<div class="oh pr m20t" ng-show="index.notAuthorized && ! index.anyOnGoingProcess">
<div class="text-center text-warning">
<i class="fi-alert"></i>
<span translate>
WARNING: Wallet not registered
</span>
</div>
<div class="text-center text-gray m15r m15l" translate>
This wallet is not registered at the given Bitcore Wallet Service (BWS). You can recreate it from the local information.
</div>
<div class="text-center m10t ">
<span class="button outline round dark-gray tiny"
ng-click="index.recreate()">
<span translate>Recreate</span>
</span>
</div>
</div>
<div class="release size-12" ng-show="newRelease && !index.isSearching" ng-click="$root.openExternalLink('https://github.com/bitpay/copay/releases/latest')">
<span>{{newRelease}}</span><i class="icon-arrow-right3 right size-18"></i>
</div>
<div class="oh pr" ng-if="index.txps[0]" ng-show="!index.isSearching">
<h4 ng-show="index.requiresMultipleSignatures" class="title m0" translate>Payment Proposals</h4>
<h4 ng-show="!index.requiresMultipleSignatures" class="title m0" translate>Unsent transactions</h4>
<div class="last-transactions pr" ng-repeat="tx in index.txps">
<div ng-include="index.txTemplateUrl"></div>
</div>
<div class="text-gray text-center size-12 p10t"
ng-show="index.lockedBalanceSat">
<span translate>Total Locked Balance</span>:
<b>{{index.lockedBalanceStr}} </b>
<span> {{index.lockedBalanceAlternative}}
{{index.alternativeIsoCode}} </span>
</div>
</div>
<!-- Activity -->
<div
class="oh pr m20t text-gray size-12 text-center"
ng-show="!index.loadingWallet && !index.txHistory[0] && !index.updatingTxHistory && !index.txHistoryError && !index.updateError" translate>
No transactions yet
</div>
<div class="oh pr" ng-show="index.txHistory[0] || index.txProgress > 5">
<h4 class="title" ng-click="index.startSearch(); search=''" ng-show="!index.isSearching">
<span translate>Activity</span>
<i class="dib m5l size-16 pointer fi-magnifying-glass"></i>
</h4>
<div ng-show="index.updatingTxHistory && index.txProgress > 5">
<div class="row p20 text-center">
<div class="columns large-12 medium-12 small-12 m10b">
<div class="spinner">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div> </div>
</div> </div>
<div class="size-12 text-gray m20t">
<div translate>{{index.txProgress}} transactions downloaded</div>
<div translate>Updating transaction history. Please stand by.</div>
</div>
</div>
</div>
<div ng-if="index.txHistory[0] && index.updatingTxHistory && index.newTx" class="row collapse last-transactions-content animated fadeInDown"> <div ng-if="index.anyOnGoingProcess">
<div class="large-6 medium-6 small-6 columns size-14"> <div class="size-36">
<div class="m10r left"> <strong>...</strong>
<img src="img/icon-new.svg" width="40"> </div>
</div> </div>
<div class="m10t" style="background:#eee; width: 8em; margin-left: 52px; line-height:0.6em"> </div> <!-- amount -->
<span>&nbsp;</span>
</div>
<div style="margin-top:5px; background:#eee; width: 6em; margin-left: 52px; line-height:0.6em">
<span>&nbsp;</span>
</div>
</div>
</div>
<div ng-show="index.isSearching" class="row searchBar"> <div class="wallet-info">
<div class="small-11 columns"> <span ng-include="'views/includes/walletInfo.html'"></span>
<div class="searchLabel"> </div>
<i class="fi-magnifying-glass size-14"></i> <div class="camera-icon" ng-show="index.isComplete">
<form> <qr-scanner on-scan="home.onQrCodeScanned(data)"></qr-scanner>
<input name="search" </div>
type="search" </div> <!-- oh -->
ng-change="index.updateSearchInput(search)"
placeholder="{{'Search transactions' | translate}}" <div class="m50b">
ng-model="search"> <div class="oh pr m20t" ng-show="index.incorrectDerivation">
</input> <div class="text-center text-warning">
</form> <i class="fi-alert"></i>
<span translate>
WARNING: Key derivation is not working on this device/wallet. Actions cannot be performed on this wallet.
</span>
</div> </div>
</div> </div>
<div class="small-1 columns"> <div class="oh pr m20t" ng-show="index.notAuthorized && !index.anyOnGoingProcess">
<a ng-click="index.cancelSearch()" translate>Cancel</a> <div class="text-center text-warning">
</div> <i class="fi-alert"></i>
</div> <span translate>
<div ng-repeat="btx in index.txHistorySearchResults track by btx.txid" WARNING: Wallet not registered
ng-click="home.openTxModal(btx)" </span>
class="row collapse last-transactions-content">
<div class="large-6 medium-6 small-6 columns size-14">
<div class="m10r left">
<img src="img/icon-receive-history.svg" alt="sync" width="40" ng-show="btx.action == 'received'">
<img src="img/icon-sent-history.svg" alt="sync" width="40" ng-show="btx.action == 'sent'">
<img src="img/icon-moved.svg" alt="sync" width="40" ng-show="btx.action == 'moved'">
</div> </div>
<div class="m10t"> <div class="text-center text-gray m15r m15l" translate>
<span ng-show="btx.action == 'received'" translate>Received</span> This wallet is not registered at the given Bitcore Wallet Service (BWS). You can recreate it from the local information.
<span ng-show="btx.action == 'sent'"> </div>
{{index.addressbook[btx.addressTo]}} <div class="text-center m10t ">
<span ng-show="!index.addressbook[btx.addressTo] && btx.message"> <span class="button outline round dark-gray tiny"
<span class="ellipsis">{{btx.message}}</span> ng-click="index.recreate()">
<span translate>Recreate</span>
</span>
</div>
</div>
<div class="release size-12" ng-show="newRelease" ng-click="$root.openExternalLink('https://github.com/bitpay/copay/releases/latest')">
<span>{{newRelease}}</span><i class="icon-arrow-right3 right size-18"></i>
</div>
<div class="oh pr" ng-if="index.txps[0]">
<h4 ng-show="index.requiresMultipleSignatures" class="title m0" translate>Payment Proposals</h4>
<h4 ng-show="!index.requiresMultipleSignatures" class="title m0" translate>Unsent transactions</h4>
<div ng-repeat="tx in index.txps">
<div ng-include="index.txTemplateUrl">
</div>
</div>
<div class="text-gray text-center size-12 p10t"
ng-show="index.lockedBalanceSat">
<span translate>Total Locked Balance</span>:
<b>{{index.lockedBalanceStr}} </b>
<span> {{index.lockedBalanceAlternative}}
{{index.alternativeIsoCode}} </span>
</div>
</div>
<!-- Activity -->
<h4 class="title" ng-click="index.startSearch(); openSearchModal()" ng-show="!index.notAuthorized">
<span translate>Activity</span>
<i class="dib m5l size-16 pointer fi-magnifying-glass"></i>
</h4>
<div class="oh pr m20t text-gray size-12 text-center"
ng-show="!index.loadingWallet && !index.txHistory[0] && !index.updatingTxHistory && !index.txHistoryError && !index.updateError && !index.notAuthorized"
translate>No transactions yet
</div>
<div class="oh pr" ng-show="(index.txHistory[0] || index.txProgress > 5) && !index.notAuthorized">
<div ng-show="index.updatingTxHistory && index.txProgress > 5">
<div class="row p20 text-center">
<div class="columns large-12 medium-12 small-12 m10b">
<ion-spinner class="spinner-dark" icon="lines"></ion-spinner>
</div>
<div class="size-12 text-gray m20t">
<div translate>{{index.txProgress}} transactions downloaded</div>
<div translate>Updating transaction history. Please stand by.</div>
</div>
</div>
</div>
<div ng-if="index.txHistory[0] && index.updatingTxHistory && index.newTx" class="row collapse last-transactions-content animated fadeInDown">
<div class="large-6 medium-6 small-6 columns size-14">
<div class="m10r left">
<img src="img/icon-new.svg" width="40">
</div>
<div class="m10t" style="background:#eee; width: 8em; margin-left: 52px; line-height:0.6em">
<span>&nbsp;</span>
</div>
<div style="margin-top:5px; background:#eee; width: 6em; margin-left: 52px; line-height:0.6em">
<span>&nbsp;</span>
</div>
</div>
</div>
<div ng-repeat="btx in index.txHistory track by btx.txid"
ng-click="home.openTxModal(btx)"
class="row collapse last-transactions-content">
<div class="large-6 medium-6 small-6 columns size-14">
<div class="m10r left">
<img src="img/icon-receive-history.svg" alt="sync" width="40" ng-show="btx.action == 'received'">
<img src="img/icon-sent-history.svg" alt="sync" width="40" ng-show="btx.action == 'sent'">
<img src="img/icon-moved.svg" alt="sync" width="40" ng-show="btx.action == 'moved'">
</div>
<div class="m10t">
<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 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 ng-show="btx.action == 'moved'" translate>Moved</span>
<span class="label tu warning radius" ng-show="btx.action == 'invalid'" translate>Invalid</span>
</div>
</div>
<div class="large-5 medium-5 small-5 columns text-right" >
<span class="size-16" ng-class="{'text-bold': btx.recent}">
<span ng-if="btx.action == 'received'">+</span>
<span ng-if="btx.action == 'sent'">-</span>
<span class="size-12" ng-if="btx.action == 'invalid'" translate>
(possible double spend)
</span>
<span ng-if="btx.action != 'invalid'">
{{btx.amountStr}}
</span>
</span> </span>
<span ng-show="!index.addressbook[btx.addressTo] && !btx.message"> <div class="size-12 text-gray">
<span translate> Sent</span> <time ng-if="btx.time">{{btx.time * 1000 | amTimeAgo}}</time>
</span> <span translate class="text-warning"
</span> ng-show="!btx.time && (!btx.confirmations || btx.confirmations == 0)">
<span ng-show="btx.action == 'moved'" translate>Moved</span> Unconfirmed
<span class="label tu warning radius" ng-show="btx.action == 'invalid'" translate>Invalid</span> </span>
</div>
</div>
<div class="large-1 medium-1 small-1 columns text-right m10t">
<i class="icon-arrow-right3 size-18"></i>
</div>
</div> </div>
</div>
<div class="large-5 medium-5 small-5 columns text-right" > <div class="row m20t text-center" ng-show="index.historyRendering && !index.ching">
<span class="size-16" ng-class="{'text-bold': btx.recent}"> <div class="columns large-12 medium-12 small-12">
<span ng-if="btx.action == 'received'">+</span> <ion-spinner class="spinner-stable" icon="lines"></ion-spinner>
<span ng-if="btx.action == 'sent'">-</span> </div>
<span class="size-12" ng-if="btx.action == 'invalid'" translate>
(possible double spend)
</span>
<span ng-if="btx.action != 'invalid'">
{{btx.amountStr}}
</span>
</span>
<div class="size-12 text-gray">
<time ng-if="btx.time">{{btx.time * 1000 | amTimeAgo}}</time>
<span translate class="text-warning"
ng-show="!btx.time && (!btx.confirmations || btx.confirmations == 0)">
Unconfirmed
</span>
</div> </div>
</div>
<div class="large-1 medium-1 small-1 columns text-right m10t"> <div class="text-gray text-center size-12 p10t" ng-if="index.historyShowMore">
<i class="icon-arrow-right3 size-18"></i> <span class="size-12">{{index.completeHistory.length - index.txHistory.length}}</span>
<span class="size-12" translate> more</span>&nbsp;<i class="icon-arrow-down4"></i>
</div>
<ion-infinite-scroll
ng-if="index.historyShowMore"
on-infinite="index.showMore()"
distance="1%">
</ion-infinite-scroll>
</div> </div>
</div> </div>
<div class="row m20t text-center" ng-show="index.historyRendering && !index.isSearching"> </ion-content>
<div class="columns large-12 medium-12 small-12">
<div class="spinner">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div>
</div>
<div class="m20t text-center">
<a class="text-gray size-12"
ng-show="index.historyShowMore"
ng-click="index.showMore()">
<span translate>Show more</span>
<span ng-if="!index.isSearching">
({{index.completeHistory.length - index.txHistory.length}})
</span>
<span ng-if="index.isSearching">
({{index.result.length - index.txHistorySearchResults.length}})
</span>
<i class="icon-arrow-down4"></i>
</a>
</div>
</div>
<div class="extra-margin-bottom"></div> <div class="extra-margin-bottom"></div>
</div> <!-- END WalletHome --> </ion-content> <!-- END WalletHome -->
<!-- <!--
@ -342,13 +303,7 @@
<qrcode size="220" data="bitcoin:{{home.addr}}"></qrcode> <qrcode size="220" data="bitcoin:{{home.addr}}"></qrcode>
<div ng-show="home.generatingAddress" style="position:relative; top:-226px; height:0px"> <div ng-show="home.generatingAddress" style="position:relative; top:-226px; height:0px">
<div style="height:220px; width:220px; margin:auto; background: white"> <div style="height:220px; width:220px; margin:auto; background: white">
<div class="spinner" style="margin-top:85px"> <ion-spinner class="spinner-stable" icon="lines" style="margin-top: 85px"></ion-spinner>
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div> </div>
</div> </div>
<div class="m10t" > <div class="m10t" >
@ -396,7 +351,7 @@
<div id="send" class="send tab-view"> <div id="send" class="send tab-view">
<div class="pr p25b"> <div class="pr p25b">
<h4 class="title m0"> <h4 class="title m0">
<available-balance></available-balance> <available-balance ng-show="!index.shouldHideBalance"></available-balance>
<span <span
ng-show="home.lockedCurrentFeePerKb || home.blockUx || home.lockAmount" ng-show="home.lockedCurrentFeePerKb || home.blockUx || home.lockAmount"
class="text-gray" translate>Send Max</span> class="text-gray" translate>Send Max</span>
@ -446,7 +401,7 @@
</div> </div>
<a class="postfix size-12 m0 text-gray" <a class="postfix size-12 m0 text-gray"
ng-style="{'color':index.backgroundColor}" ng-style="{'color':index.backgroundColor}"
ng-click="home.openDestinationAddressModal(index.otherWallets, _address)"> ng-click="home.openAddressbookModal(index.otherWallets, _address)">
<i class="icon-wallet text-bold size-18"></i> <i class="icon-wallet text-bold size-18"></i>
</a> </a>
</div> </div>
@ -500,7 +455,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>

100
src/css/ionic-migration.css Normal file
View file

@ -0,0 +1,100 @@
/*
*
* Ionic migration CSS
* These styles reapply foundation.css styles AFTER iconic.css has been applied.
* This has the effect of allowing all foundation styles be available at the same time as all Ionic styles being available.
* Where there are conflicts between foundation and css, this file resolves the conflict with a compromise which the developer must manage.
* This stylesheet is merged into copay.css.
*/
/* Foundation.css styles re-applied. These styles are exactly as described in foundation.css */
/* Conflicting styles that are customized as a compromise. These styles are a merge or compromise of foundation.css and ionic.css. */
.row {
display: inherit;
}
.behind {
z-index: -1;
}
.popup-container.active .popup {
border-radius: 10px;
}
.popup-container .popup {
width: 300px;
}
.popup-head {
display: none;
}
.popup-body {
padding: 0px;
}
.bct {
background-color: transparent !important;
margin-top: -25px;
padding-bottom: 30px;
right: -25px !important;
border-width: 0px;
}
.r0 {
right: 0px !important;
}
.item-toggle .toggle {
right: 28px;
}
.toggle-label {
color: rgb(41, 55, 68);
font-size: 14px;
margin-left: 5px;
}
button, .button {
min-width: inherit;
min-height: inherit;
text-overflow: inherit;
}
.modal-open {
pointer-events: inherit;
}
/* Defeat Ionic .row+.row in transaction history */
.last-transactions-content+.last-transactions-content, .row+.last-transactions-content {
padding: 0.8rem 1rem;
cursor: pointer;
margin: inherit;
}
/* Add margins to the session log */
.row.columns.large-centered.medium-centered {
padding-left: 0.9375rem;
padding-right: 0.9375rem;
}
/* Defeat Ionic .row+.row in backup failed view */
.backup .row {
margin-top: 10px;
padding: 0;
}
/* Override the default modal size for large devices */
@media (min-width: 680px) {
.modal {
top: 10%;
right: 20%;
bottom: 10%;
left: 20%;
min-height: 240px;
width: 60%;
}
}

View file

@ -6,11 +6,10 @@ var modules = [
'mm.foundation', 'mm.foundation',
'monospaced.qrcode', 'monospaced.qrcode',
'gettext', 'gettext',
'ionic',
'ngLodash', 'ngLodash',
'uiSwitch', 'ngSanitize',
'ngSanitize',
'ngCsv', 'ngCsv',
'ngTouch',
'bwcModule', 'bwcModule',
'copayApp.filters', 'copayApp.filters',
'copayApp.services', 'copayApp.services',

View file

@ -16,14 +16,11 @@ angular.module('copayApp.controllers').controller('backupController',
}); });
}; };
if (fc.credentials && !fc.credentials.mnemonicEncrypted && !fc.credentials.mnemonic) if (fc.isPrivKeyEncrypted() && !isDeletedSeed()) {
self.deleted = true;
if (fc.isPrivKeyEncrypted() && !self.deleted) {
self.credentialsEncrypted = true; self.credentialsEncrypted = true;
passwordRequest(); passwordRequest();
} else { } else {
if (!self.deleted) if (!isDeletedSeed())
initWords(); initWords();
} }
@ -34,12 +31,18 @@ angular.module('copayApp.controllers').controller('backupController',
self.shuffledMnemonicWords = shuffledWords(self.mnemonicWords); self.shuffledMnemonicWords = shuffledWords(self.mnemonicWords);
self.customWords = []; self.customWords = [];
self.step = 1; self.step = 1;
self.deleted = false; self.deleted = isDeletedSeed();
self.credentialsEncrypted = false; self.credentialsEncrypted = false;
self.selectComplete = false; self.selectComplete = false;
self.backupError = false; self.backupError = false;
}; };
function isDeletedSeed() {
if (lodash.isEmpty(fc.credentials.mnemonic) && lodash.isEmpty(fc.credentials.mnemonicEncrypted))
return true;
return false;
};
self.goToStep = function(n) { self.goToStep = function(n) {
self.step = n; self.step = n;
if (self.step == 1) if (self.step == 1)

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('buyCoinbaseController', angular.module('copayApp.controllers').controller('buyCoinbaseController',
function($scope, $modal, $log, $timeout, lodash, profileService, coinbaseService, animationService, bwsError, addressService, walletService) { function($scope, $modal, $log, $timeout, lodash, profileService, coinbaseService, animationService, bwsError, addressService) {
window.ignoreMobilePause = true; window.ignoreMobilePause = true;
var self = this; var self = this;
@ -72,7 +72,7 @@ angular.module('copayApp.controllers').controller('buyCoinbaseController',
$scope.selectWallet = function(walletId, walletName) { $scope.selectWallet = function(walletId, walletName) {
var client = profileService.getClient(walletId); var client = profileService.getClient(walletId);
walletService.isReady(client, function(err) { profileService.isReady(client, function(err) {
if (err) { if (err) {
self.error = {errors: [{ message: err }]}; self.error = {errors: [{ message: err }]};
$modalInstance.dismiss('cancel'); $modalInstance.dismiss('cancel');

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('buyGlideraController', angular.module('copayApp.controllers').controller('buyGlideraController',
function($scope, $timeout, $modal, profileService, addressService, glideraService, bwsError, lodash, animationService, walletService) { function($scope, $timeout, $modal, profileService, addressService, glideraService, bwsError, lodash, animationService) {
var self = this; var self = this;
this.show2faCodeInput = null; this.show2faCodeInput = null;
@ -51,7 +51,7 @@ angular.module('copayApp.controllers').controller('buyGlideraController',
$scope.selectWallet = function(walletId, walletName) { $scope.selectWallet = function(walletId, walletName) {
var client = profileService.getClient(walletId); var client = profileService.getClient(walletId);
walletService.isReady(client, function(err) { profileService.isReady(client, function(err) {
if (err) { if (err) {
self.error = err; self.error = err;
$modalInstance.dismiss('cancel'); $modalInstance.dismiss('cancel');

View file

@ -1,23 +0,0 @@
'use strict';
angular.module('copayApp.controllers').controller('confirmTxController', function(configService, feeService, rateService) {
this.processFee = function(amount, fee) {
var walletSettings = configService.getSync().wallet.settings;
var feeAlternativeIsoCode = walletSettings.alternativeIsoCode;
this.feeLevel = feeService.feeOpts[feeService.getCurrentFeeLevel()];
this.feeAlternativeStr = parseFloat((rateService.toFiat(fee, feeAlternativeIsoCode)).toFixed(2), 10) + ' ' + feeAlternativeIsoCode;
this.feeRateStr = (fee / (amount + fee) * 100).toFixed(2) + '%' ;
};
this.close = function(cb) {
return cb();
};
this.accept = function(cb) {
return cb(true);
};
});

View file

@ -54,15 +54,15 @@ angular.module('copayApp.controllers').controller('copayersController',
modalInstance.result.then(function(ok) { modalInstance.result.then(function(ok) {
if (ok) { if (ok) {
_deleteWallet(); doDeleteWallet();
} }
}); });
}; };
var _deleteWallet = function() { var doDeleteWallet = function() {
var fc = profileService.focusedClient; var fc = profileService.focusedClient;
var walletName = fc.credentials.walletName; var walletName = fc.credentials.walletName;
profileService.deleteWalletFC({}, function(err) { profileService.deleteWalletClient(fc, function(err) {
if (err) { if (err) {
self.error = err.message || err; self.error = err.message || err;
$timeout(function() { $timeout(function() {

View file

@ -1,13 +1,12 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('createController', angular.module('copayApp.controllers').controller('createController',
function($scope, $location, $anchorScroll, $rootScope, $timeout, $log, lodash, go, profileService, configService, gettext, ledger, trezor, platformInfo, derivationPathHelper) { function($scope, $ionicScrollDelegate, $rootScope, $timeout, $log, lodash, go, profileService, configService, gettext, ledger, trezor, platformInfo, derivationPathHelper) {
var isChromeApp = platformInfo.isChromeApp; var isChromeApp = platformInfo.isChromeApp;
var isCordova = platformInfo.isCordova; var isCordova = platformInfo.isCordova;
var isDevel = platformInfo.isDevel; var isDevel = platformInfo.isDevel;
var self = this; var self = this;
var defaults = configService.getDefaults(); var defaults = configService.getDefaults();
this.isWindowsPhoneApp = platformInfo.isWP && isCordova; this.isWindowsPhoneApp = platformInfo.isWP && isCordova;
@ -79,7 +78,6 @@ angular.module('copayApp.controllers').controller('createController',
self.seedSourceId = $scope.seedSource.id; self.seedSourceId = $scope.seedSource.id;
}; };
this.setSeedSource = function(src) { this.setSeedSource = function(src) {
self.seedSourceId = $scope.seedSource.id; self.seedSourceId = $scope.seedSource.id;
@ -91,6 +89,7 @@ angular.module('copayApp.controllers').controller('createController',
this.create = function(form) { this.create = function(form) {
if (form && form.$invalid) { if (form && form.$invalid) {
this.error = gettext('Please enter the required fields'); this.error = gettext('Please enter the required fields');
$ionicScrollDelegate.scrollTop();
return; return;
} }
@ -99,7 +98,7 @@ angular.module('copayApp.controllers').controller('createController',
n: $scope.totalCopayers, n: $scope.totalCopayers,
name: $scope.walletName, name: $scope.walletName,
myName: $scope.totalCopayers > 1 ? $scope.myName : null, myName: $scope.totalCopayers > 1 ? $scope.myName : null,
networkName: $scope.isTestnet ? 'testnet' : 'livenet', networkName: $scope.testnetEnabled ? 'testnet' : 'livenet',
bwsurl: $scope.bwsurl, bwsurl: $scope.bwsurl,
walletPrivKey: $scope._walletPrivKey, // Only for testing walletPrivKey: $scope._walletPrivKey, // Only for testing
}; };
@ -117,6 +116,7 @@ angular.module('copayApp.controllers').controller('createController',
var pathData = derivationPathHelper.parse($scope.derivationPath); var pathData = derivationPathHelper.parse($scope.derivationPath);
if (!pathData) { if (!pathData) {
this.error = gettext('Invalid derivation path'); this.error = gettext('Invalid derivation path');
$ionicScrollDelegate.scrollTop();
return; return;
} }
@ -130,6 +130,7 @@ angular.module('copayApp.controllers').controller('createController',
if (setSeed && !opts.mnemonic && !opts.extendedPrivateKey) { if (setSeed && !opts.mnemonic && !opts.extendedPrivateKey) {
this.error = gettext('Please enter the wallet recovery phrase'); this.error = gettext('Please enter the wallet recovery phrase');
$ionicScrollDelegate.scrollTop();
return; return;
} }
@ -137,10 +138,11 @@ angular.module('copayApp.controllers').controller('createController',
var account = $scope.account; var account = $scope.account;
if (!account || account < 1) { if (!account || account < 1) {
this.error = gettext('Invalid account number'); this.error = gettext('Invalid account number');
$ionicScrollDelegate.scrollTop();
return; return;
} }
if ( self.seedSourceId == 'trezor') if (self.seedSourceId == 'trezor')
account = account - 1; account = account - 1;
opts.account = account; opts.account = account;
@ -151,6 +153,7 @@ angular.module('copayApp.controllers').controller('createController',
self.hwWallet = false; self.hwWallet = false;
if (err) { if (err) {
self.error = err; self.error = err;
$ionicScrollDelegate.scrollTop();
$scope.$apply(); $scope.$apply();
return; return;
} }
@ -166,12 +169,12 @@ angular.module('copayApp.controllers').controller('createController',
self.loading = true; self.loading = true;
$timeout(function() { $timeout(function() {
profileService.createWallet(opts, function(err, walletId) { profileService.createWallet(opts, function(err) {
self.loading = false; self.loading = false;
if (err) { if (err) {
$log.warn(err); $log.warn(err);
self.error = err; self.error = err;
scrollUp('notification'); $ionicScrollDelegate.scrollTop();
$timeout(function() { $timeout(function() {
$rootScope.$apply(); $rootScope.$apply();
}); });
@ -183,12 +186,6 @@ angular.module('copayApp.controllers').controller('createController',
}, 100); }, 100);
} }
function scrollUp(location){
if(!location) return;
$location.hash(location);
$anchorScroll();
};
this.formFocus = function(what) { this.formFocus = function(what) {
if (!this.isWindowsPhoneApp) return if (!this.isWindowsPhoneApp) return

View file

@ -1,49 +1,53 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('disclaimerController', angular.module('copayApp.controllers').controller('disclaimerController',
function($scope, $timeout, $log, profileService, applicationService, gettextCatalog, uxLanguage, go) { function($scope, $timeout, $log, $ionicSideMenuDelegate, profileService, applicationService, gettextCatalog, uxLanguage, go) {
var self = this; var self = this;
self.tries = 0; self.tries = 0;
$scope.creatingProfile = true; self.creatingProfile = true;
var create = function(noWallet) {
profileService.create({
noWallet: noWallet
}, function(err) {
var create = function(opts) {
opts = opts || {};
$log.debug('Creating profile');
profileService.create(opts, function(err) {
if (err) { if (err) {
$log.warn(err); $log.warn(err);
$scope.error = err; $scope.error = err;
$scope.$apply(); $scope.$apply();
$timeout(function() {
return $timeout(function() {
$log.warn('Retrying to create profile......'); $log.warn('Retrying to create profile......');
if (self.tries == 3) { if (self.tries == 3) {
self.tries == 0; self.tries == 0;
create(true); return create({
noWallet: true
});
} else { } else {
self.tries += 1; self.tries += 1;
create(false); return create();
} }
}, 3000); }, 3000);
} else { };
$scope.error = "";
$scope.creatingProfile = false; $scope.error = "";
} self.creatingProfile = false;
}); });
}; };
this.init = function() { this.init = function(opts) {
$ionicSideMenuDelegate.canDragContent(false);
self.lang = uxLanguage.currentLanguage; self.lang = uxLanguage.currentLanguage;
profileService.getProfile(function(err, profile) { profileService.getProfile(function(err, profile) {
if (!profile) { if (!profile) {
create(false); create(opts);
} else { } else {
$log.debug('There is a profile already'); $log.info('There is already a profile');
$scope.creatingProfile = false; self.creatingProfile = false;
profileService.bindProfile(profile, function(err) { profileService.bindProfile(profile, function(err) {
if (!err || !err.message || !err.message.match('NONAGREEDDISCLAIMER')) { if (!err || !err.message || !err.message.match('NONAGREEDDISCLAIMER')) {
$log.debug('Disclaimer already accepted at #disclaimer. Redirect to Wallet Home.') $log.debug('Disclaimer already accepted at #disclaimer. Redirect to Wallet Home.');
$ionicSideMenuDelegate.canDragContent(true);
go.walletHome(); go.walletHome();
} }
}); });
@ -54,7 +58,10 @@ angular.module('copayApp.controllers').controller('disclaimerController',
this.accept = function() { this.accept = function() {
profileService.setDisclaimerAccepted(function(err) { profileService.setDisclaimerAccepted(function(err) {
if (err) $log.error(err); if (err) $log.error(err);
else go.walletHome(); else {
$ionicSideMenuDelegate.canDragContent(true);
go.walletHome();
}
}); });
}; };
}); });

View file

@ -8,18 +8,18 @@ angular.module('copayApp.controllers').controller('exportController',
self.error = null; self.error = null;
self.success = null; self.success = null;
$scope.metaData = true; $scope.metaDataEnabled = true;
var fc = profileService.focusedClient; var fc = profileService.focusedClient;
self.isEncrypted = fc.isPrivKeyEncrypted(); self.isEncrypted = fc.isPrivKeyEncrypted();
self.downloadWalletBackup = function() { self.downloadWalletBackup = function() {
self.getMetaData($scope.metaData, function(err, txsFromLocal, localAddressBook) { self.getMetaData($scope.metaDataEnabled, function(err, txsFromLocal, localAddressBook) {
if (err) { if (err) {
self.error = true; self.error = true;
return; return;
} }
var opts = { var opts = {
noSign: $scope.noSign, noSign: $scope.noSignEnabled,
historyCache: txsFromLocal, historyCache: txsFromLocal,
addressBook: localAddressBook addressBook: localAddressBook
}; };
@ -29,7 +29,6 @@ angular.module('copayApp.controllers').controller('exportController',
self.error = true; self.error = true;
return; return;
} }
$rootScope.$emit('Local/BackupDone'); $rootScope.$emit('Local/BackupDone');
notification.success(gettext('Success'), gettext('Encrypted export file saved')); notification.success(gettext('Success'), gettext('Encrypted export file saved'));
go.walletHome(); go.walletHome();
@ -83,13 +82,13 @@ angular.module('copayApp.controllers').controller('exportController',
} }
self.getBackup = function(cb) { self.getBackup = function(cb) {
self.getMetaData($scope.metaData, function(err, txsFromLocal, localAddressBook) { self.getMetaData($scope.metaDataEnabled, function(err, txsFromLocal, localAddressBook) {
if (err) { if (err) {
self.error = true; self.error = true;
return cb(null); return cb(null);
} }
var opts = { var opts = {
noSign: $scope.noSign, noSign: $scope.noSignEnabled,
historyCache: txsFromLocal, historyCache: txsFromLocal,
addressBook: localAddressBook addressBook: localAddressBook
}; };
@ -139,7 +138,7 @@ angular.module('copayApp.controllers').controller('exportController',
var ew = backup; var ew = backup;
if (!ew) return; if (!ew) return;
if ($scope.noSign) if ($scope.noSignEnabled)
name = name + '(No Private Key)'; name = name + '(No Private Key)';
var subject = 'Copay Wallet Backup: ' + name; var subject = 'Copay Wallet Backup: ' + name;

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('importController', angular.module('copayApp.controllers').controller('importController',
function($scope, $rootScope, $location, $timeout, $log, profileService, configService, notification, go, sjcl, gettext, lodash, ledger, trezor, derivationPathHelper, platformInfo) { function($scope, $rootScope, $ionicScrollDelegate, $timeout, $log, profileService, configService, notification, go, sjcl, gettext, lodash, ledger, trezor, derivationPathHelper, platformInfo) {
var isChromeApp = platformInfo.isChromeApp; var isChromeApp = platformInfo.isChromeApp;
var isDevel = platformInfo.isDevel; var isDevel = platformInfo.isDevel;
@ -39,8 +39,6 @@ angular.module('copayApp.controllers').controller('importController',
} }
}; };
this.setType = function(type) { this.setType = function(type) {
$scope.type = type; $scope.type = type;
this.error = null; this.error = null;
@ -60,6 +58,7 @@ angular.module('copayApp.controllers').controller('importController',
if (err) { if (err) {
self.error = err; self.error = err;
$ionicScrollDelegate.scrollTop();
$timeout(function() { $timeout(function() {
$rootScope.$apply(); $rootScope.$apply();
}); });
@ -75,6 +74,7 @@ angular.module('copayApp.controllers').controller('importController',
self.loading = false; self.loading = false;
if (err) { if (err) {
self.error = err; self.error = err;
$ionicScrollDelegate.scrollTop();
} else { } else {
$rootScope.$emit('Local/WalletImported', walletId); $rootScope.$emit('Local/WalletImported', walletId);
notification.success(gettext('Success'), gettext('Your wallet has been imported correctly')); notification.success(gettext('Success'), gettext('Your wallet has been imported correctly'));
@ -92,6 +92,7 @@ angular.module('copayApp.controllers').controller('importController',
self.loading = false; self.loading = false;
if (err) { if (err) {
self.error = err; self.error = err;
$ionicScrollDelegate.scrollTop();
return $timeout(function() { return $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
@ -111,6 +112,7 @@ angular.module('copayApp.controllers').controller('importController',
self.loading = false; self.loading = false;
if (err) { if (err) {
self.error = err; self.error = err;
$ionicScrollDelegate.scrollTop();
return $timeout(function() { return $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
@ -136,7 +138,7 @@ angular.module('copayApp.controllers').controller('importController',
this.importBlob = function(form) { this.importBlob = function(form) {
if (form.$invalid) { if (form.$invalid) {
this.error = gettext('There is an error in the form'); this.error = gettext('There is an error in the form');
$ionicScrollDelegate.scrollTop();
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
@ -149,6 +151,7 @@ angular.module('copayApp.controllers').controller('importController',
if (!backupFile && !backupText) { if (!backupFile && !backupText) {
this.error = gettext('Please, select your backup file'); this.error = gettext('Please, select your backup file');
$ionicScrollDelegate.scrollTop();
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
@ -168,7 +171,7 @@ angular.module('copayApp.controllers').controller('importController',
this.importMnemonic = function(form) { this.importMnemonic = function(form) {
if (form.$invalid) { if (form.$invalid) {
this.error = gettext('There is an error in the form'); this.error = gettext('There is an error in the form');
$ionicScrollDelegate.scrollTop();
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
@ -183,6 +186,7 @@ angular.module('copayApp.controllers').controller('importController',
var pathData = derivationPathHelper.parse($scope.derivationPath); var pathData = derivationPathHelper.parse($scope.derivationPath);
if (!pathData) { if (!pathData) {
this.error = gettext('Invalid derivation path'); this.error = gettext('Invalid derivation path');
$ionicScrollDelegate.scrollTop();
return; return;
} }
opts.account = pathData.account; opts.account = pathData.account;
@ -194,13 +198,16 @@ angular.module('copayApp.controllers').controller('importController',
if (!words) { if (!words) {
this.error = gettext('Please enter the recovery phrase'); this.error = gettext('Please enter the recovery phrase');
$ionicScrollDelegate.scrollTop();
} else if (words.indexOf('xprv') == 0 || words.indexOf('tprv') == 0) { } else if (words.indexOf('xprv') == 0 || words.indexOf('tprv') == 0) {
return _importExtendedPrivateKey(words, opts); return _importExtendedPrivateKey(words, opts);
} else { } else {
var wordList = words.split(/[\u3000\s]+/); var wordList = words.split(/[\u3000\s]+/);
if ((wordList.length % 3) != 0) if ((wordList.length % 3) != 0) {
this.error = gettext('Wrong number of recovery words:') + wordList.length; this.error = gettext('Wrong number of recovery words:') + wordList.length;
$ionicScrollDelegate.scrollTop();
}
} }
if (this.error) { if (this.error) {
@ -222,6 +229,7 @@ angular.module('copayApp.controllers').controller('importController',
self.hwWallet = false; self.hwWallet = false;
if (err) { if (err) {
self.error = err; self.error = err;
$ionicScrollDelegate.scrollTop();
$scope.$apply(); $scope.$apply();
return; return;
} }
@ -235,6 +243,7 @@ angular.module('copayApp.controllers').controller('importController',
self.loading = false; self.loading = false;
if (err) { if (err) {
self.error = err; self.error = err;
$ionicScrollDelegate.scrollTop();
return $timeout(function() { return $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
@ -247,8 +256,9 @@ angular.module('copayApp.controllers').controller('importController',
}; };
this.importHW = function(form) { this.importHW = function(form) {
if (form.$invalid || $scope.account < 0 ) { if (form.$invalid || $scope.account < 0) {
this.error = gettext('There is an error in the form'); this.error = gettext('There is an error in the form');
$ionicScrollDelegate.scrollTop();
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
@ -256,11 +266,12 @@ angular.module('copayApp.controllers').controller('importController',
} }
this.error = ''; this.error = '';
var account = + $scope.account; var account = +$scope.account;
if (self.seedSourceId == 'trezor') { if (self.seedSourceId == 'trezor') {
if ( account < 1) { if (account < 1) {
this.error = gettext('Invalid account number'); this.error = gettext('Invalid account number');
$ionicScrollDelegate.scrollTop();
return; return;
} }
account = account - 1; account = account - 1;
@ -296,6 +307,7 @@ angular.module('copayApp.controllers').controller('importController',
self.hwWallet = false; self.hwWallet = false;
if (err) { if (err) {
self.error = err; self.error = err;
$ionicScrollDelegate.scrollTop();
$scope.$apply(); $scope.$apply();
return; return;
} }
@ -309,6 +321,7 @@ angular.module('copayApp.controllers').controller('importController',
self.loading = false; self.loading = false;
if (err) { if (err) {
self.error = err; self.error = err;
$ionicScrollDelegate.scrollTop();
return $timeout(function() { return $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('indexController', function($rootScope, $scope, $log, $filter, $timeout, latestReleaseService, bwcService, pushNotificationsService, lodash, go, profileService, configService, rateService, storageService, addressService, gettext, gettextCatalog, amMoment, addonManager, bwsError, txFormatService, uxLanguage, glideraService, coinbaseService, platformInfo, addressbookService, walletService) { angular.module('copayApp.controllers').controller('indexController', function($rootScope, $scope, $log, $filter, $timeout, $ionicSideMenuDelegate, $ionicScrollDelegate, $ionicPopup, latestReleaseService, feeService, bwcService, pushNotificationsService, lodash, go, profileService, configService, rateService, storageService, addressService, gettext, gettextCatalog, amMoment, addonManager, bwsError, txFormatService, uxLanguage, glideraService, coinbaseService, platformInfo, addressbookService) {
var self = this; var self = this;
var SOFT_CONFIRMATION_LIMIT = 12; var SOFT_CONFIRMATION_LIMIT = 12;
var errors = bwcService.getErrors(); var errors = bwcService.getErrors();
@ -16,7 +16,7 @@ angular.module('copayApp.controllers').controller('indexController', function($r
ret.isWindowsPhoneApp = platformInfo.isWP; ret.isWindowsPhoneApp = platformInfo.isWP;
ret.onGoingProcess = {}; ret.onGoingProcess = {};
ret.historyShowLimit = 10; ret.historyShowLimit = 10;
ret.historyShowMoreLimit = 100; ret.historyShowMoreLimit = 10;
ret.isSearching = false; ret.isSearching = false;
ret.prevState = 'walletHome'; ret.prevState = 'walletHome';
@ -69,6 +69,23 @@ angular.module('copayApp.controllers').controller('indexController', function($r
go.walletHome(); go.walletHome();
}; };
self.onDrag = function() {
if (!isCordova)
$ionicSideMenuDelegate.canDragContent(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, function() {});
}
self.setOngoingProcess = function(processName, isOn) { self.setOngoingProcess = function(processName, isOn) {
$log.debug('onGoingProcess', processName, isOn); $log.debug('onGoingProcess', processName, isOn);
self[processName] = isOn; self[processName] = isOn;
@ -153,6 +170,8 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.initGlidera(); self.initGlidera();
self.initCoinbase(); self.initCoinbase();
self.hideBalance();
self.setCustomBWSFlag(); self.setCustomBWSFlag();
if (!self.isComplete) { if (!self.isComplete) {
@ -165,7 +184,7 @@ angular.module('copayApp.controllers').controller('indexController', function($r
} }
} }
walletService.isBackupNeeded(fc, function(needsBackup) { profileService.needsBackup(fc, function(needsBackup) {
self.needsBackup = needsBackup; self.needsBackup = needsBackup;
self.openWallet(function() { self.openWallet(function() {
if (!self.isComplete) { if (!self.isComplete) {
@ -249,53 +268,6 @@ angular.module('copayApp.controllers').controller('indexController', function($r
}; };
self._updateRemotePreferencesFor = function(clients, prefs, cb) {
var client = clients.shift();
if (!client)
return cb();
$log.debug('Saving remote preferences', client.credentials.walletName, prefs);
client.savePreferences(prefs, function(err) {
// we ignore errors here
if (err) $log.warn(err);
self._updateRemotePreferencesFor(clients, prefs, cb);
});
};
self.updateRemotePreferences = function(opts, cb) {
var prefs = opts.preferences || {};
var fc = profileService.focusedClient;
// Update this JIC.
var config = configService.getSync().wallet.settings;
//prefs.email (may come from arguments)
prefs.language = self.defaultLanguageIsoCode;
prefs.unit = config.unitCode;
var clients = [];
if (opts.saveAll) {
clients = lodash.values(profileService.walletClients);
} else {
clients = [fc];
};
self._updateRemotePreferencesFor(clients, prefs, function(err) {
if (err) return cb(err);
if (!fc) return cb();
fc.getPreferences(function(err, preferences) {
if (err) {
return cb(err);
}
self.preferences = preferences;
return cb();
});
});
};
var _walletStatusHash = function(walletStatus) { var _walletStatusHash = function(walletStatus) {
var bal; var bal;
@ -307,7 +279,9 @@ angular.module('copayApp.controllers').controller('indexController', function($r
return bal; return bal;
}; };
// TODO move this to wallet service
self.updateAll = function(opts, initStatusHash, tries) { self.updateAll = function(opts, initStatusHash, tries) {
$scope.$broadcast('scroll.refreshComplete');
tries = tries || 0; tries = tries || 0;
opts = opts || {}; opts = opts || {};
var fc = profileService.focusedClient; var fc = profileService.focusedClient;
@ -575,6 +549,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;
@ -677,148 +656,6 @@ angular.module('copayApp.controllers').controller('indexController', function($r
} }
}; };
self.csvHistory = function() {
function saveFile(name, data) {
var chooser = document.querySelector(name);
chooser.addEventListener("change", function(evt) {
var fs = require('fs');
fs.writeFile(this.value, data, function(err) {
if (err) {
$log.debug(err);
}
});
}, false);
chooser.click();
}
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)
@ -879,6 +716,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
@ -933,24 +771,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();
});
}); });
}); });
}); });
@ -965,12 +831,12 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.historyShowMore = false; self.historyShowMore = false;
} else { } else {
self.txHistory = self.completeHistory.slice(0, self.nextTxHistory); self.txHistory = self.completeHistory.slice(0, self.nextTxHistory);
self.txHistorySearchResults = self.txHistory; $log.debug('Total txs: ', self.txHistory.length + '/' + self.completeHistory.length);
$log.debug('Total txs: ', self.txHistorySearchResults.length + '/' + self.completeHistory.length); if (self.txHistory.length >= self.completeHistory.length)
if (self.txHistorySearchResults.length >= self.completeHistory.length)
self.historyShowMore = false; self.historyShowMore = false;
} }
self.nextTxHistory += self.historyShowMoreLimit; self.nextTxHistory += self.historyShowMoreLimit;
$scope.$broadcast('scroll.infiniteScrollComplete');
}, 100); }, 100);
}; };
@ -993,6 +859,7 @@ angular.module('copayApp.controllers').controller('indexController', function($r
if (isCordova) if (isCordova)
window.plugins.toast.hide(); window.plugins.toast.hide();
self.throttleSearch(); self.throttleSearch();
$ionicScrollDelegate.resize();
} }
self.throttleSearch = lodash.throttle(function() { self.throttleSearch = lodash.throttle(function() {
@ -1005,8 +872,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) {
@ -1095,7 +963,6 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.isSearching = false; self.isSearching = false;
self.nextTxHistory = self.historyShowMoreLimit; self.nextTxHistory = self.historyShowMoreLimit;
self.txHistory = self.completeHistory ? self.completeHistory.slice(0, self.historyShowLimit) : null; self.txHistory = self.completeHistory ? self.completeHistory.slice(0, self.historyShowLimit) : null;
self.txHistorySearchResults = self.txHistory;
self.historyShowMore = self.completeHistory ? self.completeHistory.length > self.historyShowLimit : null; self.historyShowMore = self.completeHistory ? self.completeHistory.length > self.historyShowLimit : null;
}; };
@ -1109,15 +976,23 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.showErrorPopup = function(msg, cb) { self.showErrorPopup = function(msg, cb) {
$log.warn('Showing err popup:' + msg); $log.warn('Showing err popup:' + msg);
self.showAlert = {
msg: msg, function openErrorPopup(msg, cb) {
close: function() { $scope.msg = msg;
self.showAlert = null;
if (cb) return cb(); self.errorPopup = $ionicPopup.show({
}, templateUrl: 'views/includes/alert.html',
}; scope: $scope,
$timeout(function() { });
$rootScope.$apply();
$scope.close = function() {
return cb();
};
}
openErrorPopup(msg, function() {
self.errorPopup.close();
if (cb) return cb();
}); });
}; };
@ -1134,23 +1009,21 @@ angular.module('copayApp.controllers').controller('indexController', function($r
return; return;
} }
profileService.setWalletClients(); profileService.bindWalletClient(fc, {
force: true
});
self.startScan(self.walletId); self.startScan(self.walletId);
}); });
}; };
self.openMenu = function() { self.toggleLeftMenu = function() {
profileService.isDisclaimerAccepted(function(val) { profileService.isDisclaimerAccepted(function(val) {
if (val) go.swipe(true); if (val) go.toggleLeftMenu();
else else
$log.debug('Disclaimer not accepted, cannot open menu'); $log.debug('Disclaimer not accepted, cannot open menu');
}); });
}; };
self.closeMenu = function() {
go.swipe();
};
self.retryScan = function() { self.retryScan = function() {
var self = this; var self = this;
self.startScan(self.walletId); self.startScan(self.walletId);
@ -1175,14 +1048,6 @@ angular.module('copayApp.controllers').controller('indexController', function($r
}); });
}; };
self.setUxLanguage = function(cb) {
uxLanguage.update(function(lang) {
var userLang = lang;
self.defaultLanguageIsoCode = userLang;
self.defaultLanguageName = uxLanguage.getName(userLang);
if (cb) return cb();
});
};
self.initGlidera = function(accessToken) { self.initGlidera = function(accessToken) {
self.glideraEnabled = configService.getSync().glidera.enabled; self.glideraEnabled = configService.getSync().glidera.enabled;
@ -1354,16 +1219,19 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.coinbasePendingTransactions = lodash.isEmpty(txs) ? null : txs; self.coinbasePendingTransactions = lodash.isEmpty(txs) ? null : txs;
lodash.forEach(txs, function(dataFromStorage, txId) { lodash.forEach(txs, function(dataFromStorage, txId) {
if ((dataFromStorage.type == 'sell' && dataFromStorage.status == 'completed') || if ((dataFromStorage.type == 'sell' && dataFromStorage.status == 'completed') ||
(dataFromStorage.type == 'buy' && dataFromStorage.status == 'completed') || (dataFromStorage.type == 'buy' && dataFromStorage.status == 'completed') ||
dataFromStorage.status == 'error' || dataFromStorage.status == 'error' ||
(dataFromStorage.type == 'send' && dataFromStorage.status == 'completed')) return; (dataFromStorage.type == 'send' && dataFromStorage.status == 'completed')) return;
coinbaseService.getTransaction(accessToken, accountId, txId, function(err, tx) { coinbaseService.getTransaction(accessToken, accountId, txId, function(err, tx) {
if (err) { if (err) {
if (err.errors[0] && err.errors[0].id == 'expired_token') { if (err.errors[0] && err.errors[0].id == 'expired_token') {
self.refreshCoinbaseToken(); self.refreshCoinbaseToken();
return; return;
} }
coinbaseService.savePendingTransaction(dataFromStorage, {status: 'error', error: err}, function(err) { coinbaseService.savePendingTransaction(dataFromStorage, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err); if (err) $log.debug(err);
}); });
return; return;
@ -1377,7 +1245,10 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.refreshCoinbaseToken(); self.refreshCoinbaseToken();
return; return;
} }
coinbaseService.savePendingTransaction(dataFromStorage, {status: 'error', error: err}, function(err) { coinbaseService.savePendingTransaction(dataFromStorage, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err); if (err) $log.debug(err);
}); });
return; return;
@ -1387,8 +1258,15 @@ angular.module('copayApp.controllers').controller('indexController', function($r
if (variance < dataFromStorage.price_sensitivity.value) { if (variance < dataFromStorage.price_sensitivity.value) {
self.sellPending(tx.data); self.sellPending(tx.data);
} else { } else {
var error = {errors: [{ message: 'Price falls over the selected percentage' }]}; var error = {
coinbaseService.savePendingTransaction(dataFromStorage, {status: 'error', error: error}, function(err) { errors: [{
message: 'Price falls over the selected percentage'
}]
};
coinbaseService.savePendingTransaction(dataFromStorage, {
status: 'error',
error: error
}, function(err) {
if (err) $log.debug(err); if (err) $log.debug(err);
}); });
} }
@ -1406,8 +1284,8 @@ angular.module('copayApp.controllers').controller('indexController', function($r
}, 1000); }, 1000);
var _updateCoinbasePendingTransactions = function (obj/*, …*/) { var _updateCoinbasePendingTransactions = function(obj /*, …*/ ) {
for (var i=1; i<arguments.length; i++) { for (var i = 1; i < arguments.length; i++) {
for (var prop in arguments[i]) { for (var prop in arguments[i]) {
var val = arguments[i][prop]; var val = arguments[i][prop];
if (typeof val == "object") if (typeof val == "object")
@ -1453,21 +1331,31 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.refreshCoinbaseToken(); self.refreshCoinbaseToken();
return; return;
} }
coinbaseService.savePendingTransaction(tx, {status: 'error', error: err}, function(err) { coinbaseService.savePendingTransaction(tx, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err); if (err) $log.debug(err);
}); });
} else { } else {
if (!res.data.id) { if (!res.data.id) {
coinbaseService.savePendingTransaction(tx, {status: 'error', error: err}, function(err) { coinbaseService.savePendingTransaction(tx, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err); if (err) $log.debug(err);
}); });
return; return;
} }
coinbaseService.getTransaction(self.coinbaseToken, self.coinbaseAccount.id, res.data.id, function(err, sendTx) { coinbaseService.getTransaction(self.coinbaseToken, self.coinbaseAccount.id, res.data.id, function(err, sendTx) {
coinbaseService.savePendingTransaction(tx, {remove: true}, function(err) { coinbaseService.savePendingTransaction(tx, {
remove: true
}, function(err) {
coinbaseService.savePendingTransaction(sendTx.data, {}, function(err) { coinbaseService.savePendingTransaction(sendTx.data, {}, function(err) {
$timeout(function() { $timeout(function() {
self.updateCoinbase({updateAccount: true}); self.updateCoinbase({
updateAccount: true
});
}, 1000); }, 1000);
}); });
}); });
@ -1486,22 +1374,32 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.refreshCoinbaseToken(); self.refreshCoinbaseToken();
return; return;
} }
coinbaseService.savePendingTransaction(tx, {status: 'error', error: err}, function(err) { coinbaseService.savePendingTransaction(tx, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err); if (err) $log.debug(err);
}); });
} else { } else {
if (!res.data.transaction) { if (!res.data.transaction) {
coinbaseService.savePendingTransaction(tx, {status: 'error', error: err}, function(err) { coinbaseService.savePendingTransaction(tx, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err); if (err) $log.debug(err);
}); });
return; return;
} }
coinbaseService.savePendingTransaction(tx, {remove: true}, function(err) { coinbaseService.savePendingTransaction(tx, {
remove: true
}, function(err) {
coinbaseService.getTransaction(self.coinbaseToken, self.coinbaseAccount.id, res.data.transaction.id, function(err, updatedTx) { coinbaseService.getTransaction(self.coinbaseToken, self.coinbaseAccount.id, res.data.transaction.id, function(err, updatedTx) {
coinbaseService.savePendingTransaction(updatedTx.data, {}, function(err) { coinbaseService.savePendingTransaction(updatedTx.data, {}, function(err) {
if (err) $log.debug(err); if (err) $log.debug(err);
$timeout(function() { $timeout(function() {
self.updateCoinbase({updateAccount: true}); self.updateCoinbase({
updateAccount: true
});
}, 1000); }, 1000);
}); });
}); });
@ -1560,28 +1458,6 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.updateAll(); self.updateAll();
}); });
$rootScope.$on('Local/ProfileBound', function() {
storageService.getRemotePrefsStoredFlag(function(err, val) {
if (err || val) return;
self.updateRemotePreferences({
saveAll: true
}, function() {
$log.debug('Remote preferences saved');
storageService.setRemotePrefsStoredFlag(function() {});
});
});
});
$rootScope.$on('Local/LanguageSettingUpdated', function() {
self.setUxLanguage(function() {
self.updateRemotePreferences({
saveAll: true
}, function() {
$log.debug('Remote preferences saved')
});
});
});
$rootScope.$on('Local/GlideraUpdated', function(event, accessToken) { $rootScope.$on('Local/GlideraUpdated', function(event, accessToken) {
self.initGlidera(accessToken); self.initGlidera(accessToken);
}); });
@ -1608,19 +1484,6 @@ angular.module('copayApp.controllers').controller('indexController', function($r
self.updateAll({ self.updateAll({
triggerTxUpdate: true, triggerTxUpdate: true,
}); });
self.updateRemotePreferences({
saveAll: true
}, function() {
$log.debug('Remote preferences saved')
});
});
$rootScope.$on('Local/EmailSettingUpdated', function(event, email, cb) {
self.updateRemotePreferences({
preferences: {
email: email || null
},
}, cb);
}); });
$rootScope.$on('Local/WalletCompleted', function(event, walletId) { $rootScope.$on('Local/WalletCompleted', function(event, walletId) {
@ -1632,14 +1495,6 @@ angular.module('copayApp.controllers').controller('indexController', function($r
} }
}); });
$rootScope.$on('Local/ProfileCreated', function(event) {
self.updateRemotePreferences({
saveAll: true
}, function() {
$log.debug('Remote preferences saved');
});
});
self.debouncedUpdate = lodash.throttle(function() { self.debouncedUpdate = lodash.throttle(function() {
self.updateAll({ self.updateAll({
quiet: true quiet: true
@ -1692,13 +1547,6 @@ angular.module('copayApp.controllers').controller('indexController', function($r
}); });
}); });
$rootScope.$on('Local/TxModal', function(event, tx) {
self.showTx = tx;
$timeout(function() {
$rootScope.$apply();
});
});
$rootScope.$on('NewIncomingTx', function() { $rootScope.$on('NewIncomingTx', function() {
self.newTx = true; self.newTx = true;
self.updateAll({ self.updateAll({
@ -1800,13 +1648,13 @@ angular.module('copayApp.controllers').controller('indexController', function($r
}); });
$rootScope.$on('Local/NoWallets', function(event) { $rootScope.$on('Local/NoWallets', function(event) {
$timeout(function() { $timeout(function() {
self.hasProfile = true; self.hasProfile = true;
self.noFocusedWallet = true; self.noFocusedWallet = true;
self.isComplete = null; self.isComplete = null;
self.walletName = null; self.walletName = null;
self.setUxLanguage(); uxLanguage.update();
profileService.isDisclaimerAccepted(function(v) { profileService.isDisclaimerAccepted(function(v) {
if (v) { if (v) {
go.path('import'); go.path('import');
@ -1816,7 +1664,7 @@ angular.module('copayApp.controllers').controller('indexController', function($r
}); });
$rootScope.$on('Local/NewFocusedWallet', function() { $rootScope.$on('Local/NewFocusedWallet', function() {
self.setUxLanguage(); uxLanguage.update();
self.setFocusedWallet(); self.setFocusedWallet();
self.updateHistory(); self.updateHistory();
storageService.getCleanAndScanAddresses(function(err, walletId) { storageService.getCleanAndScanAddresses(function(err, walletId) {
@ -1840,29 +1688,93 @@ angular.module('copayApp.controllers').controller('indexController', function($r
}); });
$rootScope.$on('Local/NeedsConfirmation', function(event, txp, cb) { $rootScope.$on('Local/NeedsConfirmation', function(event, txp, cb) {
self.confirmTx = {
txp: txFormatService.processTx(txp), function openConfirmationPopup(txp, cb) {
callback: function(accept) {
self.confirmTx = null; $scope.tx = txFormatService.processTx(txp);
return cb(accept);
} self.confirmationPopup = $ionicPopup.show({
}; templateUrl: 'views/includes/confirm-tx.html',
$timeout(function() { scope: $scope,
$rootScope.$apply(); });
$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) { $rootScope.$on('Local/NeedsPassword', function(event, isSetup, cb) {
self.askPassword = {
isSetup: isSetup, function openPasswordPopup(isSetup, cb) {
callback: function(err, pass) { $scope.data = {};
self.askPassword = null; $scope.data.password = null;
return cb(err, pass); $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.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);
};
}; };
$timeout(function() {
$rootScope.$apply(); 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) { lodash.each(['NewCopayer', 'CopayerUpdated'], function(eventName) {

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('joinController', angular.module('copayApp.controllers').controller('joinController',
function($scope, $rootScope, $timeout, go, notification, profileService, configService, storageService, applicationService, $modal, gettext, lodash, ledger, trezor, platformInfo, derivationPathHelper) { function($scope, $rootScope, $timeout, $ionicScrollDelegate, go, notification, profileService, configService, storageService, applicationService, $modal, gettext, lodash, ledger, trezor, platformInfo, derivationPathHelper) {
var isChromeApp = platformInfo.isChromeApp; var isChromeApp = platformInfo.isChromeApp;
var isDevel = platformInfo.isDevel; var isDevel = platformInfo.isDevel;
@ -18,7 +18,6 @@ angular.module('copayApp.controllers').controller('joinController',
$scope.joinForm.secret.$render(); $scope.joinForm.secret.$render();
}; };
var updateSeedSourceSelect = function() { var updateSeedSourceSelect = function() {
self.seedOptions = [{ self.seedOptions = [{
id: 'new', id: 'new',
@ -56,6 +55,7 @@ angular.module('copayApp.controllers').controller('joinController',
this.join = function(form) { this.join = function(form) {
if (form && form.$invalid) { if (form && form.$invalid) {
self.error = gettext('Please enter the required fields'); self.error = gettext('Please enter the required fields');
$ionicScrollDelegate.scrollTop();
return; return;
} }
@ -65,7 +65,7 @@ angular.module('copayApp.controllers').controller('joinController',
bwsurl: $scope.bwsurl, bwsurl: $scope.bwsurl,
} }
var setSeed = self.seedSourceId =='set'; var setSeed = self.seedSourceId == 'set';
if (setSeed) { if (setSeed) {
var words = form.privateKey.$modelValue; var words = form.privateKey.$modelValue;
if (words.indexOf(' ') == -1 && words.indexOf('prv') == 1 && words.length > 108) { if (words.indexOf(' ') == -1 && words.indexOf('prv') == 1 && words.length > 108) {
@ -78,6 +78,7 @@ angular.module('copayApp.controllers').controller('joinController',
var pathData = derivationPathHelper.parse($scope.derivationPath); var pathData = derivationPathHelper.parse($scope.derivationPath);
if (!pathData) { if (!pathData) {
this.error = gettext('Invalid derivation path'); this.error = gettext('Invalid derivation path');
$ionicScrollDelegate.scrollTop();
return; return;
} }
opts.account = pathData.account; opts.account = pathData.account;
@ -91,6 +92,7 @@ angular.module('copayApp.controllers').controller('joinController',
if (setSeed && !opts.mnemonic && !opts.extendedPrivateKey) { if (setSeed && !opts.mnemonic && !opts.extendedPrivateKey) {
this.error = gettext('Please enter the wallet recovery phrase'); this.error = gettext('Please enter the wallet recovery phrase');
$ionicScrollDelegate.scrollTop();
return; return;
} }
@ -98,14 +100,14 @@ angular.module('copayApp.controllers').controller('joinController',
var account = $scope.account; var account = $scope.account;
if (!account || account < 1) { if (!account || account < 1) {
this.error = gettext('Invalid account number'); this.error = gettext('Invalid account number');
$ionicScrollDelegate.scrollTop();
return; return;
} }
if ( self.seedSourceId == 'trezor') if (self.seedSourceId == 'trezor')
account = account - 1; account = account - 1;
opts.account = account; opts.account = account;
self.hwWallet = self.seedSourceId == 'ledger' ? 'Ledger' : 'Trezor'; self.hwWallet = self.seedSourceId == 'ledger' ? 'Ledger' : 'Trezor';
var src = self.seedSourceId == 'ledger' ? ledger : trezor; var src = self.seedSourceId == 'ledger' ? ledger : trezor;
@ -113,6 +115,7 @@ angular.module('copayApp.controllers').controller('joinController',
self.hwWallet = false; self.hwWallet = false;
if (err) { if (err) {
self.error = err; self.error = err;
$ionicScrollDelegate.scrollTop();
$scope.$apply(); $scope.$apply();
return; return;
} }
@ -132,6 +135,7 @@ angular.module('copayApp.controllers').controller('joinController',
if (err) { if (err) {
self.loading = false; self.loading = false;
self.error = err; self.error = err;
$ionicScrollDelegate.scrollTop();
$rootScope.$apply(); $rootScope.$apply();
return; return;
} }

View file

@ -0,0 +1,153 @@
'use strict';
angular.module('copayApp.controllers').controller('addressbookController', function($rootScope, $scope, $timeout, profileService, addressService, addressbookService) {
var self = $scope.self;
var fc = profileService.focusedClient;
self.lockAddress = false;
self._address = null;
$scope.editAddressbook = false;
$scope.addAddressbookEntry = false;
$scope.selectedAddressbook = {};
$scope.newAddress = address;
$scope.walletName = fc.credentials.walletName;
$scope.color = fc.backgroundColor;
$scope.addressbook = {
'address': ($scope.newAddress || ''),
'label': ''
};
$scope.checkClipboard = function() {
if (!$scope.newAddress) {
getClipboard(function(value) {
$scope.newAddress = value;
});
}
};
$scope.beforeQrCodeScann = function() {
$scope.error = null;
$scope.addAddressbookEntry = true;
$scope.editAddressbook = false;
};
$scope.onQrCodeScanned = function(data, addressbookForm) {
$timeout(function() {
var form = addressbookForm;
if (data && form) {
data = data.replace('bitcoin:', '');
form.address.$setViewValue(data);
form.address.$isValid = true;
form.address.$render();
}
$scope.$digest();
}, 100);
};
$scope.toggleEditAddressbook = function() {
$scope.editAddressbook = !$scope.editAddressbook;
$scope.selectedAddressbook = {};
$scope.addAddressbookEntry = false;
};
$scope.selectAddressbook = function(addr) {
self.setForm(addr);
$scope.cancel();
};
$scope.toggleSelectAddressbook = function(addr) {
$scope.selectedAddressbook[addr] = $scope.selectedAddressbook[addr] ? false : true;
};
$scope.toggleAddAddressbookEntry = function() {
$scope.error = null;
$scope.addressbook = {
'address': '',
'label': ''
};
$scope.addAddressbookEntry = !$scope.addAddressbookEntry;
};
$scope.list = function() {
$scope.error = null;
addressbookService.list(function(err, ab) {
if (err) {
$scope.error = err;
return;
}
$scope.list = ab;
$timeout(function() {
$scope.$digest();
});
});
};
$scope.add = function(addressbook) {
$scope.error = null;
$timeout(function() {
addressbookService.add(addressbook, function(err, ab) {
if (err) {
$scope.error = err;
return;
}
$rootScope.$emit('Local/AddressbookUpdated', ab);
$scope.list = ab;
$scope.editAddressbook = true;
$scope.toggleEditAddressbook();
$scope.$digest();
});
}, 100);
};
$scope.remove = function(addr) {
$scope.error = null;
$timeout(function() {
addressbookService.remove(addr, function(err, ab) {
if (err) {
$scope.error = err;
return;
}
$rootScope.$emit('Local/AddressbookUpdated', ab);
$scope.list = ab;
$scope.$digest();
});
}, 100);
};
$scope.selectWallet = function(walletId, walletName) {
var client = profileService.getClient(walletId);
$scope.errorSelectedWallet = {};
profileService.isReady(client, function(err) {
if (err) $scope.errorSelectedWallet[walletId] = err;
else {
$scope.gettingAddress = true;
$scope.selectedWalletName = walletName;
$timeout(function() {
$scope.$apply();
});
addressService.getAddress(walletId, false, function(err, addr) {
$scope.gettingAddress = false;
if (err) {
self.error = err;
$scope.cancelAddress();
return;
}
self.setForm(addr);
$scope.cancel();
});
}
});
};
$scope.cancelAddress = function() {
self.resetForm();
$scope.cancel();
};
$scope.cancel = function() {
$scope.addressbookModal.hide();
};
});

View file

@ -0,0 +1,82 @@
'use strict';
angular.module('copayApp.controllers').controller('customAmountController', function($scope, $timeout, $filter, platformInfo, rateService) {
var self = $scope.self;
$scope.unitName = self.unitName;
$scope.alternativeAmount = self.alternativeAmount;
$scope.alternativeName = self.alternativeName;
$scope.alternativeIsoCode = self.alternativeIsoCode;
$scope.isRateAvailable = self.isRateAvailable;
$scope.unitToSatoshi = self.unitToSatoshi;
$scope.unitDecimals = self.unitDecimals;
var satToUnit = 1 / self.unitToSatoshi;
$scope.showAlternative = false;
$scope.isCordova = platformInfo.isCordova;
Object.defineProperty($scope,
"_customAlternative", {
get: function() {
return $scope.customAlternative;
},
set: function(newValue) {
$scope.customAlternative = newValue;
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
$scope.customAmount = parseFloat((rateService.fromFiat(newValue, $scope.alternativeIsoCode) * satToUnit).toFixed($scope.unitDecimals), 10);
} else {
$scope.customAmount = null;
}
},
enumerable: true,
configurable: true
});
Object.defineProperty($scope,
"_customAmount", {
get: function() {
return $scope.customAmount;
},
set: function(newValue) {
$scope.customAmount = newValue;
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
$scope.customAlternative = parseFloat((rateService.toFiat(newValue * $scope.unitToSatoshi, $scope.alternativeIsoCode)).toFixed(2), 10);
} else {
$scope.customAlternative = null;
}
$scope.alternativeAmount = $scope.customAlternative;
},
enumerable: true,
configurable: true
});
$scope.submitForm = function(form) {
var satToBtc = 1 / 100000000;
var amount = form.amount.$modelValue;
var amountSat = parseInt((amount * $scope.unitToSatoshi).toFixed(0));
$timeout(function() {
$scope.customizedAmountUnit = amount + ' ' + $scope.unitName;
$scope.customizedAlternativeUnit = $filter('noFractionNumber')(form.alternative.$modelValue, 2) + ' ' + $scope.alternativeIsoCode;
if ($scope.unitName == 'bits') {
amount = (amountSat * satToBtc).toFixed(8);
}
$scope.customizedAmountBtc = amount;
}, 1);
};
$scope.toggleAlternative = function() {
$scope.showAlternative = !$scope.showAlternative;
};
$scope.shareAddress = function(uri) {
if (platformInfo.isCordova) {
if (platformInfo.isAndroid || platformInfo.isWP) {
window.ignoreMobilePause = true;
}
window.plugins.socialsharing.share(uri, null, null, null);
}
};
$scope.cancel = function() {
$scope.customAmountModal.hide();
};
});

View file

@ -0,0 +1,15 @@
'use strict';
angular.module('copayApp.controllers').controller('payproController', function($scope) {
var self = $scope.self;
$scope.alternative = self.alternativeAmount;
$scope.alternativeIsoCode = self.alternativeIsoCode;
$scope.isRateAvailable = self.isRateAvailable;
$scope.unitTotal = ($scope.paypro.amount * self.satToUnit).toFixed(self.unitDecimals);
$scope.unitName = self.unitName;
$scope.cancel = function() {
$scope.payproModal.hide();
};
});

View file

@ -0,0 +1,10 @@
'use strict';
angular.module('copayApp.controllers').controller('searchController', function($scope) {
var self = $scope.self;
$scope.search = '';
$scope.cancel = function() {
$scope.searchModal.hide();
};
});

View file

@ -0,0 +1,93 @@
'use strict';
angular.module('copayApp.controllers').controller('txDetailsController', function($rootScope, $log, $scope, $filter, $ionicPopup, gettextCatalog, profileService, configService, lodash) {
var self = $scope.self;
var fc = profileService.focusedClient;
var config = configService.getSync();
var configWallet = config.wallet;
var walletSettings = configWallet.settings;
$scope.alternativeIsoCode = walletSettings.alternativeIsoCode;
$scope.color = fc.backgroundColor;
$scope.copayerId = fc.credentials.copayerId;
$scope.isShared = fc.credentials.n > 1;
$scope.showCommentPopup = function() {
$scope.data = {
comment: $scope.btx.note ? $scope.btx.note.body : '',
};
var commentPopup = $ionicPopup.show({
templateUrl: "views/includes/note.html",
scope: $scope,
});
$scope.commentPopupClose = function() {
commentPopup.close();
};
$scope.commentPopupSave = function() {
$log.debug('Saving note');
var args = {
txid: $scope.btx.txid,
};
if (!lodash.isEmpty($scope.data.comment)) {
args.body = $scope.data.comment;
};
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();
});
};
};
$scope.getAlternativeAmount = function() {
var satToBtc = 1 / 100000000;
fc.getFiatRate({
code: $scope.alternativeIsoCode,
ts: $scope.btx.time * 1000
}, function(err, res) {
if (err) {
$log.debug('Could not get historic rate');
return;
}
if (res && res.rate) {
var alternativeAmountBtc = ($scope.btx.amount * satToBtc).toFixed(8);
$scope.rateDate = res.fetchedOn;
$scope.rateStr = res.rate + ' ' + $scope.alternativeIsoCode;
$scope.alternativeAmountStr = $filter('noFractionNumber')(alternativeAmountBtc * res.rate, 2) + ' ' + $scope.alternativeIsoCode;
$scope.$apply();
}
});
};
$scope.getShortNetworkName = function() {
var n = fc.credentials.network;
return n.substring(0, 4);
};
$scope.copyToClipboard = function(addr) {
if (!addr) return;
self.copyToClipboard(addr);
};
$scope.cancel = function() {
$scope.txDetailsModal.hide();
};
});

View file

@ -0,0 +1,240 @@
'use strict';
angular.module('copayApp.controllers').controller('txpDetailsController', function($scope, $rootScope, $timeout, $interval, platformInfo, txStatus, $ionicScrollDelegate, txFormatService, fingerprintService, bwsError, gettextCatalog, lodash, profileService, walletService) {
var self = $scope.self;
var tx = $scope.tx;
var copayers = $scope.copayers;
var isGlidera = $scope.isGlidera;
var now = Math.floor(Date.now() / 1000);
var fc = profileService.focusedClient;
$scope.loading = null;
$scope.copayerId = fc.credentials.copayerId;
$scope.isShared = fc.credentials.n > 1;
$scope.canSign = fc.canSign() || fc.isPrivKeyExternal();
$scope.color = fc.backgroundColor;
checkPaypro();
// ToDo: use tx.customData instead of tx.message
if (tx.message === 'Glidera transaction' && isGlidera) {
tx.isGlidera = true;
if (tx.canBeRemoved) {
tx.canBeRemoved = (Date.now() / 1000 - (tx.ts || tx.createdOn)) > GLIDERA_LOCK_TIME;
}
}
$scope.sign = function(txp) {
$scope.error = null;
$scope.loading = 'Signing Transaction';
fingerprintService.check(fc, function(err) {
if (err) {
$scope.error = err;
$scope.loading = null;
return;
}
handleEncryptedWallet(function(err) {
if (err) {
$scope.error = err;
$scope.loading = null;
return;
}
walletService.signTx(fc, txp, function(err, signedTxp) {
walletService.lock(fc);
if (err) {
$scope.error = err;
$scope.loading = null;
return;
}
if (signedTxp.status == 'accepted') {
$scope.loading = 'Broadcasting Transaction';
walletService.broadcastTx(fc, signedTxp, function(err, broadcastedTxp) {
$scope.loading = null;
$scope.$emit('UpdateTx');
$scope.close(broadcastedTxp);
if (err) {
$scope.error = err;
}
});
} else {
$scope.loading = null;
$scope.$emit('UpdateTx');
$scope.close(signedTxp);
}
});
});
});
};
$scope.reject = function(txp) {
$scope.loading = 'Rejecting payment';
$scope.error = null;
$timeout(function() {
walletService.rejectTx(fc, txp, function(err, txpr) {
$scope.loading = null;
if (err) {
$scope.$emit('UpdateTx');
$scope.error = bwsError.msg(err, gettextCatalog.getString('Could not reject payment'));
$scope.$digest();
} else {
$scope.close(txpr);
}
});
}, 10);
};
$scope.remove = function(txp) {
$scope.loading = 'Deleting Payment';
$scope.error = null;
$timeout(function() {
walletService.removeTx(fc, txp, function(err) {
$scope.loading = null;
// Hacky: request tries to parse an empty response
if (err && !(err.message && err.message.match(/Unexpected/))) {
$scope.$emit('UpdateTx');
$scope.error = bwsError.msg(err, gettextCatalog.getString('Could not delete payment proposal'));
$scope.$digest();
return;
}
$scope.close();
});
}, 10);
};
$scope.broadcast = function(txp) {
$scope.loading = 'Broadcasting Payment';
$scope.error = null;
$timeout(function() {
walletService.broadcastTx(fc, txp, function(err, txpb) {
$scope.loading = null;
if (err) {
$scope.error = bwsError.msg(err, gettextCatalog.getString('Could not broadcast payment'));
$scope.$digest();
return;
}
$scope.close(txpb);
});
}, 10);
};
$scope.getShortNetworkName = function() {
return fc.credentials.networkName.substring(0, 4);
};
function checkPaypro() {
if (tx.payProUrl && !platformInfo.isChromeApp) {
fc.fetchPayPro({
payProUrl: tx.payProUrl,
}, function(err, paypro) {
if (err) return;
tx.paypro = paypro;
paymentTimeControl(tx.paypro.expires);
$timeout(function() {
$ionicScrollDelegate.resize();
}, 100);
});
}
};
function paymentTimeControl(expirationTime) {
$scope.paymentExpired = false;
setExpirationTime();
self.countDown = $interval(function() {
setExpirationTime();
}, 1000);
function setExpirationTime() {
var now = Math.floor(Date.now() / 1000);
if (now > expirationTime) {
$scope.paymentExpired = true;
if (self.countDown) $interval.cancel(self.countDown);
return;
}
var totalSecs = expirationTime - now;
var m = Math.floor(totalSecs / 60);
var s = totalSecs % 60;
$scope.expires = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
};
};
lodash.each(['TxProposalRejectedBy', 'TxProposalAcceptedBy', 'transactionProposalRemoved', 'TxProposalRemoved', 'NewOutgoingTx', 'UpdateTx'], function(eventName) {
$scope.$on(eventName, function() {
fc.getTx($scope.tx.id, function(err, tx) {
if (err) {
if (err.message && err.message == 'TX_NOT_FOUND' &&
(eventName == 'transactionProposalRemoved' || eventName == 'TxProposalRemoved')) {
$scope.tx.removed = true;
$scope.tx.canBeRemoved = false;
$scope.tx.pendingForUs = false;
$scope.$apply();
return;
}
return;
}
var action = lodash.find(tx.actions, {
copayerId: fc.credentials.copayerId
});
$scope.tx = txFormatService.processTx(tx);
if (!action && tx.status == 'pending')
$scope.tx.pendingForUs = true;
$scope.updateCopayerList();
$scope.$apply();
});
});
});
$scope.updateCopayerList = function() {
lodash.map($scope.copayers, function(cp) {
lodash.each($scope.tx.actions, function(ac) {
if (cp.id == ac.copayerId) {
cp.action = ac.type;
}
});
});
};
function handleEncryptedWallet(cb) {
if (!walletService.isEncrypted(fc)) return cb();
$rootScope.$emit('Local/NeedsPassword', false, function(err, password) {
if (err) return cb(err);
return cb(null, walletService.unlock(fc, password));
});
};
$scope.copyToClipboard = function(addr) {
if (!addr) return;
self.copyToClipboard(addr);
};
$scope.close = function(txp) {
$scope.loading = null;
if (txp) {
txStatus.notify(txp, function() {
$scope.$emit('Local/TxProposalAction', txp.status == 'broadcasted');
});
} else {
$timeout(function() {
$scope.$emit('Local/TxProposalAction');
}, 100);
}
$scope.cancel();
};
$scope.cancel = function() {
$scope.txpDetailsModal.hide();
};
});

View file

@ -1,43 +0,0 @@
'use strict';
angular.module('copayApp.controllers').controller('passwordController',
function($rootScope, $scope, $timeout, profileService, notification, go, gettext) {
var pass1;
this.isVerification = false;
document.getElementById("passwordInput").focus();
this.close = function(cb) {
return cb('No spending password given');
};
this.set = function(isSetup, cb) {
this.loading = true;
this.error = false;
var self = this;
$timeout(function() {
if (isSetup && !self.isVerification) {
self.loading = false;
document.getElementById("passwordInput").focus();
self.isVerification = true;
pass1 = $scope.password;
$scope.password = null;
return;
}
if (isSetup && pass1 != $scope.password) {
self.loading = false;
self.error = gettext('Spending Passwords do not match');
self.isVerification = false;
$scope.password = null;
pass1 = null;
return;
}
return cb(null, $scope.password);
}, 100);
};
});

View file

@ -3,86 +3,119 @@
angular.module('copayApp.controllers').controller('preferencesController', angular.module('copayApp.controllers').controller('preferencesController',
function($scope, $rootScope, $timeout, $log, configService, profileService, fingerprintService, walletService) { function($scope, $rootScope, $timeout, $log, configService, profileService, fingerprintService, walletService) {
var fc = profileService.focusedClient; var self = this;
var fc;
var config = configService.getSync(); var config = configService.getSync();
$scope.deleted = false;
if (fc.credentials && !fc.credentials.mnemonicEncrypted && !fc.credentials.mnemonic) { var disableFocusListener = $rootScope.$on('Local/NewFocusedWalletReady', function() {
$scope.deleted = true; self.init();
} });
$scope.$on('$destroy', function() {
disableFocusListener();
});
this.init = function() { this.init = function() {
fc = profileService.focusedClient;
if (fc) { if (fc) {
$scope.encrypt = walletService.isEncrypted(fc); $scope.encryptEnabled = walletService.isEncrypted(fc);
this.externalSource = fc.getPrivKeyExternalSourceName() == 'ledger' ? "Ledger" : null; this.externalSource = fc.getPrivKeyExternalSourceName() == 'ledger' ? "Ledger" : null;
// TODO externalAccount // TODO externalAccount
//this.externalIndex = fc.getExternalIndex(); //this.externalIndex = fc.getExternalIndex();
} }
this.touchidAvailable = fingerprintService.isAvailable(); this.touchidAvailable = fingerprintService.isAvailable();
$scope.touchid = config.touchIdFor ? config.touchIdFor[fc.credentials.walletId] : null; $scope.touchidEnabled = config.touchIdFor ? config.touchIdFor[fc.credentials.walletId] : null;
$scope.deleted = false;
if (fc.credentials && !fc.credentials.mnemonicEncrypted && !fc.credentials.mnemonic) {
$scope.deleted = true;
}
}; };
var handleEncryptedWallet = function(client, cb) { var handleEncryptedWallet = function(cb) {
$rootScope.$emit('Local/NeedsPassword', false, function(err, password) { $rootScope.$emit('Local/NeedsPassword', false, function(err, password) {
if (err) return cb(err); if (err) return cb(err);
return cb(walletService.unlock(client, password)); return cb(walletService.unlock(fc, password));
}); });
}; };
var unwatchEncrypt = $scope.$watch('encrypt', function(val) { $scope.encryptChange = function() {
var fc = profileService.focusedClient; var self = this;
if (!fc) return; if (!fc) return;
var val = $scope.encryptEnabled;
var setPrivateKeyEncryption = function(password, cb) {
$log.debug('Encrypting private key for', fc.credentials.walletName);
fc.setPrivateKeyEncryption(password);
fc.lock();
profileService.updateCredentials(fc.export(), function() {
$log.debug('Wallet encrypted');
return cb();
});
};
var disablePrivateKeyEncryption = function(cb) {
$log.debug('Disabling private key encryption for', fc.credentials.walletName);
try {
fc.disablePrivateKeyEncryption();
} catch (e) {
return cb(e);
}
profileService.updateCredentials(fc.export(), function() {
$log.debug('Wallet encryption disabled');
return cb();
});
};
if (val && !walletService.isEncrypted(fc)) { if (val && !walletService.isEncrypted(fc)) {
$rootScope.$emit('Local/NeedsPassword', true, function(err, password) { $rootScope.$emit('Local/NeedsPassword', true, function(err, password) {
if (err || !password) { if (err || !password) {
$scope.encrypt = false; $scope.encryptEnabled = false;
return; return;
} }
profileService.setPrivateKeyEncryptionFC(password, function() { setPrivateKeyEncryption(password, function() {
$rootScope.$emit('Local/NewEncryptionSetting'); $rootScope.$emit('Local/NewEncryptionSetting');
$scope.encrypt = true; $scope.encryptEnabled = true;
}); });
}); });
} else { } else {
if (!val && walletService.isEncrypted(fc)) { if (!val && walletService.isEncrypted(fc)) {
handleEncryptedWallet(fc, function(err) { handleEncryptedWallet(function(err) {
if (err) { if (err) {
$scope.encrypt = true; $scope.encryptEnabled = true;
return; return;
} }
profileService.disablePrivateKeyEncryptionFC(function(err) { disablePrivateKeyEncryption(function(err) {
$rootScope.$emit('Local/NewEncryptionSetting'); $rootScope.$emit('Local/NewEncryptionSetting');
if (err) { if (err) {
$scope.encrypt = true; $scope.encryptEnabled = true;
$log.error(err); $log.error(err);
return; return;
} }
$scope.encrypt = false; $scope.encryptEnabled = false;
}); });
}); });
} }
} }
}); };
var unwatchRequestTouchid = $scope.$watch('touchid', function(newVal, oldVal) { $scope.touchidChange = function() {
if (newVal == oldVal || $scope.touchidError) {
$scope.touchidError = false;
return;
}
var walletId = fc.credentials.walletId; var walletId = fc.credentials.walletId;
var opts = { var opts = {
touchIdFor: {} touchIdFor: {}
}; };
opts.touchIdFor[walletId] = newVal; opts.touchIdFor[walletId] = $scope.touchidEnabled;
fingerprintService.check(fc, function(err) { fingerprintService.check(fc, function(err) {
if (err) { if (err) {
$log.debug(err); $log.debug(err);
$timeout(function() { $timeout(function() {
$scope.touchidError = true; $scope.touchidError = true;
$scope.touchid = oldVal; $scope.touchidEnabled = false;
}, 100); }, 100);
return; return;
} }
@ -90,14 +123,9 @@ angular.module('copayApp.controllers').controller('preferencesController',
if (err) { if (err) {
$log.debug(err); $log.debug(err);
$scope.touchidError = true; $scope.touchidError = true;
$scope.touchid = oldVal; $scope.touchidEnabled = false;
} }
}); });
}); });
}); };
$scope.$on('$destroy', function() {
unwatchEncrypt();
unwatchRequestTouchid();
});
}); });

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesAltCurrencyController', angular.module('copayApp.controllers').controller('preferencesAltCurrencyController',
function($scope, $timeout, $log, configService, rateService, lodash, go) { function($scope, $timeout, $log, configService, rateService, lodash, go, profileService, walletService) {
this.hideAdv = true; this.hideAdv = true;
this.hidePriv = true; this.hidePriv = true;
this.hideSecret = true; this.hideSecret = true;
@ -51,6 +51,9 @@ angular.module('copayApp.controllers').controller('preferencesAltCurrencyControl
if (err) $log.warn(err); if (err) $log.warn(err);
go.preferencesGlobal(); go.preferencesGlobal();
$scope.$emit('Local/UnitSettingUpdated'); $scope.$emit('Local/UnitSettingUpdated');
walletService.updateRemotePreferences(profileService.walletClients, {}, function() {
$log.debug('Remote preferences saved');
});
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}, 100); }, 100);

View file

@ -40,19 +40,19 @@ angular.module('copayApp.controllers').controller('preferencesDeleteWalletContro
modalInstance.result.then(function(ok) { modalInstance.result.then(function(ok) {
if (ok) { if (ok) {
_deleteWallet(); doDeleteWallet();
} }
}); });
}; };
var _deleteWallet = function() { var doDeleteWallet = function() {
$scope.isDeletingWallet = true; $scope.isDeletingWallet = true;
var fc = profileService.focusedClient; var fc = profileService.focusedClient;
var name = fc.credentials.walletName; var name = fc.credentials.walletName;
var walletName = (fc.alias || '') + ' [' + name + ']'; var walletName = (fc.alias || '') + ' [' + name + ']';
var self = this; var self = this;
profileService.deleteWalletFC({}, function(err) { profileService.deleteWalletClient(fc, function(err) {
$scope.isDeletingWallet = false; $scope.isDeletingWallet = false;
if (err) { if (err) {
self.error = err.message || err; self.error = err.message || err;
@ -72,7 +72,7 @@ angular.module('copayApp.controllers').controller('preferencesDeleteWalletContro
delete_msg, delete_msg,
function(buttonIndex) { function(buttonIndex) {
if (buttonIndex == 1) { if (buttonIndex == 1) {
_deleteWallet(); doDeleteWallet();
} }
}, },
confirm_msg, [accept_msg, cancel_msg] confirm_msg, [accept_msg, cancel_msg]

View file

@ -13,7 +13,7 @@ angular.module('copayApp.controllers').controller('preferencesDeleteWordsControl
confirmDialog.show(msg, function(ok) { confirmDialog.show(msg, function(ok) {
if (ok) { if (ok) {
fc.clearMnemonic(); fc.clearMnemonic();
profileService.updateCredentialsFC(function() { profileService.updateCredentials(fc.export(), function() {
notification.success(successMsg); notification.success(successMsg);
go.walletHome(); go.walletHome();
}); });

View file

@ -1,14 +1,22 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesEmailController', angular.module('copayApp.controllers').controller('preferencesEmailController',
function($scope, go, profileService, gettext, $log) { function($rootScope, go, profileService, gettext, $log, walletService) {
this.save = function(form) { this.save = function(form) {
var self = this; var self = this;
this.error = null; this.error = null;
var fc = profileService.focusedClient; var fc = profileService.focusedClient;
this.saving = true; this.saving = true;
$scope.$emit('Local/EmailSettingUpdated', self.email, function() { var email = self.email || '';
walletService.updateRemotePreferences(fc, {
email: email,
}, function(err) {
if (!err)
$rootScope.$emit('Local/EmailUpdated', email);
self.saving = false; self.saving = false;
go.path('preferences'); go.path('preferences');
}); });

View file

@ -36,24 +36,22 @@ angular.module('copayApp.controllers').controller('preferencesGlobalController',
}); });
} }
var unwatchSpendUnconfirmed = $scope.$watch('spendUnconfirmed', function(newVal, oldVal) { $scope.spendUnconfirmedChange = function() {
if (newVal == oldVal) return;
var opts = { var opts = {
wallet: { wallet: {
spendUnconfirmed: newVal spendUnconfirmed: $scope.spendUnconfirmed
} }
}; };
configService.set(opts, function(err) { configService.set(opts, function(err) {
$rootScope.$emit('Local/SpendUnconfirmedUpdated', newVal); $rootScope.$emit('Local/SpendUnconfirmedUpdated', $scope.spendUnconfirmed);
if (err) $log.debug(err); if (err) $log.debug(err);
}); });
}); };
var unwatchPushNotifications = $scope.$watch('pushNotifications', function(newVal, oldVal) { $scope.pushNotificationsChange = function() {
if (newVal == oldVal) return;
var opts = { var opts = {
pushNotifications: { pushNotifications: {
enabled: newVal enabled: $scope.pushNotifications
} }
}; };
configService.set(opts, function(err) { configService.set(opts, function(err) {
@ -63,38 +61,29 @@ angular.module('copayApp.controllers').controller('preferencesGlobalController',
pushNotificationsService.disableNotifications(profileService.walletClients); pushNotificationsService.disableNotifications(profileService.walletClients);
if (err) $log.debug(err); if (err) $log.debug(err);
}); });
}); };
var unwatchGlideraEnabled = $scope.$watch('glideraEnabled', function(newVal, oldVal) { $scope.glideraChange = function() {
if (newVal == oldVal) return;
var opts = { var opts = {
glidera: { glidera: {
enabled: newVal enabled: $scope.glideraEnabled
} }
}; };
configService.set(opts, function(err) { configService.set(opts, function(err) {
$rootScope.$emit('Local/GlideraUpdated'); $rootScope.$emit('Local/GlideraUpdated');
if (err) $log.debug(err); if (err) $log.debug(err);
}); });
}); };
var unwatchCoinbaseEnabled = $scope.$watch('coinbaseEnabled', function(newVal, oldVal) { $scope.coinbaseChange = function() {
if (newVal == oldVal) return;
var opts = { var opts = {
coinbase: { coinbase: {
enabled: newVal enabled: $scope.coinbaseEnabled
} }
}; };
configService.set(opts, function(err) { configService.set(opts, function(err) {
$rootScope.$emit('Local/CoinbaseUpdated'); $rootScope.$emit('Local/CoinbaseUpdated');
if (err) $log.debug(err); if (err) $log.debug(err);
}); });
}); };
$scope.$on('$destroy', function() {
unwatchSpendUnconfirmed();
unwatchGlideraEnabled();
unwatchCoinbaseEnabled();
unwatchPushNotifications();
});
}); });

View file

@ -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) {
$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) {

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesLanguageController', angular.module('copayApp.controllers').controller('preferencesLanguageController',
function($scope, $log, $timeout, configService, uxLanguage, go) { function($scope, $log, $timeout, configService, profileService, uxLanguage, walletService, go) {
this.availableLanguages = uxLanguage.getLanguages(); this.availableLanguages = uxLanguage.getLanguages();
this.currentLanguage = uxLanguage.getCurrentLanguage(); this.currentLanguage = uxLanguage.getCurrentLanguage();
@ -19,10 +19,18 @@ angular.module('copayApp.controllers').controller('preferencesLanguageController
configService.set(opts, function(err) { configService.set(opts, function(err) {
if (err) $log.warn(err); if (err) $log.warn(err);
go.preferencesGlobal(); go.preferencesGlobal();
$scope.$emit('Local/LanguageSettingUpdated');
$timeout(function() {
$scope.$apply(); uxLanguage.update(function() {
}, 100); $timeout(function() {
$scope.$apply();
}, 100);
walletService.updateRemotePreferences(profileService.getClients(), {},
function() {
$log.debug('Remote preferences saved');
});
});
}); });
}; };
}); });

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesUnitController', angular.module('copayApp.controllers').controller('preferencesUnitController',
function($scope, $timeout, $log, configService, go) { function($scope, $timeout, $log, configService, go, walletService, profileService) {
var config = configService.getSync(); var config = configService.getSync();
this.unitName = config.wallet.settings.unitName; this.unitName = config.wallet.settings.unitName;
this.unitOpts = [ this.unitOpts = [
@ -54,6 +54,9 @@ angular.module('copayApp.controllers').controller('preferencesUnitController',
if (err) $log.warn(err); if (err) $log.warn(err);
go.preferencesGlobal(); go.preferencesGlobal();
$scope.$emit('Local/UnitSettingUpdated'); $scope.$emit('Local/UnitSettingUpdated');
walletService.updateRemotePreferences(profileService.getClients(), {}, function() {
$log.debug('Remote preferences saved');
});
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}, 100); }, 100);

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('sidebarController', angular.module('copayApp.controllers').controller('sidebarController',
function($rootScope, $timeout, lodash, profileService, configService, go, platformInfo) { function($rootScope, $timeout, $ionicScrollDelegate, lodash, profileService, configService, go, platformInfo) {
var self = this; var self = this;
self.isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova; self.isWindowsPhoneApp = platformInfo.isWP && platformInfo.isCordova;
self.walletSelection = false; self.walletSelection = false;
@ -20,11 +20,6 @@ angular.module('copayApp.controllers').controller('sidebarController',
self.setWallets(); self.setWallets();
}); });
self.closeMenu = function() {
go.swipe();
};
self.signout = function() { self.signout = function() {
profileService.signout(); profileService.signout();
}; };
@ -33,6 +28,7 @@ angular.module('copayApp.controllers').controller('sidebarController',
if (selectedWalletId == currentWalletId) return; if (selectedWalletId == currentWalletId) return;
self.walletSelection = false; self.walletSelection = false;
profileService.setAndStoreFocus(selectedWalletId, function() {}); profileService.setAndStoreFocus(selectedWalletId, function() {});
$ionicScrollDelegate.$getByHandle('transactions').scrollTop();
}; };
self.toggleWalletSelection = function() { self.toggleWalletSelection = function() {

View file

@ -1,11 +1,11 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('walletHomeController', function($scope, $rootScope, $interval, $timeout, $filter, $modal, $log, notification, txStatus, profileService, lodash, configService, rateService, storageService, bitcore, gettext, gettextCatalog, platformInfo, addressService, ledger, bwsError, confirmDialog, txFormatService, animationService, addressbookService, go, feeService, walletService, fingerprintService, nodeWebkit) { angular.module('copayApp.controllers').controller('walletHomeController', function($scope, $rootScope, $interval, $timeout, $filter, $modal, $log, $ionicModal, notification, txStatus, profileService, lodash, configService, rateService, storageService, bitcore, gettext, gettextCatalog, platformInfo, addressService, ledger, bwsError, confirmDialog, txFormatService, animationService, addressbookService, go, feeService, walletService, fingerprintService, nodeWebkit) {
var isCordova = platformInfo.isCordova; var isCordova = platformInfo.isCordova;
var isWP = platformInfo.isWP; var isWP = platformInfo.isWP;
var isAndroid = platformInfo.isAndroid; var isAndroid = platformInfo.isAndroid;
var isChromeApp = platformInfo.isChromeApp; var isChromeApp = platformInfo.isChromeApp;
var self = this; var self = this;
window.ignoreMobilePause = false; window.ignoreMobilePause = false;
@ -144,178 +144,16 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
var cancel_msg = gettextCatalog.getString('Cancel'); var cancel_msg = gettextCatalog.getString('Cancel');
var confirm_msg = gettextCatalog.getString('Confirm'); var confirm_msg = gettextCatalog.getString('Confirm');
this.openDestinationAddressModal = function(wallets, address) { this.openAddressbookModal = function(wallets, address) {
$rootScope.modalOpened = true; $scope.wallets = wallets;
var fc = profileService.focusedClient; $scope.address = address;
self.lockAddress = false; $scope.self = self;
self._address = null;
var ModalInstanceCtrl = function($scope, $modalInstance) { $ionicModal.fromTemplateUrl('views/modals/addressbook.html', {
$scope.wallets = wallets; scope: $scope
$scope.editAddressbook = false; }).then(function(modal) {
$scope.addAddressbookEntry = false; $scope.addressbookModal = modal;
$scope.selectedAddressbook = {}; $scope.addressbookModal.show();
$scope.newAddress = address;
$scope.walletName = fc.credentials.walletName;
$scope.color = fc.backgroundColor;
$scope.addressbook = {
'address': ($scope.newAddress || ''),
'label': ''
};
$scope.checkClipboard = function() {
if (!$scope.newAddress) {
getClipboard(function(value) {
$scope.newAddress = value;
});
}
};
$scope.beforeQrCodeScann = function() {
$scope.error = null;
$scope.addAddressbookEntry = true;
$scope.editAddressbook = false;
};
$scope.onQrCodeScanned = function(data, addressbookForm) {
$timeout(function() {
var form = addressbookForm;
if (data && form) {
data = data.replace('bitcoin:', '');
form.address.$setViewValue(data);
form.address.$isValid = true;
form.address.$render();
}
$scope.$digest();
}, 100);
};
$scope.selectAddressbook = function(addr) {
$modalInstance.close(addr);
};
$scope.toggleEditAddressbook = function() {
$scope.editAddressbook = !$scope.editAddressbook;
$scope.selectedAddressbook = {};
$scope.addAddressbookEntry = false;
};
$scope.toggleSelectAddressbook = function(addr) {
$scope.selectedAddressbook[addr] = $scope.selectedAddressbook[addr] ? false : true;
};
$scope.toggleAddAddressbookEntry = function() {
$scope.error = null;
$scope.addressbook = {
'address': ($scope.newAddress || ''),
'label': ''
};
$scope.addAddressbookEntry = !$scope.addAddressbookEntry;
};
$scope.list = function() {
$scope.error = null;
addressbookService.list(function(err, ab) {
if (err) {
$scope.error = err;
return;
}
$scope.list = ab;
$timeout(function() {
$scope.$digest();
});
});
};
$scope.add = function(addressbook) {
$scope.error = null;
$timeout(function() {
addressbookService.add(addressbook, function(err, ab) {
if (err) {
$scope.error = err;
return;
}
$rootScope.$emit('Local/AddressbookUpdated', ab);
$scope.list = ab;
$scope.editAddressbook = true;
$scope.toggleEditAddressbook();
$scope.$digest();
});
}, 100);
};
$scope.remove = function(addr) {
$scope.error = null;
$timeout(function() {
addressbookService.remove(addr, function(err, ab) {
if (err) {
$scope.error = err;
return;
}
$rootScope.$emit('Local/AddressbookUpdated', ab);
$scope.list = ab;
$scope.$digest();
});
}, 100);
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
$scope.selectWallet = function(walletId, walletName) {
var client = profileService.getClient(walletId);
$scope.errorSelectedWallet = {};
walletService.isReady(client, function(err) {
if (err) $scope.errorSelectedWallet[walletId] = err;
else {
$scope.gettingAddress = true;
$scope.selectedWalletName = walletName;
$timeout(function() {
$scope.$apply();
});
addressService.getAddress(walletId, false, function(err, addr) {
$scope.gettingAddress = false;
if (err) {
self.error = err;
$modalInstance.dismiss('cancel');
return;
}
$modalInstance.close(addr);
});
}
});
};
};
var modalInstance = $modal.open({
templateUrl: 'views/modals/destination-address.html',
windowClass: animationService.modalAnimated.slideUp,
controller: ModalInstanceCtrl,
});
var disableCloseModal = $rootScope.$on('closeModal', function() {
modalInstance.dismiss('cancel');
});
modalInstance.result.finally(function() {
$rootScope.modalOpened = false;
disableCloseModal();
var m = angular.element(document.getElementsByClassName('reveal-modal'));
m.addClass(animationService.modalAnimated.slideOutDown);
});
modalInstance.result.then(function(addr) {
if (addr) {
self.setForm(addr);
}
}, function() {
// onRejected
self.resetForm();
}); });
}; };
@ -323,260 +161,21 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
// isGlidera flag is a security measure so glidera status is not // isGlidera flag is a security measure so glidera status is not
// only determined by the tx.message // only determined by the tx.message
this.openTxpModal = function(tx, copayers, isGlidera) { this.openTxpModal = function(tx, copayers, isGlidera) {
$rootScope.modalOpened = true; $scope.self = self;
var self = this; $scope.tx = tx;
var fc = profileService.focusedClient; $scope.copayers = copayers;
var currentSpendUnconfirmed = configWallet.spendUnconfirmed; $scope.isGlidera = isGlidera;
var ModalInstanceCtrl = function($scope, $modalInstance) { $scope.error = null;
$scope.paymentExpired = null; $scope.loading = null;
checkPaypro(); $scope.paymentExpired = null;
$scope.error = null; $scope.currentSpendUnconfirmed = configWallet.spendUnconfirmed;
$scope.copayers = copayers
$scope.copayerId = fc.credentials.copayerId;
$scope.canSign = fc.canSign() || fc.isPrivKeyExternal();
$scope.loading = null;
$scope.color = fc.backgroundColor;
$scope.isShared = fc.credentials.n > 1;
var now = Math.floor(Date.now() / 1000);
// ToDo: use tx.customData instead of tx.message $ionicModal.fromTemplateUrl('views/modals/txp-details.html', {
if (tx.message === 'Glidera transaction' && isGlidera) { scope: $scope
tx.isGlidera = true; }).then(function(modal) {
if (tx.canBeRemoved) { $scope.txpDetailsModal = modal;
tx.canBeRemoved = (Date.now() / 1000 - (tx.ts || tx.createdOn)) > GLIDERA_LOCK_TIME; $scope.txpDetailsModal.show();
}
}
$scope.tx = tx;
$scope.currentSpendUnconfirmed = currentSpendUnconfirmed;
$scope.getShortNetworkName = function() {
return fc.credentials.networkName.substring(0, 4);
};
function checkPaypro() {
if (tx.payProUrl && !isChromeApp) {
fc.fetchPayPro({
payProUrl: tx.payProUrl,
}, function(err, paypro) {
if (err) return;
tx.paypro = paypro;
paymentTimeControl(tx.paypro.expires);
});
}
};
function paymentTimeControl(expirationTime) {
$scope.paymentExpired = false;
setExpirationTime();
self.countDown = $interval(function() {
setExpirationTime();
}, 1000);
function setExpirationTime() {
var now = Math.floor(Date.now() / 1000);
if (now > expirationTime) {
$scope.paymentExpired = true;
if (self.countDown) $interval.cancel(self.countDown);
return;
}
var totalSecs = expirationTime - now;
var m = Math.floor(totalSecs / 60);
var s = totalSecs % 60;
$scope.expires = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
};
};
lodash.each(['TxProposalRejectedBy', 'TxProposalAcceptedBy', 'transactionProposalRemoved', 'TxProposalRemoved', 'NewOutgoingTx', 'UpdateTx'], function(eventName) {
$rootScope.$on(eventName, function() {
fc.getTx($scope.tx.id, function(err, tx) {
if (err) {
if (err.message && err.message == 'TX_NOT_FOUND' &&
(eventName == 'transactionProposalRemoved' || eventName == 'TxProposalRemoved')) {
$scope.tx.removed = true;
$scope.tx.canBeRemoved = false;
$scope.tx.pendingForUs = false;
$scope.$apply();
return;
}
return;
}
var action = lodash.find(tx.actions, {
copayerId: fc.credentials.copayerId
});
$scope.tx = txFormatService.processTx(tx);
if (!action && tx.status == 'pending')
$scope.tx.pendingForUs = true;
$scope.updateCopayerList();
$scope.$apply();
});
});
});
$scope.updateCopayerList = function() {
lodash.map($scope.copayers, function(cp) {
lodash.each($scope.tx.actions, function(ac) {
if (cp.id == ac.copayerId) {
cp.action = ac.type;
}
});
});
};
$scope.sign = function(txp) {
var client = profileService.focusedClient;
$scope.error = null;
$scope.loading = true;
fingerprintService.check(client, function(err) {
if (err) {
$scope.loading = false;
$scope.error = err;
return;
}
handleEncryptedWallet(client, function(err) {
if (err) {
$scope.loading = false;
$scope.error = err;
return;
}
walletService.signTx(client, txp, function(err, signedTxp) {
walletService.lock(client);
if (err) {
$scope.loading = false;
$scope.error = err;
return;
}
if (signedTxp.status == 'accepted') {
walletService.broadcastTx(client, signedTxp, function(err, broadcastedTxp) {
$scope.loading = false;
$scope.$emit('UpdateTx');
$modalInstance.close(broadcastedTxp);
if (err) {
$scope.loading = false;
$scope.error = err;
}
});
} else {
$scope.loading = false;
$scope.$emit('UpdateTx');
$modalInstance.close(signedTxp);
}
});
});
});
};
$scope.reject = function(txp) {
var client = profileService.focusedClient;
self.setOngoingProcess(gettextCatalog.getString('Rejecting payment'));
$scope.loading = true;
$scope.error = null;
$timeout(function() {
walletService.rejectTx(client, txp, function(err, txpr) {
self.setOngoingProcess();
$scope.loading = false;
if (err) {
$scope.$emit('UpdateTx');
$scope.error = bwsError.msg(err, gettextCatalog.getString('Could not reject payment'));
$scope.$digest();
} else {
$modalInstance.close(txpr);
}
});
}, 100);
};
$scope.remove = function(txp) {
var client = profileService.focusedClient;
self.setOngoingProcess(gettextCatalog.getString('Deleting payment'));
$scope.loading = true;
$scope.error = null;
$timeout(function() {
walletService.removeTx(client, txp, function(err) {
self.setOngoingProcess();
$scope.loading = false;
// Hacky: request tries to parse an empty response
if (err && !(err.message && err.message.match(/Unexpected/))) {
$scope.$emit('UpdateTx');
$scope.error = bwsError.msg(err, gettextCatalog.getString('Could not delete payment proposal'));
$scope.$digest();
return;
}
$modalInstance.close();
});
}, 100);
};
$scope.broadcast = function(txp) {
var client = profileService.focusedClient;
self.setOngoingProcess(gettextCatalog.getString('Broadcasting Payment'));
$scope.loading = true;
$scope.error = null;
$timeout(function() {
walletService.broadcastTx(client, txp, function(err, txpb) {
self.setOngoingProcess();
$scope.loading = false;
if (err) {
$scope.error = bwsError.msg(err, gettextCatalog.getString('Could not broadcast payment'));
$scope.$digest();
return;
}
$modalInstance.close(txpb);
});
}, 100);
};
$scope.copyToClipboard = function(addr) {
if (!addr) return;
self.copyToClipboard(addr);
};
$scope.cancel = lodash.debounce(function() {
$modalInstance.dismiss('cancel');
}, 0, 1000);
};
var modalInstance = $modal.open({
templateUrl: 'views/modals/txp-details.html',
windowClass: animationService.modalAnimated.slideRight,
controller: ModalInstanceCtrl,
}); });
var disableCloseModal = $rootScope.$on('closeModal', function() {
modalInstance.dismiss('cancel');
});
modalInstance.result.finally(function() {
$rootScope.modalOpened = false;
disableCloseModal();
var m = angular.element(document.getElementsByClassName('reveal-modal'));
m.addClass(animationService.modalAnimated.slideOutRight);
});
modalInstance.result.then(function(txp) {
self.setOngoingProcess();
if (txp) {
txStatus.notify(txp, function() {
$scope.$emit('Local/TxProposalAction', txp.status == 'broadcasted');
});
} else {
$timeout(function() {
$scope.$emit('Local/TxProposalAction');
}, 100);
}
});
}; };
this.setAddress = function(forceNew) { this.setAddress = function(forceNew) {
@ -626,105 +225,17 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
}; };
this.openCustomizedAmountModal = function(addr) { this.openCustomizedAmountModal = function(addr) {
$rootScope.modalOpened = true;
var self = this;
var fc = profileService.focusedClient; var fc = profileService.focusedClient;
var ModalInstanceCtrl = function($scope, $modalInstance) {
$scope.addr = addr;
$scope.color = fc.backgroundColor;
$scope.unitName = self.unitName;
$scope.alternativeAmount = self.alternativeAmount;
$scope.alternativeName = self.alternativeName;
$scope.alternativeIsoCode = self.alternativeIsoCode;
$scope.isRateAvailable = self.isRateAvailable;
$scope.unitToSatoshi = self.unitToSatoshi;
$scope.unitDecimals = self.unitDecimals;
var satToUnit = 1 / self.unitToSatoshi;
$scope.showAlternative = false;
$scope.isCordova = isCordova;
Object.defineProperty($scope, $scope.color = fc.backgroundColor;
"_customAlternative", { $scope.self = self;
get: function() { $scope.addr = addr;
return $scope.customAlternative;
},
set: function(newValue) {
$scope.customAlternative = newValue;
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
$scope.customAmount = parseFloat((rateService.fromFiat(newValue, $scope.alternativeIsoCode) * satToUnit).toFixed($scope.unitDecimals), 10);
} else {
$scope.customAmount = null;
}
},
enumerable: true,
configurable: true
});
Object.defineProperty($scope, $ionicModal.fromTemplateUrl('views/modals/customized-amount.html', {
"_customAmount", { scope: $scope
get: function() { }).then(function(modal) {
return $scope.customAmount; $scope.customAmountModal = modal;
}, $scope.customAmountModal.show();
set: function(newValue) {
$scope.customAmount = newValue;
if (typeof(newValue) === 'number' && $scope.isRateAvailable) {
$scope.customAlternative = parseFloat((rateService.toFiat(newValue * $scope.unitToSatoshi, $scope.alternativeIsoCode)).toFixed(2), 10);
} else {
$scope.customAlternative = null;
}
$scope.alternativeAmount = $scope.customAlternative;
},
enumerable: true,
configurable: true
});
$scope.submitForm = function(form) {
var satToBtc = 1 / 100000000;
var amount = form.amount.$modelValue;
var amountSat = parseInt((amount * $scope.unitToSatoshi).toFixed(0));
$timeout(function() {
$scope.customizedAmountUnit = amount + ' ' + $scope.unitName;
$scope.customizedAlternativeUnit = $filter('noFractionNumber')(form.alternative.$modelValue, 2) + ' ' + $scope.alternativeIsoCode;
if ($scope.unitName == 'bits') {
amount = (amountSat * satToBtc).toFixed(8);
}
$scope.customizedAmountBtc = amount;
}, 1);
};
$scope.toggleAlternative = function() {
$scope.showAlternative = !$scope.showAlternative;
};
$scope.shareAddress = function(uri) {
if (isCordova) {
if (isAndroid || isWP) {
window.ignoreMobilePause = true;
}
window.plugins.socialsharing.share(uri, null, null, null);
}
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
};
var modalInstance = $modal.open({
templateUrl: 'views/modals/customized-amount.html',
windowClass: animationService.modalAnimated.slideUp,
controller: ModalInstanceCtrl,
});
var disableCloseModal = $rootScope.$on('closeModal', function() {
modalInstance.dismiss('cancel');
});
modalInstance.result.finally(function() {
$rootScope.modalOpened = false;
disableCloseModal();
var m = angular.element(document.getElementsByClassName('reveal-modal'));
m.addClass(animationService.modalAnimated.slideOutDown);
}); });
}; };
@ -955,7 +466,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
txp.sendMax = true; txp.sendMax = true;
txp.inputs = self.sendMaxInfo.inputs; txp.inputs = self.sendMaxInfo.inputs;
txp.fee = self.sendMaxInfo.fee; txp.fee = self.sendMaxInfo.fee;
}else { } else {
txp.amount = amount; txp.amount = amount;
} }
@ -988,7 +499,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
}); });
}, 100); }, 100);
}; };
this.confirmTx = function(txp) { this.confirmTx = function(txp) {
var client = profileService.focusedClient; var client = profileService.focusedClient;
@ -1018,11 +529,11 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
if (err) { if (err) {
$scope.$emit('Local/TxProposalAction'); $scope.$emit('Local/TxProposalAction');
return self.setSendError( return self.setSendError(
err.message ? err.message ?
err.message : err.message :
gettext('The payment was created but could not be completed. Please try again from home screen')); gettext('The payment was created but could not be completed. Please try again from home screen'));
} }
if (signedTxp.status == 'accepted') { if (signedTxp.status == 'accepted') {
self.setOngoingProcess(gettextCatalog.getString('Broadcasting transaction')); self.setOngoingProcess(gettextCatalog.getString('Broadcasting transaction'));
walletService.broadcastTx(client, signedTxp, function(err, broadcastedTxp) { walletService.broadcastTx(client, signedTxp, function(err, broadcastedTxp) {
@ -1049,6 +560,20 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
}); });
}; };
$scope.openSearchModal = function() {
var fc = profileService.focusedClient;
$scope.color = fc.backgroundColor;
$scope.self = self;
$ionicModal.fromTemplateUrl('views/modals/search.html', {
scope: $scope,
focusFirstInput: true
}).then(function(modal) {
$scope.searchModal = modal;
$scope.searchModal.show();
});
};
this.setForm = function(to, amount, comment) { this.setForm = function(to, amount, comment) {
var form = $scope.sendForm; var form = $scope.sendForm;
if (to) { if (to) {
@ -1106,37 +631,16 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
}; };
this.openPPModal = function(paypro) { this.openPPModal = function(paypro) {
$rootScope.modalOpened = true; var fc = profileService.focusedClient;
var ModalInstanceCtrl = function($scope, $modalInstance) { $scope.color = fc.backgroundColor;
var fc = profileService.focusedClient; $scope.self = self;
var satToUnit = 1 / self.unitToSatoshi; $scope.paypro = paypro;
$scope.paypro = paypro;
$scope.alternative = self.alternativeAmount;
$scope.alternativeIsoCode = self.alternativeIsoCode;
$scope.isRateAvailable = self.isRateAvailable;
$scope.unitTotal = (paypro.amount * satToUnit).toFixed(self.unitDecimals);
$scope.unitName = self.unitName;
$scope.color = fc.backgroundColor;
$scope.cancel = function() { $ionicModal.fromTemplateUrl('views/modals/paypro.html', {
$modalInstance.dismiss('cancel'); scope: $scope
}; }).then(function(modal) {
}; $scope.payproModal = modal;
var modalInstance = $modal.open({ $scope.payproModal.show();
templateUrl: 'views/modals/paypro.html',
windowClass: animationService.modalAnimated.slideUp,
controller: ModalInstanceCtrl,
});
var disableCloseModal = $rootScope.$on('closeModal', function() {
modalInstance.dismiss('cancel');
});
modalInstance.result.finally(function() {
$rootScope.modalOpened = false;
disableCloseModal();
var m = angular.element(document.getElementsByClassName('reveal-modal'));
m.addClass(animationService.modalAnimated.slideOutDown);
}); });
}; };
@ -1308,8 +812,19 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi
return this.alternativeIsoCode; return this.alternativeIsoCode;
}; };
this.openTxModal = function(tx) { this.openTxModal = function(btx) {
$rootScope.$emit('Local/TxModal', tx); var self = this;
$scope.btx = btx;
$scope.self = self;
$ionicModal.fromTemplateUrl('views/modals/tx-details.html', {
scope: $scope,
hideDelay: 500
}).then(function(modal) {
$scope.txDetailsModal = modal;
$scope.txDetailsModal.show();
});
}; };
this.hasAction = function(actions, action) { this.hasAction = function(actions, action) {

View file

@ -1,67 +0,0 @@
'use strict';
/*
* This is a modification from https://github.com/angular/angular.js/blob/master/src/ngTouch/swipe.js
*/
function makeSwipeDirective(directiveName, direction, eventName) {
angular.module('copayApp.directives')
.directive(directiveName, ['$parse', '$swipe',
function($parse, $swipe) {
// The maximum vertical delta for a swipe should be less than 75px.
var MAX_VERTICAL_DISTANCE = 75;
// Vertical distance should not be more than a fraction of the horizontal distance.
var MAX_VERTICAL_RATIO = 0.4;
// At least a 30px lateral motion is necessary for a swipe.
var MIN_HORIZONTAL_DISTANCE = 30;
return function(scope, element, attr) {
var swipeHandler = $parse(attr[directiveName]);
var startCoords, valid;
function validSwipe(coords) {
// Check that it's within the coordinates.
// Absolute vertical distance must be within tolerances.
// Horizontal distance, we take the current X - the starting X.
// This is negative for leftward swipes and positive for rightward swipes.
// After multiplying by the direction (-1 for left, +1 for right), legal swipes
// (ie. same direction as the directive wants) will have a positive delta and
// illegal ones a negative delta.
// Therefore this delta must be positive, and larger than the minimum.
if (!startCoords) return false;
var deltaY = Math.abs(coords.y - startCoords.y);
var deltaX = (coords.x - startCoords.x) * direction;
return valid && // Short circuit for already-invalidated swipes.
deltaY < MAX_VERTICAL_DISTANCE &&
deltaX > 0 &&
deltaX > MIN_HORIZONTAL_DISTANCE &&
deltaY / deltaX < MAX_VERTICAL_RATIO;
}
var pointerTypes = ['touch'];
$swipe.bind(element, {
'start': function(coords, event) {
startCoords = coords;
valid = true;
},
'move': function(coords, event) {
if (validSwipe(coords)) {
scope.$apply(function() {
element.triggerHandler(eventName);
swipeHandler(scope, {
$event: event
});
});
}
}
}, pointerTypes);
};
}
]);
}
// Left is negative X-coordinate, right is positive.
makeSwipeDirective('ngSwipeLeft', -1, 'swipeleft');
makeSwipeDirective('ngSwipeRight', 1, 'swiperight');

View file

@ -27,60 +27,6 @@ angular.element(document).ready(function() {
document.addEventListener('deviceready', function() { document.addEventListener('deviceready', function() {
var secondBackButtonPress = 'false';
var intval = setInterval(function() {
secondBackButtonPress = 'false';
}, 5000);
document.addEventListener('pause', function() {
if (!window.ignoreMobilePause) {
setTimeout(function() {
window.location = '#/cordova/pause///';
}, 100);
}
}, false);
document.addEventListener('resume', function() {
if (!window.ignoreMobilePause) {
setTimeout(function() {
window.location = '#/cordova/resume///';
}, 100);
}
setTimeout(function() {
var loc = window.location;
var ignoreMobilePause = loc.toString().match(/(buy|sell|buycoinbase|sellcoinbase)/) ? true : false;
window.ignoreMobilePause = ignoreMobilePause;
}, 100);
}, false);
// Back button event
document.addEventListener('backbutton', function() {
var loc = window.location;
var fromDisclaimer = loc.toString().match(/disclaimer/) ? 'true' : '';
var fromHome = loc.toString().match(/index\.html#\/$/) ? 'true' : '';
if (!window.ignoreMobilePause) {
window.location = '#/cordova/backbutton/' + fromHome + '/' + fromDisclaimer + '/' + secondBackButtonPress;
if (secondBackButtonPress == 'true') {
clearInterval(intval);
} else {
secondBackButtonPress = 'true';
}
}
setTimeout(function() {
window.ignoreMobilePause = false;
}, 100);
}, false);
document.addEventListener('menubutton', function() {
window.location = '#/preferences';
}, false);
setTimeout(function() {
navigator.splashscreen.hide();
}, 1000);
window.plugins.webintent.getUri(handleBitcoinURI); window.plugins.webintent.getUri(handleBitcoinURI);
window.plugins.webintent.onNewIntent(handleBitcoinURI); window.plugins.webintent.onNewIntent(handleBitcoinURI);
window.handleOpenURL = handleBitcoinURI; window.handleOpenURL = handleBitcoinURI;
@ -96,6 +42,7 @@ angular.element(document).ready(function() {
startAngular(); startAngular();
}, false); }, false);
} else { } else {
try { try {
window.handleOpenURL = handleBitcoinURI; window.handleOpenURL = handleBitcoinURI;

View file

@ -27,6 +27,7 @@ Profile.fromObj = function(obj) {
x.credentials = obj.credentials; x.credentials = obj.credentials;
x.disclaimerAccepted = obj.disclaimerAccepted; x.disclaimerAccepted = obj.disclaimerAccepted;
x.checked = obj.checked || {}; x.checked = obj.checked || {};
x.checkedUA = obj.checkedUA || {};
if (x.credentials[0] && typeof x.credentials[0] != 'object') if (x.credentials[0] && typeof x.credentials[0] != 'object')
throw ("credentials should be an object"); throw ("credentials should be an object");
@ -39,5 +40,63 @@ Profile.fromString = function(str) {
}; };
Profile.prototype.toObj = function() { Profile.prototype.toObj = function() {
delete this.dirty;
return JSON.stringify(this); return JSON.stringify(this);
}; };
Profile.prototype.hasWallet = function(walletId) {
for (var i in this.credentials) {
var c = this.credentials[i];
if (c.walletId == walletId) return true;
};
return false;
};
Profile.prototype.isChecked = function(ua, walletId) {
return !!(this.checkedUA == ua && this.checked[walletId]);
};
Profile.prototype.setChecked = function(ua, walletId) {
if (this.checkedUA != ua) {
this.checkedUA = ua;
this.checked = {};
}
this.checked[walletId] = true;
this.dirty = true;
};
Profile.prototype.addWallet = function(credentials) {
if (this.hasWallet(credentials.walletId))
return false;
this.credentials.push(credentials);
this.dirty = true;
return true;
};
Profile.prototype.updateWallet = function(credentials) {
if (!this.hasWallet(credentials.walletId))
return false;
this.credentials = this.credentials.filter(function(c) {
return c.walletId != walletId;
});
this.addWallet(credentials);
this.dirty = true;
return true;
};
Profile.prototype.deleteWallet = function(walletId) {
if (!this.hasWallet(walletId))
return false;
this.credentials = this.credentials.filter(function(c) {
return c.walletId != walletId;
});
this.dirty = true;
return true;
};

View file

@ -12,11 +12,8 @@ if (window && window.navigator) {
} }
} }
//Setting up route //Setting up route
angular angular.module('copayApp').config(function(historicLogProvider, $provide, $logProvider, $stateProvider, $urlRouterProvider, $compileProvider) {
.module('copayApp')
.config(function(historicLogProvider, $provide, $logProvider, $stateProvider, $urlRouterProvider, $compileProvider) {
$urlRouterProvider.otherwise('/'); $urlRouterProvider.otherwise('/');
$logProvider.debugEnabled(true); $logProvider.debugEnabled(true);
@ -45,7 +42,7 @@ angular
v = JSON.stringify(v); v = JSON.stringify(v);
} }
// Trim output in mobile // Trim output in mobile
if (window.cordova) { if (platformInfo.isCordova) {
v = v.toString(); v = v.toString();
if (v.length > 3000) { if (v.length > 3000) {
v = v.substr(0, 2997) + '...'; v = v.substr(0, 2997) + '...';
@ -59,7 +56,7 @@ angular
}); });
try { try {
if (window.cordova) if (platformInfo.isCordova)
console.log(args.join(' ')); console.log(args.join(' '));
historicLog.add(level, args.join(' ')); historicLog.add(level, args.join(' '));
@ -148,11 +145,6 @@ angular
}, },
} }
}) })
.state('importProfile', {
url: '/importProfile',
templateUrl: 'views/importProfile.html',
needProfile: false
})
.state('importLegacy', { .state('importLegacy', {
url: '/importLegacy', url: '/importLegacy',
needProfile: true, needProfile: true,
@ -526,49 +518,83 @@ angular
templateUrl: 'views/add.html' templateUrl: 'views/add.html'
}, },
} }
})
.state('cordova', {
url: '/cordova/:status/:fromHome/:fromDisclaimer/:secondBackButtonPress',
views: {
'main': {
controller: function($rootScope, $state, $stateParams, $timeout, go, platformInfo, gettextCatalog) {
switch ($stateParams.status) {
case 'resume':
$rootScope.$emit('Local/Resume');
break;
case 'backbutton':
if ($stateParams.fromDisclaimer == 'true')
navigator.app.exitApp();
if (platformInfo.isCordova && $stateParams.fromHome == 'true' && !$rootScope.modalOpened) {
if ($stateParams.secondBackButtonPress == 'true') {
navigator.app.exitApp();
} else {
window.plugins.toast.showShortBottom(gettextCatalog.getString('Press again to exit'));
}
} else {
$rootScope.$emit('closeModal');
}
break;
};
$timeout(function() {
$rootScope.$emit('Local/SetTab', 'walletHome', true);
}, 100);
go.walletHome();
}
}
},
needProfile: false
}); });
}) })
.run(function($rootScope, $state, $log, uriHandler, platformInfo, profileService, $timeout, uxLanguage, animationService) { .run(function($rootScope, $state, $log, $timeout, $ionicPlatform, uriHandler, platformInfo, profileService, uxLanguage, animationService, go, gettextCatalog) {
$ionicPlatform.ready(function() {
if (platformInfo.isCordova) {
$ionicPlatform.registerBackButtonAction(function(event) {
event.preventDefault();
}, 100);
var secondBackButtonPress = false;
var intval = setInterval(function() {
secondBackButtonPress = false;
}, 5000);
$ionicPlatform.on('pause', function() {
// Nothing to do
});
$ionicPlatform.on('resume', function() {
if (!window.ignoreMobilePause) {
$rootScope.$emit('Local/Resume');
}
setTimeout(function() {
var loc = window.location;
var ignoreMobilePause = loc.toString().match(/(buy|sell|buycoinbase|sellcoinbase)/) ? true : false;
window.ignoreMobilePause = ignoreMobilePause;
}, 100);
});
$ionicPlatform.on('backbutton', function(event) {
var loc = window.location;
var fromDisclaimer = loc.toString().match(/disclaimer/) ? 'true' : '';
var fromHome = loc.toString().match(/index\.html#\/$/) ? 'true' : '';
if (fromDisclaimer == 'true')
navigator.app.exitApp();
if (platformInfo.isMobile && fromHome == 'true') {
if (secondBackButtonPress)
navigator.app.exitApp();
else
window.plugins.toast.showShortBottom(gettextCatalog.getString('Press again to exit'));
}
if (secondBackButtonPress)
clearInterval(intval);
else
secondBackButtonPress = true;
$timeout(function() {
$rootScope.$emit('Local/SetTab', 'walletHome', true);
}, 100);
go.walletHome();
setTimeout(function() {
window.ignoreMobilePause = false;
}, 100);
});
$ionicPlatform.on('menubutton', function() {
window.location = '#/preferences';
});
setTimeout(function() {
navigator.splashscreen.hide();
}, 1000);
}
});
uxLanguage.init(); uxLanguage.init();
// Register URI handler, not for mobileApp // Register URI handler, not for mobileApp
if (!platformInfo.isCordova) { if (!platformInfo.isMobile) {
uriHandler.register(); uriHandler.register();
} }
@ -606,6 +632,7 @@ angular
throw new Error(err); // TODO throw new Error(err); // TODO
} }
} else { } else {
profileService.storeProfileIfDirty();
$log.debug('Profile loaded ... Starting UX.'); $log.debug('Profile loaded ... Starting UX.');
$state.transitionTo(toState.name || toState, toParams); $state.transitionTo(toState.name || toState, toParams);
} }

View file

@ -31,25 +31,18 @@ angular.module('copayApp.services')
}); });
$log.debug("case 3"); $log.debug("case 3");
} else { } else {
// We're screwed, blob constructor unsupported entirely // We're screwed, blob constructor unsupported entirely
$log.debug("Error"); $log.debug("Error");
} }
} }
return out; return out;
}; };
var a = document.createElement("a"); var a = angular.element('<a></a>');
document.body.appendChild(a);
a.style.display = "none";
var blob = new NewBlob(ew, 'text/plain;charset=utf-8'); var blob = new NewBlob(ew, 'text/plain;charset=utf-8');
var url = window.URL.createObjectURL(blob); a.attr('href',window.URL.createObjectURL(blob));
a.href = url; a.attr('download', filename);
a.download = filename; a[0].click();
a.click();
$timeout(function() {
window.URL.revokeObjectURL(url);
}, 250);
return cb(); return cb();
}; };
@ -92,4 +85,4 @@ angular.module('copayApp.services')
_download(ew, filename, cb) _download(ew, filename, cb)
}; };
return root; return root;
}); });

View file

@ -1,36 +1,10 @@
'use strict'; 'use strict';
angular.module('copayApp.services').factory('go', function($window, $rootScope, $location, $state, $timeout, $log, profileService, platformInfo, nodeWebkit) { angular.module('copayApp.services').factory('go', function($window, $ionicSideMenuDelegate, $rootScope, $location, $state, $timeout, $log, profileService, platformInfo, nodeWebkit) {
var root = {}; var root = {};
var hideSidebars = function() {
if (typeof document === 'undefined')
return;
var elem = document.getElementById('off-canvas-wrap');
elem.className = 'off-canvas-wrap';
};
var toggleSidebar = function(invert) {
if (typeof document === 'undefined')
return;
var elem = document.getElementById('off-canvas-wrap');
var leftbarActive = elem.className.indexOf('move-right') >= 0;
if (invert) {
if (profileService.profile && !$rootScope.hideNavigation) {
elem.className = 'off-canvas-wrap move-right';
}
} else {
if (leftbarActive) {
hideSidebars();
}
}
};
root.openExternalLink = function(url, target) { root.openExternalLink = function(url, target) {
if ( platformInfo.isNW) { if (platformInfo.isNW) {
nodeWebkit.openExternalLink(url); nodeWebkit.openExternalLink(url);
} else { } else {
target = target || '_blank'; target = target || '_blank';
@ -49,11 +23,10 @@ angular.module('copayApp.services').factory('go', function($window, $rootScope,
}, function() { }, function() {
if (cb) return cb('animation in progress'); if (cb) return cb('animation in progress');
}); });
hideSidebars();
}; };
root.swipe = function(invert) { root.toggleLeftMenu = function() {
toggleSidebar(invert); $ionicSideMenuDelegate.toggleLeft();
}; };
root.walletHome = function() { root.walletHome = function() {

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.services') angular.module('copayApp.services')
.factory('profileService', function profileServiceFactory($rootScope, $timeout, $filter, $log, sjcl, lodash, storageService, bwcService, configService, notificationService, pushNotificationsService, gettext, gettextCatalog, bwsError, uxLanguage, bitcore, platformInfo) { .factory('profileService', function profileServiceFactory($rootScope, $timeout, $filter, $log, sjcl, lodash, storageService, bwcService, configService, notificationService, pushNotificationsService, gettext, gettextCatalog, bwsError, uxLanguage, bitcore, platformInfo, walletService) {
var isChromeApp = platformInfo.isChromeApp; var isChromeApp = platformInfo.isChromeApp;
@ -62,34 +62,22 @@ angular.module('copayApp.services')
}); });
}; };
root.setWalletClient = function(credentials) { // Adds a wallet client to profileService
if (root.walletClients[credentials.walletId] && root.bindWalletClient = function(client, opts) {
root.walletClients[credentials.walletId].started) { var opts = opts || {};
return; var walletId = client.credentials.walletId;
if ((root.walletClients[walletId] && root.walletClients[walletId].started) || opts.force) {
return false;
} }
root.walletClients[walletId] = client;
var getBaseURL = function(walletId) { root.walletClients[walletId].started = true;
var config = configService.getSync(); root.walletClients[walletId].doNotVerifyPayPro = isChromeApp;
var defaults = configService.getDefaults();
return ((config.bwsFor && config.bwsFor[walletId]) || defaults.bws.url);
};
$log.debug('Importing wallet:' + credentials.walletId);
var skipKeyValidation = root.profile.checked[credentials.walletId] == platformInfo.ua;
var client = bwcService.getClient(JSON.stringify(credentials), {
baseurl: getBaseURL(credentials.walletId),
skipKeyValidation: skipKeyValidation,
});
root.walletClients[credentials.walletId] = client;
if (client.incorrectDerivation) { if (client.incorrectDerivation) {
$log.warn('Key Derivation failed for wallet:' + credentials.walletId); $log.warn('Key Derivation failed for wallet:' + walletId);
storageService.clearLastAddress(credentials.walletId, function() {}); storageService.clearLastAddress(walletId, function() {});
} else if (!skipKeyValidation) {
root.profile.checked[credentials.walletId] = platformInfo.ua;
storageService.storeProfileThrottled(root.profile, function() {});
} }
client.removeAllListeners(); client.removeAllListeners();
@ -100,9 +88,9 @@ angular.module('copayApp.services')
client.on('notification', function(n) { client.on('notification', function(n) {
$log.debug('BWC Notification:', n); $log.debug('BWC Notification:', n);
notificationService.newBWCNotification(n, notificationService.newBWCNotification(n,
client.credentials.walletId, client.credentials.walletName); walletId, client.credentials.walletName);
if (root.focusedClient.credentials.walletId == client.credentials.walletId) { if (root.focusedClient.credentials.walletId == walletId) {
$rootScope.$emit(n.type, n); $rootScope.$emit(n.type, n);
} else { } else {
$rootScope.$apply(); $rootScope.$apply();
@ -112,17 +100,13 @@ angular.module('copayApp.services')
client.on('walletCompleted', function() { client.on('walletCompleted', function() {
$log.debug('Wallet completed'); $log.debug('Wallet completed');
root.updateCredentialsFC(function() { root.updateCredentials(client.export(), function() {
$rootScope.$emit('Local/WalletCompleted', client.credentials.walletId); $rootScope.$emit('Local/WalletCompleted', walletId);
}); });
}); });
root.walletClients[credentials.walletId].started = true;
root.walletClients[credentials.walletId].doNotVerifyPayPro = isChromeApp;
if (client.hasPrivKeyEncrypted() && !client.isPrivKeyEncrypted()) { if (client.hasPrivKeyEncrypted() && !client.isPrivKeyEncrypted()) {
$log.warn('Auto locking unlocked wallet:' + credentials.walletId); $log.warn('Auto locking unlocked wallet:' + walletId);
client.lock(); client.lock();
} }
@ -133,15 +117,35 @@ angular.module('copayApp.services')
} }
client.setNotificationsInterval(BACKGROUND_UPDATE_PERIOD); client.setNotificationsInterval(BACKGROUND_UPDATE_PERIOD);
}); });
}
root.setWalletClients = function() { return true;
var credentials = root.profile.credentials; };
lodash.each(credentials, function(credential) {
//$log.info("Credentials:", credentials);
root.setWalletClient(credential); // Used when reading wallets from the profile
root.bindWallet = function(credentials) {
if (!credentials.walletId)
throw 'bindWallet should receive credentials JSON';
$log.debug('Bind wallet:' + credentials.walletId);
// Create the client
var getBaseURL = function(walletId) {
var config = configService.getSync();
var defaults = configService.getDefaults();
return ((config.bwsFor && config.bwsFor[walletId]) || defaults.bws.url);
};
var skipKeyValidation = root.profile.isChecked(platformInfo.ua, credentials.walletId);
var client = bwcService.getClient(JSON.stringify(credentials), {
baseurl: getBaseURL(credentials.walletId),
skipKeyValidation: skipKeyValidation,
}); });
$rootScope.$emit('Local/WalletListUpdated');
if (!skipKeyValidation && !client.incorrectDerivation)
root.profile.setChecked(platformInfo.ua, credentials.walletId);
return root.bindWalletClient(client);
}; };
root.bindProfile = function(profile, cb) { root.bindProfile = function(profile, cb) {
@ -150,11 +154,15 @@ angular.module('copayApp.services')
configService.get(function(err) { configService.get(function(err) {
$log.debug('Preferences read'); $log.debug('Preferences read');
if (err) return cb(err); if (err) return cb(err);
root.setWalletClients();
lodash.each(root.profile.credentials, function(credentials) {
root.bindWallet(credentials);
});
$rootScope.$emit('Local/WalletListUpdated');
storageService.getFocusedWalletId(function(err, focusedWalletId) { storageService.getFocusedWalletId(function(err, focusedWalletId) {
if (err) return cb(err); if (err) return cb(err);
root._setFocus(focusedWalletId, function() { root._setFocus(focusedWalletId, function() {
$rootScope.$emit('Local/ProfileBound');
if (usePushNotifications) if (usePushNotifications)
root.pushNotificationsInit(); root.pushNotificationsInit();
root.isDisclaimerAccepted(function(val) { root.isDisclaimerAccepted(function(val) {
@ -222,12 +230,11 @@ angular.module('copayApp.services')
}); });
}; };
root._seedWallet = function(opts, cb) { var seedWallet = function(opts, cb) {
opts = opts || {}; opts = opts || {};
var walletClient = bwcService.getClient(null, opts); var walletClient = bwcService.getClient(null, opts);
var network = opts.networkName || 'livenet'; var network = opts.networkName || 'livenet';
if (opts.mnemonic) { if (opts.mnemonic) {
try { try {
opts.mnemonic = root._normalizeMnemonic(opts.mnemonic); opts.mnemonic = root._normalizeMnemonic(opts.mnemonic);
@ -285,45 +292,54 @@ angular.module('copayApp.services')
return cb(null, walletClient); return cb(null, walletClient);
}; };
root._createNewProfile = function(opts, cb) { // Creates a wallet on BWC/BWS
var doCreateWallet = function(opts, cb) {
if (opts.noWallet) {
return cb(null, Profile.create());
}
root._seedWallet({}, function(err, walletClient) {
if (err) return cb(err);
var walletName = gettextCatalog.getString('Personal Wallet');
var me = gettextCatalog.getString('me');
walletClient.createWallet(walletName, me, 1, 1, {
network: 'livenet'
}, function(err) {
if (err) return bwsError.cb(err, gettext('Error creating wallet'), cb);
var p = Profile.create({
credentials: [JSON.parse(walletClient.export())],
});
return cb(null, p);
});
})
};
root.createWallet = function(opts, cb) {
$log.debug('Creating Wallet:', opts); $log.debug('Creating Wallet:', opts);
root._seedWallet(opts, function(err, walletClient) { seedWallet(opts, function(err, walletClient) {
if (err) return cb(err); if (err) return cb(err);
walletClient.createWallet(opts.name, opts.myName || 'me', opts.m, opts.n, { var name = opts.name || gettextCatalog.getString('Personal Wallet');
var myName = opts.myName || gettextCatalog.getString('me');
walletClient.createWallet(name, myName, opts.m, opts.n, {
network: opts.networkName, network: opts.networkName,
walletPrivKey: opts.walletPrivKey, walletPrivKey: opts.walletPrivKey,
}, function(err, secret) { }, function(err, secret) {
if (err) return bwsError.cb(err, gettext('Error creating wallet'), cb); if (err) return bwsError.cb(err, gettext('Error creating wallet'), cb);
return cb(null, walletClient, secret);
root._addWalletClient(walletClient, opts, cb); });
})
}); });
}; };
// Creates the default Copay profile and its wallet
root.createDefaultProfile = function(opts, cb) {
var p = Profile.create();
if (opts.noWallet) {
return cb(null, p);
}
opts.m = 1;
opts.n = 1;
opts.network = 'livenet';
doCreateWallet(opts, function(err, walletClient) {
if (err) return bwsError.cb(err, gettext('Error creating wallet'), cb);
p.addWallet(JSON.parse(walletClient.export()));
return cb(null, p);
});
};
// create and store a wallet
root.createWallet = function(opts, cb) {
doCreateWallet(opts, function(err, walletClient, secret) {
root.addAndBindWalletClient(walletClient, {
bwsurl: opts.bwsurl
}, cb);
});
};
// joins and stores a wallet
root.joinWallet = function(opts, cb) { root.joinWallet = function(opts, cb) {
var walletClient = bwcService.getClient(); var walletClient = bwcService.getClient();
$log.debug('Joining Wallet:', opts); $log.debug('Joining Wallet:', opts);
@ -344,12 +360,14 @@ angular.module('copayApp.services')
opts.networkName = walletData.network; opts.networkName = walletData.network;
$log.debug('Joining Wallet:', opts); $log.debug('Joining Wallet:', opts);
root._seedWallet(opts, function(err, walletClient) { seedWallet(opts, function(err, walletClient) {
if (err) return cb(err); if (err) return cb(err);
walletClient.joinWallet(opts.secret, opts.myName || 'me', {}, function(err) { walletClient.joinWallet(opts.secret, opts.myName || 'me', {}, function(err) {
if (err) return bwsError.cb(err, gettext('Could not join wallet'), cb); if (err) return bwsError.cb(err, gettext('Could not join wallet'), cb);
root._addWalletClient(walletClient, opts, cb); root.addAndBindWalletClient(walletClient, {
bwsurl: opts.bwsurl
}, cb);
}); });
}); });
}; };
@ -358,39 +376,31 @@ angular.module('copayApp.services')
return root.walletClients[walletId]; return root.walletClients[walletId];
}; };
root.deleteWalletFC = function(opts, cb) { root.deleteWalletClient = function(client, cb) {
var fc = root.focusedClient; var walletId = client.credentials.walletId;
var walletId = fc.credentials.walletId;
pushNotificationsService.unsubscribe(root.getClient(walletId), function(err) { pushNotificationsService.unsubscribe(root.getClient(walletId), function(err) {
if (err) $log.warn('Unsubscription error: ' + err.message); if (err) $log.warn('Unsubscription error: ' + err.message);
else $log.debug('Unsubscribed from push notifications service'); else $log.debug('Unsubscribed from push notifications service');
}); });
$log.debug('Deleting Wallet:', fc.credentials.walletName); $log.debug('Deleting Wallet:', client.credentials.walletName);
client.removeAllListeners();
fc.removeAllListeners(); root.profile.deleteWallet(walletId);
root.profile.credentials = lodash.reject(root.profile.credentials, {
walletId: walletId
});
delete root.walletClients[walletId]; delete root.walletClients[walletId];
root.focusedClient = null; root.focusedClient = null;
storageService.clearLastAddress(walletId, function(err) {
storageService.removeAllWalletData(walletId, function(err) {
if (err) $log.warn(err); if (err) $log.warn(err);
}); });
storageService.removeTxHistory(walletId, function(err) {
if (err) $log.warn(err);
});
storageService.clearBackupFlag(walletId, function(err) {
if (err) $log.warn(err);
});
$timeout(function() { $timeout(function() {
root.setWalletClients(); $rootScope.$emit('Local/WalletListUpdated');
root.setAndStoreFocus(null, function() { root.setAndStoreFocus(null, function() {
storageService.storeProfile(root.profile, function(err) { storageService.storeProfile(root.profile, function(err) {
if (err) return cb(err); if (err) return cb(err);
@ -419,59 +429,74 @@ angular.module('copayApp.services')
}); });
} }
root._addWalletClient = function(walletClient, opts, cb) { // Adds and bind a new client to the profile
var walletId = walletClient.credentials.walletId; root.addAndBindWalletClient = function(client, opts, cb) {
var walletId = client.credentials.walletId
// check if exist if (!root.profile.addWallet(JSON.parse(client.export())))
var w = lodash.find(root.profile.credentials, { return cb(gettext('Wallet already in Copay'));
'walletId': walletId
root.bindWalletClient(client);
$rootScope.$emit('Local/WalletListUpdated', client);
var saveBwsUrl = function(cb) {
var defaults = configService.getDefaults();
var bwsFor = {};
bwsFor[walletId] = opts.bwsurl || defaults.bws.url;
// Dont save the default
if (bwsFor[walletId] == defaults.bws.url)
return cb();
configService.set({
bwsFor: bwsFor,
}, function(err) {
if (err) $log.warn(err);
return cb();
});
};
var handleImportedClient = function(cb) {
if (!opts.isImport) return cb();
$rootScope.$emit('Local/BackupDone', walletId);
if (!client.isComplete())
return cb();
storageService.setCleanAndScanAddresses(walletId, cb);
};
walletService.updateRemotePreferences(client, {}, function() {
$log.debug('Remote preferences saved for:' + walletId)
}); });
if (w) {
return cb(gettext('Wallet already in Copay' + ": ") + w.walletName);
}
var config = configService.getSync(); saveBwsUrl(function() {
var defaults = configService.getDefaults(); handleImportedClient(function() {
var bwsFor = {};
bwsFor[walletId] = opts.bwsurl || defaults.bws.url;
configService.set({
bwsFor: bwsFor,
}, function(err) {
if (err) console.log(err);
root.profile.credentials.push(JSON.parse(walletClient.export()));
root.setWalletClients();
var handleImport = function(cb) {
var isImport = opts.mnemonic || opts.externalSource || opts.extendedPrivateKey;
if (!isImport)
return cb();
$rootScope.$emit('Local/BackupDone', walletId);
if (!walletClient.isComplete())
return cb();
storageService.setCleanAndScanAddresses(walletId, cb);
};
handleImport(function() {
root.setAndStoreFocus(walletId, function() { root.setAndStoreFocus(walletId, function() {
storageService.storeProfile(root.profile, function(err) { storageService.storeProfile(root.profile, function(err) {
$rootScope.$emit('Local/ProfileCreated'); var config = configService.getSync();
if (config.pushNotifications.enabled) if (config.pushNotifications.enabled)
pushNotificationsService.enableNotifications(root.walletClients); pushNotificationsService.enableNotifications(root.walletClients);
return cb(err, walletId); return cb(err, walletId);
}); });
}); });
}); });
}); });
}; };
root.storeProfileIfDirty = function(cb) {
if (root.profile.dirty) {
storageService.storeProfile(root.profile, function(err) {
$log.debug('Saved modified Profile');
if (cb) return cb(err);
});
} else {
if (cb) return cb();
};
};
root.importWallet = function(str, opts, cb) { root.importWallet = function(str, opts, cb) {
var walletClient = bwcService.getClient(null, opts); var walletClient = bwcService.getClient(null, opts);
@ -491,7 +516,13 @@ angular.module('copayApp.services')
var addressBook = str.addressBook || {}; var addressBook = str.addressBook || {};
var historyCache = str.historyCache ||  []; var historyCache = str.historyCache ||  [];
root._addWalletClient(walletClient, opts, function(err, walletId) { if (!walletClient.incorrectDerivation)
root.profile.setChecked(platformInfo.ua, walletClient.credentials.walletId);
root.addAndBindWalletClient(walletClient, {
bwsurl: opts.bwsurl,
isImport: true
}, function(err, walletId) {
if (err) return cb(err); if (err) return cb(err);
root.setMetaData(walletClient, addressBook, historyCache, function(error) { root.setMetaData(walletClient, addressBook, historyCache, function(error) {
if (error) $log.warn(error); if (error) $log.warn(error);
@ -508,7 +539,10 @@ angular.module('copayApp.services')
if (err) if (err)
return bwsError.cb(err, gettext('Could not import'), cb); return bwsError.cb(err, gettext('Could not import'), cb);
root._addWalletClient(walletClient, opts, cb); root.addAndBindWalletClient(walletClient, {
bwsurl: opts.bwsurl,
isImport: true
}, cb);
}); });
}; };
@ -533,7 +567,10 @@ angular.module('copayApp.services')
if (err) if (err)
return bwsError.cb(err, gettext('Could not import'), cb); return bwsError.cb(err, gettext('Could not import'), cb);
root._addWalletClient(walletClient, opts, cb); root.addAndBindWalletClient(walletClient, {
bwsurl: opts.bwsurl,
isImport: true
}, cb);
}); });
}; };
@ -554,16 +591,20 @@ angular.module('copayApp.services')
return bwsError.cb(err, gettext('Could not import'), cb); return bwsError.cb(err, gettext('Could not import'), cb);
} }
root._addWalletClient(walletClient, opts, cb); root.addAndBindWalletClient(walletClient, {
bwsurl: opts.bwsurl,
isImport: true
}, cb);
}); });
}; };
root.create = function(opts, cb) { root.create = function(opts, cb) {
$log.info('Creating profile'); $log.info('Creating profile', opts);
var defaults = configService.getDefaults(); var defaults = configService.getDefaults();
configService.get(function(err) { configService.get(function(err) {
root._createNewProfile(opts, function(err, p) {
root.createDefaultProfile(opts, function(err, p) {
if (err) return cb(err); if (err) return cb(err);
root.bindProfile(p, function(err) { root.bindProfile(p, function(err) {
@ -605,59 +646,45 @@ angular.module('copayApp.services')
walletClient.createWalletFromOldCopay(username, password, blob, function(err, existed) { walletClient.createWalletFromOldCopay(username, password, blob, function(err, existed) {
if (err) return cb(gettext('Error importing wallet: ') + err); if (err) return cb(gettext('Error importing wallet: ') + err);
if (root.walletClients[walletClient.credentials.walletId]) { if (root.profile.hasWallet(walletClient.credentials.walletId)) {
$log.debug('Wallet:' + walletClient.credentials.walletName + ' already imported'); $log.debug('Wallet:' + walletClient.credentials.walletName + ' already imported');
return cb(gettext('Wallet Already Imported: ') + walletClient.credentials.walletName); return cb(gettext('Wallet Already Imported: ') + walletClient.credentials.walletName);
}; };
$log.debug('Creating Wallet:', walletClient.credentials.walletName); root.addAndBindWalletClient(walletClient, {
root.profile.credentials.push(JSON.parse(walletClient.export())); isImport: true
root.setWalletClients(); }, cb);
root.setAndStoreFocus(walletClient.credentials.walletId, function() {
storageService.storeProfile(root.profile, function(err) {
return cb(null, walletClient.credentials.walletId, walletClient.credentials.walletName, existed);
});
});
}); });
}; };
root.updateCredentialsFC = function(cb) { root.updateCredentials = function(credentials, cb) {
var fc = root.focusedClient; root.profile.updateWallet(credentials);
var newCredentials = lodash.reject(root.profile.credentials, {
walletId: fc.credentials.walletId
});
newCredentials.push(JSON.parse(fc.export()));
root.profile.credentials = newCredentials;
storageService.storeProfileThrottled(root.profile, cb); storageService.storeProfileThrottled(root.profile, cb);
}; };
root.getClients = function() {
return lodash.values(root.walletClients);
};
root.setPrivateKeyEncryptionFC = function(password, cb) { root.needsBackup = function(client, cb) {
var fc = root.focusedClient;
$log.debug('Encrypting private key for', fc.credentials.walletName);
fc.setPrivateKeyEncryption(password); if (!walletService.needsBackup(client))
fc.lock(); return cb(false);
root.updateCredentialsFC(function() {
$log.debug('Wallet encrypted'); storageService.getBackupFlag(client.credentials.walletId, function(err, val) {
return cb(); if (err) $log.error(err);
if (val) return cb(false);
return cb(true);
}); });
}; };
root.isReady = function(client, cb) {
if (!client.isComplete())
return cb('WALLET_NOT_COMPLETE');
root.disablePrivateKeyEncryptionFC = function(cb) { root.needsBackup(client, function(needsBackup) {
var fc = root.focusedClient; if (needsBackup)
$log.debug('Disabling private key encryption for', fc.credentials.walletName); return cb('WALLET_NEEDS_BACKUP');
try {
fc.disablePrivateKeyEncryption();
} catch (e) {
return cb(e);
}
root.updateCredentialsFC(function() {
$log.debug('Wallet encryption disabled');
return cb(); return cb();
}); });
}; };

View file

@ -24,21 +24,6 @@ angular.module('copayApp.services')
}, cb); }, cb);
}; };
var encryptOnMobile = function(text, cb) {
// UUID encryption is disabled.
return cb(null, text);
//
// getUUID(function(uuid) {
// if (uuid) {
// $log.debug('Encrypting profile');
// text = sjcl.encrypt(uuid, text);
// }
// return cb(null, text);
// });
};
var decryptOnMobile = function(text, cb) { var decryptOnMobile = function(text, cb) {
var json; var json;
try { try {
@ -107,18 +92,14 @@ angular.module('copayApp.services')
}; };
root.storeNewProfile = function(profile, cb) { root.storeNewProfile = function(profile, cb) {
encryptOnMobile(profile.toObj(), function(err, x) { storage.create('profile', profile.toObj(), cb);
storage.create('profile', x, cb);
});
}; };
root.storeProfile = function(profile, cb) { root.storeProfile = function(profile, cb) {
encryptOnMobile(profile.toObj(), function(err, x) { storage.set('profile', profile.toObj(), cb);
storage.set('profile', x, cb);
});
}; };
root.storeProfileThrottled = lodash.throttle(root.storeProfile, 5000); root.storeProfileThrottled = lodash.throttle(root.storeProfile, 5000);
root.getProfile = function(cb) { root.getProfile = function(cb) {
storage.get('profile', function(err, str) { storage.get('profile', function(err, str) {
@ -200,6 +181,14 @@ angular.module('copayApp.services')
storage.remove('config', cb); storage.remove('config', cb);
}; };
root.setHideBalanceFlag = function(walletId, val, cb) {
storage.set('hideBalance-' + walletId, val, cb);
};
root.getHideBalanceFlag = function(walletId, cb) {
storage.get('hideBalance-' + walletId, cb);
};
//for compatibility //for compatibility
root.getCopayDisclaimerFlag = function(cb) { root.getCopayDisclaimerFlag = function(cb) {
storage.get('agreeDisclaimer', cb); storage.get('agreeDisclaimer', cb);
@ -285,5 +274,17 @@ angular.module('copayApp.services')
storage.remove('coinbaseTxs-' + network, cb); storage.remove('coinbaseTxs-' + network, cb);
}; };
root.removeAllWalletData = function(walletId, cb) {
root.clearLastAddress(walletId, function(err) {
if (err) return cb(err);
root.removeTxHistory(walletId, function(err) {
if (err) return cb(err);
root.clearBackupFlag(walletId, function(err) {
return cb(err);
});
});
});
};
return root; return root;
}); });

View file

@ -3,6 +3,8 @@ angular.module('copayApp.services')
.factory('uxLanguage', function languageService($log, lodash, gettextCatalog, amMoment, configService) { .factory('uxLanguage', function languageService($log, lodash, gettextCatalog, amMoment, configService) {
var root = {}; var root = {};
root.currentLanguage = null;
root.availableLanguages = [{ root.availableLanguages = [{
name: 'English', name: 'English',
isoCode: 'en', isoCode: 'en',
@ -33,7 +35,6 @@ angular.module('copayApp.services')
isoCode: 'ru', isoCode: 'ru',
}]; }];
root.currentLanguage = null;
root._detect = function(cb) { root._detect = function(cb) {
@ -99,20 +100,20 @@ angular.module('copayApp.services')
var userLang = configService.getSync().wallet.settings.defaultLanguage; var userLang = configService.getSync().wallet.settings.defaultLanguage;
if (!userLang) { if (!userLang) {
root._detect(function(lang) { root._detect(function(lang) {
userLang = lang; userLang = lang;
if (userLang != root.currentLanguage) { if (userLang != root.currentLanguage) {
root._set(lang); root._set(lang);
} }
return cb(userLang); if (cb) return cb(userLang);
}); });
} else { } else {
if (userLang != root.currentLanguage) { if (userLang != root.currentLanguage) {
root._set(userLang); root._set(userLang);
} }
return cb(userLang);
if (cb) return cb(userLang);
} }
}; };

View file

@ -1,6 +1,9 @@
'use strict'; 'use strict';
angular.module('copayApp.services').factory('walletService', function($log, lodash, trezor, ledger, storageService) { // DO NOT INCLUDE STORAGE HERE \/ \/
angular.module('copayApp.services').factory('walletService', function($log, lodash, trezor, ledger, storageService, configService, uxLanguage) {
// DO NOT INCLUDE STORAGE HERE ^^
var root = {}; var root = {};
var _signWithLedger = function(client, txp, cb) { var _signWithLedger = function(client, txp, cb) {
@ -31,27 +34,14 @@ angular.module('copayApp.services').factory('walletService', function($log, loda
}); });
}; };
root.isBackupNeeded = function(client, cb) { root.needsBackup = function(client) {
if (client.isPrivKeyExternal()) return cb(false); if (client.isPrivKeyExternal()) return false;
if (!client.credentials.mnemonic) return cb(false); if (!client.credentials.mnemonic) return false;
if (client.credentials.network == 'testnet') return cb(false); if (client.credentials.network == 'testnet') return false;
storageService.getBackupFlag(client.credentials.walletId, function(err, val) { return true;
if (err) $log.error(err);
if (val) return cb(false);
return cb(true);
});
}; };
root.isReady = function(client, cb) {
if(!client.isComplete())
return cb('WALLET_NOT_COMPLETE');
root.isBackupNeeded(client, function(needsBackup) {
if (needsBackup)
return cb('WALLET_NEEDS_BACKUP');
return cb();
});
};
root.isEncrypted = function(client) { root.isEncrypted = function(client) {
if (lodash.isEmpty(client)) return; if (lodash.isEmpty(client)) return;
@ -82,7 +72,7 @@ angular.module('copayApp.services').factory('walletService', function($log, loda
}; };
root.createTx = function(client, txp, cb) { root.createTx = function(client, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(client)) if (lodash.isEmpty(txp) || lodash.isEmpty(client))
return cb('MISSING_PARAMETER'); return cb('MISSING_PARAMETER');
if (txp.sendMax) { if (txp.sendMax) {
@ -97,7 +87,7 @@ angular.module('copayApp.services').factory('walletService', function($log, loda
var feeLevelValue = lodash.find(levels, { var feeLevelValue = lodash.find(levels, {
level: txp.feeLevel level: txp.feeLevel
}); });
if (!feeLevelValue || !feeLevelValue.feePerKB) if (!feeLevelValue || !feeLevelValue.feePerKB)
return cb({ return cb({
message: 'Could not get dynamic fee for level: ' + feeLevel message: 'Could not get dynamic fee for level: ' + feeLevel
@ -118,10 +108,12 @@ angular.module('copayApp.services').factory('walletService', function($log, loda
}; };
root.publishTx = function(client, txp, cb) { root.publishTx = function(client, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(client)) if (lodash.isEmpty(txp) || lodash.isEmpty(client))
return cb('MISSING_PARAMETER'); return cb('MISSING_PARAMETER');
client.publishTxProposal({txp: txp}, function(err, publishedTx) { client.publishTxProposal({
txp: txp
}, function(err, publishedTx) {
if (err) return cb(err); if (err) return cb(err);
else { else {
$log.debug('Transaction published'); $log.debug('Transaction published');
@ -131,9 +123,9 @@ angular.module('copayApp.services').factory('walletService', function($log, loda
}; };
root.signTx = function(client, txp, cb) { root.signTx = function(client, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(client)) if (lodash.isEmpty(txp) || lodash.isEmpty(client))
return cb('MISSING_PARAMETER'); return cb('MISSING_PARAMETER');
if (client.isPrivKeyExternal()) { if (client.isPrivKeyExternal()) {
switch (client.getPrivKeyExternalSourceName()) { switch (client.getPrivKeyExternalSourceName()) {
case 'ledger': case 'ledger':
@ -160,14 +152,14 @@ angular.module('copayApp.services').factory('walletService', function($log, loda
}; };
root.broadcastTx = function(client, txp, cb) { root.broadcastTx = function(client, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(client)) if (lodash.isEmpty(txp) || lodash.isEmpty(client))
return cb('MISSING_PARAMETER'); return cb('MISSING_PARAMETER');
if (txp.status != 'accepted') if (txp.status != 'accepted')
return cb('TX_NOT_ACCEPTED'); return cb('TX_NOT_ACCEPTED');
client.broadcastTxProposal(txp, function(err, broadcastedTxp, memo) { client.broadcastTxProposal(txp, function(err, broadcastedTxp, memo) {
if (err) if (err)
return cb(err); return cb(err);
$log.debug('Transaction broadcasted'); $log.debug('Transaction broadcasted');
@ -178,9 +170,9 @@ angular.module('copayApp.services').factory('walletService', function($log, loda
}; };
root.rejectTx = function(client, txp, cb) { root.rejectTx = function(client, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(client)) if (lodash.isEmpty(txp) || lodash.isEmpty(client))
return cb('MISSING_PARAMETER'); return cb('MISSING_PARAMETER');
client.rejectTxProposal(txp, null, function(err, rejectedTxp) { client.rejectTxProposal(txp, null, function(err, rejectedTxp) {
$log.debug('Transaction rejected'); $log.debug('Transaction rejected');
return cb(err, rejectedTxp); return cb(err, rejectedTxp);
@ -188,14 +180,50 @@ angular.module('copayApp.services').factory('walletService', function($log, loda
}; };
root.removeTx = function(client, txp, cb) { root.removeTx = function(client, txp, cb) {
if (lodash.isEmpty(txp) || lodash.isEmpty(client)) if (lodash.isEmpty(txp) || lodash.isEmpty(client))
return cb('MISSING_PARAMETER'); return cb('MISSING_PARAMETER');
client.removeTxProposal(txp, function(err) { client.removeTxProposal(txp, function(err) {
$log.debug('Transaction removed'); $log.debug('Transaction removed');
return cb(err); return cb(err);
}); });
}; };
root.updateRemotePreferences = function(clients, prefs, cb) {
prefs = prefs || {};
if (!lodash.isArray(clients))
clients = [clients];
function updateRemotePreferencesFor(clients, prefs, cb) {
var client = clients.shift();
if (!client) return cb();
$log.debug('Saving remote preferences', client.credentials.walletName, prefs);
client.savePreferences(prefs, function(err) {
// we ignore errors here
if (err) $log.warn(err);
updateRemotePreferencesFor(clients, prefs, cb);
});
};
// Update this JIC.
var config = configService.getSync().wallet.settings;
//prefs.email (may come from arguments)
prefs.language = uxLanguage.getCurrentLanguage();
prefs.unit = config.unitCode;
updateRemotePreferencesFor(clients, prefs, function(err) {
if (err) return cb(err);
lodash.each(clients, function(c) {
c.preferences = lodash.assign(prefs, c.preferences);
});
return cb();
});
};
return root; return root;
}); });

View file

@ -327,7 +327,7 @@ button.radius, .button.radius {
left: 0; left: 0;
width: 100%; width: 100%;
padding: 0.8rem; padding: 0.8rem;
z-index: 9999; z-index: 1;
} }
.backup .button-box { .backup .button-box {

View file

@ -89,7 +89,7 @@ h4.title a {
} }
} }
.modal-content h4, .glidera h4, .coinbase h4, .txModal h4 { .modal-content h4, .glidera h4, .coinbase h4 {
background: #F6F7F9; background: #F6F7F9;
padding: 25px 0px 5px 10px; padding: 25px 0px 5px 10px;
text-transform: uppercase; text-transform: uppercase;
@ -100,12 +100,12 @@ h4.title a {
} }
.walletHome h4.title { .walletHome h4.title {
padding: 0 0 10px 15px; padding: 0px 0 10px 15px;
margin: 5px 0 5px 0; margin: 5px 0 5px 0;
font-size: 16px; font-size: 16px;
} }
.preferences ul, .modal-content ul, .txModal ul { .preferences ul, .modal-content ul {
font-size: 14px; font-size: 14px;
background: white; background: white;
} }
@ -255,6 +255,12 @@ a {
position: relative; position: relative;
} }
.half-row {
width: 50%;
padding: 5px;
}
.content { .content {
width: 100%; width: 100%;
position: absolute; position: absolute;
@ -600,6 +606,10 @@ ul.manage li {
margin-right: 10px; margin-right: 10px;
} }
.m10l {
margin-left: 10px;
}
.m5l { .m5l {
margin-left: 5px; margin-left: 5px;
} }
@ -693,6 +703,10 @@ ul.manage li {
padding-top: 20px; padding-top: 20px;
} }
.p50t {
padding-top: 50px;
}
.p10 { .p10 {
padding: 10px; padding: 10px;
} }
@ -825,10 +839,6 @@ ul.manage li {
} }
} }
.sidebar li.nav-item.selected .name-wallet {
color: #fff;
}
.locked { .locked {
font-size: 11px; font-size: 11px;
color: #7A8C9E; color: #7A8C9E;
@ -863,7 +873,8 @@ ul.manage li {
.header-modal { .header-modal {
background: #fff; background: #fff;
width: 100%; width: 100%;
padding: 0.8rem; padding-top: 60px;
padding-bottom: 20px;
position: relative; position: relative;
} }
@ -1334,61 +1345,6 @@ input.ng-invalid-match {
} }
} }
/* Confirmation popup */
.confirmTxModal {
background: white;
border-radius: 5px;
position: absolute;
width: 90%;
left: 0;
right: 0;
margin: 15% auto;
z-index: 1100;
text-align: center;
}
.confirmHead {
padding: 10px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.txModal-content {
overflow: auto;
height: 100%;
padding-bottom: 50px;
}
.txModal {
background: #f6f7f9;
position: absolute;
width: 100%;
top: 0;
bottom: 0;
overflow: hidden;
z-index: 1100;
}
.alertModal, .passModal {
background: #FFFFFF;
border-radius: 5px;
position: absolute;
width: 90%;
left: 5%;
top: 15%;
z-index: 1100;
}
.modalMask {
position: absolute;
width: 100%;
height: 100%;
z-index: 1099;
opacity: 0.8;
background: black;
}
.tx-details-blockchain li { .tx-details-blockchain li {
cursor: pointer !important; cursor: pointer !important;
} }
@ -1591,14 +1547,6 @@ input.ng-invalid-match {
left: 0; left: 0;
} }
.right-off-canvas-menu {
background-color: #213140;
}
.off-canvas-wrap, .inner-wrap {
height: 100%;
}
.walletHome { .walletHome {
.avatar-wallet { .avatar-wallet {
padding: 0.5rem; padding: 0.5rem;
@ -1664,6 +1612,11 @@ a.missing-copayers {
margin-bottom: 50px; margin-bottom: 50px;
} }
.sidebar ion-content {
background: #2C3E50;
top: 115px;
}
.sidebar { .sidebar {
background: #2C3E50; background: #2C3E50;
.icon { .icon {
@ -1734,16 +1687,24 @@ a.missing-copayers {
color: #fff; color: #fff;
} }
} }
ul.off-canvas-list { }
margin-bottom: 30px;
li a { .sidebar li {
font-size: 12px; overflow: hidden;
font-weight: 300; border-bottom-style: solid;
border-bottom: transparent; border-bottom-width: 1px;
color: #A5B2BF;
padding: 1rem 0.7rem; padding: 1rem 0.7rem;
} font-size: 12px;
} font-weight: 300;
}
.sidebar ul {
margin: 0 0 30px 0;
}
.sidebar a {
color: #A5B2BF;
} }
.modal-content ul li a { .modal-content ul li a {
@ -1767,11 +1728,6 @@ a.missing-copayers {
} }
} }
.sidebar .left-off-canvas-menu {
background: #E4E8EC;
line-height: 24px;
}
/* /*
* Remove all vendors hover / shadow / fade * Remove all vendors hover / shadow / fade
*/ */
@ -1784,23 +1740,6 @@ a.missing-copayers {
box-shadow: none; box-shadow: none;
} }
ul.off-canvas-list li a:hover {
background: none;
}
.move-right .close-menu {
cursor: pointer;
box-shadow: none;
display: block;
position: absolute;
background: none;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1002;
}
/* === */ /* === */
/* Have to set height explicity on ui-view /* Have to set height explicity on ui-view
@ -1991,12 +1930,9 @@ to prevent collapsing during animation*/
} }
.modal-content { .modal-content {
position: fixed; position: relative;
overflow-y: auto;
height: 100%; height: 100%;
width: 100%; width: 100%;
top: 45px;
padding-bottom: 50px;
-webkit-transform: translate3d(0, 0, 0); -webkit-transform: translate3d(0, 0, 0);
background: #f6f7f9; background: #f6f7f9;
} }
@ -2021,13 +1957,6 @@ body.modal-open {
} }
} }
.txModal.animated {
&.slideInRight, &.slideOutRight {
-webkit-animation-duration: 0.3s;
animation-duration: 0.3s;
}
}
.reveal-modal.animated { .reveal-modal.animated {
&.fadeOutUp, &.slideInUp, &.slideInDown { &.fadeOutUp, &.slideInUp, &.slideInDown {
-webkit-animation-duration: 0.3s; -webkit-animation-duration: 0.3s;
@ -2073,7 +2002,7 @@ body.modal-open {
.payment-proposal-head { .payment-proposal-head {
color: #fff; color: #fff;
padding: 0 10px 20px 10px; padding: 40px 10px 20px 10px;
text-align: center; text-align: center;
} }
@ -2083,7 +2012,7 @@ body.modal-open {
padding: 5px 15px; padding: 5px 15px;
background-color: rgba(0, 0, 0, 0.1); background-color: rgba(0, 0, 0, 0.1);
i { i {
position: absolute; position: inherit;
left: 25px; left: 25px;
padding-right: 10px; padding-right: 10px;
border-right: 1px solid; border-right: 1px solid;
@ -2144,4 +2073,3 @@ body.modal-open {
color: #2C3E50; color: #2C3E50;
} }
} }

View file

@ -1,6 +1,9 @@
describe('createController', function() { describe('createController', function() {
var fixtures = { var fixtures = {
// Store prefs
'1eda3e702196b8d5d82fae129249bc79f0d5be2f5309a4e39855e7eb4ad31428': {},
'31f5deeef4cf7fd8fc67297179232e8e4590532960454ad958009132fef3daae': {},
// createWallet 1-1 // createWallet 1-1
'56db6f58f2c212591afb4d508d03e5fb40bb786f23dc56c43b98bde42dc513e5': { '56db6f58f2c212591afb4d508d03e5fb40bb786f23dc56c43b98bde42dc513e5': {
"walletId": "267bfa75-5575-4af7-8aa3-f5186bc99262" "walletId": "267bfa75-5575-4af7-8aa3-f5186bc99262"

View file

@ -0,0 +1,79 @@
describe('disclaimerController', function() {
var walletService;
var storeProfile;
var fixtures = {
'8dc332881e99c908c655147dc6bc605e102b0bd3cf2dbee02ed2a0f4daf2925a': {
"walletId": "eddaef15-f412-462f-9d3e-a793a7f6f6ba"
},
'654145bc3f15f03a8b1ccf55aa1bdcd1cfd5bbe3de90e909fd4e7f9f69ec4d79': {
"copayerId": "1a91ead1b6d13da882a25377a20e460df557e77008ea4f60eecbf984f786cf03",
"wallet": {
"version": "1.0.0",
"createdOn": 1465152783,
"id": "eddaef15-f412-462f-9d3e-a793a7f6f6ba",
"name": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"/gaG7FIkhCiwsWKZUR0sL/cxH+zHMK0=\"}",
"m": 1,
"n": 1,
"status": "complete",
"publicKeyRing": [{
"xPubKey": "xpub6Cb7MYAX7mJR28MfFueCsoDVVHhoWkQxRC4viAeHanYwRNgDo5xMF42xmAeExzfyPXX3GaALNA8hWFMekVYvDF2BALommUhMgZ52szh88fd",
"requestPubKey": "029a167eebe3ccd9987d41743477f8b75e1f3c30463187e1b106e0cc1155efa4dd"
}],
"copayers": [{
"version": 2,
"createdOn": 1465152783,
"xPubKey": "xpub6Cb7MYAX7mJR28MfFueCsoDVVHhoWkQxRC4viAeHanYwRNgDo5xMF42xmAeExzfyPXX3GaALNA8hWFMekVYvDF2BALommUhMgZ52szh88fd",
"id": "1a91ead1b6d13da882a25377a20e460df557e77008ea4f60eecbf984f786cf03",
"name": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"wwZd+2LQgYR6cA==\"}",
"requestPubKey": "029a167eebe3ccd9987d41743477f8b75e1f3c30463187e1b106e0cc1155efa4dd",
"signature": "3045022100ac3f31ef145eabde6a125958aa9d63c2bd4aa27717d7f6905c3e3ff1e733ee8e02206a43200b775ee5c8f7a85c4d3309d155240d5de46a7d9c5e60045bf49779f40b",
"requestPubKeys": [{
"key": "029a167eebe3ccd9987d41743477f8b75e1f3c30463187e1b106e0cc1155efa4dd",
"signature": "3045022100ac3f31ef145eabde6a125958aa9d63c2bd4aa27717d7f6905c3e3ff1e733ee8e02206a43200b775ee5c8f7a85c4d3309d155240d5de46a7d9c5e60045bf49779f40b"
}],
"customData": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"9l63hoVnA71LshCC5xbOTHA+ivBzux7u8SAci56p4aaVIF4qzXQhQKFX+sAFGfBjULm/E1st6awdXnxbAgjbF7D0zsbBFLFOSCw+ko5Xc6o=\"}"
}],
"pubKey": "026d95bb5cc2a30c19e22379ae78b4757aaa2dd0ccbd15a1db054fb50cb98ed361",
"network": "livenet",
"derivationStrategy": "BIP44",
"addressType": "P2PKH",
"addressManager": {
"version": 2,
"derivationStrategy": "BIP44",
"receiveAddressIndex": 0,
"changeAddressIndex": 0,
"copayerIndex": 2147483647
},
"scanStatus": null
}
},
}; // TODO: Read from file
beforeEach(function(done) {
mocks.init(fixtures, 'disclaimerController', {
initController: true,
noDisclaimer: true,
}, done);
});
afterEach(function(done) {
mocks.clear({}, done);
});
it('should be defined', function() {
should.exist(ctrl);
});
it('should create the initial profile', function(done) {
localStorage.clear();
ctrl.init({
walletPrivKey: 'Kz4CFSTgLzoYfMkt97BTBotUbZYXjMts6Ej9HbVfCf5oLmun1BXy',
mnemonic: 'tunnel fork scare industry noble snow tank bullet over gesture nuclear next',
});
setTimeout(function() {
ctrl.creatingProfile.should.equal(false);
done();
}, 100);
});
});

View file

@ -3,6 +3,7 @@ describe('importController', function() {
var storeProfile; var storeProfile;
var fixtures = { var fixtures = {
'31f5deeef4cf7fd8fc67297179232e8e4590532960454ad958009132fef3daae': {},
'4599136eff6deb4c9c78043fa84113617a16d75c45920d662305f6227ae8f0a0': { '4599136eff6deb4c9c78043fa84113617a16d75c45920d662305f6227ae8f0a0': {
"wallet": { "wallet": {
"version": "1.0.0", "version": "1.0.0",

View file

@ -60,7 +60,7 @@ describe('index', function() {
}); });
it('should updates remote preferences', function(done) { it.skip('should updates remote preferences', function(done) {
ctrl.updateRemotePreferences({}, function() { ctrl.updateRemotePreferences({}, function() {
done(); done();
}); });

View 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();
});
});
});
});

View file

@ -12,7 +12,6 @@ describe('walletHome', function() {
mocks.clear({}, done); mocks.clear({}, done);
}); });
it('should be defined', function() { it('should be defined', function() {
should.exist(ctrl); should.exist(ctrl);
}); });

View file

@ -52,6 +52,9 @@ var walletInfo = {
var FIXTURES = { var FIXTURES = {
// store preferences
'1eda3e702196b8d5d82fae129249bc79f0d5be2f5309a4e39855e7eb4ad31428': {},
// Incomplete wallet status // Incomplete wallet status
'd05582c35aa545494e3f3be9713efa9df112d36a324350f6b7141996b824bce2': walletInfo, 'd05582c35aa545494e3f3be9713efa9df112d36a324350f6b7141996b824bce2': walletInfo,
// ^ same thing, twostep=1 // ^ same thing, twostep=1

View file

@ -80,8 +80,11 @@ mocks.init = function(fixtures, controllerName, opts, done) {
angular.module('stateMock', []); angular.module('stateMock', []);
angular.module('stateMock').service("$state", mocks.$state.bind()); angular.module('stateMock').service("$state", mocks.$state.bind());
module('ionic');
module('ngLodash'); module('ngLodash');
module('angularMoment');
module('gettext'); module('gettext');
module('stateMock');
module('bwcModule', function($provide) { module('bwcModule', function($provide) {
$provide.decorator('bwcService', function($delegate, lodash) { $provide.decorator('bwcService', function($delegate, lodash) {
var getClient = $delegate.getClient; var getClient = $delegate.getClient;
@ -104,7 +107,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));
@ -153,8 +156,6 @@ mocks.init = function(fixtures, controllerName, opts, done) {
}); });
}); });
module('angularMoment');
module('stateMock');
module('copayApp.services', { module('copayApp.services', {
$modal: mocks.modal, $modal: mocks.modal,
$timeout: mocks.$timeout, $timeout: mocks.$timeout,
@ -162,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_;
@ -197,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));
@ -212,6 +220,9 @@ mocks.init = function(fixtures, controllerName, opts, done) {
noWallet: true noWallet: true
}, function(err) { }, function(err) {
should.not.exist(err, err); should.not.exist(err, err);
if (opts.noDisclaimer){
return done();
}
_profileService_.setDisclaimerAccepted(function() { _profileService_.setDisclaimerAccepted(function() {
if (!opts.initController) if (!opts.initController)
startController(); startController();

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