html2apk 0.1.0 → 0.2.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 CHANGED
@@ -179,6 +179,9 @@ html2apk build --debug
179
179
  html2apk build --release
180
180
  html2apk build --mode fullscreen
181
181
  html2apk build --mode standalone
182
+ html2apk build --mode floating
183
+ html2apk build --orientation vertical
184
+ html2apk build --min-sdk 24
182
185
  html2apk build --entry-file index.html
183
186
  html2apk build --web-root .
184
187
  html2apk build --app-name MeuApp
@@ -203,6 +206,9 @@ Exemplo completo:
203
206
  "packageId": "com.seuapp.meuapp",
204
207
  "version": "1.0.0",
205
208
  "mode": "fullscreen",
209
+ "orientation": "default",
210
+ "minSdkVersion": 24,
211
+ "themeColor": "#126fff",
206
212
  "icon": "",
207
213
  "splash": "",
208
214
  "permissions": ["INTERNET", "CAMERA", "POST_NOTIFICATIONS", "VIBRATE"],
@@ -229,7 +235,10 @@ Campos principais:
229
235
  | `appName` | Nome visivel do app. |
230
236
  | `packageId` | Identificador Android. Precisa ter formato como `com.empresa.app`. |
231
237
  | `version` | Versao do app. |
232
- | `mode` | `fullscreen` para tela cheia ou `standalone` para modo normal. |
238
+ | `mode` | `fullscreen` para tela cheia, `standalone` para modo normal ou `floating` para icone flutuante. |
239
+ | `orientation` | `default`, `vertical`, `horizontal`, `portrait` ou `landscape`. |
240
+ | `minSdkVersion` | Versao minima do Android em API level. Padrao: `24`. |
241
+ | `themeColor` | Cor base do tema/splash Android, em hexadecimal. |
233
242
  | `entryFile` | Arquivo HTML inicial. Normalmente `index.html`. |
234
243
  | `webRoot` | Pasta onde estao os arquivos web. Normalmente `"."`. |
235
244
  | `permissions` | Permissoes Android adicionadas ao app. |
@@ -4,6 +4,9 @@
4
4
  "packageId": "com.seuapp.meuapp",
5
5
  "version": "1.0.0",
6
6
  "mode": "fullscreen",
7
+ "orientation": "default",
8
+ "minSdkVersion": 24,
9
+ "themeColor": "#126fff",
7
10
  "icon": "",
8
11
  "splash": "",
9
12
  "permissions": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "html2apk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Node CLI and library to turn an HTML/CSS/JS folder into an Android APK through Cordova.",
5
5
  "main": "index.js",
6
6
  "bin": {
package/src/cli/index.js CHANGED
@@ -10,7 +10,7 @@ function printHelp() {
10
10
 
11
11
  Usage:
12
12
  html2apk init
13
- html2apk build [--release] [--debug] [--mode fullscreen|standalone] [--android-platform android@15.0.0]
13
+ html2apk build [--release] [--debug] [--mode fullscreen|standalone|floating] [--orientation vertical|horizontal] [--min-sdk 24] [--android-platform android@15.0.0]
14
14
  html2apk doctor
15
15
 
16
16
  The current working directory is always treated as the user app root.`);
@@ -28,6 +28,15 @@ function parseBuildArgs(args) {
28
28
  } else if (arg === "--mode") {
29
29
  options.mode = args[index + 1];
30
30
  index += 1;
31
+ } else if (arg === "--orientation") {
32
+ options.orientation = args[index + 1];
33
+ index += 1;
34
+ } else if (arg === "--theme-color") {
35
+ options.themeColor = args[index + 1];
36
+ index += 1;
37
+ } else if (arg === "--min-sdk" || arg === "--min-sdk-version" || arg === "--minSdkVersion") {
38
+ options.minSdkVersion = args[index + 1];
39
+ index += 1;
31
40
  } else if (arg === "--entry-file") {
32
41
  options.entryFile = args[index + 1];
33
42
  index += 1;
@@ -64,6 +73,9 @@ function createPlaceholderConfig(projectName = "MeuApp") {
64
73
  packageId: `com.seuapp.${packageSegment(appName)}`,
65
74
  version: "1.0.0",
66
75
  mode: "fullscreen",
76
+ orientation: "default",
77
+ minSdkVersion: 24,
78
+ themeColor: "#126fff",
67
79
  icon: "",
68
80
  splash: "",
69
81
  permissions: [
@@ -2,6 +2,10 @@
2
2
 
3
3
  const fs = require("fs/promises");
4
4
  const path = require("path");
5
+ const {
6
+ DEFAULT_ANDROID_MIN_SDK_VERSION,
7
+ MAX_ANDROID_MIN_SDK_VERSION
8
+ } = require("../core/defaults");
5
9
 
6
10
  function xmlEscape(value) {
7
11
  return String(value)
@@ -17,12 +21,16 @@ function androidPermissionName(permission) {
17
21
  }
18
22
 
19
23
  function renderPermissions(permissions) {
20
- if (!permissions.length) {
24
+ const uniquePermissions = Array.from(new Set((permissions || [])
25
+ .map((permission) => String(permission).trim())
26
+ .filter(Boolean)
27
+ .map(androidPermissionName)));
28
+ if (!uniquePermissions.length) {
21
29
  return "";
22
30
  }
23
31
 
24
- const items = permissions
25
- .map((permission) => ` <uses-permission android:name="${xmlEscape(androidPermissionName(permission))}" />`)
32
+ const items = uniquePermissions
33
+ .map((permission) => ` <uses-permission android:name="${xmlEscape(permission)}" />`)
26
34
  .join("\n");
27
35
 
28
36
  return ` <config-file target="AndroidManifest.xml" parent="/manifest">
@@ -45,25 +53,48 @@ function renderPreference(name, value) {
45
53
  return ` <preference name="${xmlEscape(name)}" value="${xmlEscape(value)}" />`;
46
54
  }
47
55
 
56
+ function normalizeMinSdkVersion(value) {
57
+ const parsed = Number.parseInt(value, 10);
58
+ return Number.isInteger(parsed)
59
+ && parsed >= DEFAULT_ANDROID_MIN_SDK_VERSION
60
+ && parsed <= MAX_ANDROID_MIN_SDK_VERSION
61
+ ? parsed
62
+ : DEFAULT_ANDROID_MIN_SDK_VERSION;
63
+ }
64
+
48
65
  function renderAndroidSplashPreferences(options) {
49
66
  const splashIcon = options.androidSplashScreenAnimatedIcon || options.splash || options.icon;
50
67
  if (!splashIcon) {
51
68
  return "";
52
69
  }
53
70
 
71
+ const themeColor = options.themeColor || options.splashBackgroundColor || options.backgroundColor || "#FFFFFF";
72
+
54
73
  return [
55
74
  renderPreference("AndroidWindowSplashScreenAnimatedIcon", splashIcon),
56
- renderPreference("AndroidWindowSplashScreenBackground", options.splashBackgroundColor || options.backgroundColor || "#FFFFFF"),
75
+ renderPreference("AndroidWindowSplashScreenBackground", themeColor),
57
76
  renderPreference("AndroidWindowSplashScreenAnimationDuration", options.splashAnimationDuration || "200")
58
77
  ].filter(Boolean).join("\n");
59
78
  }
60
79
 
61
80
  function renderConfigXml(options) {
62
81
  const fullscreen = options.mode === "fullscreen" ? "true" : "false";
63
- const permissions = renderPermissions(options.permissions || []);
82
+ const permissionsList = options.mode === "floating"
83
+ ? [...(options.permissions || []), "SYSTEM_ALERT_WINDOW"]
84
+ : (options.permissions || []);
85
+ const permissions = renderPermissions(permissionsList);
64
86
  const icon = renderIcon(options.icon);
65
87
  const splashPreferences = renderAndroidSplashPreferences(options);
66
88
  const platformItems = [permissions, icon, splashPreferences].filter(Boolean).join("\n");
89
+ const backgroundPreference = renderPreference("BackgroundColor", options.themeColor || options.backgroundColor);
90
+ const modePreference = renderPreference("Html2ApkMode", options.mode || "standalone");
91
+ const minSdkPreference = renderPreference(
92
+ "android-minSdkVersion",
93
+ normalizeMinSdkVersion(options.minSdkVersion || options.androidMinSdkVersion)
94
+ );
95
+ const orientationPreference = ["portrait", "landscape"].includes(options.orientation)
96
+ ? renderPreference("Orientation", options.orientation)
97
+ : "";
67
98
 
68
99
  return `<?xml version="1.0" encoding="UTF-8"?>
69
100
  <widget id="${xmlEscape(options.packageId)}"
@@ -91,6 +122,9 @@ function renderConfigXml(options) {
91
122
  <preference name="AndroidLaunchMode" value="singleTop" />
92
123
  <preference name="DisallowOverscroll" value="true" />
93
124
  <preference name="GradlePluginKotlinEnabled" value="true" />
125
+ ${minSdkPreference}
126
+ ${modePreference}
127
+ ${orientationPreference ? `${orientationPreference}\n` : ""}${backgroundPreference ? `${backgroundPreference}\n` : ""}
94
128
 
95
129
  <platform name="android">
96
130
  ${platformItems || " <!-- Extra Android options are generated here. -->"}
@@ -2,7 +2,11 @@
2
2
 
3
3
  const fs = require("fs/promises");
4
4
  const path = require("path");
5
- const { createDefaultOptions } = require("./defaults");
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,17 +62,35 @@ 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
+
61
74
  function normalizeOptions(options) {
62
75
  const normalized = { ...options };
63
76
 
64
- normalized.mode = normalized.mode === "fullscreen" ? "fullscreen" : "standalone";
77
+ normalized.mode = ["fullscreen", "floating"].includes(normalized.mode) ? normalized.mode : "standalone";
78
+ if (normalized.orientation === "vertical") {
79
+ normalized.orientation = "portrait";
80
+ } else if (normalized.orientation === "horizontal") {
81
+ normalized.orientation = "landscape";
82
+ }
83
+ normalized.orientation = ["portrait", "landscape"].includes(normalized.orientation)
84
+ ? normalized.orientation
85
+ : "default";
65
86
  normalized.debug = Boolean(normalized.debug);
66
87
  normalized.release = Boolean(normalized.release);
88
+ normalized.minSdkVersion = normalizeMinSdkVersion(normalized.minSdkVersion || normalized.androidMinSdkVersion);
67
89
  normalized.permissions = Array.isArray(normalized.permissions)
68
- ? normalized.permissions.filter(Boolean)
90
+ ? normalized.permissions.map((permission) => String(permission).trim()).filter(Boolean)
69
91
  : [];
70
92
  normalized.plugins = Array.isArray(normalized.plugins)
71
- ? normalized.plugins.filter(Boolean)
93
+ ? normalized.plugins.map((plugin) => String(plugin).trim()).filter(Boolean)
72
94
  : [];
73
95
  normalized.entryFile = normalized.entryFile || "index.html";
74
96
  normalized.webRoot = normalized.webRoot || ".";
@@ -95,5 +117,6 @@ module.exports = {
95
117
  loadProjectConfig,
96
118
  resolveBuildOptions,
97
119
  mergeDeep,
98
- normalizeOptions
120
+ normalizeOptions,
121
+ normalizeMinSdkVersion
99
122
  };
@@ -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,17 @@ 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",
28
34
  files: null,
29
35
  entryFile: "index.html",
30
36
  webRoot: ".",
@@ -33,5 +39,7 @@ function createDefaultOptions(projectRoot) {
33
39
  }
34
40
 
35
41
  module.exports = {
42
+ DEFAULT_ANDROID_MIN_SDK_VERSION,
43
+ MAX_ANDROID_MIN_SDK_VERSION,
36
44
  createDefaultOptions
37
45
  };
@@ -14,12 +14,19 @@ 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
+ app.setName(APP_NAME);
26
+
27
+ if (process.platform === "win32") {
28
+ app.setAppUserModelId(APP_ID);
29
+ }
23
30
 
24
31
  function rootPath(...parts) {
25
32
  return path.resolve(__dirname, "..", "..", ...parts);
@@ -57,7 +64,7 @@ function createWindow() {
57
64
  center: true,
58
65
  show: false,
59
66
  frame: false,
60
- title: "html2apk",
67
+ title: APP_NAME,
61
68
  icon: iconPath(),
62
69
  backgroundColor: WINDOW_BACKGROUNDS.light,
63
70
  webPreferences: {
@@ -134,8 +141,8 @@ function cleanBuildOptions(options = {}) {
134
141
  release: Boolean(options.release)
135
142
  };
136
143
 
137
- for (const key of ["mode", "appName", "packageId", "version", "icon", "androidPlatform"]) {
138
- if (options[key]) {
144
+ for (const key of ["mode", "appName", "packageId", "version", "icon", "androidPlatform", "minSdkVersion", "themeColor", "orientation", "permissions"]) {
145
+ if (Array.isArray(options[key]) || options[key]) {
139
146
  output[key] = options[key];
140
147
  }
141
148
  }
@@ -359,7 +366,7 @@ app.on("window-all-closed", () => {
359
366
  });
360
367
 
361
368
  ipcMain.handle("app:info", () => ({
362
- name: "html2apk",
369
+ name: APP_NAME,
363
370
  version: app.getVersion(),
364
371
  credit: "Dev Caio Multiversando",
365
372
  iconPath: iconPath()
@@ -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,48 @@
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">fullscreen</option>
144
- <option value="standalone">standalone</option>
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 color-field">
180
+ <span data-i18n="appThemeColor">Cor do tema do app</span>
181
+ <div class="color-picker">
182
+ <input id="themeColorInput" type="color" value="#126fff">
183
+ <input id="themeColorTextInput" type="text" value="#126fff" maxlength="7">
184
+ </div>
185
+ </label>
151
186
  <div class="field icon-field">
152
187
  <span data-i18n="appIcon">Icone do app</span>
153
188
  <div class="icon-picker">
@@ -158,6 +193,10 @@
158
193
  </div>
159
194
  </div>
160
195
  </div>
196
+ <div class="field permissions-field">
197
+ <span data-i18n="androidPermissions">Permissoes Android</span>
198
+ <div id="permissionGrid" class="permission-grid"></div>
199
+ </div>
161
200
  </div>
162
201
 
163
202
  <div class="toggle-grid">
@@ -275,6 +314,17 @@
275
314
  <div id="logConsole" class="log-console"></div>
276
315
  </section>
277
316
 
317
+ <section id="view-codes" class="view">
318
+ <header class="view-header compact">
319
+ <div>
320
+ <p class="eyebrow" data-i18n="codesEyebrow">Bridge nativa</p>
321
+ <h1 data-i18n="codesTitle">Codigos interpretados</h1>
322
+ </div>
323
+ </header>
324
+ <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>
325
+ <div id="nativeCodeGrid" class="code-grid"></div>
326
+ </section>
327
+
278
328
  <section id="view-help" class="view">
279
329
  <header class="view-header compact">
280
330
  <div>