Merge pull request #5196 from ajp8164/feat/balance-modal

Initial implementation for balance modal.
This commit is contained in:
Javier Donadío 2016-12-15 13:58:11 -03:00 committed by GitHub
commit 685a11cbfc
12 changed files with 350 additions and 55 deletions

View file

@ -67,6 +67,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
setPendingTxps(status.pendingTxps);
$scope.status = status;
}
refreshAmountSection();
$timeout(function() {
$scope.$apply();
});
@ -107,6 +108,19 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
});
};
$scope.openBalanceModal = function() {
$ionicModal.fromTemplateUrl('views/modals/wallet-balance.html', {
scope: $scope
}).then(function(modal) {
$scope.walletBalanceModal = modal;
$scope.walletBalanceModal.show();
});
$scope.close = function() {
$scope.walletBalanceModal.hide();
};
};
$scope.recreate = function() {
walletService.recreate($scope.wallet, function(err) {
if (err) return;
@ -234,11 +248,10 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
};
var prevPos;
var screenInactive = true;
function getScrollPosition() {
var scrollPosition = $ionicScrollDelegate.getScrollPosition();
if (!scrollPosition || screenInactive) {
if (!scrollPosition) {
$window.requestAnimationFrame(function() {
getScrollPosition();
});
@ -252,16 +265,21 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
return;
}
prevPos = pos;
var amountHeight = 180 - pos;
refreshAmountSection(pos);
};
function refreshAmountSection(scrollPos) {
scrollPos = scrollPos || 0;
var amountHeight = 210 - scrollPos;
if (amountHeight < 80) {
amountHeight = 80;
}
var contentMargin = amountHeight;
if (contentMargin > 180) {
contentMargin = 180;
if (contentMargin > 210) {
contentMargin = 210;
}
var amountScale = (amountHeight / 180);
var amountScale = (amountHeight / 210);
if (amountScale < 0.5) {
amountScale = 0.5;
}
@ -271,11 +289,31 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
var s = amountScale;
// Make space for the balance button when it needs to display.
var TOP_NO_BALANCE_BUTTON = 45;
var TOP_BALANCE_BUTTON = 10;
var top = TOP_NO_BALANCE_BUTTON;
$scope.showBalanceButton = ($scope.wallet.status.totalBalanceSat != $scope.wallet.status.spendableAmount);
if ($scope.showBalanceButton) {
top = TOP_BALANCE_BUTTON;
$scope.showBalanceButton = true;
}
var amountTop = ((amountScale - 0.5) / 0.5) * top;
if (amountTop < 5) {
amountTop = 5;
}
if (amountTop > top) {
amountTop = top;
}
var t = amountTop;
$scope.altAmountOpacity = (amountHeight - 100) / 80;
$window.requestAnimationFrame(function() {
$scope.amountHeight = amountHeight + 'px';
$scope.contentMargin = contentMargin + 'px';
$scope.amountScale = 'scale3d(' + s + ',' + s + ',' + s + ')';
$scope.amountScale = 'scale3d(' + s + ',' + s + ',' + s + ') translateY(' + t + 'px)';
$scope.$digest();
getScrollPosition();
});
@ -285,16 +323,10 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
$scope.$on("$ionicView.enter", function(event, data) {
if ($scope.isCordova && $scope.isAndroid) setAndroidStatusBarColor();
$timeout(function() {
screenInactive = false;
}, 200);
if (scrollWatcherInitialized || !$scope.amountIsCollapsible) {
return;
}
scrollWatcherInitialized = true;
$timeout(function() {
getScrollPosition();
}, 100);
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
@ -308,6 +340,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
});
$scope.updateAll();
refreshAmountSection();
listeners = [
$rootScope.$on('bwsEvent', function(e, walletId) {
@ -328,7 +361,6 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
});
$scope.$on("$ionicView.leave", function(event, data) {
screenInactive = true;
lodash.each(listeners, function(x) {
x();
});

View file

@ -160,19 +160,22 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
// Address with Balance
cache.balanceByAddress = balance.byAddress;
// Total wallet balance is same regardless of 'spend unconfirmed funds' setting.
cache.totalBalanceSat = balance.totalAmount;
// Spend unconfirmed funds
if (config.spendUnconfirmed) {
cache.totalBalanceSat = balance.totalAmount;
cache.lockedBalanceSat = balance.lockedAmount;
cache.availableBalanceSat = balance.availableAmount;
cache.totalBytesToSendMax = balance.totalBytesToSendMax;
cache.pendingAmount = null;
cache.pendingAmount = 0;
cache.spendableAmount = balance.totalAmount - balance.lockedAmount;
} else {
cache.totalBalanceSat = balance.totalConfirmedAmount;
cache.lockedBalanceSat = balance.lockedConfirmedAmount;
cache.availableBalanceSat = balance.availableConfirmedAmount;
cache.totalBytesToSendMax = balance.totalBytesToSendConfirmedMax;
cache.pendingAmount = balance.totalAmount - balance.totalConfirmedAmount;
cache.spendableAmount = balance.totalConfirmedAmount - balance.lockedAmount;
}
// Selected unit
@ -184,13 +187,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
cache.totalBalanceStr = txFormatService.formatAmount(cache.totalBalanceSat) + ' ' + cache.unitName;
cache.lockedBalanceStr = txFormatService.formatAmount(cache.lockedBalanceSat) + ' ' + cache.unitName;
cache.availableBalanceStr = txFormatService.formatAmount(cache.availableBalanceSat) + ' ' + cache.unitName;
cache.pendingBalanceStr = txFormatService.formatAmount(cache.totalBalanceSat + (cache.pendingAmount === null ? 0 : cache.pendingAmount)) + ' ' + cache.unitName;
if (cache.pendingAmount !== null && cache.pendingAmount !== 0) {
cache.pendingAmountStr = txFormatService.formatAmount(cache.pendingAmount) + ' ' + cache.unitName;
} else {
cache.pendingAmountStr = null;
}
cache.spendableBalanceStr = txFormatService.formatAmount(cache.spendableAmount) + ' ' + cache.unitName;
cache.pendingBalanceStr = txFormatService.formatAmount(cache.pendingAmount) + ' ' + cache.unitName;
cache.alternativeName = config.settings.alternativeName;
cache.alternativeIsoCode = config.settings.alternativeIsoCode;
@ -209,11 +207,15 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
rateService.whenAvailable(function() {
var totalBalanceAlternative = rateService.toFiat(cache.totalBalanceSat, cache.alternativeIsoCode);
var pendingBalanceAlternative = rateService.toFiat(cache.pendingAmount, cache.alternativeIsoCode);
var lockedBalanceAlternative = rateService.toFiat(cache.lockedBalanceSat, cache.alternativeIsoCode);
var spendableBalanceAlternative = rateService.toFiat(cache.spendableAmount, cache.alternativeIsoCode);
var alternativeConversionRate = rateService.toFiat(100000000, cache.alternativeIsoCode);
cache.totalBalanceAlternative = $filter('formatFiatAmount')(totalBalanceAlternative);
cache.pendingBalanceAlternative = $filter('formatFiatAmount')(pendingBalanceAlternative);
cache.lockedBalanceAlternative = $filter('formatFiatAmount')(lockedBalanceAlternative);
cache.spendableBalanceAlternative = $filter('formatFiatAmount')(spendableBalanceAlternative);
cache.alternativeConversionRate = $filter('formatFiatAmount')(alternativeConversionRate);
cache.alternativeBalanceAvailable = true;

View file

@ -111,6 +111,13 @@
font-weight: 300;
color: $light-gray;
}
&__status-icon {
font-size: 18px;
margin-left: 5px;
position: relative;
top: 1px;
color: $light-gray;
}
}
}
.release {

View file

@ -9,6 +9,7 @@
@import "tab-scan";
@import "tab-send";
@import "tab-settings";
@import "walletBalance";
@import "walletDetails";
@import "advancedSettings";
@import "bitpayCard";

View file

@ -0,0 +1,113 @@
.wallet-balance {
&__amount {
font-size: 16px;
&--total {
color: $mid-gray;
}
&--available {
color: #09C286;;
}
&--confirming {
color: #FF9900;
}
&--locked {
color: #FF9900;
}
&--alternative {
color: $light-gray;
font-size: 14px;
}
}
&__title {
flex-grow: 1;
color: $dark-gray;
overflow: hidden;
}
&__icon {
float: left;
margin-right: 1rem;
color: $light-gray;
font-size: 24px;
}
&__list {
background: #f5f5f5;
}
.item {
display: flex;
align-items: center;
background: #fff;
padding-left: 1rem;
}
&__item {
display: flex;
align-items: center;
background: #fff;
padding: 0;
margin: 0;
border: 0;
padding-left: 1rem;
}
&__content {
display: flex;
align-items: center;
flex-grow: 1;
padding: 0.6rem 0;
padding-right: 1rem;
border-bottom: 1px solid rgb(245, 245, 245);
overflow: hidden;
&.no-border {
border: 0;
}
}
&__amount {
white-space: nowrap;
}
&__heading {
font-size: 17px;
color: #445;
margin: 1rem 1rem 1rem 1rem;
}
&__description {
font-size: 12.5px;
color: #667;
margin: 0.7rem;
line-height: 16px;
}
}
#wallet-balance {
.bar-header {
border: 0;
background: none;
.title, .button {
color: #fff;
}
.button {
background-color: transparent;
}
}
ion-content {
background: #F5F5F5;
}
}

View file

@ -130,7 +130,7 @@
ion-content {
&.collapsible {
margin-top: 180px;
margin-top: 210px;
}
padding-top: 0;
@ -168,9 +168,9 @@
width: 100%;
text-align: center;
color: #fff;
height: 180px;
height: 210px;
padding-top: 40px;
display: flex;
display: block;
align-items: center;
justify-content: center;
@ -179,8 +179,7 @@
}
&__balance {
transform: scale3d(1, 1, 1);
margin-top: 5px;
transform: scale3d(1, 1, 1) translateY(45px);
}
&__updating {
@ -192,9 +191,11 @@
line-height: 36px;
}
strong {
line-height: 100%;
&__button-balance {
background-color: transparent;
border: 1px solid rgba(255,255,255,0.25);
}
}
.item.item-footer {
font-weight: lighter;
@ -214,7 +215,7 @@
position: absolute;
top: inherit;
left: 10px;
bottom: 15px;
bottom: 5px;
font-size: 20px;
color: #fff;
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="33px" height="33px" viewBox="0 0 33 33" enable-background="new 0 0 33 33" xml:space="preserve">
<title>Group 2</title>
<desc>Created with Sketch.</desc>
<g id="Group-2" transform="translate(8.000000, 8.000000)">
<g id="Group" transform="translate(8.000000, 7.500000)">
<path id="Shape" fill="none" stroke="#7A8C9E" d="M-14.5,1c0-8.733,6.724-15.881,14.94-15.881c5.828,0,10.904,3.491,13.298,8.733"
/>
<path id="Shape_1_" fill="none" stroke="#7A8C9E" d="M15.5,1c0,8.732-6.73,15.881-14.94,15.881c-5.828,0-10.903-3.49-13.298-8.732
"/>
<polyline id="Shape_2_" fill="none" stroke="#7A8C9E" points="15.5,-14.881 14.248,-6.06 4.917,-7.258 "/>
<polyline id="Shape_3_" fill="none" stroke="#7A8C9E" points="-14.5,16.881 -13.254,8.061 -3.911,9.258 "/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
<path fill="#9B9BAB" d="M50,0C22.41,0,0,22.409,0,50s22.41,50,50,50c27.59,0,50-22.409,50-50S77.59,0,50,0z M50,4
c25.429,0,46,20.571,46,46S75.429,96,50,96S4,75.429,4,50S24.571,4,50,4z M47.562,25.812c-0.43,0-0.688,0.257-0.688,0.688v5.25
c0,0.43,0.255,0.75,0.688,0.75h4.875c0.428,0,0.688-0.32,0.688-0.75V26.5c0-0.43-0.258-0.688-0.688-0.688H47.562z M47.438,40.25
C47.176,40.351,47,40.615,47,40.938v32.938c0,0.43,0.257,0.688,0.688,0.688h4.625c0.43,0,0.688-0.256,0.688-0.688V40.938
c0-0.43-0.256-0.688-0.688-0.688h-4.625C47.581,40.25,47.525,40.216,47.438,40.25z"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

15
www/img/icon-sigma.svg Normal file
View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" id="svg602" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="400px" height="500px" viewBox="0 0 400 500" enable-background="new 0 0 400 500" xml:space="preserve">
<g id="XMLID_1_">
<g>
<path fill="#9B9BAB" d="M383,353l12,3c-8.49,45.05-18.39,92.31-22,138L3.77,495.91l0.55-14.26l190.41-226.54L6.41,20.35L6,5h356
l10,121h-13c-0.99-12.58-3.73-25.02-7.67-37C328.26,18.92,245.31,33,187,33l-90.96,0.36l156.25,195.32L79,438h170
c30.3,0,64.83,2.32,91-16.04C363.87,405.21,371.82,378.23,383,353z"/>
</g>
<g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 859 B

View file

@ -0,0 +1,96 @@
<ion-modal-view id="wallet-balance">
<ion-header-bar align-title="center" ng-style="{'background-color': wallet.color}">
<button class="button button-clear" ng-click="close()" translate>
Close
</button>
<div class="title">
{{wallet.name}}
</div>
<div class="buttons buttons-right header-item">
<span class="secondary-buttons">
<button class="button button-clear" ng-click="updateAll(true)">
<i class="icon ion-ios-refresh-empty"></i>
</button>
</span>
</div>
</ion-header-bar>
<ion-content>
<div class="wallet-balance__heading" translate>All of your bitcoin wallet balance may not be available for immediate spending.</div>
<div class="wallet-balance__list list card">
<div class="wallet-balance__item item">
<img class="wallet-balance__icon" src="img/icon-sigma.svg" height="18" width="18"\>
<div class="wallet-balance__content no-border">
<div class="wallet-balance__title" translate>Total</div>
<span class="item-note text-right wallet-balance__amount">
<span class="wallet-balance__amount wallet-balance__amount--total">
{{status.totalBalanceStr}}
</span>
<div>
<span class="wallet-balance__amount--alternative ng-binding">
{{status.totalBalanceAlternative}} {{status.alternativeIsoCode}}
</span>
</div>
</span>
</div>
</div>
<div class="wallet-balance__description" translate>The total amount of bitcoin stored in this wallet.</div>
</div>
<div class="wallet-balance__list list card">
<div class="wallet-balance__item item">
<i class="wallet-balance__icon icon ion-ios-checkmark-outline"></i>
<div class="wallet-balance__content no-border">
<div class="wallet-balance__title" translate>Available</div>
<span class="item-note text-right wallet-balance__amount">
<span class="wallet-balance__amount wallet-balance__amount--available">
{{status.spendableBalanceStr}}
</span>
<div>
<span class="wallet-balance__amount--alternative ng-binding">
{{status.spendableBalanceAlternative}} {{status.alternativeIsoCode}}
</span>
</div>
</span>
</div>
</div>
<div class="wallet-balance__description" translate>The amount of bitcoin immediately spendable from this wallet.</div>
</div>
<div class="wallet-balance__list list card">
<div class="wallet-balance__item item">
<img class="wallet-balance__icon" src="img/icon-confirming-clear.svg" width="18"\>
<div class="wallet-balance__content no-border">
<div class="wallet-balance__title" translate>Confirming</div>
<span class="item-note text-right wallet-balance__amount">
<span class="wallet-balance__amount wallet-balance__amount--confirming">
{{status.pendingBalanceStr}}
</span>
<div>
<span class="wallet-balance__amount--alternative ng-binding">
{{status.pendingBalanceAlternative}} {{status.alternativeIsoCode}}
</span>
</div>
</span>
</div>
</div>
<div class="wallet-balance__description" translate>The amount of bitcoin stored in this wallet with less than 1 blockchain confirmation.</div>
</div>
<div class="wallet-balance__list list card">
<div class="wallet-balance__item item">
<img class="wallet-balance__icon" src="img/icon-lock.svg" width="18"\>
<div class="wallet-balance__content no-border">
<div class="wallet-balance__title" translate>Locked</div>
<span class="item-note text-right wallet-balance__amount">
<span class="wallet-balance__amount wallet-balance__amount--locked">
{{status.lockedBalanceStr}}
</span>
<div>
<span class="wallet-balance__amount--alternative ng-binding">
{{status.lockedBalanceAlternative}} {{status.alternativeIsoCode}}
</span>
</div>
</span>
</div>
</div>
<div class="wallet-balance__description" translate>The amount of bitcoin stored in this wallet that is allocated as inputs to your pending transaction proposals. The amount is determined using unspent transaction outputs associated with this wallet and may be more than the actual amounts associated with your pending transaction proposals.</div>
</div>
</ion-content>
</ion-modal-view>

View file

@ -80,11 +80,12 @@
Incomplete
</span>
<span ng-if="wallet.isComplete()">
<span ng-if="!wallet.balanceHidden">{{wallet.status.pendingBalanceStr}}</span>
<span ng-if="!wallet.balanceHidden">{{wallet.status.totalBalanceStr}}</span>
<span ng-if="wallet.balanceHidden" translate>[Balance Hidden]</span>
<span class="tab-home__wallet__multisig-number" ng-if="wallet.n > 1">
{{wallet.m}}-of-{{wallet.n}}
</span>
<i ng-if="!wallet.balanceHidden && (wallet.status.totalBalanceSat != wallet.status.spendableAmount)" class="tab-home__wallet__status-icon ion-ios-timer-outline"></i>
<span class="assertive" ng-if="wallet.error">{{wallet.error}}</span>
</span>
&nbsp;

View file

@ -27,11 +27,12 @@
<div ng-if="!notAuthorized && !updatingStatus">
<div ng-show="updateStatusError">
<span class="size-12 db m10">{{updateStatusError}}</span>
<a class="button button-outline button-light button-small" ng-click='update()' translate>Tap to retry</a>
</div>
<div ng-show="walletNotRegistered">
<span class="size-12 db m10b" translate>This wallet is not registered at the given Bitcore Wallet Service (BWS). You can recreate it from the local information.</span>
<span class="size-12 db m10" translate>This wallet is not registered at the given Bitcore Wallet Service (BWS). You can recreate it from the local information.</span>
<a class="button button-outline button-light button-small" ng-click='recreate()' translate>Recreate</a>
</div>
@ -45,21 +46,15 @@
ng-show="!updateStatusError && wallet.walletScanStatus != 'error' && !wallet.balanceHidden"
on-hold="hideToggle()"
ng-style="{'transform': amountScale}"
class="amount__balance"
>
<strong ng-if="!status.pendingAmount" class="size-36">{{status.totalBalanceStr}}</strong>
class="amount__balance">
<strong class="size-36">{{status.totalBalanceStr}}</strong>
<div
ng-if="!status.pendingAmount"
class="size-14 amount-alternative"
ng-if="status.totalBalanceAlternative"
ng-style="{opacity: altAmountOpacity}"
>
{{status.totalBalanceAlternative}} {{status.alternativeIsoCode}}
</div>
<div class="size-20" ng-if="status.pendingAmount">
<div style="margin-bottom:.5rem"><span translate>Available</span>: {{status.totalBalanceStr}}</div>
<div><span translate>Confirming</span>: {{status.pendingAmountStr}}</div>
</div>
</div>
<div ng-style="{'transform': amountScale}"
@ -71,6 +66,20 @@
Tap and hold to show
</div>
</div>
<div ng-if="!wallet.balanceHidden && showBalanceButton" ng-style="{'opacity': altAmountOpacity, 'transform': amountScale}">
<button class="button button-standard button-primary amount__button-balance size-14" ng-click="openBalanceModal()">
<i class="icon ion-ios-checkmark-outline"></i>
<strong>
{{status.spendableBalanceStr}}
</strong>
&nbsp;
<span>
{{status.spendableBalanceAlternative}} {{status.alternativeIsoCode}}
</span>
</button>
</div>
</div>
<div ng-if="updatingStatus" class="amount__updating">
<div class="size-36">
@ -78,7 +87,6 @@
</div>
</div>
</div>
<div class="wallet-details-wallet-info" ng-style="{opacity: altAmountOpacity}">
<span ng-include="'views/includes/walletInfo.html'"></span>
</div>
@ -105,24 +113,14 @@
<div ng-if="!updatingStatus">
<div ng-show="updateStatusError">
<span class="size-12 db m10b">{{updateStatusError}}</span>
<span class="size-12 db m10">{{updateStatusError}}</span>
<a class="button button-outline button-light button-small" ng-click='updateAll()' translate>Tap to retry</a>
</div>
<div ng-show="walletNotRegistered">
<span class="size-12 db m10b" translate>This wallet is not registered at the given Bitcore Wallet Service (BWS). You can recreate it from the local information.</span>
<span class="size-12 db m10" translate>This wallet is not registered at the given Bitcore Wallet Service (BWS). You can recreate it from the local information.</span>
<a class="button button-outline button-light button-small" ng-click='recreate()' translate>Recreate</a>
</div>
<div ng-click='updateAll(true)' ng-show="!updateStatusError && !wallet.balanceHidden" on-hold="hideToggle()">
<strong ng-if="!status.pendingAmount" class="size-36">{{status.totalBalanceStr}}</strong>
<div ng-if="!status.pendingAmount" class="size-14 amount-alternative" ng-if="status.totalBalanceAlternative">{{status.totalBalanceAlternative}} {{status.alternativeIsoCode}}</div>
<div class="size-20" ng-if="status.pendingAmount">
<div style="margin-bottom:.5rem"><span translate>Available</span>: {{status.totalBalanceStr}}</div>
<div><span translate>Confirming</span>: {{status.pendingAmountStr}}</div>
</div>
</div>
<div ng-show="!updateStatusError && wallet.balanceHidden" on-hold="hideToggle()">
<strong class="size-24" translate>[Balance Hidden]</strong>
<div class="size-14" translate>