Merge pull request #5726 from JDonadio/feat/pin-locker

Feat Lock App - Pin/Fingerprint
This commit is contained in:
Matias Alejo Garcia 2017-04-04 18:17:08 -03:00 committed by GitHub
commit 4fc5920c82
19 changed files with 785 additions and 13 deletions

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('advancedSettingsController', function($scope, $rootScope, $log, $window, lodash, configService, uxLanguage, platformInfo, profileService, feeService, storageService, $ionicHistory, $timeout, $ionicScrollDelegate) {
angular.module('copayApp.controllers').controller('advancedSettingsController', function($scope, $log, configService) {
var updateConfig = function() {
var config = configService.getSync();
@ -11,7 +11,6 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
$scope.recentTransactionsEnabled = {
value: config.recentTransactions.enabled
};
$scope.hideNextSteps = {
value: config.hideNextSteps.enabled
};
@ -31,7 +30,7 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
$scope.nextStepsChange = function() {
var opts = {
hideNextSteps: {
enabled: $scope.hideNextSteps.value
enabled: $scope.hideNextSteps.value
},
};
configService.set(opts, function(err) {

119
src/js/controllers/lock.js Normal file
View file

@ -0,0 +1,119 @@
'use strict';
angular.module('copayApp.controllers').controller('lockController', function($state, $scope, $timeout, $log, configService, popupService, gettextCatalog, appConfigService, fingerprintService, profileService, lodash) {
function init() {
var config = configService.getSync();
$scope.locking = config.lock.method != 'pin';
$scope.options = [
{
method: 'none',
label: gettextCatalog.getString('Disabled'),
value: config.lock.method == '',
},
{
method: 'pin',
label: gettextCatalog.getString('Enable PIN'),
value: config.lock.method == 'pin',
needsBackup: null,
},
];
if (fingerprintService.isAvailable()) {
$scope.options.push({
method: 'fingerprint',
label: gettextCatalog.getString('Enable Fingerprint'),
value: config.lock.method == 'fingerprint',
needsBackup: null,
});
}
$scope.currentOption = lodash.find($scope.options, 'value');
processWallets();
};
$scope.$on("$ionicView.beforeEnter", function(event) {
init();
});
function processWallets() {
var wallets = profileService.getWallets();
var singleLivenetWallet = wallets.length == 1 && wallets[0].network == 'livenet' && wallets[0].needsBackup;
var atLeastOneLivenetWallet = lodash.any(wallets, function(w) {
return w.network == 'livenet' && w.needsBackup;
});
if (singleLivenetWallet) {
$scope.errorMsg = gettextCatalog.getString('Backup your wallet before using this function');
disableOptsUntilBackup();
} else if (atLeastOneLivenetWallet) {
$scope.errorMsg = gettextCatalog.getString('Backup all livenet wallets before using this function');
disableOptsUntilBackup();
} else {
enableOptsAfterBackup();
$scope.errorMsg = null;
}
function enableOptsAfterBackup() {
$scope.options[1].needsBackup = false;
if ($scope.options[2]) $scope.options[2].needsBackup = false;
};
function disableOptsUntilBackup() {
$scope.options[1].needsBackup = true;
if ($scope.options[2]) $scope.options[2].needsBackup = true;
};
$timeout(function() {
$scope.$apply();
});
};
$scope.select = function(method) {
if (method == 'none')
saveConfig();
else if (method == 'fingerprint') {
var config = configService.getSync();
if (config.lock.method == 'pin') {
askForDisablePin(function(disablePin) {
if (disablePin) saveConfig('fingerprint');
else init();
});
} else saveConfig('fingerprint');
} else if (method == 'pin') {
$state.transitionTo('tabs.lock.pin', {
fromSettings: true,
locking: $scope.locking
});
}
$timeout(function() {
$scope.$apply();
});
};
function askForDisablePin(cb) {
var message = gettextCatalog.getString('{{appName}} is protected by Pin. Are you sure you want to disable it?', {
appName: appConfigService.nameCase
});
var okText = gettextCatalog.getString('Continue');
var cancelText = gettextCatalog.getString('Cancel');
popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) return cb(false);
return cb(true);
});
};
function saveConfig(method) {
var opts = {
lock: {
method: method || '',
value: '',
}
};
configService.set(opts, function(err) {
if (err) $log.debug(err);
});
};
});

View file

@ -0,0 +1,17 @@
'use strict';
angular.module('copayApp.controllers').controller('lockedViewController', function($state, $scope, $ionicHistory, fingerprintService, appConfigService, gettextCatalog) {
$scope.$on("$ionicView.beforeEnter", function(event) {
$scope.title = appConfigService.nameCase + ' ' + gettextCatalog.getString('is locked');
$scope.appName = appConfigService.name;
});
$scope.requestFingerprint = function() {
fingerprintService.check('unlockingApp', function(err) {
if (err) return;
$state.transitionTo('tabs.home').then(function() {
$ionicHistory.clearHistory();
});
});
};
});

179
src/js/controllers/pin.js Normal file
View file

@ -0,0 +1,179 @@
'use strict';
angular.module('copayApp.controllers').controller('pinController', function($state, $interval, $stateParams, $ionicHistory, $timeout, $scope, $log, configService, appConfigService) {
var ATTEMPT_LIMIT = 3;
var ATTEMPT_LOCK_OUT_TIME = 5 * 60;
$scope.$on("$ionicView.beforeEnter", function(event) {
$scope.currentPin = $scope.confirmPin = '';
$scope.fromSettings = $stateParams.fromSettings == 'true' ? true : false;
$scope.locking = $stateParams.locking == 'true' ? true : false;
$scope.match = $scope.error = $scope.disableButtons = false;
$scope.currentAttempts = 0;
$scope.appName = appConfigService.name;
});
$scope.$on("$ionicView.enter", function(event) {
configService.whenAvailable(function(config) {
$scope.bannedUntil = config.lock.bannedUntil || null;
if ($scope.bannedUntil) {
var now = Math.floor(Date.now() / 1000);
if (now < $scope.bannedUntil) {
$scope.error = $scope.disableButtons = true;
lockTimeControl($scope.bannedUntil);
}
}
});
});
function checkAttempts() {
$scope.currentAttempts += 1;
$log.debug('Attempts to unlock:', $scope.currentAttempts);
if ($scope.currentAttempts === ATTEMPT_LIMIT) {
$scope.currentAttempts = 0;
var limitTime = Math.floor(Date.now() / 1000) + ATTEMPT_LOCK_OUT_TIME;
var config = configService.getSync();
var opts = {
lock: {
method: 'pin',
value: config.lock.value,
bannedUntil: limitTime,
attempts: config.lock.attempts + 1,
}
};
configService.set(opts, function(err) {
if (err) $log.debug(err);
lockTimeControl(limitTime);
});
}
};
function lockTimeControl(limitTime) {
$scope.limitTimeExpired = false;
setExpirationTime();
var countDown = $interval(function() {
setExpirationTime();
}, 1000);
function setExpirationTime() {
var now = Math.floor(Date.now() / 1000);
if (now > limitTime) {
$scope.limitTimeExpired = true;
if (countDown) reset();
} else {
$scope.disableButtons = true;
var totalSecs = limitTime - now;
var m = Math.floor(totalSecs / 60);
var s = totalSecs % 60;
$scope.expires = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
}
};
function reset() {
$scope.expires = $scope.error = $scope.disableButtons = null;
$scope.currentPin = $scope.confirmPin = '';
$interval.cancel(countDown);
$timeout(function() {
$scope.$apply();
});
return;
};
};
$scope.getFilledClass = function(limit) {
return $scope.currentPin.length >= limit ? 'filled-' + $scope.appName : null;
};
$scope.delete = function() {
if ($scope.disableButtons) return;
if ($scope.currentPin.length > 0) {
$scope.currentPin = $scope.currentPin.substring(0, $scope.currentPin.length - 1);
$scope.error = false;
$scope.updatePin();
}
};
$scope.isComplete = function() {
if ($scope.currentPin.length < 4) return false;
else return true;
};
$scope.updatePin = function(value) {
if ($scope.disableButtons) return;
$scope.error = false;
if (value && !$scope.isComplete()) {
$scope.currentPin = $scope.currentPin + value;
$timeout(function() {
$scope.$apply();
});
}
$scope.save();
};
$scope.save = function() {
if (!$scope.isComplete()) return;
var config = configService.getSync();
$scope.match = config.lock && config.lock.method == 'pin' && config.lock.value == $scope.currentPin ? true : false;
if (!$scope.locking) {
if ($scope.match) {
if ($scope.fromSettings) saveSettings();
else {
saveSettings('pin', $scope.currentPin);
$scope.error = false;
}
} else {
$timeout(function() {
$scope.confirmPin = $scope.currentPin = '';
$scope.error = true;
}, 200);
checkAttempts();
}
} else {
processCodes();
}
};
function processCodes() {
if (!$scope.confirmPin) {
$timeout(function() {
$scope.confirmPin = $scope.currentPin;
$scope.currentPin = '';
}, 200);
} else {
if ($scope.confirmPin == $scope.currentPin)
saveSettings('pin', $scope.confirmPin);
else {
$scope.confirmPin = $scope.currentPin = '';
$scope.error = true;
}
}
$timeout(function() {
$scope.$apply();
});
};
function saveSettings(method, value) {
var config = configService.getSync();
var opts = {
lock: {
method: method || '',
value: value || '',
bannedUntil: null,
attempts: config.lock.attempts + 1,
}
};
configService.set(opts, function(err) {
if (err) $log.debug(err);
$scope.close();
});
};
$scope.close = function(delay) {
$timeout(function() {
$ionicHistory.viewHistory().backView ? $ionicHistory.goBack() : $state.go('tabs.home');
}, delay || 1);
};
});

View file

@ -51,7 +51,12 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isCordova = platformInfo.isCordova;
$scope.isDevel = platformInfo.isDevel;
$scope.appName = appConfigService.nameCase;
configService.whenAvailable(function(config) {
$scope.locked = config.lock && config.lock.method != '' ? true : false;
$scope.method = config.lock && config.lock.method != '' ? config.lock.method.charAt(0).toUpperCase() + config.lock.method.slice(1) : gettextCatalog.getString('Disabled');
});
});
$scope.$on("$ionicView.enter", function(event, data) {

View file

@ -119,6 +119,30 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
})
/*
*
* Pin
*
*/
.state('pin', {
url: '/pin/',
controller: 'pinController',
templateUrl: 'views/pin.html',
})
/*
*
* Locked
*
*/
.state('lockedView', {
url: '/lockedView/',
controller: 'lockedViewController',
templateUrl: 'views/lockedView.html',
})
/*
*
* URI
@ -439,6 +463,25 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
}
})
.state('tabs.lock', {
url: '/lock',
views: {
'tab-settings@tabs': {
controller: 'lockController',
templateUrl: 'views/lock.html',
}
}
})
.state('tabs.lock.pin', {
url: '/pin/:fromSettings/:locking',
views: {
'tab-settings@tabs': {
controller: 'pinController',
templateUrl: 'views/pin.html',
cache: false
}
}
})
/*
*
@ -1091,7 +1134,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
});
})
.run(function($rootScope, $state, $location, $log, $timeout, $ionicHistory, $ionicPlatform, $window, appConfigService, lodash, platformInfo, profileService, uxLanguage, gettextCatalog, openURLService, storageService, scannerService, /* plugins START HERE => */ coinbaseService, glideraService, amazonService, bitpayCardService) {
.run(function($rootScope, $state, $location, $log, $timeout, startupService, fingerprintService, $ionicHistory, $ionicPlatform, $window, appConfigService, lodash, platformInfo, profileService, uxLanguage, gettextCatalog, openURLService, storageService, scannerService, configService, /* plugins START HERE => */ coinbaseService, glideraService, amazonService, bitpayCardService) {
uxLanguage.init();
@ -1153,7 +1196,28 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
});
$ionicPlatform.on('resume', function() {
// Nothing to do
if (platformInfo.isCordova || platformInfo.isDevel) {
configService.whenAvailable(function(config) {
var nextView;
var lock = config.lock;
if (lock && lock.method == 'fingerprint' && fingerprintService.isAvailable()) {
fingerprintService.check('unlockingApp', function(err) {
if (err) nextView = 'lockedView';
else if ($ionicHistory.currentStateName() == 'lockedView') nextView = 'tabs.home';
else nextView = $ionicHistory.currentStateName();
goTo(nextView);
});
} else if (lock && lock.method == 'pin') {
goTo('pin');
}
function goTo(nextView) {
$state.transitionTo(nextView).then(function() {
if (nextView == 'lockedView') $ionicHistory.clearHistory();
});
};
});
}
});
$ionicPlatform.on('menubutton', function() {
@ -1196,10 +1260,27 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
disableAnimate: true,
historyRoot: true
});
$state.transitionTo('tabs.home').then(function() {
// Clear history
$ionicHistory.clearHistory();
});
if (platformInfo.isCordova || platformInfo.isDevel) {
startupService.ready();
configService.whenAvailable(function(config) {
var lock = config.lock;
if (fingerprintService.isAvailable() && lock && lock.method == 'fingerprint') {
fingerprintService.check('unlockingApp', function(err) {
if (err) goTo('lockedView');
else goTo('tabs.home');
});
} else if (lock && lock.method == 'pin') {
goTo('pin');
} else
goTo('tabs.home');
function goTo(nextView) {
$state.transitionTo(nextView).then(function() {
$ionicHistory.clearHistory();
});
};
});
}
});
}

View file

@ -53,6 +53,13 @@ angular.module('copayApp.services').factory('configService', function(storageSer
}
},
lock: {
method: '',
value: '',
bannedUntil: null,
attempts: null,
},
// External services
recentTransactions: {
enabled: true,

View file

@ -14,7 +14,7 @@ angular.module('copayApp.services').factory('fingerprintService', function($log,
function(msg) {
FingerprintAuth.isAvailable(function(result) {
if (result.isAvailable)
if (result.isAvailable)
_isAvailable = 'ANDROID';
}, function() {
@ -71,6 +71,7 @@ angular.module('copayApp.services').factory('fingerprintService', function($log,
var isNeeded = function(client) {
if (!_isAvailable) return false;
if (client === 'unlockingApp') return true;
var config = configService.getSync();
config.touchIdFor = config.touchIdFor || {};
@ -84,7 +85,7 @@ angular.module('copayApp.services').factory('fingerprintService', function($log,
root.check = function(client, cb) {
if (isNeeded(client)) {
$log.debug('FingerPrint Service:', _isAvailable);
$log.debug('FingerPrint Service:', _isAvailable);
if (_isAvailable == 'IOS')
return requestTouchId(cb);
else

View file

@ -0,0 +1,42 @@
#locked-view {
@mixin img-frame {
height: 60px;
width: 60px;
box-shadow: none;
margin: auto;
}
.img-container-copay {
padding: 20%;
@media(min-width: 480px) {
max-height: 150px;
}
.big-icon-svg {
> .bg {
@include img-frame;
background-image: url("../img/icon-fingerprint-copay.svg");
}
}
}
.img-container-bitpay {
padding: 20%;
@media(min-width: 480px) {
max-height: 150px;
}
.big-icon-svg {
> .bg {
@include img-frame;
background-image: url("../img/icon-fingerprint-bitpay.svg");
}
}
}
.comments {
text-align: center;
.header {
font-size: 20px;
}
.text-content {
width: 90%;
margin: 5% auto;
}
}
}

95
src/sass/views/pin.scss Normal file
View file

@ -0,0 +1,95 @@
#pin {
background-color: #FAFAFA;
.bar.bar-clear {
background-color: transparent;
border: none;
.back-button .icon:before {
color: #2d3f50;
}
}
.content {
text-align: center;
width: 100%;
height: 100%;
.app-icon {
margin-top: -55px;
.big-icon-svg {
> .bg {
background-image: url("../img/app/icon.png");
height: 60px;
width: 60px;
margin: auto;
}
}
}
.block-text {
align-items: center;
background-color: #F1F1F1;
height: 30%;
border-bottom: 1px solid #c5c5c5;
.message {
margin: auto;
}
span {
width: 60%;
margin: 10% auto;
}
@media(min-width: 480px) {
span {
font-size: 30px;
width: 90%;
}
}
}
.block-code {
width: 50%;
margin: auto;
@media(min-width: 480px) {
width: 25%;
}
}
.block-buttons {
.col {
padding: 5%;
}
color: $v-dark-gray;
font-size: 1.7rem;
font-family: $v-font-family-light;
cursor: pointer;
position: absolute;
bottom: 3%;
left: 5%;
width: 90%;
@media(min-width: 480px) {
left: 15%;
width: 70%;
max-height: 55%;
}
}
}
@mixin circle {
border-radius: 50%;
box-shadow: 0 0 3px 0px #5b5b5b;
transition: background-color .2s ease-in-out;
padding: 7%;
margin: 5%;
}
.circle-copay {
@include circle;
border: 1px solid $v-accent-color;
}
.circle-bitpay {
@include circle;
border: 1px solid $v-primary-color;
}
.filled-copay {
background-color: $v-accent-color;
}
.filled-bitpay {
background-color: #1f3598;
}
.error {
color: #f13333;
max-width: 70%;
}
}

View file

@ -46,3 +46,5 @@
@import "includes/accountSelector";
@import "integrations/integrations";
@import "custom-amount";
@import "pin";
@import "lockedView";

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 425.034 425.034" style="enable-background:new 0 0 425.034 425.034;" xml:space="preserve" width="512px" height="512px">
<g>
<path d="M133.822,384.2c-33.836-24.953-54.037-64.915-54.037-106.898V147.731c0-3.798,0.162-7.633,0.482-11.396 c0.351-4.127-2.711-7.757-6.838-8.108c-4.123-0.347-7.757,2.711-8.108,6.838c-0.355,4.184-0.536,8.446-0.536,12.666v129.571 c0,23.526,5.669,46.938,16.395,67.704c10.375,20.087,25.5,37.815,43.739,51.266c1.34,0.988,2.899,1.464,4.446,1.464 c2.301,0,4.572-1.055,6.042-3.049C137.865,391.354,137.156,386.659,133.822,384.2z" fill="#1e3186"/>
<path d="M168.819,402.675c-4.619-1.61-9.197-3.499-13.61-5.615c-3.734-1.791-8.215-0.215-10.005,3.521 c-1.791,3.735-0.215,8.215,3.52,10.005c4.915,2.356,10.014,4.46,15.158,6.253c0.817,0.285,1.65,0.42,2.469,0.42 c3.102,0,6.003-1.939,7.082-5.033C174.795,408.315,172.73,404.039,168.819,402.675z" fill="#1e3186"/>
<path d="M352.749,243.219c-4.143,0-7.5,3.358-7.5,7.5v26.584c0,73.188-59.543,132.731-132.732,132.731 c-5.179,0-10.395-0.301-15.502-0.895c-4.12-0.479-7.838,2.469-8.316,6.584c-0.479,4.115,2.469,7.838,6.583,8.316 c5.681,0.661,11.479,0.995,17.235,0.995c81.459,0,147.731-66.272,147.731-147.731v-26.584 C360.249,246.576,356.891,243.219,352.749,243.219z" fill="#1e3186"/>
<path d="M212.517,0c-31.895,0-62.263,10.003-87.824,28.928C99.64,47.478,81.373,72.889,71.867,102.417 c-1.27,3.943,0.898,8.168,4.841,9.438c3.944,1.268,8.168-0.898,9.438-4.841c8.54-26.525,24.955-49.358,47.473-66.03 C156.577,23.985,183.86,15,212.517,15c73.188,0,132.731,59.543,132.731,132.731v72.987c0,4.142,3.357,7.5,7.5,7.5 s7.5-3.358,7.5-7.5v-72.987C360.249,66.272,293.976,0,212.517,0z" fill="#1e3186"/>
<path d="M111.172,318.746c-3.715,1.833-5.24,6.33-3.408,10.044c9.504,19.263,24.13,35.549,42.296,47.096 c18.678,11.873,40.275,18.149,62.457,18.149c16.784,0,32.996-3.504,48.187-10.415c3.771-1.715,5.437-6.162,3.722-9.932 c-1.716-3.77-6.162-5.438-9.933-3.721c-13.227,6.017-27.35,9.068-41.976,9.068c-19.327,0-38.142-5.466-54.411-15.808 c-15.846-10.073-28.603-24.276-36.89-41.073C119.384,318.439,114.885,316.913,111.172,318.746z" fill="#1e3186"/>
<path d="M284.635,366.78c1.761,0,3.529-0.616,4.954-1.872c25.204-22.199,39.659-54.13,39.659-87.605V147.732 c0-24.963-7.801-48.792-22.559-68.909c-2.449-3.34-7.142-4.061-10.483-1.611c-3.34,2.45-4.061,7.144-1.61,10.484 c12.856,17.526,19.652,38.286,19.652,60.036v129.571c0,29.169-12.602,56.997-34.573,76.349c-3.108,2.738-3.409,7.477-0.671,10.585 C280.487,365.92,282.556,366.78,284.635,366.78z" fill="#1e3186"/>
<path d="M270.318,64.059c1.304,0.903,2.792,1.337,4.266,1.337c2.377,0,4.715-1.127,6.171-3.228 c2.359-3.405,1.513-8.077-1.892-10.437c-5.678-3.935-11.733-7.382-18-10.244c-3.771-1.721-8.217-0.061-9.938,3.706 c-1.721,3.768-0.062,8.217,3.706,9.938C260.091,57.625,265.369,60.628,270.318,64.059z" fill="#1e3186"/>
<path d="M232.047,32.634C225.635,31.55,219.064,31,212.517,31C148.152,31,95.786,83.366,95.786,147.732v125.987 c0,4.142,3.358,7.5,7.5,7.5s7.5-3.358,7.5-7.5V147.732C110.786,91.637,156.423,46,212.517,46c5.711,0,11.44,0.479,17.027,1.424 c4.096,0.69,7.955-2.061,8.646-6.144C238.882,37.196,236.131,33.325,232.047,32.634z" fill="#1e3186"/>
<path d="M141.452,99.821c-2.32,3.431-1.419,8.094,2.012,10.414c3.431,2.319,8.094,1.419,10.414-2.012 C167.097,88.672,189.018,77,212.517,77c39.001,0,70.731,31.73,70.731,70.732v83.987c0,4.142,3.357,7.5,7.5,7.5s7.5-3.358,7.5-7.5 v-83.987c0-47.273-38.459-85.732-85.731-85.732C184.031,62,157.465,76.139,141.452,99.821z" fill="#1e3186"/>
<path d="M134.286,173.218c4.142,0,7.5-3.358,7.5-7.5v-17.986c0-4.142-3.358-7.5-7.5-7.5s-7.5,3.358-7.5,7.5v17.986 C126.786,169.86,130.144,173.218,134.286,173.218z" fill="#1e3186"/>
<path d="M126.786,277.303c0,31.025,16.878,59.725,44.048,74.901c1.158,0.647,2.412,0.954,3.65,0.954 c2.629,0,5.182-1.385,6.555-3.844c2.02-3.616,0.726-8.185-2.891-10.205c-22.429-12.528-36.362-36.21-36.362-61.805v-81.584 c0-4.142-3.358-7.5-7.5-7.5s-7.5,3.358-7.5,7.5V277.303z" fill="#1e3186"/>
<path d="M298.249,277.303v-13.584c0-4.142-3.357-7.5-7.5-7.5s-7.5,3.358-7.5,7.5v13.584c0,39.001-31.73,70.732-70.731,70.732 c-5.698,0-11.369-0.682-16.853-2.028c-4.025-0.987-8.084,1.474-9.071,5.497s1.474,8.084,5.497,9.071 c6.653,1.632,13.526,2.46,20.427,2.46C259.79,363.034,298.249,324.575,298.249,277.303z" fill="#1e3186"/>
<path d="M267.249,148.524c0-30.05-24.079-54.953-53.677-55.514c-14.822-0.28-28.786,5.283-39.369,15.668 c-10.587,10.388-16.417,24.258-16.417,39.054v129.571c0,4.142,3.358,7.5,7.5,7.5s7.5-3.358,7.5-7.5V147.732 c0-10.736,4.234-20.803,11.923-28.347c7.685-7.541,17.873-11.576,28.578-11.377c21.483,0.407,38.962,18.583,38.962,40.517v71.194 c0,4.142,3.357,7.5,7.5,7.5s7.5-3.358,7.5-7.5V148.524z" fill="#1e3186"/>
<path d="M188.786,285.987c0,21.632,17.599,39.232,39.231,39.232c21.632,0,39.231-17.599,39.231-39.232v-36.269 c0-4.142-3.357-7.5-7.5-7.5s-7.5,3.358-7.5,7.5v36.269c0,13.361-10.87,24.232-24.231,24.232s-24.231-10.87-24.231-24.232v-64.268 c0-4.142-3.358-7.5-7.5-7.5s-7.5,3.358-7.5,7.5V285.987z" fill="#1e3186"/>
<path d="M228.749,290.603c4.143,0,7.5-3.358,7.5-7.5v-134.9c0-12.932-9.902-23.55-22.545-24.174 c-6.567-0.323-12.787,1.991-17.541,6.516c-4.688,4.463-7.377,10.727-7.377,17.187v41.987c0,4.142,3.358,7.5,7.5,7.5 s7.5-3.358,7.5-7.5v-41.987c0-2.407,0.966-4.653,2.72-6.322c1.75-1.666,4.034-2.518,6.46-2.399 c4.567,0.225,8.283,4.349,8.283,9.192v134.9C221.249,287.245,224.606,290.603,228.749,290.603z" fill="#1e3186"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 425.034 425.034" style="enable-background:new 0 0 425.034 425.034;" xml:space="preserve" width="512px" height="512px">
<g>
<path d="M133.822,384.2c-33.836-24.953-54.037-64.915-54.037-106.898V147.731c0-3.798,0.162-7.633,0.482-11.396 c0.351-4.127-2.711-7.757-6.838-8.108c-4.123-0.347-7.757,2.711-8.108,6.838c-0.355,4.184-0.536,8.446-0.536,12.666v129.571 c0,23.526,5.669,46.938,16.395,67.704c10.375,20.087,25.5,37.815,43.739,51.266c1.34,0.988,2.899,1.464,4.446,1.464 c2.301,0,4.572-1.055,6.042-3.049C137.865,391.354,137.156,386.659,133.822,384.2z" fill="#1abb9b"/>
<path d="M168.819,402.675c-4.619-1.61-9.197-3.499-13.61-5.615c-3.734-1.791-8.215-0.215-10.005,3.521 c-1.791,3.735-0.215,8.215,3.52,10.005c4.915,2.356,10.014,4.46,15.158,6.253c0.817,0.285,1.65,0.42,2.469,0.42 c3.102,0,6.003-1.939,7.082-5.033C174.795,408.315,172.73,404.039,168.819,402.675z" fill="#1abb9b"/>
<path d="M352.749,243.219c-4.143,0-7.5,3.358-7.5,7.5v26.584c0,73.188-59.543,132.731-132.732,132.731 c-5.179,0-10.395-0.301-15.502-0.895c-4.12-0.479-7.838,2.469-8.316,6.584c-0.479,4.115,2.469,7.838,6.583,8.316 c5.681,0.661,11.479,0.995,17.235,0.995c81.459,0,147.731-66.272,147.731-147.731v-26.584 C360.249,246.576,356.891,243.219,352.749,243.219z" fill="#1abb9b"/>
<path d="M212.517,0c-31.895,0-62.263,10.003-87.824,28.928C99.64,47.478,81.373,72.889,71.867,102.417 c-1.27,3.943,0.898,8.168,4.841,9.438c3.944,1.268,8.168-0.898,9.438-4.841c8.54-26.525,24.955-49.358,47.473-66.03 C156.577,23.985,183.86,15,212.517,15c73.188,0,132.731,59.543,132.731,132.731v72.987c0,4.142,3.357,7.5,7.5,7.5 s7.5-3.358,7.5-7.5v-72.987C360.249,66.272,293.976,0,212.517,0z" fill="#1abb9b"/>
<path d="M111.172,318.746c-3.715,1.833-5.24,6.33-3.408,10.044c9.504,19.263,24.13,35.549,42.296,47.096 c18.678,11.873,40.275,18.149,62.457,18.149c16.784,0,32.996-3.504,48.187-10.415c3.771-1.715,5.437-6.162,3.722-9.932 c-1.716-3.77-6.162-5.438-9.933-3.721c-13.227,6.017-27.35,9.068-41.976,9.068c-19.327,0-38.142-5.466-54.411-15.808 c-15.846-10.073-28.603-24.276-36.89-41.073C119.384,318.439,114.885,316.913,111.172,318.746z" fill="#1abb9b"/>
<path d="M284.635,366.78c1.761,0,3.529-0.616,4.954-1.872c25.204-22.199,39.659-54.13,39.659-87.605V147.732 c0-24.963-7.801-48.792-22.559-68.909c-2.449-3.34-7.142-4.061-10.483-1.611c-3.34,2.45-4.061,7.144-1.61,10.484 c12.856,17.526,19.652,38.286,19.652,60.036v129.571c0,29.169-12.602,56.997-34.573,76.349c-3.108,2.738-3.409,7.477-0.671,10.585 C280.487,365.92,282.556,366.78,284.635,366.78z" fill="#1abb9b"/>
<path d="M270.318,64.059c1.304,0.903,2.792,1.337,4.266,1.337c2.377,0,4.715-1.127,6.171-3.228 c2.359-3.405,1.513-8.077-1.892-10.437c-5.678-3.935-11.733-7.382-18-10.244c-3.771-1.721-8.217-0.061-9.938,3.706 c-1.721,3.768-0.062,8.217,3.706,9.938C260.091,57.625,265.369,60.628,270.318,64.059z" fill="#1abb9b"/>
<path d="M232.047,32.634C225.635,31.55,219.064,31,212.517,31C148.152,31,95.786,83.366,95.786,147.732v125.987 c0,4.142,3.358,7.5,7.5,7.5s7.5-3.358,7.5-7.5V147.732C110.786,91.637,156.423,46,212.517,46c5.711,0,11.44,0.479,17.027,1.424 c4.096,0.69,7.955-2.061,8.646-6.144C238.882,37.196,236.131,33.325,232.047,32.634z" fill="#1abb9b"/>
<path d="M141.452,99.821c-2.32,3.431-1.419,8.094,2.012,10.414c3.431,2.319,8.094,1.419,10.414-2.012 C167.097,88.672,189.018,77,212.517,77c39.001,0,70.731,31.73,70.731,70.732v83.987c0,4.142,3.357,7.5,7.5,7.5s7.5-3.358,7.5-7.5 v-83.987c0-47.273-38.459-85.732-85.731-85.732C184.031,62,157.465,76.139,141.452,99.821z" fill="#1abb9b"/>
<path d="M134.286,173.218c4.142,0,7.5-3.358,7.5-7.5v-17.986c0-4.142-3.358-7.5-7.5-7.5s-7.5,3.358-7.5,7.5v17.986 C126.786,169.86,130.144,173.218,134.286,173.218z" fill="#1abb9b"/>
<path d="M126.786,277.303c0,31.025,16.878,59.725,44.048,74.901c1.158,0.647,2.412,0.954,3.65,0.954 c2.629,0,5.182-1.385,6.555-3.844c2.02-3.616,0.726-8.185-2.891-10.205c-22.429-12.528-36.362-36.21-36.362-61.805v-81.584 c0-4.142-3.358-7.5-7.5-7.5s-7.5,3.358-7.5,7.5V277.303z" fill="#1abb9b"/>
<path d="M298.249,277.303v-13.584c0-4.142-3.357-7.5-7.5-7.5s-7.5,3.358-7.5,7.5v13.584c0,39.001-31.73,70.732-70.731,70.732 c-5.698,0-11.369-0.682-16.853-2.028c-4.025-0.987-8.084,1.474-9.071,5.497s1.474,8.084,5.497,9.071 c6.653,1.632,13.526,2.46,20.427,2.46C259.79,363.034,298.249,324.575,298.249,277.303z" fill="#1abb9b"/>
<path d="M267.249,148.524c0-30.05-24.079-54.953-53.677-55.514c-14.822-0.28-28.786,5.283-39.369,15.668 c-10.587,10.388-16.417,24.258-16.417,39.054v129.571c0,4.142,3.358,7.5,7.5,7.5s7.5-3.358,7.5-7.5V147.732 c0-10.736,4.234-20.803,11.923-28.347c7.685-7.541,17.873-11.576,28.578-11.377c21.483,0.407,38.962,18.583,38.962,40.517v71.194 c0,4.142,3.357,7.5,7.5,7.5s7.5-3.358,7.5-7.5V148.524z" fill="#1abb9b"/>
<path d="M188.786,285.987c0,21.632,17.599,39.232,39.231,39.232c21.632,0,39.231-17.599,39.231-39.232v-36.269 c0-4.142-3.357-7.5-7.5-7.5s-7.5,3.358-7.5,7.5v36.269c0,13.361-10.87,24.232-24.231,24.232s-24.231-10.87-24.231-24.232v-64.268 c0-4.142-3.358-7.5-7.5-7.5s-7.5,3.358-7.5,7.5V285.987z" fill="#1abb9b"/>
<path d="M228.749,290.603c4.143,0,7.5-3.358,7.5-7.5v-134.9c0-12.932-9.902-23.55-22.545-24.174 c-6.567-0.323-12.787,1.991-17.541,6.516c-4.688,4.463-7.377,10.727-7.377,17.187v41.987c0,4.142,3.358,7.5,7.5,7.5 s7.5-3.358,7.5-7.5v-41.987c0-2.407,0.966-4.653,2.72-6.322c1.75-1.666,4.034-2.518,6.46-2.399 c4.567,0.225,8.283,4.349,8.283,9.192v134.9C221.249,287.245,224.606,290.603,228.749,290.603z" fill="#1abb9b"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

View file

@ -13,7 +13,7 @@
<div class="comment" translate>
If enabled, wallets will also try to spend unconfirmed funds. This option may cause transaction delays.
</div>
<div class="item item-divider"></div>
<ion-toggle class="has-comment" ng-show="!isWP" ng-model="recentTransactionsEnabled.value" toggle-class="toggle-balanced" ng-change="recentTransactionsChange()">

17
www/views/lock.html Normal file
View file

@ -0,0 +1,17 @@
<ion-view class="settings">
<ion-nav-bar class="bar-royal">
<ion-nav-title>{{'Lock App' | translate}}</ion-nav-title>
<ion-nav-back-button>
</ion-nav-back-button>
</ion-nav-bar>
<ion-content>
<ion-radio ng-repeat="opt in options" ng-value="opt" ng-model="currentOption" ng-click="select(opt.method)" ng-disabled="opt.needsBackup">
<span ng-class="{'disabled': opt.needsBackup}" translate>{{opt.label}}</span>
</ion-radio>
<div class="assertive" style="text-align: center; margin: 4rem" ng-if="errorMsg">
{{errorMsg}}
</div>
</ion-content>
</ion-view>

21
www/views/lockedView.html Normal file
View file

@ -0,0 +1,21 @@
<ion-view id="locked-view">
<ion-nav-bar class="bar-royal">
<ion-nav-title>{{title}}</ion-nav-title>
</ion-nav-bar>
<ion-content>
<div ng-if="appName == 'copay'" class="img-container-copay" ng-click="requestFingerprint()">
<i class="icon big-icon-svg"><div class="bg"></div></i>
</div>
<div ng-if="appName == 'bitpay'" class="img-container-bitpay" ng-click="requestFingerprint()">
<i class="icon big-icon-svg"><div class="bg"></div></i>
</div>
<div class="comments" ng-click="requestFingerprint()">
<div class="header" translate>One-touch Sign In</div>
<div class="text-content" translate>Please place your fingertip on the scanner to verify your identity</div>
</div>
<button type="submit" style="margin-top: 15%"
class="button button-standard button-primary" ng-click="requestFingerprint()" translate>Scan again
</button>
</ion-content>
</ion-view>

73
www/views/pin.html Normal file
View file

@ -0,0 +1,73 @@
<ion-view id="pin" hide-tabs hide-back-button="!fromSettings">
<ion-nav-bar class="bar-clear">
<ion-nav-back-button>
</ion-nav-back-button>
</ion-nav-bar>
<div class="content">
<div class="block-text row">
<div class="message" ng-if="!confirmPin && !error" translate>Please enter your PIN</div>
<div class="message" ng-if="confirmPin && !error" translate>Confirm your PIN</div>
<div class="message error" ng-if="error">
<div ng-if="!expires" translate>Incorrect PIN, try again.</div>
<time ng-if="expires" translate>Try again in {{expires}}</time>
</div>
</div>
<div class="app-icon">
<i class="icon big-icon-svg">
<div class="bg"></div>
</i>
</div>
<div class="block-code">
<div class="row">
<div class="col circle-{{appName}}" ng-class="getFilledClass(1)"></div>
<div class="col circle-{{appName}}" ng-class="getFilledClass(2)"></div>
<div class="col circle-{{appName}}" ng-class="getFilledClass(3)"></div>
<div class="col circle-{{appName}}" ng-class="getFilledClass(4)"></div>
</div>
</div>
<div class="block-buttons">
<div class="row">
<div class="col" ng-click="updatePin('1')">
<div class="keyboard">1</div>
</div>
<div class="col" ng-click="updatePin('2')">
<div class="keyboard">2</div>
</div>
<div class="col" ng-click="updatePin('3')">
<div class="keyboard">3</div>
</div>
</div>
<div class="row">
<div class="col" ng-click="updatePin('4')">
<div class="keyboard">4</div>
</div>
<div class="col" ng-click="updatePin('5')">
<div class="keyboard">5</div>
</div>
<div class="col" ng-click="updatePin('6')">
<div class="keyboard">6</div>
</div>
</div>
<div class="row">
<div class="col" ng-click="updatePin('7')">
<div class="keyboard">7</div>
</div>
<div class="col" ng-click="updatePin('8')">
<div class="keyboard">8</div>
</div>
<div class="col" ng-click="updatePin('9')">
<div class="keyboard">9</div>
</div>
</div>
<div class="row">
<div class="col"></div>
<div class="col" ng-click="updatePin('0')">
<div class="">0</div>
</div>
<div class="col" ng-click="delete()">
<div class="keyboard icon ion-arrow-left-a"></div>
</div>
</div>
</div>
</div>
</ion-view>

View file

@ -91,7 +91,7 @@
</span>
<span ng-if="wallet.isComplete()">
<span ng-if="!wallet.balanceHidden"> {{wallet.status.totalBalanceStr ? wallet.status.totalBalanceStr : ( wallet.cachedBalance ? wallet.cachedBalance + (wallet.cachedBalanceUpdatedOn ? ' &middot; ' + ( wallet.cachedBalanceUpdatedOn * 1000 | amTimeAgo) : '') : '' ) }} </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}}

View file

@ -89,6 +89,16 @@
<i class="icon bp-arrow-right"></i>
</a>
<a class="item has-setting-value item-icon-left item-icon-right" ui-sref="tabs.lock" ng-if="isCordova || isDevel">
<i class="icon ion-ios-locked-outline" ng-if="locked"></i>
<i class="icon ion-ios-unlocked-outline" ng-if="!locked"></i>
<span class="setting-title">{{'Lock App' | translate}}</span>
<span class="setting-value">
{{method}}
</span>
<i class="icon bp-arrow-right"></i>
</a>
<div class="item item-divider" ng-show="wallets[0]">{{'Wallets & Integrations' | translate}}</div>
<a class="item item-icon-left item-icon-right" href