Cordova cordova-plugin-qrscanner: Opaque camera view - javascript

I am working through quasar-framework and I do the wrap with cordova for android platform.
The scanner works fine but blindly.
When QRScanner.show() starts I am getting full opaque view. I try to do all html elements transparent, hide and even remove some of them after and before QRScanner.show() call but always I see the opaque view. Someone knows how to fix this?
<script>
export default {
/*
Fuentes:
camera
https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-camera/index.html#takePicture
qrscanner
https://github.com/bitpay/cordova-plugin-qrscanner#prepare
*/
mounted () {
this.prepDevice()
},
data () {
return {
imageURI: '',
authorized: false,
selection: 'standard',
selectOptions: [
{
label: 'Camera-thumbnail',
value: 'camera-thmb'
},
{
label: 'Standard',
value: 'standard'
}
],
enableVisibility: 'hidden',
backColor: 'transparent'
}
},
methods: {
prepDevice () {
QRScanner.prepare(this.onDone)
},
onDone: function (err, status) {
if(err) {
alert("preparing: error code = " + err.code)
}
if(status.authorized) {
this.authorized = true
} else if (status.denied || !status.authorized) {
this.openSettings()
} else {
//No se obtuvo permiso
}
},
goScan: function () {
//--->>> Funciona pero el escaneo es a ciegas (vista en negro) <<<---
this.authorized = false
QRScanner.show()
/*
var html = document.getElementsByTagName("*")
for (var i=0; i<html.length; i++) {
html[i].style.backgroundColor = "transparent"
}
*/
//QRScanner.enableLight()
QRScanner.scan(this.displayContents)
},
displayContents: function (err, text) {
if(err){
alert("scanning: error code = " + err.code)
if(err.name === 'SCAN_CANCELED') {
alert("The scan was canceled before a QR code was found.")
}
} else {
alert(text)
}
//QRScanner.hide()
//QRScanner.disableLight()
QRScanner.destroy() // hide, cancelScan...
this.authorized = true
},
cancelScan() {
QRScanner.cancelScan()
this.authorized = true
},
openSettings() {
if(status.canOpenSettings){
if(confirm("Would you like to enable QR code scanning? You can allow camera access in your settings.")){
QRScanner.openSettings();
}
}
}
}
}
And the html where I call the goScan function:
<button v-if="authorized" class="secondary push" #click="goScan()">Go Scan</button>
Resource: https://github.com/bitpay/cordova-plugin-qrscanner
As I said the scan works fine but blindly with the full opaque camera view.
Thanks.

If scanning is already working, you're nearly there. Ensuring the video preview is visible basically requires stepping through the layers of your application and confirming that the layer isn't obscuring the preview.
Start by inspecting the DOM of your app while it's running on the device. Try setting a background of none transparent on each element covering the view, including the body and html elements. In almost all cases, you'll find a rogue container with a white background somewhere in the layers of your app.
If you're entirely convinced the entire web view is transparent (note: this is very unusual), you'll need to inspect the native layers of your app to determine if another plugin or configuration setting is interfering with visibility. Instructions for this step would be very platform specific, so it's best to consult documentation/forums for the platform in question.

Related

Testing Cross Browser Extension With Jest, How To Mock Chrome Storage API?

After putting off testing for a while now due to Cypress not allowing visiting chrome:// urls, I decided to finally understand how to unit/integration test my extension - TabMerger. This comes after the many times that I had to manually test the ever growing functionality and in some cases forgot to check a thing or two. Having automated testing will certainly speed up the process and help me be more at peace when adding new functionality.
To do this, I chose Jest since my extension was made with React (CRA). I also used React Testing Library (#testing-library/react) to render all React components for testing.
As I recently made TabMerger open source, the full testing script can be found here
Here is the test case that I want to focus on for this question:
import React from "react";
import { render, fireEvent } from "#testing-library/react";
import * as TabFunc from "../src/Tab/Tab_functions";
import Tab from "../src/Tab/Tab";
var init_groups = {
"group-0": {
color: "#d6ffe0",
created: "11/12/2020 # 22:13:24",
tabs: [
{
title:
"Stack Overflow - Where Developers Learn, Share, & Build Careersaaaaaaaaaaaaaaaaaaaaaa",
url: "https://stackoverflow.com/",
},
{
title: "lichess.org • Free Online Chess",
url: "https://lichess.org/",
},
{
title: "Chess.com - Play Chess Online - Free Games",
url: "https://www.chess.com/",
},
],
title: "Chess",
},
"group-1": {
color: "#c7eeff",
created: "11/12/2020 # 22:15:11",
tabs: [
{
title: "Twitch",
url: "https://www.twitch.tv/",
},
{
title: "reddit: the front page of the internet",
url: "https://www.reddit.com/",
},
],
title: "Social",
},
};
describe("removeTab", () => {
it("correctly adjusts groups and counts when a tab is removed", () => {
var tabs = init_groups["group-0"].tabs;
const { container } = render(<Tab init_tabs={tabs} />);
expect(container.getElementsByClassName("draggable").length).toEqual(3);
var removeTabSpy = jest.spyOn(TabFunc, "removeTab");
fireEvent.click(container.querySelector(".close-tab"));
expect(removeTabSpy).toHaveBeenCalledTimes(1);
expect(container.getElementsByClassName("draggable").length).toEqual(2); // fails (does not remove the tab for some reason)
});
});
I mocked the Chrome API according to my needs, but feel that something is missing. To mock the Chrome API I followed this post (along with many others, even for other test runners like Jasmine): testing chrome.storage.local.set with jest.
Even though the Chrome storage API is mocked, I think the issue lies in this function which gets called upon initial render. That is, I think the chrome.storage.local.get is not actually being executed, but am not sure why.
// ./src/Tab/Tab_functions.js
/**
* Sets the initial tabs based on Chrome's local storage upon initial render.
* If Chrome's local storage is empty, this is set to an empty array.
* #param {function} setTabs For re-rendering the group's tabs
* #param {string} id Used to get the correct group tabs
*/
export function setInitTabs(setTabs, id) {
chrome.storage.local.get("groups", (local) => {
var groups = local.groups;
setTabs((groups && groups[id] && groups[id].tabs) || []);
});
}
The reason I think the mocked Chrome storage API is not working properly is because when I manually set it in my tests, the number of tabs does not increase from 0. Which forced me to pass a prop (props.init_tabs) to my Tab component for testing purposes (https://github.com/lbragile/TabMerger/blob/f78a2694786d11e8270454521f92e679d182b577/src/Tab/Tab.js#L33-L35) - something I want to avoid if possible via setting local storage.
Can someone point me in the right direction? I would like to avoid using libraries like jest-chrome since they abstract too much and make it harder for me to understand what is going on in my tests.
I think I have a solution for this now, so I will share with others.
I made proper mocks for my chrome storage API to use localStorage:
// __mocks__/chromeMock.js
...
storage: {
local: {
...,
get: function (key, cb) {
const item = JSON.parse(localStorage.getItem(key));
cb({ [key]: item });
},
...,
set: function (obj, cb) {
const key = Object.keys(obj)[0];
localStorage.setItem(key, JSON.stringify(obj[key]));
cb();
},
},
...
},
...
Also, to simulate the tab settings on initial render, I have a beforeEach hook which sets my localStorage using the above mock:
// __tests__/Tab.spec.js
var init_ls_entry, init_tabs, mockSet;
beforeEach(() => {
chrome.storage.local.set({ groups: init_groups }, () => {});
init_ls_entry = JSON.parse(localStorage.getItem("groups"));
init_tabs = init_ls_entry["group-0"].tabs;
mockSet = jest.fn(); // mock for setState hooks
});
AND most importantly, when I render(<Tab/>), I noticed that I wasn't supplying the id prop which caused nothing to render (in terms of tabs from localStorage), so now I have this:
// __tests__/Tab.spec.js
describe("removeTab", () => {
it("correctly adjusts storage when a tab is removed", async () => {
const { container } = render(
<Tab id="group-0" setTabTotal={mockSet} setGroups={mockSet} />
);
var removeTabSpy = jest.spyOn(TabFunc, "removeTab");
var chromeSetSpy = jest.spyOn(chrome.storage.local, "set");
fireEvent.click(container.querySelector(".close-tab"));
await waitFor(() => {
expect(chromeSetSpy).toHaveBeenCalled();
});
chrome.storage.local.get("groups", (local) => {
expect(init_tabs.length).toEqual(3);
expect(local.groups["group-0"].tabs.length).toEqual(2);
expect(removeTabSpy).toHaveBeenCalledTimes(1);
});
expect.assertions(4);
});
});
Which passes!!
Now on to drag and drop testing 😊

Snackbar is not shown when internet connection is changed from ON to OFF

I used the NetInfo library to detect whether the internet connection is available. It is working perfectly , but the snackbar inside the NetInfo function is not shown when internet is changed from on to off. Snackbar is shown perfectly when started with no internet and also started with internet.
When Internet is changed from off to on every time, snackbar is shown every time BUT not shown when changed from ON to OFF. All the logs shows perfectly in every conditions. If I place a alertbox instead of snackbar, everything is perfect.
handleConnectivityChange = (isConnected) => {
NetInfo.isConnected.fetch().done((isConnected) => {
console.log('Dashboard ConnectivityChanged');
console.log('IsConnectedValue:'+ isConnected );
if(isConnected == true)
{
console.log('Dashboard Connected');
Snackbar.show({
title: 'Connected to Internet',
duration: Snackbar.LENGTH_LONG,
action: {
title: 'DISMISS',
color: 'rgb(216,21,88)',
onPress: () => { /* Dismiss snackbar default */ },
},
});
}
else
{
console.log('Dashboard No Internet');
Snackbar.show({
title: 'No Internet Connection',
duration: Snackbar.LENGTH_LONG,
action: {
title: 'DISMISS',
color: 'rgb(216,21,88)',
onPress: () => { /* Dismiss snackbar default */ },
},
});
}
});
}
I had a similar issue. Tbh I'm not sure what's causing it but as a temporary fix wrapping it inside a setTimeout seems to do the job, i.e.
setTimeout(() => {
Snackbar.show({
title: 'Connected to Internet',
duration: Snackbar.LENGTH_LONG,
action: {
title: 'DISMISS',
color: 'rgb(216,21,88)',
onPress: () => { /* Dismiss snackbar default */ },
},
});
}, 1000);
Also, in order to prevent the Snackbar import throughout your app and code reusability, consider putting Snackbar.show() in a separate function.
Currently I have this in a utils.js that I call throughout my entire app:
export const showSnackbar = ({ title, duration = Snackbar.LENGTH_SHORT, action }) => {
Snackbar.dismiss(); // dismiss if a snackbar is still "alive"
if (!action) {
Snackbar.show({
title: message,
duration,
});
} else {
Snackbar.show({
title,
duration,
action
});
}
};
Note that in the beginning I dismiss the previously shown snackbar if it's still on screen. Hope this helps :)

RelayJS infinite scroll

I'm attempting to make a infinite scroll pagination in my React-Relay frontend, but without a success.
At this moment, I have this React component ...
class List extends Component {
state = { loading: false };
componentDidMount() {
window.onscroll = () => {
if (!this.state.loading
&& (window.innerHeight + window.scrollY)
>= document.body.offsetHeight) {
this.setState({loading: true}, () => {
this.props.relay.setVariables({
count: this.props.relay.variables.count + 5
}, (readyState) => {
if (readyState.done) {
this.setState({ loading: false });
}
});
});
}
}
}
render() {
return (
<div>
{this.props.viewer.products.edges.map(function(product, i) {
return (<Item key={i} product={product.node} />);
})}
</div>
);
}
};
... wrapped in Relay container
export default Relay.createContainer(List, {
initialVariables: {
count: 5
},
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
products(first: $count) {
total
edges {
node {
${Item.getFragment('product')}
}
}
}
}
`,
}
});
The logic behind this seems quite easy. If you reach a certain scrollY position, set to count variable a new inceremented value, which extends your list of Edges.
But this concept will lead to a situation, where in the beginning I query the database for first 5 records, but lately while I keep scrolling a will query for N+5 records. In the end I will query the database for a whole table (thousands of records)! Which is quite unacceptable.
So I'm trying to implement a cursors, but I don't know how to just fetch data from a connection and extend the result. This approach returns me a "paginated" list.
Also, the above code gives me this error, when I scroll down
resolveImmediate.js?39d8:27 Uncaught Invariant Violation: performUpdateIfNecessary: Unexpected batch number (current 19, pending 18)
I will be very thankful for any help or example!
Problem solved!
About my concern of fetching N+5 records over and over again with each scroll, it seems that Relay is quite intelligent, because it automatically "paginate" my connection by querying cursor and managing the after arg in my connection.
And with little help from graphql-sequelize on my backend, it simply turns into this query
SELECT ... FROM product ORDER BY product.id ASC LIMIT 5 OFFSET 10
Which actually solves my first problem about the performance. :)
So my Relay.QL query has underhood transpiled into this
query ListPage_ViewerRelayQL($id_0:ID!) {
node(id:$id_0) {
...F1
}
}
fragment F0 on Product {
id,
title,
price
}
fragment F1 on Viewer {
_products4tSFq4:products(after:"YXJyYXljb25uZWN0aW9uJDUkNA==",first:5) {
edges {
cursor,
node {
id,
...F0
}
},
pageInfo {
hasNextPage,
hasPreviousPage
}
},
id
}
and when I ran this query on my GraphQL backend, it returned me empty result
{
"data": {
"node": null
}
}
Then I immediately realized where is the problem and why my Relay crashes down.
The problem is with refetching the viewer. I did not properly implemented nodeDefinitions and when the viewer ID hit idFetcher and typeResolver, it failed and returned a null. After that on the client side, Relay was unable to finish the refetching of my connection and crashed down!
After small repairment and fixes on the Backend-side, my infinite scroll works like a charm! :)

Handling Direct Update with navigation to native page in IBM Mobile first

I am developing a project, in which i need to call a native page in wlCommonInit()
function wlCommonInit(){
WL.NativePage.show(nativePageClassName, backFromNativePage, params);
}
I want my project to receive the direct update with persession mode. So to connect with the Mobile First Server, I have called WL.Client.connect()
function wlCommonInit(){
busyind = new WL.BusyIndicator;
busyind.show();
WL.Client.connect({onSuccess: connectSuccess, onFailure: connectFail});
WL.NativePage.show(nativePageClassName, backFromNativePage, params);
}
More over I want to handle the direct update so I have added the required code.
wl_directUpdateChallengeHandler.handleDirectUpdate = function(directUpdateData,
directUpdateContext) {
// custom WL.SimpleDialog for Direct Update
var customDialogTitle = 'Custom Title Text';
var customDialogMessage = 'Custom Message Text';
var customButtonText1 = 'Update Application';
var customButtonText2 = 'Not Now';
WL.SimpleDialog.show(customDialogTitle, customDialogMessage, [{
text: customButtonText1,
handler: function() {
directUpdateContext.start(directUpdateCustomListener);
}
}, {
text: customButtonText2,
handler: function() {
wl_directUpdateChallengeHandler.submitFailure();
}
}]);
};
var directUpdateCustomListener = {
onStart: function(totalSize) {},
onProgress: function(status, totalSize, completeSize) {},
onFinish: function(status) {
WL.SimpleDialog.show('New Update Available', 'Press reload button to update to new version', [{
text: WL.ClientMessages.reload,
handler: WL.Client.reloadApp
}]);
}
};
Here the problem is, the application is navigating to the native page
before it can go to the direct update handler function when the direct
update is available.
Is there any way to resolve it?
I think what you should do instead if use the API [WL.Client.checkForDirectUpdate.
This way you will have the ability to first check for direct update - handle it if there is an update and then execute the function for opening the native page.
The code that is running is async, so you can't control it if you're not following the above suggestion.

Do I need any server adjustments in order to use fully HTML5 History API?

I am using HTML5 History API (on Chrome), at the following link:
http://jsbin.com/zuqijofole/1
You can see a simple application which shows/hides div (Views).
Script works fine using browser backward and forward buttons but if I type directly in the browser the following address (in order to see View 2)
http://jsbin.com/zuqijofole/2
the document is not found. I need instead the second View to be shown.
I would like to know:
Should I implement on server side some logic which map URL? Which
coul be a solution using latest PHP?
Or am I missing some implementation in my JS?
Notes: solution should work in a SPA application, so all data is rendered by JS app.
window.app = {
data: {
views: [
{ id: 0, isActive: false },
{ id: 1, isActive: false },
{ id: 2, isActive: false },
]
},
start: function () {
this.listeners();
// default entry
var activeView = this.wm.activeView;
history.replaceState({ activeView: activeView }, document.title, document.location.href);
window.app.wm.hideViews();
window.app.wm.showView();
},
listeners: function () {
window.addEventListener('popstate', function (event) {
// fires when backing/forwarding in history
console.log(event);
console.log(window.history.state);
this.wm.showHideBl(event.state);
}.bind(this));
var elm = document.getElementById('btn-prev');
elm.addEventListener('click', function () {
window.app.wm.snowPrevView();
});
elm = document.getElementById('btn-next');
elm.addEventListener('click', function () {
window.app.wm.snowNextView();
});
},
wm: {
activeView: 0, // default
showView: function () {
var elm = document.getElementById('view-' + this.activeView);
elm.style.display = '';
},
showHideBl: function (data) {
this.hideView();
this.activeView = data.activeView;
this.showView();
},
snowNextView: function () {
// bl
if (this.activeView < window.app.data.views.length - 1) {
this.hideView();
this.activeView++;
this.showView();
history.pushState({ activeView: this.activeView }, '', this.activeView);
}
},
snowPrevView: function () {
// bl
if (this.activeView > 0) {
this.hideView();
this.activeView--;
this.showView();
history.pushState({ activeView: this.activeView }, '', this.activeView);
}
},
hideView: function () {
var elm = document.getElementById('view-' + this.activeView);
elm.style.display = 'none';
},
hideViews: function () {
window.app.data.views.forEach(function (item, index, array) {
var elm = document.getElementById('view-' + item.id);
elm.style.display = 'none';
}.bind(this));
}
}
};
Yes, for a completely seamless experience, you want that all URLs created by the History API to be mapped to actual URLs that the server can use.
For example, if you have a table that you can sort client side, you can use the history API to save the sorting state into the URL. The server should be able to read that URL and serve a table already sorted when the page is refreshed.
The best way to make sure everything works as intended is to disable JavaScript and make sure you can still navigate and use the site correctly (even though the page refreshes all the time).

Categories

Resources