implement slide to pay

This commit is contained in:
Marty Alcala 2016-10-07 20:03:51 -04:00
commit 777c2efc32
22 changed files with 659 additions and 50 deletions

View file

@ -7,6 +7,7 @@ var modules = [
'ionic',
'ionic-toast',
'angular-clipboard',
'ngTouch',
'ngLodash',
'ngCsv',
'angular-md5',

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, walletService, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, profileService, bitcore, gettext, txFormatService, ongoingProcess, $ionicModal, popupService) {
angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, walletService, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, profileService, bitcore, gettext, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory) {
var cachedTxp = {};
var isChromeApp = platformInfo.isChromeApp;
@ -273,9 +273,10 @@ angular.module('copayApp.controllers').controller('confirmController', function(
if (err) return setSendError(err);
});
}
ongoingProcess.set('creatingTx', true);
ongoingProcess.set('creatingTx', true, onSendStatusChange);
createTx(wallet, false, function(err, txp) {
ongoingProcess.set('creatingTx', false);
ongoingProcess.set('creatingTx', false, onSendStatusChange);
if (err) return;
var config = configService.getSync();
@ -308,9 +309,29 @@ angular.module('copayApp.controllers').controller('confirmController', function(
});
};
function onSendStatusChange(processName, showName, isOn) {
if(processName === 'broadcastingTx' && !isOn) {
$scope.sendStatus = 'success';
$scope.$digest();
} else if(showName) {
$scope.sendStatus = showName;
}
}
$scope.onConfirm = function() {
$scope.approve(true);
};
$scope.onSuccessConfirm = function() {
$ionicHistory.nextViewOptions({
disableAnimate: true
});
$state.go('tabs.send');
};
function publishAndSign(wallet, txp) {
walletService.publishAndSign(wallet, txp, function(err, txp) {
if (err) return setSendError(err);
});
};
}, onSendStatusChange);
}
});

View file

@ -44,10 +44,11 @@ angular.module('copayApp.controllers').controller('tourController',
};
ongoingProcess.set('creatingWallet', false);
var wallet = walletClient;
$state.go('onboarding.collectEmail', {
fromOnboarding: true,
walletId: wallet.credentials.walletId
});
// $state.go('onboarding.collectEmail', {
// fromOnboarding: true,
// walletId: wallet.credentials.walletId
// });
$state.go('tabs.home');
});
};

View file

@ -162,23 +162,4 @@ angular.module('copayApp.directives')
});
}
}
})
.directive('accept', function() {
return {
restrict: 'E',
templateUrl: 'views/includes/acceptSlide.html',
scope: {},
link: function(scope, element, attrs) {
scope.$on("$ionicSlides.sliderInitialized", function(event, data) {
scope.slider = data.slider;
});
scope.$on("$ionicSlides.slideChangeEnd", function(event, data) {
if (data.slider.activeIndex == 0) {
scope.slider.slideNext();
scope.$emit('accepted');
}
});
}
}
});

View file

@ -0,0 +1,249 @@
'use strict';
angular.module('copayApp.directives')
.directive('slideToAccept', function($timeout, $window, $q) {
return {
restrict: 'E',
templateUrl: 'views/includes/slideToAccept.html',
transclude: true,
scope: {
sendStatus: '=slideSendStatus',
onConfirm: '&slideOnConfirm'
},
link: function(scope, element, attrs) {
var KNOB_WIDTH = 71;
var MAX_SLIDE_START_PERCENTAGE = 50;
var FULLY_SLID_PERCENTAGE = 72;
var PERCENTAGE_BUMP = 5;
var JIGGLE_EASING = linear;
var JIGGLE_DURATION = 100;
var RECEDE_DURATION = 250;
var INITIAL_TAP_EASE_DURATION = 75;
var elm = element[0];
var isSliding = false;
var curSliderPct = getKnobWidthPercentage();
var curBitcoinPct = 0;
var curTextPct = 0;
var currentEaseStartTime;
var bezier = $window.BezierEasing(0.175, 0.885, 0.320, 1.275);
scope.isSlidFully = false;
scope.displaySendStatus = '';
scope.$watch('sendStatus', function() {
if(scope.sendStatus === 'success') {
scope.displaySendStatus = '';
reset();
} else {
scope.displaySendStatus = scope.sendStatus;
}
});
function easePosition(fromPct, pct, duration, easeFx, animateFx) {
var deferred = $q.defer();
currentEaseStartTime = Date.now();
var startTime = currentEaseStartTime;
var initialPct = fromPct;
var distance = pct - fromPct;
function ease() {
if(startTime !== currentEaseStartTime) {
return;
}
$window.requestAnimationFrame(function() {
var now = Date.now();
var elapsed = now - startTime;
var normalizedElapsedTime = elapsed/duration;
var newVal = easeFx(normalizedElapsedTime);
var newPct = newVal*distance + initialPct;
animateFx(newPct);
scope.$digest();
if(elapsed < duration) {
ease();
} else {
deferred.resolve();
}
});
}
ease();
return deferred.promise;
}
function linear(t) {
return t;
}
function easeInOutBack(t) {
return bezier(t);
}
function reset() {
$timeout(function() {
scope.isSlidFully = false;
isSliding = false;
setNewSliderStyle(getKnobWidthPercentage());
setNewBitcoinStyle(0);
setNewTextStyle(0);
}, 500);
}
function setNewSliderStyle(pct) {
var knobWidthPct = getKnobWidthPercentage();
var translatePct = pct - knobWidthPct;
if(isSliding) {
translatePct += 0.35*pct;
}
scope.sliderStyle = getTransformStyle(translatePct);
curSliderPct = pct;
}
function setNewBitcoinStyle(pct) {
var translatePct = -2.25*pct;
scope.bitcoinStyle = getTransformStyle(translatePct);
curBitcoinPct = pct;
}
function setNewTextStyle(pct) {
var translatePct = -0.1*pct;
scope.textStyle = getTransformStyle(translatePct);
curTextPct = pct;
}
function getTransformStyle(translatePct) {
return {'transform': 'translateX(' + translatePct + '%)'};
}
function getKnobWidthPercentage() {
var knobWidthPct = (KNOB_WIDTH/elm.clientWidth)*100;
return knobWidthPct;
}
function setSliderPosition(pct) {
setNewSliderStyle(pct);
setNewBitcoinStyle(pct);
setNewTextStyle(pct);
}
function easeSliderPosition(pct) {
var duration = INITIAL_TAP_EASE_DURATION;
easePosition(curSliderPct, pct, duration, JIGGLE_EASING, function(pct) {
setNewSliderStyle(pct);
});
easePosition(curBitcoinPct, pct, duration, JIGGLE_EASING, function(pct) {
setNewBitcoinStyle(pct);
});
easePosition(curTextPct, pct, duration, JIGGLE_EASING, function(pct) {
setNewTextStyle(pct);
});
}
function jiggleSlider() {
var pct = getKnobWidthPercentage() + PERCENTAGE_BUMP;
var duration = JIGGLE_DURATION;
var p1 = easePosition(curSliderPct, pct, duration, JIGGLE_EASING, function(pct) {
setNewSliderStyle(pct);
});
var p2 = easePosition(curBitcoinPct, pct, duration, JIGGLE_EASING, function(pct) {
setNewBitcoinStyle(pct);
});
$q.all([p1, p2]).then(function() {
recede();
});
}
function recede() {
var duration = RECEDE_DURATION;
easePosition(curSliderPct, getKnobWidthPercentage(), duration, easeInOutBack, function(pct) {
setNewSliderStyle(pct);
});
easePosition(curBitcoinPct, 0, duration, easeInOutBack, function(pct) {
setNewBitcoinStyle(pct);
});
easePosition(curTextPct, 0, duration, easeInOutBack, function(pct) {
setNewTextStyle(pct);
});
}
function alertSlidFully() {
scope.isSlidFully = true;
scope.onConfirm();
}
function getTouchXPosition($event) {
var x;
if($event.touches || $event.changedTouches) {
if($event.touches.length) {
x = $event.touches[0].clientX;
} else {
x = $event.changedTouches[0].clientX;
}
} else {
x = $event.clientX;
}
return x;
}
function getSlidPercentage($event) {
var x = getTouchXPosition($event);
var width = elm.clientWidth;
var pct = (x/width)*100;
if(x >= width) {
pct = 100;
}
return pct;
}
scope.onTouchstart = function($event) {
if(scope.isSlidFully) {
return;
}
if(!isSliding) {
var pct = getSlidPercentage($event);
if (pct > MAX_SLIDE_START_PERCENTAGE) {
jiggleSlider();
return;
} else {
isSliding = true;
var knobWidthPct = getKnobWidthPercentage();
if(pct < knobWidthPct) {
pct = knobWidthPct;
}
pct += PERCENTAGE_BUMP;
easeSliderPosition(pct);
}
}
};
scope.onTouchmove = function($event) {
if(!isSliding || scope.isSlidFully) {
return;
}
var pct = getSlidPercentage($event);
var knobWidthPct = getKnobWidthPercentage();
if(pct < knobWidthPct) {
pct = knobWidthPct;
}
pct += PERCENTAGE_BUMP;
currentEaseStartTime = null;
setSliderPosition(pct);
};
scope.onTouchend = function($event) {
if(scope.isSlidFully) {
return;
}
var pct = getSlidPercentage($event);
if(isSliding && pct > FULLY_SLID_PERCENTAGE) {
pct = 100;
setSliderPosition(pct);
alertSlidFully();
} else {
recede();
}
isSliding = false;
};
}
};
});

View file

@ -0,0 +1,31 @@
'use strict';
angular.module('copayApp.directives')
.directive('slideToAcceptSuccess', function($timeout) {
return {
restrict: 'E',
templateUrl: 'views/includes/slideToAcceptSuccess.html',
transclude: true,
scope: {
isShown: '=slideSuccessShow',
onConfirm: '&slideSuccessOnConfirm'
},
link: function(scope, element, attrs) {
var elm = element[0];
elm.style.display = 'none';
scope.$watch('isShown', function() {
if(scope.isShown) {
elm.style.display = 'flex';
$timeout(function() {
scope.fillScreen = true;
}, 10);
}
});
scope.onConfirmButtonClick = function() {
scope.onConfirm();
scope.fillScreen = false;
elm.style.display = 'none';
};
}
};
});

View file

@ -32,6 +32,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
// NAV BACK-BUTTON TEXT/ICON
$ionicConfigProvider.backButton.icon('icon ion-ios-arrow-thin-left').text('');
$ionicConfigProvider.backButton.previousTitleText(false);
$ionicConfigProvider.views.swipeBackEnabled(false);
$logProvider.debugEnabled(true);
$provide.decorator('$log', ['$delegate', 'platformInfo',

View file

@ -48,7 +48,7 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
return ongoingProcess[processName];
};
root.set = function(processName, isOn) {
root.set = function(processName, isOn, customHandler) {
$log.debug('ongoingProcess', processName, isOn);
root[processName] = isOn;
ongoingProcess[processName] = isOn;
@ -64,7 +64,9 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
var showName = $filter('translate')(processNames[name] || name);
if (root.onGoingProcessName) {
if(customHandler) {
customHandler(processName, showName, isOn);
} else if (root.onGoingProcessName) {
if (isCordova) {
window.plugins.spinnerDialog.show(null, showName, true);
} else {

View file

@ -915,7 +915,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
});
};
root.publishAndSign = function(wallet, txp, cb) {
root.publishAndSign = function(wallet, txp, cb, customStatusHandler) {
var publishFn = root.publishTx;
@ -929,12 +929,13 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
root.prepare(wallet, function(err, password) {
if (err) return cb('Prepare error: ' + err);
ongoingProcess.set('sendingTx', true);
ongoingProcess.set('sendingTx', true, customStatusHandler);
publishFn(wallet, txp, function(err, publishedTxp) {
ongoingProcess.set('sendingTx', false);
ongoingProcess.set('sendingTx', false, customStatusHandler);
if (err) return cb('Send Error: ' + err);
ongoingProcess.set('signingTx', true);
ongoingProcess.set('signingTx', true, customStatusHandler);
root.signTx(wallet, publishedTxp, password, function(err, signedTxp) {
ongoingProcess.set('signingTx', false);
root.invalidateCache(wallet);
@ -952,22 +953,29 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
}
if (signedTxp.status == 'accepted') {
ongoingProcess.set('broadcastingTx', true);
ongoingProcess.set('broadcastingTx', true, customStatusHandler);
root.broadcastTx(wallet, signedTxp, function(err, broadcastedTxp) {
ongoingProcess.set('broadcastingTx', false);
ongoingProcess.set('broadcastingTx', false, customStatusHandler);
if (err) return cb('sign error' + err);
$rootScope.$emit('Local/TxAction', wallet.id);
var type = root.getViewStatus(wallet, broadcastedTxp);
root.openStatusModal(type, broadcastedTxp, function() {});
return cb(null, broadcastedTxp)
if(!customStatusHandler) {
root.openStatusModal(type, broadcastedTxp, function() {});
}
return cb(null, broadcastedTxp);
});
} else {
$rootScope.$emit('Local/TxAction', wallet.id);
var type = root.getViewStatus(wallet, signedTxp);
root.openStatusModal(type, signedTxp, function() {});
if(!customStatusHandler) {
root.openStatusModal(type, signedTxp, function() {});
}
return cb(null, signedTxp);
}
});

View file

@ -0,0 +1,126 @@
slide-to-accept {
$slide-bg-color: #647CE8;
$slider-bg-color: #5063B9;
$slide-text-color: #FFFFFF;
position: fixed;
bottom: 0;
height: 92px;
width: 100%;
background: $slide-bg-color;
@mixin center-vertically {
display: flex;
align-items: center;
height: 100%;
position: absolute;
}
.slide {
&__listener {
height: 100%;
width: 100%;
overflow: hidden;
position: relative;
}
&__slider {
@include center-vertically;
height: 100%;
width: 100%;
background: $slider-bg-color;
transform: translateX(0);
margin-left: -100%;
z-index: 2;
&::before {
@include center-vertically;
content: '';
width: 10000px;
left: -10000px + 1;
background: $slider-bg-color;
}
&::after {
@include center-vertically;
content: '';
width: 15px;
right: -10px;
background: $slider-bg-color;
}
&__tip {
@include center-vertically;
width: 124px;
height: 116px;
background: $slider-bg-color;
right: -71px;
border-radius: 50%;
top: 50%;
transform: translateY(-47%);
}
}
&__bitcoin {
@include center-vertically;
left: 20px;
z-index: 3;
> img {
transform: rotateZ(-5deg);
}
}
&__button-text {
@include center-vertically;
justify-content: center;
top: 0;
left: 0;
width: 100%;
color: $slide-text-color;
font-size: 18px;
font-weight: 600;
letter-spacing: .03rem;
z-index: 1;
}
&__status-text {
@include center-vertically;
justify-content: center;
color: $slide-text-color;
z-index: 4;
width: 100%;
font-size: 17px;
letter-spacing: 0.02rem;
text-transform: capitalize;
transform: translateY(2rem);
opacity: 0;
transition: transform 250ms ease, opacity 250ms ease;
&.enter {
transform: translateY(0);
opacity: 1;
}
> img {
margin-right: 10px;
animation-name: spin;
animation-duration: 500ms;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
}
&__arrow {
@include center-vertically;
right: 20px;
}
}
@keyframes spin {
from {
transform:rotate(0deg);
}
to {
transform:rotate(360deg);
}
}
}

View file

@ -0,0 +1,96 @@
slide-to-accept-success {
$slider-bg-color: #5063B9;
$success-bg-color: #11D1A6;
height: 100%;
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 99999;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
.slide-success {
$duration: 400ms;
&__background {
$start-radius: 5;
$scale-factor: 20;
height: 10vmax;
width: 10vmax;
background: $slider-bg-color;
bottom: 0;
position: absolute;
left: calc(50% - 5vmax);
border-radius: 50%;
transition: transform $duration*1.5 ease, background $duration*1.5 ease;
&.fill-screen {
transform: scale3d($scale-factor, $scale-factor, 1) translateY(-40%);
background: $success-bg-color;
}
}
&__content {
position: relative;
z-index: 1;
margin-top: -20vh;
> img {
margin-bottom: 1.8rem;
transform: translateY(5rem);
opacity: 0;
transition: transform $duration ease, opacity $duration ease;
transition-delay: 200ms;
&.reveal {
transform: translateY(0);
opacity: 1;
}
}
&__header {
color: #FFFFFF;
font-size: 26px;
transform: translateY(5rem);
opacity: 0;
transition: transform $duration ease, opacity $duration ease;
transition-delay: 250ms;
&.reveal {
transform: translateY(0);
opacity: 1;
}
}
}
&__footer {
position: absolute;
left: 0;
bottom: 0;
padding: 0 1.75rem;
width: 100%;
transform: translateY(5rem);
opacity: 0;
transition: transform $duration ease, opacity $duration ease;
transition-delay: 250ms;
&.reveal {
transform: translateY(0);
opacity: 1;
}
&__btn {
display: block;
color: #FFFFFF;
font-size: 18px;
font-weight: 600;
letter-spacing: 2.86px;
padding: 1rem 0 1.1rem;
border-top: 1px solid rgba(255, 255, 255, .45);
cursor: pointer;
}
}
}
}

View file

@ -19,6 +19,8 @@
@import "includes/walletActivity";
@import "includes/wallets";
@import "includes/modals/modals";
@import "includes/slideToAccept";
@import "includes/slideToAcceptSuccess";
@import "includes/tx-details";
@import "includes/txp-details";
@import "includes/tx-status";