html2apk 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +318 -3
- package/examples/minimal/app.json +5 -0
- package/examples/minimal/dist/MeuApp-1.0.0-debug.apk +0 -0
- package/package.json +3 -2
- package/src/cli/index.js +22 -1
- package/src/cordova/config-xml.js +98 -6
- package/src/core/build-apk.js +150 -1
- package/src/core/config.js +74 -5
- package/src/core/defaults.js +15 -1
- package/src/desktop/main.js +30 -4
- package/src/desktop/preload.js +1 -0
- package/src/desktop/renderer/index.html +69 -2
- package/src/desktop/renderer/renderer.js +583 -2
- package/src/desktop/renderer/styles.css +131 -4
- package/src/templates/cordova-plugin-html2apk-bridge/plugin.xml +6 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/FloatingIconService.java +141 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/Html2ApkBridge.java +1674 -45
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/NotificationReceiver.java +19 -5
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/NotificationStore.java +13 -1
- package/src/templates/cordova-plugin-html2apk-bridge/www/html2apk-bridge.js +525 -1
- package/src/templates/html2apk-auto-theme.js +144 -0
- package/src/templates/html2apk-onesignal.js +155 -0
package/src/core/build-apk.js
CHANGED
|
@@ -8,11 +8,15 @@ const { validateEntryFile, validateRequiredOptions } = require("./validation");
|
|
|
8
8
|
const { createCordovaProject, addAndroidPlatform, buildAndroid, addCordovaPlugin } = require("../cordova/project");
|
|
9
9
|
const { writeConfigXml } = require("../cordova/config-xml");
|
|
10
10
|
const { findApk } = require("../cordova/apk-finder");
|
|
11
|
-
const { copyWebAssets, ensureDir, removePath, copyFile } = require("../utils/fs-extra");
|
|
11
|
+
const { copyWebAssets, ensureDir, removePath, copyFile, copyDirectory } = require("../utils/fs-extra");
|
|
12
12
|
const { createCommandRunner } = require("../utils/command-runner");
|
|
13
13
|
const { installBridgePlugin } = require("../bridge/install-bridge");
|
|
14
14
|
const { getRuntimeEnvironment } = require("../runtime-manager");
|
|
15
15
|
|
|
16
|
+
const AUTO_THEME_SCRIPT_NAME = "html2apk-auto-theme.js";
|
|
17
|
+
const ONESIGNAL_SCRIPT_NAME = "html2apk-onesignal.js";
|
|
18
|
+
const ONESIGNAL_PLUGIN_PACKAGE = "onesignal-cordova-plugin";
|
|
19
|
+
|
|
16
20
|
function isRemoteAsset(assetPath) {
|
|
17
21
|
return /^https?:\/\//i.test(String(assetPath || ""));
|
|
18
22
|
}
|
|
@@ -34,6 +38,141 @@ function toCordovaPath(value) {
|
|
|
34
38
|
return String(value).replace(/\\/g, "/");
|
|
35
39
|
}
|
|
36
40
|
|
|
41
|
+
function isAutoTheme(options) {
|
|
42
|
+
return String(options.themeMode || options.theme || "").toLowerCase() === "auto";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function oneSignalAppId(options) {
|
|
46
|
+
return String(options.oneSignalAppId || options.onesignalAppId || options.oneSignal?.appId || options.onesignal?.appId || "").trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function hasOneSignal(options) {
|
|
50
|
+
return oneSignalAppId(options).length > 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function scriptTag(scriptPath) {
|
|
54
|
+
return `<script src="${scriptPath}"></script>`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function injectScriptIntoHtml(htmlPath, scriptPath) {
|
|
58
|
+
let html = await fs.readFile(htmlPath, "utf8");
|
|
59
|
+
if (html.includes(scriptPath)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const tag = scriptTag(scriptPath);
|
|
64
|
+
if (/<\/body>/i.test(html)) {
|
|
65
|
+
html = html.replace(/<\/body>/i, ` ${tag}\n</body>`);
|
|
66
|
+
} else {
|
|
67
|
+
html = `${html}\n${tag}\n`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await fs.writeFile(htmlPath, html, "utf8");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function installAutoThemeScript(buildDir, options) {
|
|
74
|
+
if (!isAutoTheme(options)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const wwwDir = path.join(buildDir, "www");
|
|
79
|
+
const source = path.resolve(__dirname, "..", "templates", AUTO_THEME_SCRIPT_NAME);
|
|
80
|
+
const target = path.join(wwwDir, AUTO_THEME_SCRIPT_NAME);
|
|
81
|
+
const entryHtmlPath = path.resolve(wwwDir, options.entryFile || "index.html");
|
|
82
|
+
const scriptPath = toCordovaPath(path.relative(path.dirname(entryHtmlPath), target));
|
|
83
|
+
|
|
84
|
+
if (!isInside(wwwDir, entryHtmlPath)) {
|
|
85
|
+
throw new Error(`Entry file must stay inside the Cordova www folder: ${options.entryFile}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await copyFile(source, target);
|
|
89
|
+
await injectScriptIntoHtml(entryHtmlPath, scriptPath || AUTO_THEME_SCRIPT_NAME);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function jsString(value) {
|
|
94
|
+
return JSON.stringify(String(value || ""));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function installOneSignalScript(buildDir, options) {
|
|
98
|
+
const appId = oneSignalAppId(options);
|
|
99
|
+
if (!appId) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const wwwDir = path.join(buildDir, "www");
|
|
104
|
+
const source = path.resolve(__dirname, "..", "templates", ONESIGNAL_SCRIPT_NAME);
|
|
105
|
+
const target = path.join(wwwDir, ONESIGNAL_SCRIPT_NAME);
|
|
106
|
+
const entryHtmlPath = path.resolve(wwwDir, options.entryFile || "index.html");
|
|
107
|
+
const scriptPath = toCordovaPath(path.relative(path.dirname(entryHtmlPath), target));
|
|
108
|
+
const template = await fs.readFile(source, "utf8");
|
|
109
|
+
|
|
110
|
+
if (!isInside(wwwDir, entryHtmlPath)) {
|
|
111
|
+
throw new Error(`Entry file must stay inside the Cordova www folder: ${options.entryFile}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
await fs.writeFile(
|
|
115
|
+
target,
|
|
116
|
+
template.replace("__HTML2APK_ONESIGNAL_APP_ID__", jsString(appId)),
|
|
117
|
+
"utf8"
|
|
118
|
+
);
|
|
119
|
+
await injectScriptIntoHtml(entryHtmlPath, scriptPath || ONESIGNAL_SCRIPT_NAME);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function removePluginPlatform(pluginXml, platformName) {
|
|
124
|
+
const pattern = new RegExp(`\\n?\\s*<platform name="${platformName}">[\\s\\S]*?\\n\\s*</platform>`, "i");
|
|
125
|
+
return pluginXml.replace(pattern, "");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function prepareBundledPlugin(buildDir, packageName) {
|
|
129
|
+
const localPath = path.resolve(__dirname, "..", "..", "node_modules", packageName);
|
|
130
|
+
try {
|
|
131
|
+
await fs.access(path.join(localPath, "plugin.xml"));
|
|
132
|
+
} catch {
|
|
133
|
+
return packageName;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const destination = path.join(buildDir, `${packageName}-android`);
|
|
137
|
+
await removePath(destination);
|
|
138
|
+
await copyDirectory(localPath, destination, () => false);
|
|
139
|
+
|
|
140
|
+
const packageJsonPath = path.join(destination, "package.json");
|
|
141
|
+
const sourcePackage = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
|
|
142
|
+
const pluginPackage = {
|
|
143
|
+
name: sourcePackage.name,
|
|
144
|
+
version: sourcePackage.version,
|
|
145
|
+
description: sourcePackage.description,
|
|
146
|
+
license: sourcePackage.license,
|
|
147
|
+
main: sourcePackage.main || "dist/index.cjs",
|
|
148
|
+
types: sourcePackage.types,
|
|
149
|
+
cordova: {
|
|
150
|
+
id: sourcePackage.cordova?.id || packageName,
|
|
151
|
+
platforms: ["android"]
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
Object.keys(pluginPackage).forEach((key) => {
|
|
155
|
+
if (pluginPackage[key] === undefined) {
|
|
156
|
+
delete pluginPackage[key];
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
await fs.writeFile(packageJsonPath, `${JSON.stringify(pluginPackage, null, 2)}\n`, "utf8");
|
|
160
|
+
|
|
161
|
+
const pluginXmlPath = path.join(destination, "plugin.xml");
|
|
162
|
+
let pluginXml = await fs.readFile(pluginXmlPath, "utf8");
|
|
163
|
+
pluginXml = removePluginPlatform(pluginXml, "ios");
|
|
164
|
+
await fs.writeFile(pluginXmlPath, pluginXml, "utf8");
|
|
165
|
+
|
|
166
|
+
return destination;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function hasPlugin(plugins, packageName) {
|
|
170
|
+
return (plugins || []).some((plugin) => {
|
|
171
|
+
const text = String(plugin || "").replace(/\\/g, "/").toLowerCase();
|
|
172
|
+
return text === packageName || text.endsWith(`/node_modules/${packageName}`) || text.endsWith(`/${packageName}`);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
37
176
|
async function copyCordovaAsset(projectRoot, buildDir, assetPath, assetName) {
|
|
38
177
|
if (!assetPath || isRemoteAsset(assetPath)) {
|
|
39
178
|
return assetPath;
|
|
@@ -136,10 +275,20 @@ async function buildApk(overrides = {}) {
|
|
|
136
275
|
cordovaOptions.androidSplashScreenAnimatedIcon = toBuildAssetPath(buildDir, cordovaOptions.splash);
|
|
137
276
|
await writeConfigXml(path.join(buildDir, "config.xml"), cordovaOptions);
|
|
138
277
|
await copyWebAssets(webRoot, path.join(buildDir, "www"), options, projectRoot);
|
|
278
|
+
if (await installAutoThemeScript(buildDir, options)) {
|
|
279
|
+
log("Theme mode: auto (system bars follow the visible screen color).");
|
|
280
|
+
}
|
|
281
|
+
if (await installOneSignalScript(buildDir, options)) {
|
|
282
|
+
log("OneSignal: enabled for remote push notifications.");
|
|
283
|
+
}
|
|
139
284
|
|
|
140
285
|
const bridgePluginPath = await installBridgePlugin(buildDir);
|
|
141
286
|
await addCordovaPlugin(buildDir, bridgePluginPath, runner);
|
|
142
287
|
|
|
288
|
+
if (hasOneSignal(options) && !hasPlugin(options.plugins, ONESIGNAL_PLUGIN_PACKAGE)) {
|
|
289
|
+
await addCordovaPlugin(buildDir, await prepareBundledPlugin(buildDir, ONESIGNAL_PLUGIN_PACKAGE), runner);
|
|
290
|
+
}
|
|
291
|
+
|
|
143
292
|
for (const plugin of options.plugins) {
|
|
144
293
|
await addCordovaPlugin(buildDir, plugin, runner);
|
|
145
294
|
}
|
package/src/core/config.js
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("fs/promises");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const {
|
|
5
|
+
const {
|
|
6
|
+
DEFAULT_ANDROID_MIN_SDK_VERSION,
|
|
7
|
+
MAX_ANDROID_MIN_SDK_VERSION,
|
|
8
|
+
createDefaultOptions
|
|
9
|
+
} = require("./defaults");
|
|
6
10
|
|
|
7
11
|
const CONFIG_FILES = ["app.json", "config.json"];
|
|
8
12
|
|
|
@@ -58,24 +62,85 @@ function mergeDeep(base, next) {
|
|
|
58
62
|
return output;
|
|
59
63
|
}
|
|
60
64
|
|
|
65
|
+
function normalizeMinSdkVersion(value) {
|
|
66
|
+
const parsed = Number.parseInt(value, 10);
|
|
67
|
+
return Number.isInteger(parsed)
|
|
68
|
+
&& parsed >= DEFAULT_ANDROID_MIN_SDK_VERSION
|
|
69
|
+
&& parsed <= MAX_ANDROID_MIN_SDK_VERSION
|
|
70
|
+
? parsed
|
|
71
|
+
: DEFAULT_ANDROID_MIN_SDK_VERSION;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function normalizeThemeMode(value) {
|
|
75
|
+
return String(value || "").trim().toLowerCase() === "auto" ? "auto" : "fixed";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeThemeColor(value) {
|
|
79
|
+
const color = String(value || "").trim();
|
|
80
|
+
return /^#[0-9a-fA-F]{6}$/.test(color) ? color : "#126fff";
|
|
81
|
+
}
|
|
82
|
+
|
|
61
83
|
function normalizeOptions(options) {
|
|
62
84
|
const normalized = { ...options };
|
|
63
85
|
|
|
64
|
-
normalized.mode =
|
|
86
|
+
normalized.mode = ["fullscreen", "floating"].includes(normalized.mode) ? normalized.mode : "standalone";
|
|
87
|
+
if (normalized.orientation === "vertical") {
|
|
88
|
+
normalized.orientation = "portrait";
|
|
89
|
+
} else if (normalized.orientation === "horizontal") {
|
|
90
|
+
normalized.orientation = "landscape";
|
|
91
|
+
}
|
|
92
|
+
normalized.orientation = ["portrait", "landscape"].includes(normalized.orientation)
|
|
93
|
+
? normalized.orientation
|
|
94
|
+
: "default";
|
|
65
95
|
normalized.debug = Boolean(normalized.debug);
|
|
66
96
|
normalized.release = Boolean(normalized.release);
|
|
97
|
+
const themeColorText = String(normalized.themeColor || "").trim().toLowerCase();
|
|
98
|
+
const themeModeText = String(normalized.themeMode || "").trim().toLowerCase();
|
|
99
|
+
const themeText = String(normalized.theme || "").trim().toLowerCase();
|
|
100
|
+
normalized.themeMode = themeModeText === "auto" || themeText === "auto" || themeColorText === "auto" ? "auto" : "fixed";
|
|
101
|
+
normalized.theme = normalized.themeMode;
|
|
102
|
+
normalized.themeColor = normalizeThemeColor(themeColorText === "auto" ? normalized.backgroundColor : normalized.themeColor);
|
|
103
|
+
normalized.oneSignalAppId = normalizeOneSignalAppId(normalized.oneSignalAppId || normalized.onesignalAppId || normalized.oneSignal?.appId || normalized.onesignal?.appId);
|
|
104
|
+
normalized.minSdkVersion = normalizeMinSdkVersion(normalized.minSdkVersion || normalized.androidMinSdkVersion);
|
|
67
105
|
normalized.permissions = Array.isArray(normalized.permissions)
|
|
68
|
-
? normalized.permissions.filter(Boolean)
|
|
106
|
+
? normalized.permissions.map((permission) => String(permission).trim()).filter(Boolean)
|
|
69
107
|
: [];
|
|
70
108
|
normalized.plugins = Array.isArray(normalized.plugins)
|
|
71
|
-
? normalized.plugins.filter(Boolean)
|
|
109
|
+
? normalized.plugins.map((plugin) => String(plugin).trim()).filter(Boolean)
|
|
72
110
|
: [];
|
|
111
|
+
normalized.deepLinks = normalizeDeepLinks(normalized.deepLinks);
|
|
73
112
|
normalized.entryFile = normalized.entryFile || "index.html";
|
|
74
113
|
normalized.webRoot = normalized.webRoot || ".";
|
|
75
114
|
|
|
76
115
|
return normalized;
|
|
77
116
|
}
|
|
78
117
|
|
|
118
|
+
function normalizeOneSignalAppId(value) {
|
|
119
|
+
return String(value || "").trim();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeDeepLinks(value) {
|
|
123
|
+
const input = value && typeof value === "object" ? value : {};
|
|
124
|
+
const schemes = Array.isArray(input.schemes)
|
|
125
|
+
? input.schemes.map((scheme) => String(scheme).trim()).filter(Boolean)
|
|
126
|
+
: [];
|
|
127
|
+
const appLinks = Array.isArray(input.appLinks)
|
|
128
|
+
? input.appLinks
|
|
129
|
+
.filter((item) => item && typeof item === "object" && item.host)
|
|
130
|
+
.map((item) => ({
|
|
131
|
+
scheme: item.scheme || "https",
|
|
132
|
+
host: String(item.host).trim(),
|
|
133
|
+
paths: Array.isArray(item.paths) ? item.paths.map((pathItem) => String(pathItem).trim()).filter(Boolean) : [],
|
|
134
|
+
autoVerify: Boolean(item.autoVerify)
|
|
135
|
+
}))
|
|
136
|
+
: [];
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
schemes,
|
|
140
|
+
appLinks
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
79
144
|
async function resolveBuildOptions(overrides = {}) {
|
|
80
145
|
const projectRoot = path.resolve(overrides.projectRoot || process.cwd());
|
|
81
146
|
const { config, configPath } = await loadProjectConfig(projectRoot);
|
|
@@ -95,5 +160,9 @@ module.exports = {
|
|
|
95
160
|
loadProjectConfig,
|
|
96
161
|
resolveBuildOptions,
|
|
97
162
|
mergeDeep,
|
|
98
|
-
normalizeOptions
|
|
163
|
+
normalizeOptions,
|
|
164
|
+
normalizeMinSdkVersion,
|
|
165
|
+
normalizeThemeMode,
|
|
166
|
+
normalizeOneSignalAppId,
|
|
167
|
+
normalizeDeepLinks
|
|
99
168
|
};
|
package/src/core/defaults.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require("path");
|
|
4
4
|
|
|
5
|
+
const DEFAULT_ANDROID_MIN_SDK_VERSION = 24;
|
|
6
|
+
const MAX_ANDROID_MIN_SDK_VERSION = 36;
|
|
7
|
+
|
|
5
8
|
function toPackageSegment(value) {
|
|
6
9
|
return String(value || "app")
|
|
7
10
|
.toLowerCase()
|
|
@@ -17,14 +20,23 @@ function createDefaultOptions(projectRoot) {
|
|
|
17
20
|
packageId: `com.html2apk.${toPackageSegment(appName)}`,
|
|
18
21
|
version: "1.0.0",
|
|
19
22
|
mode: "standalone",
|
|
23
|
+
orientation: "default",
|
|
20
24
|
debug: false,
|
|
21
25
|
icon: null,
|
|
22
26
|
splash: null,
|
|
23
|
-
permissions: ["INTERNET"],
|
|
27
|
+
permissions: ["INTERNET", "POST_NOTIFICATIONS", "VIBRATE"],
|
|
24
28
|
plugins: [],
|
|
25
29
|
release: false,
|
|
26
30
|
keystore: null,
|
|
27
31
|
androidPlatform: "android@15.0.0",
|
|
32
|
+
minSdkVersion: DEFAULT_ANDROID_MIN_SDK_VERSION,
|
|
33
|
+
themeColor: "#126fff",
|
|
34
|
+
themeMode: "fixed",
|
|
35
|
+
oneSignalAppId: "",
|
|
36
|
+
deepLinks: {
|
|
37
|
+
schemes: [],
|
|
38
|
+
appLinks: []
|
|
39
|
+
},
|
|
28
40
|
files: null,
|
|
29
41
|
entryFile: "index.html",
|
|
30
42
|
webRoot: ".",
|
|
@@ -33,5 +45,7 @@ function createDefaultOptions(projectRoot) {
|
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
module.exports = {
|
|
48
|
+
DEFAULT_ANDROID_MIN_SDK_VERSION,
|
|
49
|
+
MAX_ANDROID_MIN_SDK_VERSION,
|
|
36
50
|
createDefaultOptions
|
|
37
51
|
};
|
package/src/desktop/main.js
CHANGED
|
@@ -14,12 +14,20 @@ const { runDoctor, formatDoctorReport } = require("../runtime-manager/doctor");
|
|
|
14
14
|
|
|
15
15
|
let mainWindow = null;
|
|
16
16
|
const smokeTest = process.env.HTML2APK_DESKTOP_SMOKE === "1";
|
|
17
|
+
const APP_ID = "dev.caiomultiversando.html2apk";
|
|
18
|
+
const APP_NAME = "html2apk";
|
|
17
19
|
const WINDOW_BACKGROUNDS = {
|
|
18
20
|
light: "#f7fbff",
|
|
19
21
|
dark: "#10141b"
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
app.commandLine.appendSwitch("disable-crash-reporter");
|
|
25
|
+
process.title = APP_NAME;
|
|
26
|
+
app.setName(APP_NAME);
|
|
27
|
+
|
|
28
|
+
if (process.platform === "win32") {
|
|
29
|
+
app.setAppUserModelId(APP_ID);
|
|
30
|
+
}
|
|
23
31
|
|
|
24
32
|
function rootPath(...parts) {
|
|
25
33
|
return path.resolve(__dirname, "..", "..", ...parts);
|
|
@@ -57,7 +65,7 @@ function createWindow() {
|
|
|
57
65
|
center: true,
|
|
58
66
|
show: false,
|
|
59
67
|
frame: false,
|
|
60
|
-
title:
|
|
68
|
+
title: APP_NAME,
|
|
61
69
|
icon: iconPath(),
|
|
62
70
|
backgroundColor: WINDOW_BACKGROUNDS.light,
|
|
63
71
|
webPreferences: {
|
|
@@ -134,8 +142,8 @@ function cleanBuildOptions(options = {}) {
|
|
|
134
142
|
release: Boolean(options.release)
|
|
135
143
|
};
|
|
136
144
|
|
|
137
|
-
for (const key of ["mode", "appName", "packageId", "version", "icon", "androidPlatform"]) {
|
|
138
|
-
if (options[key]) {
|
|
145
|
+
for (const key of ["mode", "appName", "packageId", "version", "icon", "androidPlatform", "minSdkVersion", "themeColor", "themeMode", "theme", "oneSignalAppId", "orientation", "permissions", "deepLinks"]) {
|
|
146
|
+
if (Array.isArray(options[key]) || options[key]) {
|
|
139
147
|
output[key] = options[key];
|
|
140
148
|
}
|
|
141
149
|
}
|
|
@@ -343,6 +351,10 @@ async function installWithWingetAndroidCli(sender) {
|
|
|
343
351
|
}
|
|
344
352
|
|
|
345
353
|
app.whenReady().then(() => {
|
|
354
|
+
app.setName(APP_NAME);
|
|
355
|
+
if (process.platform === "win32") {
|
|
356
|
+
app.setAppUserModelId(APP_ID);
|
|
357
|
+
}
|
|
346
358
|
createWindow();
|
|
347
359
|
|
|
348
360
|
app.on("activate", () => {
|
|
@@ -359,7 +371,7 @@ app.on("window-all-closed", () => {
|
|
|
359
371
|
});
|
|
360
372
|
|
|
361
373
|
ipcMain.handle("app:info", () => ({
|
|
362
|
-
name:
|
|
374
|
+
name: APP_NAME,
|
|
363
375
|
version: app.getVersion(),
|
|
364
376
|
credit: "Dev Caio Multiversando",
|
|
365
377
|
iconPath: iconPath()
|
|
@@ -520,3 +532,17 @@ ipcMain.handle("shell:show-item", async (_event, targetPath) => {
|
|
|
520
532
|
shell.showItemInFolder(targetPath);
|
|
521
533
|
return true;
|
|
522
534
|
});
|
|
535
|
+
|
|
536
|
+
ipcMain.handle("shell:open-external", async (_event, targetUrl) => {
|
|
537
|
+
if (!targetUrl) {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const url = new URL(String(targetUrl));
|
|
542
|
+
if (!["https:", "http:"].includes(url.protocol)) {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
await shell.openExternal(url.toString());
|
|
547
|
+
return true;
|
|
548
|
+
});
|
package/src/desktop/preload.js
CHANGED
|
@@ -12,6 +12,7 @@ contextBridge.exposeInMainWorld("html2apkDesktop", {
|
|
|
12
12
|
runBuild: (options) => ipcRenderer.invoke("build:run", options),
|
|
13
13
|
openPath: (targetPath) => ipcRenderer.invoke("shell:open-path", targetPath),
|
|
14
14
|
showItem: (targetPath) => ipcRenderer.invoke("shell:show-item", targetPath),
|
|
15
|
+
openExternalUrl: (targetUrl) => ipcRenderer.invoke("shell:open-external", targetUrl),
|
|
15
16
|
minimizeWindow: () => ipcRenderer.invoke("window:minimize"),
|
|
16
17
|
toggleMaximizeWindow: () => ipcRenderer.invoke("window:toggle-maximize"),
|
|
17
18
|
closeWindow: () => ipcRenderer.invoke("window:close"),
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
<button class="nav-item" data-view="settings"><span class="nav-icon">C</span><span data-i18n="navSettings">Configuracoes</span></button>
|
|
59
59
|
<button class="nav-item" data-view="appearance"><span class="nav-icon">A</span><span data-i18n="navAppearance">Aparencia</span></button>
|
|
60
60
|
<button class="nav-item" data-view="build"><span class="nav-icon">B</span><span data-i18n="navBuild">Build</span></button>
|
|
61
|
+
<button class="nav-item" data-view="codes"><span class="nav-icon">J</span><span data-i18n="navCodes">Codigos</span></button>
|
|
61
62
|
<button class="nav-item" data-view="logs"><span class="nav-icon">L</span><span data-i18n="navLogs">Logs</span></button>
|
|
62
63
|
</nav>
|
|
63
64
|
|
|
@@ -140,14 +141,60 @@
|
|
|
140
141
|
<span data-i18n="mode">Modo</span>
|
|
141
142
|
<select id="modeInput">
|
|
142
143
|
<option value="" data-i18n="chooseMode">Escolha o modo</option>
|
|
143
|
-
<option value="fullscreen">
|
|
144
|
-
<option value="standalone">
|
|
144
|
+
<option value="fullscreen" data-i18n="modeFullscreen">Tela cheia</option>
|
|
145
|
+
<option value="standalone" data-i18n="modeStandalone">Normal</option>
|
|
146
|
+
<option value="floating" data-i18n="modeFloating">Flutuante</option>
|
|
147
|
+
</select>
|
|
148
|
+
</label>
|
|
149
|
+
<label class="field">
|
|
150
|
+
<span data-i18n="orientation">Orientacao</span>
|
|
151
|
+
<select id="orientationInput">
|
|
152
|
+
<option value="default" data-i18n="orientationDefault">Automatico</option>
|
|
153
|
+
<option value="portrait" data-i18n="orientationPortrait">Vertical</option>
|
|
154
|
+
<option value="landscape" data-i18n="orientationLandscape">Horizontal</option>
|
|
155
|
+
</select>
|
|
156
|
+
</label>
|
|
157
|
+
<label class="field">
|
|
158
|
+
<span data-i18n="minSdkVersion">Android minimo</span>
|
|
159
|
+
<select id="minSdkVersionInput">
|
|
160
|
+
<option value="24">Android 7.0 (API 24)</option>
|
|
161
|
+
<option value="25">Android 7.1 (API 25)</option>
|
|
162
|
+
<option value="26">Android 8.0 (API 26)</option>
|
|
163
|
+
<option value="27">Android 8.1 (API 27)</option>
|
|
164
|
+
<option value="28">Android 9 (API 28)</option>
|
|
165
|
+
<option value="29">Android 10 (API 29)</option>
|
|
166
|
+
<option value="30">Android 11 (API 30)</option>
|
|
167
|
+
<option value="31">Android 12 (API 31)</option>
|
|
168
|
+
<option value="32">Android 12L (API 32)</option>
|
|
169
|
+
<option value="33">Android 13 (API 33)</option>
|
|
170
|
+
<option value="34">Android 14 (API 34)</option>
|
|
171
|
+
<option value="35">Android 15 (API 35)</option>
|
|
172
|
+
<option value="36">Android 16 (API 36)</option>
|
|
145
173
|
</select>
|
|
146
174
|
</label>
|
|
147
175
|
<label class="field">
|
|
148
176
|
<span>cordova-android</span>
|
|
149
177
|
<input id="androidPlatformInput" type="text" placeholder="android@15.0.0">
|
|
150
178
|
</label>
|
|
179
|
+
<label class="field">
|
|
180
|
+
<span data-i18n="themeMode">Tema do APK</span>
|
|
181
|
+
<select id="themeModeInput">
|
|
182
|
+
<option value="fixed" data-i18n="themeModeFixed">Cor fixa</option>
|
|
183
|
+
<option value="auto" data-i18n="themeModeAuto">Automatico pela tela</option>
|
|
184
|
+
</select>
|
|
185
|
+
</label>
|
|
186
|
+
<label class="field color-field">
|
|
187
|
+
<span data-i18n="appThemeColor">Cor do tema do app</span>
|
|
188
|
+
<div class="color-picker">
|
|
189
|
+
<input id="themeColorInput" type="color" value="#126fff">
|
|
190
|
+
<input id="themeColorTextInput" type="text" value="#126fff" maxlength="7">
|
|
191
|
+
</div>
|
|
192
|
+
</label>
|
|
193
|
+
<label class="field onesignal-field">
|
|
194
|
+
<span data-i18n="oneSignalAppId">OneSignal App ID</span>
|
|
195
|
+
<input id="oneSignalAppIdInput" type="text" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx">
|
|
196
|
+
<small data-i18n="oneSignalAppIdHint">Opcional. Use o App ID do OneSignal, nao a REST API Key.</small>
|
|
197
|
+
</label>
|
|
151
198
|
<div class="field icon-field">
|
|
152
199
|
<span data-i18n="appIcon">Icone do app</span>
|
|
153
200
|
<div class="icon-picker">
|
|
@@ -158,6 +205,10 @@
|
|
|
158
205
|
</div>
|
|
159
206
|
</div>
|
|
160
207
|
</div>
|
|
208
|
+
<div class="field permissions-field">
|
|
209
|
+
<span data-i18n="androidPermissions">Permissoes Android</span>
|
|
210
|
+
<div id="permissionGrid" class="permission-grid"></div>
|
|
211
|
+
</div>
|
|
161
212
|
</div>
|
|
162
213
|
|
|
163
214
|
<div class="toggle-grid">
|
|
@@ -275,6 +326,17 @@
|
|
|
275
326
|
<div id="logConsole" class="log-console"></div>
|
|
276
327
|
</section>
|
|
277
328
|
|
|
329
|
+
<section id="view-codes" class="view">
|
|
330
|
+
<header class="view-header compact">
|
|
331
|
+
<div>
|
|
332
|
+
<p class="eyebrow" data-i18n="codesEyebrow">Bridge nativa</p>
|
|
333
|
+
<h1 data-i18n="codesTitle">Codigos interpretados</h1>
|
|
334
|
+
</div>
|
|
335
|
+
</header>
|
|
336
|
+
<p class="view-intro" data-i18n="codesIntro">Estas funcoes chamadas no JavaScript do app sao interpretadas pelo plugin Cordova e executadas no Java Android.</p>
|
|
337
|
+
<div id="nativeCodeGrid" class="code-grid"></div>
|
|
338
|
+
</section>
|
|
339
|
+
|
|
278
340
|
<section id="view-help" class="view">
|
|
279
341
|
<header class="view-header compact">
|
|
280
342
|
<div>
|
|
@@ -295,6 +357,11 @@
|
|
|
295
357
|
<strong data-i18n="helpDepsTitle">Dependencias no EXE</strong>
|
|
296
358
|
<p data-i18n="helpDeps">O executavel empacota html2apk, a interface e dependencias Node/Cordova. JDK e Android SDK ainda precisam existir na maquina por tamanho e licenca.</p>
|
|
297
359
|
</article>
|
|
360
|
+
<article class="creator-card">
|
|
361
|
+
<strong data-i18n="helpCreatorTitle">Criador</strong>
|
|
362
|
+
<p data-i18n="helpCreatorText">Conheca o Dev Caio Multiversando, criador desta linda aplicacao html2apk.</p>
|
|
363
|
+
<button id="devInstagramButton" class="secondary-action instagram-action" type="button" data-i18n="openInstagram">Conhecer o dev</button>
|
|
364
|
+
</article>
|
|
298
365
|
</div>
|
|
299
366
|
</section>
|
|
300
367
|
</main>
|