/* This file is part of Ezra Bible App.
Copyright (C) 2019 - 2023 Ezra Bible App Development Team <contact@ezrabibleapp.net>
Ezra Bible App is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Ezra Bible App is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Ezra Bible App. See the file LICENSE.
If not, see <http://www.gnu.org/licenses/>. */
/* eslint-disable no-undef */
const IpcGeneral = require('../ipc/ipc_general.js');
const IpcI18n = require('../ipc/ipc_i18n.js');
const i18nController = require('../controllers/i18n_controller.js');
/**
* This class controls Cordova platform specific functionality and it also contains the entry point to startup on Cordova (init):
* - Init code with permission handling and nodejs startup
* - Cordova-specific fullscreen toggling
* - Code to keep screen awake (keep the display turned on)
* - Code to copy text to clipboard
*/
class CordovaPlatform {
constructor() {}
init() {
console.log("Initializing app on Cordova platform ...");
this._isFullScreenMode = false;
// In the Cordova app the first thing we do is showing the global loading indicator.
// This would happen later again, but only after the nodejs engine is started.
// In the meanwhile the screen would be just white and that's what we would like to avoid.
uiHelper.showGlobalLoadingIndicator();
document.addEventListener('deviceready', async () => {
var isDebug = await this.isDebug();
// Enable to test Sentry in debug version
// isDebug = false;
window.open = cordova.InAppBrowser.open;
if (!isDebug) {
var version = await cordova.getAppVersion.getVersionNumber();
console.log("Configuring Sentry (WebView) with app version: " + version);
try {
// Loading Sentry in a try/catch block, because we have observed failures related to this step.
// If it fails ... startup is broken. Why did it fail previously? After a sentry upgrade the
// path to the sources had changed and the require statement did not work anymore.
window.Sentry = require('@sentry/browser/cjs');
Sentry.init({
dsn: 'https://977e321b83ec4e47b7d28ffcbdf0c6a1@sentry.io/1488321',
release: version
});
} catch (error) {
console.error('Sentry initialization failed with an error!');
console.log(error);
}
}
// cordova-plugin-ionic-keyboard event binding
// eslint-disable-next-line no-unused-vars
window.addEventListener('keyboardDidShow', (event) => {
document.body.classList.add('keyboard-shown');
});
// cordova-plugin-ionic-keyboard event binding
// eslint-disable-next-line no-unused-vars
window.addEventListener('keyboardDidHide', (event) => {
document.body.classList.remove('keyboard-shown');
});
this.startNodeJsEngine();
}, false);
}
getAndroidVersion() {
var userAgent = navigator.userAgent.toLowerCase();
// eslint-disable-next-line no-useless-escape
var match = userAgent.match(/android\s([0-9\.]*)/i);
var version = match ? match[1] : undefined;
if (version !== undefined) {
version = parseInt(version, 10);
}
return version;
}
async hasPermission() {
return new Promise((resolve, reject) => {
var permissions = cordova.plugins.permissions;
return permissions.checkPermission(permissions.WRITE_EXTERNAL_STORAGE, (status) => {
resolve(status.hasPermission);
}, () => {
reject("Failed to check permissions!");
});
});
}
onRequestPermissionClick() {
this.permissionRequestTime = new Date();
this.requestPermission().then((permissionsGranted) => {
if (permissionsGranted) {
this.onPermissionGranted();
} else {
this.onPermissionDenied();
}
}, (error) => {
console.log(error);
});
}
onPermissionGranted() {
console.log("Permission to access storage has been GRANTED!");
this.getPermissionBox().dialog('close');
this.initPersistenceAndStart();
}
onPermissionDenied() {
console.log("Permission to access storage has been DENIED!");
var noSystemPermissionsDialogShownTiming = 500;
var timeSinceRequest = new Date() - this.permissionRequestTime;
if (timeSinceRequest < noSystemPermissionsDialogShownTiming) {
// If the request came back in a very short time we assume that the user permanently denied the permission
// and show a corresponding message.
var permanentPermissionDecisionInfoPart1 = i18n.t('cordova.permanent-permission-decision-part1');
var permanentPermissionDecisionInfoPart2 = i18n.t('cordova.permanent-permission-decision-part2');
$('#permission-decision').html(`
${permanentPermissionDecisionInfoPart1}
<br>
<br>
${permanentPermissionDecisionInfoPart2}
`);
$('#enable-access').html('');
} else {
var storagePermissionDenied = i18n.t('cordova.storage-permission-denied');
$('#permission-decision').html(storagePermissionDenied);
}
}
requestPermission() {
// Note that the following code depends on having cordova-plugin-android-permissions available
console.log("Getting permissions ...");
return new Promise((resolve, reject) => {
var permissions = cordova.plugins.permissions;
return permissions.requestPermission(
permissions.WRITE_EXTERNAL_STORAGE,
(status) => { // success
if ( status.hasPermission ) {
resolve(true);
} else {
resolve(false);
}
},
() => { // error
reject("Failed to request permission to write on external storage");
}
);
});
}
isDebug() {
// The following code depends on having cordova-plugin-is-debug available
return new Promise((resolve, reject) => {
cordova.plugins.IsDebug.getIsDebug((isDebug) => {
resolve(isDebug);
}, (err) => {
reject(err);
});
});
}
getPermissionBox() {
return $('#permissions-box');
}
getPermissionInfoMessage() {
var storageJustification = i18n.t('cordova.storage-justification');
var enableAccessButtonLabel = i18n.t('cordova.enable-storage-access');
var infoMessage = `
<br>
${storageJustification}
<p id='permission-decision' style='color: red;'></p>
<p id='enable-access' style='text-align: center; margin-top: 2em; margin-bottom: 2em;'>
<button id='request-write-permissions'
class='fg-button ui-corner-all ui-state-default'
style='height: 3em;'>
${enableAccessButtonLabel}
</button>
</p>
`;
return infoMessage;
}
showPermissionInfo() {
console.log("Showing permissions info!");
var infoMessage = this.getPermissionInfoMessage();
this.getPermissionBox().find('#permissions-box-content').html(infoMessage);
$('#request-write-permissions').click(() => {
this.onRequestPermissionClick();
});
uiHelper.hideGlobalLoadingIndicator();
var welcomeTitle = i18n.t("general.welcome-to-ezra-bible-app");
let dialogOptions = uiHelper.getDialogOptions(400, null, false, null);
dialogOptions.dialogClass = 'ezra-dialog welcome-dialog';
dialogOptions.title = welcomeTitle;
dialogOptions.dialogClass = 'ezra-dialog dialog-without-close-button android-dialog-large-fontsize';
this.getPermissionBox().dialog(dialogOptions);
uiHelper.fixDialogCloseIconOnAndroid('welcome-dialog');
}
isAndroidWithScopedStorage() {
var androidVersion = this.getAndroidVersion();
const FIRST_ANDROID_VERSION_WITH_SCOPED_STORAGE = 11;
return androidVersion >= FIRST_ANDROID_VERSION_WITH_SCOPED_STORAGE;
}
async startNodeJsEngine() {
var isDebug = await this.isDebug();
console.log("Starting up nodejs engine!");
nodejs.channel.setListener(this.mainProcessListener);
nodejs.startWithScript(`
const Main = require('main.js');
global.main = new Main();
main.init(${isDebug});
`, async () => {
uiHelper.updateLoadingSubtitle("cordova.init-i18n", "Initializing i18n");
window.ipcI18n = new IpcI18n();
await i18nController.initI18N();
const androidVersion = this.getAndroidVersion();
if (androidVersion >= 11) {
// On Android 11 we start directly without asking for storage permissions, because we store everything internally
this.initPersistenceAndStart();
} else {
// On Android < 11 we first need to check storage permissions, because we are using the external storage (/sdcard).
this.hasPermission().then((result) => {
if (result == true) {
this.initPersistenceAndStart();
} else {
this.showPermissionInfo();
}
}, () => {
console.log("Failed to check existing permissions ...");
});
}
});
}
async initPersistenceAndStart() {
uiHelper.showGlobalLoadingIndicator();
const androidVersion = this.getAndroidVersion();
window.ipcGeneral = new IpcGeneral();
uiHelper.updateLoadingSubtitle("cordova.init-sword", "Initializing SWORD");
await ipcGeneral.initPersistentIpc(androidVersion);
uiHelper.updateLoadingSubtitle("cordova.init-database", "Initializing database");
await ipcGeneral.initDatabase(androidVersion, navigator.connection.type);
await startup.initApplication();
}
mainProcessListener(message) {
console.log(message);
}
toggleFullScreen() {
// Note that the following code depends on having cordova-plugin-fullscreen available
if (this._isFullScreenMode) {
this._isFullScreenMode = false;
AndroidFullScreen.showSystemUI(() => {
// console.log("Left fullscreen mode");
}, () => {
console.error("Could not leave immersive mode");
});
} else {
this._isFullScreenMode = true;
AndroidFullScreen.immersiveMode(() => {
// console.log("Entered immersive / fullscreen mode");
}, () => {
console.error("Could not switch to immersive mode");
});
}
}
isFullScreen() {
return this._isFullScreenMode;
}
keepScreenAwake() {
// Note that the following code depends on having cordova-plugin-insomnia available
window.plugins.insomnia.keepAwake();
}
allowScreenToSleep() {
// Note that the following code depends on having cordova-plugin-insomnia available
window.plugins.insomnia.allowSleepAgain();
}
copyTextToClipboard(text) {
// Note that the following code depends on having cordova-clipboard available
cordova.plugins.clipboard.copy(text);
}
copyHtmlToClipboard(html) {
this.copyTextToClipboard(html);
}
copyToClipboard(text, html) {
this.copyTextToClipboard(text);
}
}
module.exports = CordovaPlatform;
Source