Merge pull request #365 from bitjson/feature/new-qrscanner-permissions
Polish QR scanning for desktop, iOS, and Android
This commit is contained in:
commit
767b523499
9 changed files with 358 additions and 278 deletions
|
|
@ -117,7 +117,6 @@ module.exports = function(grunt) {
|
|||
angular: {
|
||||
src: [
|
||||
'bower_components/qrcode-generator/js/qrcode.js',
|
||||
'bower_components/qrcode-decoder-js/lib/qrcode-decoder.js',
|
||||
'bower_components/moment/min/moment-with-locales.js',
|
||||
'bower_components/angular-moment/angular-moment.js',
|
||||
'bower_components/ng-lodash/build/ng-lodash.js',
|
||||
|
|
|
|||
|
|
@ -1,110 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('scannerController', function($scope, $timeout, storageService, $ionicModal, platformInfo) {
|
||||
|
||||
// QR code Scanner
|
||||
var video;
|
||||
var canvas;
|
||||
var $video;
|
||||
var context;
|
||||
var localMediaStream;
|
||||
var prevResult;
|
||||
var scanTimer;
|
||||
|
||||
var _scan = function(evt) {
|
||||
if (localMediaStream) {
|
||||
context.drawImage(video, 0, 0, 300, 225);
|
||||
try {
|
||||
qrcode.decode();
|
||||
} catch (e) {
|
||||
//qrcodeError(e);
|
||||
}
|
||||
}
|
||||
scanTimer = $timeout(_scan, 800);
|
||||
};
|
||||
|
||||
var _scanStop = function() {
|
||||
$timeout.cancel(scanTimer);
|
||||
if (localMediaStream && localMediaStream.active) {
|
||||
var localMediaStreamTrack = localMediaStream.getTracks();
|
||||
for (var i = 0; i < localMediaStreamTrack.length; i++) {
|
||||
localMediaStreamTrack[i].stop();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
localMediaStream.stop();
|
||||
} catch (e) {
|
||||
// Older Chromium not support the STOP function
|
||||
};
|
||||
}
|
||||
localMediaStream = null;
|
||||
video.src = '';
|
||||
};
|
||||
|
||||
qrcode.callback = function(data) {
|
||||
if (prevResult != data) {
|
||||
prevResult = data;
|
||||
return;
|
||||
}
|
||||
_scanStop();
|
||||
$scope.cancel();
|
||||
$scope.onScan({
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
var _successCallback = function(stream) {
|
||||
video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
|
||||
localMediaStream = stream;
|
||||
video.play();
|
||||
$timeout(_scan, 1000);
|
||||
};
|
||||
|
||||
var _videoError = function(err) {
|
||||
$scope.cancel();
|
||||
};
|
||||
|
||||
var setScanner = function() {
|
||||
navigator.getUserMedia = navigator.getUserMedia ||
|
||||
navigator.webkitGetUserMedia || navigator.mozGetUserMedia ||
|
||||
navigator.msGetUserMedia;
|
||||
window.URL = window.URL || window.webkitURL ||
|
||||
window.mozURL || window.msURL;
|
||||
};
|
||||
|
||||
$scope.init = function() {
|
||||
scannerInit();
|
||||
};
|
||||
|
||||
$scope.$on('TipsModalClosed', function(event) {
|
||||
scannerInit();
|
||||
});
|
||||
|
||||
function scannerInit() {
|
||||
setScanner();
|
||||
$timeout(function() {
|
||||
if ($scope.beforeScan) {
|
||||
$scope.beforeScan();
|
||||
}
|
||||
canvas = document.getElementById('qr-canvas');
|
||||
context = canvas.getContext('2d');
|
||||
|
||||
video = document.getElementById('qrcode-scanner-video');
|
||||
$video = angular.element(video);
|
||||
canvas.width = 300;
|
||||
canvas.height = 225;
|
||||
context.clearRect(0, 0, 300, 225);
|
||||
|
||||
navigator.getUserMedia({
|
||||
video: true
|
||||
}, _successCallback, _videoError);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
_scanStop();
|
||||
$scope.scannerModal.hide();
|
||||
$scope.scannerModal.remove();
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -1,29 +1,111 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.controllers').controller('tabScanController', function($scope, $log, $timeout, scannerService, incomingData) {
|
||||
angular.module('copayApp.controllers').controller('tabScanController', function($scope, $log, $timeout, scannerService, incomingData, $state, $ionicHistory, $rootScope) {
|
||||
|
||||
$scope.$on("$ionicView.beforeEnter", function() {
|
||||
$log.debug('Preparing to display available controls.');
|
||||
var scannerStates = {
|
||||
unauthorized: 'unauthorized',
|
||||
denied: 'denied',
|
||||
unavailable: 'unavailable',
|
||||
loading: 'loading',
|
||||
visible: 'visible'
|
||||
};
|
||||
$scope.scannerStates = scannerStates;
|
||||
|
||||
function _updateCapabilities(){
|
||||
var capabilities = scannerService.getCapabilities();
|
||||
$scope.scannerIsAvailable = capabilities.isAvailable;
|
||||
$scope.scannerHasPermission = capabilities.hasPermission;
|
||||
$scope.scannerIsDenied = capabilities.isDenied;
|
||||
$scope.scannerIsRestricted = capabilities.isRestricted;
|
||||
$scope.canEnableLight = capabilities.canEnableLight;
|
||||
$scope.canChangeCamera = capabilities.canChangeCamera;
|
||||
$scope.canOpenSettings = capabilities.canOpenSettings;
|
||||
}
|
||||
|
||||
function _handleCapabilities(){
|
||||
// always update the view
|
||||
$timeout(function(){
|
||||
if(!scannerService.isInitialized()){
|
||||
$scope.currentState = scannerStates.loading;
|
||||
} else if(!$scope.scannerIsAvailable){
|
||||
$scope.currentState = scannerStates.unavailable;
|
||||
} else if($scope.scannerIsDenied){
|
||||
$scope.currentState = scannerStates.denied;
|
||||
} else if($scope.scannerIsRestricted){
|
||||
$scope.currentState = scannerStates.denied;
|
||||
} else if(!$scope.scannerHasPermission){
|
||||
$scope.currentState = scannerStates.unauthorized;
|
||||
}
|
||||
$log.debug('Scan view state set to: ' + $scope.currentState);
|
||||
});
|
||||
}
|
||||
|
||||
function _refreshScanView(){
|
||||
_updateCapabilities();
|
||||
_handleCapabilities();
|
||||
if($scope.scannerHasPermission){
|
||||
activate();
|
||||
}
|
||||
}
|
||||
|
||||
// This could be much cleaner with a Promise API
|
||||
// (needs a polyfill for some platforms)
|
||||
$rootScope.$on('scannerServiceInitialized', function(){
|
||||
$log.debug('Scanner initialization finished, reinitializing scan view...');
|
||||
_refreshScanView();
|
||||
});
|
||||
|
||||
$scope.$on("$ionicView.afterEnter", function() {
|
||||
scannerService.activate(function(){
|
||||
scannerService.scan(function(err, contents){
|
||||
if(err){
|
||||
$log.debug('Scan canceled.');
|
||||
} else {
|
||||
incomingData.redir(contents);
|
||||
}
|
||||
});
|
||||
});
|
||||
// try initializing and refreshing status any time the view is entered
|
||||
scannerService.gentleInitialize();
|
||||
});
|
||||
|
||||
function activate(){
|
||||
scannerService.activate(function(){
|
||||
_updateCapabilities();
|
||||
_handleCapabilities();
|
||||
$log.debug('Scanner activated, setting to visible...');
|
||||
$scope.currentState = scannerStates.visible;
|
||||
// pause to update the view
|
||||
$timeout(function(){
|
||||
scannerService.scan(function(err, contents){
|
||||
if(err){
|
||||
$log.debug('Scan canceled.');
|
||||
} else if ($state.params.passthroughMode) {
|
||||
$rootScope.scanResult = contents;
|
||||
goBack();
|
||||
} else {
|
||||
handleSuccessfulScan(contents);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
$scope.activate = activate;
|
||||
|
||||
$scope.authorize = function(){
|
||||
scannerService.initialize(function(){
|
||||
_refreshScanView();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$on("$ionicView.afterLeave", function() {
|
||||
scannerService.deactivate();
|
||||
});
|
||||
|
||||
function handleSuccessfulScan(contents){
|
||||
$log.debug('Scan returned: "' + contents + '"');
|
||||
incomingData.redir(contents);
|
||||
}
|
||||
|
||||
$scope.openSettings = function(){
|
||||
scannerService.openSettings();
|
||||
};
|
||||
|
||||
$scope.attemptToReactivate = function(){
|
||||
scannerService.reinitialize();
|
||||
};
|
||||
|
||||
$scope.toggleLight = function(){
|
||||
scannerService.toggleLight(function(lightEnabled){
|
||||
$scope.lightActive = lightEnabled;
|
||||
|
|
@ -42,4 +124,14 @@ angular.module('copayApp.controllers').controller('tabScanController', function(
|
|||
});
|
||||
};
|
||||
|
||||
$scope.canGoBack = function(){
|
||||
return $state.params.passthroughMode;
|
||||
}
|
||||
function goBack(){
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$ionicHistory.backView().go();
|
||||
}
|
||||
$scope.goBack = goBack;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,77 +1,31 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.directives')
|
||||
.directive('qrScanner', function($rootScope, $timeout, $ionicModal, gettextCatalog, platformInfo) {
|
||||
|
||||
var isCordova = platformInfo.isCordova;
|
||||
var isWP = platformInfo.isWP;
|
||||
var isIOS = platformInfo.isIOS;
|
||||
|
||||
var controller = function($scope) {
|
||||
|
||||
var onSuccess = function(result) {
|
||||
$timeout(function() {
|
||||
window.plugins.spinnerDialog.hide();
|
||||
}, 100);
|
||||
if (isWP && result.cancelled) return;
|
||||
|
||||
$timeout(function() {
|
||||
var data = isIOS ? result : result.text;
|
||||
$scope.onScan({
|
||||
data: data
|
||||
});
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
var onError = function(error) {
|
||||
$timeout(function() {
|
||||
window.plugins.spinnerDialog.hide();
|
||||
}, 100);
|
||||
};
|
||||
|
||||
$scope.cordovaOpenScanner = function() {
|
||||
window.plugins.spinnerDialog.show(null, gettextCatalog.getString('Preparing camera...'), true);
|
||||
$timeout(function() {
|
||||
if (isIOS) {
|
||||
cloudSky.zBar.scan({}, onSuccess, onError);
|
||||
} else {
|
||||
cordova.plugins.barcodeScanner.scan(onSuccess, onError);
|
||||
}
|
||||
if ($scope.beforeScan) {
|
||||
$scope.beforeScan();
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
$scope.modalOpenScanner = function() {
|
||||
$ionicModal.fromTemplateUrl('views/modals/scanner.html', {
|
||||
scope: $scope,
|
||||
animation: 'slide-in-up'
|
||||
}).then(function(modal) {
|
||||
$scope.scannerModal = modal;
|
||||
$scope.scannerModal.show();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.openScanner = function() {
|
||||
if (isCordova) {
|
||||
$scope.cordovaOpenScanner();
|
||||
} else {
|
||||
$scope.modalOpenScanner();
|
||||
}
|
||||
};
|
||||
$scope.setFn({theScanFn: $scope.openScanner});
|
||||
};
|
||||
.directive('qrScanner', function($state, $rootScope, $log, $ionicHistory) {
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
onScan: "&",
|
||||
setFn: "&",
|
||||
beforeScan: "&"
|
||||
onScan: "&"
|
||||
},
|
||||
controller: controller,
|
||||
replace: true,
|
||||
template: '<a on-tap="openScanner()"><i class="icon ion-qr-scanner"></i></a>'
|
||||
template: '<a on-tap="openScanner()" nav-transition="none"><i class="icon ion-qr-scanner"></i></a>',
|
||||
link: function(scope, el, attrs) {
|
||||
|
||||
scope.openScanner = function() {
|
||||
$log.debug('Opening scanner by directive...');
|
||||
$ionicHistory.nextViewOptions({
|
||||
disableAnimate: true
|
||||
});
|
||||
$state.go('scanner', { passthroughMode: 1 });
|
||||
};
|
||||
|
||||
$rootScope.$on('$ionicView.afterEnter', function() {
|
||||
if($rootScope.scanResult) {
|
||||
scope.onScan({ data: $rootScope.scanResult });
|
||||
$rootScope.scanResult = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -217,6 +217,14 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
|
|||
}
|
||||
}
|
||||
})
|
||||
.state('scanner', {
|
||||
url: '/scanner',
|
||||
params: {
|
||||
passthroughMode: null,
|
||||
},
|
||||
controller: 'tabScanController',
|
||||
templateUrl: 'views/tab-scan.html'
|
||||
})
|
||||
.state('tabs.send', {
|
||||
url: '/send',
|
||||
views: {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('copayApp.services').service('scannerService', function($log, $timeout, platformInfo) {
|
||||
angular.module('copayApp.services').service('scannerService', function($log, $timeout, platformInfo, $rootScope) {
|
||||
|
||||
var isDesktop = !platformInfo.isCordova;
|
||||
var QRScanner = window.QRScanner;
|
||||
|
|
@ -8,21 +8,39 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
var backCamera = true; // the plugin defaults to the back camera
|
||||
|
||||
// Initalize known capabilities
|
||||
var hasPermission = isDesktop? true: false;
|
||||
var isAvailable = isDesktop? false: true; // assume camera exists on mobile
|
||||
var hasPermission = isDesktop? true: false; // assume desktop has permission
|
||||
var isDenied = false;
|
||||
var isRestricted = false;
|
||||
var canEnableLight = false;
|
||||
var canChangeCamera = false;
|
||||
var canOpenSettings = false;
|
||||
|
||||
function _checkCapabilities(status){
|
||||
$log.debug('scannerService is reviewing platform capabilities...');
|
||||
// Permission can be assumed on the desktop builds
|
||||
hasPermission = (isDesktop || status.authorized)? true: false;
|
||||
isDenied = status.denied? true : false;
|
||||
isRestricted = status.restricted? true : false;
|
||||
canEnableLight = status.canEnableLight? true : false;
|
||||
canChangeCamera = status.canChangeCamera? true : false;
|
||||
function orIsNot(bool){
|
||||
canOpenSettings = status.canOpenSettings? true : false;
|
||||
_logCapabilities();
|
||||
}
|
||||
|
||||
function _logCapabilities(){
|
||||
function _orIsNot(bool){
|
||||
return bool? '' : 'not ';
|
||||
}
|
||||
$log.debug('A light is ' + orIsNot(canEnableLight) + 'available on this platform.');
|
||||
$log.debug('A second camera is ' + orIsNot(canChangeCamera) + 'available on this platform.');
|
||||
$log.debug('A camera is ' + _orIsNot(isAvailable) + 'available to this app.');
|
||||
var access = 'not authorized';
|
||||
if(hasPermission) access = 'authorized';
|
||||
if(isDenied) access = 'denied';
|
||||
if(isRestricted) access = 'restricted';
|
||||
$log.debug('Camera access is ' + access + '.');
|
||||
$log.debug('Support for opening device settings is ' + _orIsNot(canOpenSettings) + 'available on this platform.');
|
||||
$log.debug('A light is ' + _orIsNot(canEnableLight) + 'available on this platform.');
|
||||
$log.debug('A second camera is ' + _orIsNot(canChangeCamera) + 'available on this platform.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -30,12 +48,17 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
*/
|
||||
this.getCapabilities = function(){
|
||||
return {
|
||||
isAvailable: isAvailable,
|
||||
hasPermission: hasPermission,
|
||||
isDenied: isDenied,
|
||||
isRestricted: isRestricted,
|
||||
canEnableLight: canEnableLight,
|
||||
canChangeCamera: canChangeCamera
|
||||
canChangeCamera: canChangeCamera,
|
||||
canOpenSettings: canOpenSettings
|
||||
}
|
||||
}
|
||||
|
||||
var initializeStarted = false;
|
||||
/**
|
||||
* If camera access has been granted, pre-initialize the QRScanner. This method
|
||||
* can be safely called before the scanner is visible to improve perceived
|
||||
|
|
@ -44,39 +67,70 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
* The `status` of QRScanner is returned to the callback.
|
||||
*/
|
||||
this.gentleInitialize = function(callback) {
|
||||
if(initializeStarted){
|
||||
QRScanner.getStatus(function(status){
|
||||
_completeInitialization(status, callback);
|
||||
});
|
||||
return;
|
||||
}
|
||||
initializeStarted = true;
|
||||
$log.debug('Trying to pre-initialize QRScanner.');
|
||||
if(!isDesktop){
|
||||
QRScanner.getStatus(function(status){
|
||||
_checkCapabilities(status);
|
||||
if(status.authorized){
|
||||
$log.debug('Camera permission already granted.');
|
||||
_initalize();
|
||||
initialize(callback);
|
||||
} else {
|
||||
$log.debug('QRScanner not authorized, waiting to initalize.');
|
||||
if(typeof callback === "function"){
|
||||
callback && callback(status);
|
||||
}
|
||||
_completeInitialization(status, callback);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$log.debug('Camera permission assumed on desktop.');
|
||||
_initalize();
|
||||
}
|
||||
function _initalize(){
|
||||
$log.debug('Preparing scanner...');
|
||||
QRScanner.prepare(function(err, status){
|
||||
if(err){
|
||||
$log.error(err);
|
||||
}
|
||||
_checkCapabilities(status);
|
||||
callback && callback(status);
|
||||
});
|
||||
initialize(callback);
|
||||
}
|
||||
};
|
||||
|
||||
function initialize(callback){
|
||||
$log.debug('Initializing scanner...');
|
||||
QRScanner.prepare(function(err, status){
|
||||
if(err){
|
||||
$log.error(err);
|
||||
// does not return `status` if there is an error
|
||||
QRScanner.getStatus(function(status){
|
||||
_completeInitialization(status, callback);
|
||||
|
||||
});
|
||||
} else {
|
||||
isAvailable = true;
|
||||
_completeInitialization(status, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.initialize = initialize;
|
||||
|
||||
// This could be much cleaner with a Promise API
|
||||
// (needs a polyfill for some platforms)
|
||||
var initializeCompleted = false;
|
||||
function _completeInitialization(status, callback){
|
||||
_checkCapabilities(status);
|
||||
initializeCompleted = true;
|
||||
$rootScope.$emit('scannerServiceInitialized');
|
||||
if(typeof callback === "function"){
|
||||
callback(status);
|
||||
}
|
||||
}
|
||||
this.isInitialized = function(){
|
||||
return initializeCompleted;
|
||||
}
|
||||
this.initializeStarted = function(){
|
||||
return initializeStarted;
|
||||
}
|
||||
|
||||
var nextHide = null;
|
||||
var nextDestroy = null;
|
||||
var hideAfterSeconds = 15;
|
||||
var hideAfterSeconds = 10;
|
||||
var destroyAfterSeconds = 5 * 60;
|
||||
|
||||
/**
|
||||
|
|
@ -89,7 +143,9 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
$log.debug('Activating scanner...');
|
||||
QRScanner.show(function(status){
|
||||
_checkCapabilities(status);
|
||||
callback(status);
|
||||
if(typeof callback === "function"){
|
||||
callback(status);
|
||||
}
|
||||
});
|
||||
if(nextHide !== null){
|
||||
$timeout.cancel(nextHide);
|
||||
|
|
@ -121,7 +177,7 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
*/
|
||||
this.deactivate = function(callback) {
|
||||
$log.debug('Deactivating scanner...');
|
||||
QRScanner.cancelScan();
|
||||
// QRScanner.cancelScan();
|
||||
nextHide = $timeout(_hide, hideAfterSeconds * 1000);
|
||||
nextDestroy = $timeout(_destroy, destroyAfterSeconds * 1000);
|
||||
};
|
||||
|
|
@ -140,6 +196,12 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
QRScanner.destroy();
|
||||
}
|
||||
|
||||
this.reinitialize = function(callback){
|
||||
initializeCompleted = false;
|
||||
QRScanner.destroy();
|
||||
initialize(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the device light (if available).
|
||||
*
|
||||
|
|
@ -185,4 +247,9 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti
|
|||
callback(status);
|
||||
});
|
||||
};
|
||||
|
||||
this.openSettings = function() {
|
||||
$log.debug('Attempting to open device settings...');
|
||||
QRScanner.openSettings();
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,55 +1,114 @@
|
|||
$scannerBackgroundColor: #060d2d;
|
||||
|
||||
#tab-scan {
|
||||
// view background is transparent to show video preview
|
||||
background: none transparent;
|
||||
.scanner-controls {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
background: transparent none;
|
||||
.bar-header {
|
||||
opacity: .9;
|
||||
}
|
||||
.guides {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
&-has-problems,
|
||||
&-loading-camera {
|
||||
background-color: $scannerBackgroundColor;
|
||||
}
|
||||
&-has-problems {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.qr-scan-guides {
|
||||
width: 60%;
|
||||
max-width: 400px;
|
||||
margin-bottom: 8em;
|
||||
max-height: 50%;
|
||||
}
|
||||
.icon-flash, .icon-camera-toggle {
|
||||
border-radius: 50%;
|
||||
width: 4em;
|
||||
height: 4em;
|
||||
background-color: rgba(13, 13, 13, 0.79);
|
||||
background-repeat: no-repeat;
|
||||
background-clip: padding-box;
|
||||
background-size: 100%;
|
||||
display: inline-block;
|
||||
margin: 2em 1em;
|
||||
cursor: pointer;
|
||||
// hover for desktop only
|
||||
body:not(.platform-cordova) &:hover {
|
||||
background-color: rgba(31, 40, 78, 0.79);
|
||||
}
|
||||
&.active, &:active {
|
||||
background-color: rgba(100, 124, 232, 0.79);
|
||||
|
||||
.zero-state {
|
||||
&-icon {
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
padding: 13px;
|
||||
box-shadow: $subtle-box-shadow;
|
||||
background-color: #fff;
|
||||
}
|
||||
&-heading {
|
||||
font-size: 20px;
|
||||
margin: 1rem;
|
||||
}
|
||||
&-description {
|
||||
margin: 0 2rem 120px;
|
||||
opacity: .6;
|
||||
max-width: 300px;
|
||||
}
|
||||
&-tldr {
|
||||
margin: 1rem auto;
|
||||
}
|
||||
&-description,
|
||||
&-tldr {
|
||||
max-width: 300px;
|
||||
}
|
||||
&-cta {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding-bottom: 6vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon-flash {
|
||||
background-image: url("../img/icon-flash.svg");
|
||||
&-loading-camera {
|
||||
height: 100%;
|
||||
width: 100%
|
||||
}
|
||||
.icon-camera-toggle {
|
||||
background-image: url("../img/icon-camera-toggle.svg");
|
||||
&-camera-ready {
|
||||
// view background is transparent to show video preview
|
||||
background: none transparent;
|
||||
.scanner-controls {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
.guides {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.qr-scan-guides {
|
||||
width: 60%;
|
||||
max-width: 400px;
|
||||
margin-bottom: 8em;
|
||||
max-height: 50%;
|
||||
}
|
||||
.icon-flash, .icon-camera-toggle {
|
||||
border-radius: 50%;
|
||||
width: 4em;
|
||||
height: 4em;
|
||||
background-color: rgba(13, 13, 13, 0.79);
|
||||
background-repeat: no-repeat;
|
||||
background-clip: padding-box;
|
||||
background-size: 100%;
|
||||
display: inline-block;
|
||||
margin: 2em 1em;
|
||||
cursor: pointer;
|
||||
// hover for desktop only
|
||||
body:not(.platform-cordova) &:hover {
|
||||
background-color: rgba(31, 40, 78, 0.79);
|
||||
}
|
||||
&.active, &:active {
|
||||
background-color: rgba(100, 124, 232, 0.79);
|
||||
}
|
||||
}
|
||||
.icon-flash {
|
||||
background-image: url("../img/icon-flash.svg");
|
||||
}
|
||||
.icon-camera-toggle {
|
||||
background-image: url("../img/icon-camera-toggle.svg");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#cordova-plugin-qrscanner-still, #cordova-plugin-qrscanner-video-preview {
|
||||
background-color: #060d2d !important;
|
||||
background-color: $scannerBackgroundColor !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
<ion-modal-view ng-controller="scannerController" ng-init="init()">
|
||||
<ion-header-bar align-title="center" class="bar-royal">
|
||||
<button ng-click="cancel()" class="button button-back button-clear" translate>
|
||||
Close
|
||||
</button>
|
||||
<h1 class="title ellipsis" translate>QR-Scanner</h1>
|
||||
</ion-header-bar>
|
||||
<ion-content >
|
||||
<canvas id="qr-canvas" width="200" height="150"></canvas>
|
||||
<video id="qrcode-scanner-video" width="300" height="225"></video>
|
||||
</ion-content>
|
||||
</ion-modal-view>
|
||||
|
|
@ -1,22 +1,45 @@
|
|||
<ion-view id="tab-scan">
|
||||
<ion-nav-bar class="bar-royal">
|
||||
<ion-nav-title>{{'Scan' | translate}}</ion-nav-title>
|
||||
<ion-nav-buttons side="primary">
|
||||
<button class="button back-button button-clear ng-hide" ng-click="goBack()" ng-show="canGoBack()">
|
||||
<i class="icon ion-ios-close-empty"></i>
|
||||
</button>
|
||||
</ion-nav-buttons>
|
||||
</ion-nav-bar>
|
||||
<ion-content scroll="false">
|
||||
<div class="guides">
|
||||
<img class="qr-scan-guides" src="img/bitpay-wallet-qr-scan-guides.svg">
|
||||
<div class="ng-hide" id="tab-scan-has-problems" ng-show="currentState === scannerStates.unauthorized || currentState === scannerStates.denied || currentState === scannerStates.unavailable">
|
||||
<i class="icon zero-state-icon">
|
||||
<img src="img/tab-icons/ico-receive.svg"/>
|
||||
</i>
|
||||
<div class="zero-state-heading" translate>Scan QR Codes</div>
|
||||
<div class="zero-state-description" translate>You can scan bitcoin addresses, payment requests, paper wallets, and more.</div>
|
||||
<div class="zero-state-cta">
|
||||
<div class="ng-hide zero-state-tldr" ng-show="currentState === scannerStates.unauthorized" translate>Enable the camera to get started.</div>
|
||||
<div class="ng-hide zero-state-tldr" ng-show="currentState === scannerStates.denied" translate>Enable camera access in your device settings to get started.</div>
|
||||
<div class="ng-hide zero-state-tldr" ng-show="currentState === scannerStates.unavailable" translate>Please connect a camera to get started.</div>
|
||||
<button ng-show="currentState === scannerStates.unauthorized" class="ng-hide button button-standard button-primary" ng-click="authorize()">Allow Camera Access</button>
|
||||
<button ng-show="currentState === scannerStates.denied && canOpenSettings" class="ng-hide button button-standard button-primary" ng-click="openSettings()">Open Settings</button>
|
||||
<button ng-show="currentState === scannerStates.unavailable" class="ng-hide button button-standard button-primary" ng-click="attemptToReactivate()">Retry Camera</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scanner-controls">
|
||||
<a ng-click="toggleLight()" ng-show="canEnableLight">
|
||||
<i class="icon">
|
||||
<div class="icon-flash" ng-class="{'active': lightActive}"></div>
|
||||
</i>
|
||||
</a>
|
||||
<a ng-click="toggleCamera()" ng-show="canChangeCamera">
|
||||
<i class="icon">
|
||||
<div class="icon-camera-toggle" ng-class="{'active': cameraToggleActive}"></div>
|
||||
</i>
|
||||
</a>
|
||||
<div class="ng-show" id="tab-scan-loading-camera" ng-show="currentState === scannerStates.loading"></div>
|
||||
<div class="ng-hide" id="tab-scan-camera-ready" ng-show="currentState === scannerStates.visible">
|
||||
<div class="guides">
|
||||
<img class="qr-scan-guides" src="img/bitpay-wallet-qr-scan-guides.svg">
|
||||
</div>
|
||||
<div class="scanner-controls">
|
||||
<a ng-click="toggleLight()" ng-show="canEnableLight">
|
||||
<i class="icon">
|
||||
<div class="icon-flash" ng-class="{'active': lightActive}"></div>
|
||||
</i>
|
||||
</a>
|
||||
<a ng-click="toggleCamera()" ng-show="canChangeCamera">
|
||||
<i class="icon">
|
||||
<div class="icon-camera-toggle" ng-class="{'active': cameraToggleActive}"></div>
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-view>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue