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 +10 -1
- package/examples/minimal/app.json +3 -0
- package/examples/minimal/dist/MeuApp-1.0.0-debug.apk +0 -0
- package/package.json +1 -1
- package/src/cli/index.js +13 -1
- package/src/cordova/config-xml.js +39 -5
- package/src/core/config.js +28 -5
- package/src/core/defaults.js +9 -1
- package/src/desktop/main.js +11 -4
- package/src/desktop/renderer/index.html +52 -2
- package/src/desktop/renderer/renderer.js +243 -2
- package/src/desktop/renderer/styles.css +93 -4
- package/src/templates/cordova-plugin-html2apk-bridge/plugin.xml +2 -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 +178 -1
- package/src/templates/cordova-plugin-html2apk-bridge/www/html2apk-bridge.js +40 -0
|
@@ -10,6 +10,7 @@ const i18n = {
|
|
|
10
10
|
navSettings: "Configuracoes",
|
|
11
11
|
navAppearance: "Aparencia",
|
|
12
12
|
navBuild: "Build",
|
|
13
|
+
navCodes: "Codigos",
|
|
13
14
|
navLogs: "Logs",
|
|
14
15
|
navHelp: "Ajuda",
|
|
15
16
|
theme: "Tema",
|
|
@@ -33,7 +34,17 @@ const i18n = {
|
|
|
33
34
|
appVersion: "Versao do app",
|
|
34
35
|
mode: "Modo",
|
|
35
36
|
chooseMode: "Escolha o modo",
|
|
37
|
+
modeFullscreen: "Tela cheia",
|
|
38
|
+
modeStandalone: "Normal",
|
|
39
|
+
modeFloating: "Flutuante",
|
|
40
|
+
orientation: "Orientacao",
|
|
41
|
+
orientationDefault: "Automatico",
|
|
42
|
+
orientationPortrait: "Vertical",
|
|
43
|
+
orientationLandscape: "Horizontal",
|
|
44
|
+
minSdkVersion: "Android minimo",
|
|
36
45
|
appIcon: "Icone do app",
|
|
46
|
+
appThemeColor: "Cor do tema do app",
|
|
47
|
+
androidPermissions: "Permissoes Android",
|
|
37
48
|
chooseIcon: "Escolher icone PNG",
|
|
38
49
|
reviewBuild: "Revisar build",
|
|
39
50
|
debugBuild: "Debug tecnico",
|
|
@@ -58,6 +69,9 @@ const i18n = {
|
|
|
58
69
|
showApk: "Mostrar APK",
|
|
59
70
|
logsEyebrow: "Ao vivo",
|
|
60
71
|
logsTitle: "Logs do processo",
|
|
72
|
+
codesEyebrow: "Bridge nativa",
|
|
73
|
+
codesTitle: "Codigos interpretados",
|
|
74
|
+
codesIntro: "Estas funcoes chamadas no JavaScript do app sao interpretadas pelo plugin Cordova e executadas no Java Android.",
|
|
61
75
|
clearLogs: "Limpar logs",
|
|
62
76
|
helpEyebrow: "Sem misterio",
|
|
63
77
|
helpTitle: "Doctor, build e dependencias",
|
|
@@ -92,6 +106,8 @@ const i18n = {
|
|
|
92
106
|
missingMode: "Escolha o modo do app.",
|
|
93
107
|
missingIcon: "Escolha o icone do app.",
|
|
94
108
|
invalidIconType: "Use um icone PNG para evitar falhas no Android.",
|
|
109
|
+
invalidThemeColor: "Use uma cor hexadecimal valida, exemplo: #126fff.",
|
|
110
|
+
invalidMinSdkVersion: "Escolha uma versao minima do Android entre API 24 e API 36.",
|
|
95
111
|
iconSelected: "Icone selecionado",
|
|
96
112
|
progressLabel: "Progresso",
|
|
97
113
|
progressIdle: "Aguardando pasta",
|
|
@@ -123,6 +139,7 @@ const i18n = {
|
|
|
123
139
|
navSettings: "Settings",
|
|
124
140
|
navAppearance: "Appearance",
|
|
125
141
|
navBuild: "Build",
|
|
142
|
+
navCodes: "Code",
|
|
126
143
|
navLogs: "Logs",
|
|
127
144
|
navHelp: "Help",
|
|
128
145
|
theme: "Theme",
|
|
@@ -146,7 +163,17 @@ const i18n = {
|
|
|
146
163
|
appVersion: "App version",
|
|
147
164
|
mode: "Mode",
|
|
148
165
|
chooseMode: "Choose mode",
|
|
166
|
+
modeFullscreen: "Fullscreen",
|
|
167
|
+
modeStandalone: "Normal",
|
|
168
|
+
modeFloating: "Floating",
|
|
169
|
+
orientation: "Orientation",
|
|
170
|
+
orientationDefault: "Auto",
|
|
171
|
+
orientationPortrait: "Portrait",
|
|
172
|
+
orientationLandscape: "Landscape",
|
|
173
|
+
minSdkVersion: "Minimum Android",
|
|
149
174
|
appIcon: "App icon",
|
|
175
|
+
appThemeColor: "App theme color",
|
|
176
|
+
androidPermissions: "Android permissions",
|
|
150
177
|
chooseIcon: "Choose PNG icon",
|
|
151
178
|
reviewBuild: "Review build",
|
|
152
179
|
debugBuild: "Technical debug",
|
|
@@ -171,6 +198,9 @@ const i18n = {
|
|
|
171
198
|
showApk: "Show APK",
|
|
172
199
|
logsEyebrow: "Live",
|
|
173
200
|
logsTitle: "Process logs",
|
|
201
|
+
codesEyebrow: "Native bridge",
|
|
202
|
+
codesTitle: "Interpreted code",
|
|
203
|
+
codesIntro: "These functions called in your app JavaScript are interpreted by the Cordova plugin and executed in Android Java.",
|
|
174
204
|
clearLogs: "Clear logs",
|
|
175
205
|
helpEyebrow: "Plain and simple",
|
|
176
206
|
helpTitle: "Doctor, build and dependencies",
|
|
@@ -205,6 +235,8 @@ const i18n = {
|
|
|
205
235
|
missingMode: "Choose the app mode.",
|
|
206
236
|
missingIcon: "Choose the app icon.",
|
|
207
237
|
invalidIconType: "Use a PNG icon to avoid Android build failures.",
|
|
238
|
+
invalidThemeColor: "Use a valid hex color, example: #126fff.",
|
|
239
|
+
invalidMinSdkVersion: "Choose a minimum Android version between API 24 and API 36.",
|
|
208
240
|
iconSelected: "Icon selected",
|
|
209
241
|
progressLabel: "Progress",
|
|
210
242
|
progressIdle: "Waiting for folder",
|
|
@@ -241,6 +273,69 @@ const animatedBuildLines = [
|
|
|
241
273
|
"Procurando APK final / Finding final APK"
|
|
242
274
|
];
|
|
243
275
|
|
|
276
|
+
const DEFAULT_PERMISSIONS = ["INTERNET", "POST_NOTIFICATIONS", "VIBRATE"];
|
|
277
|
+
const DEFAULT_MIN_SDK_VERSION = 24;
|
|
278
|
+
const MIN_SDK_OPTIONS = [24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36];
|
|
279
|
+
|
|
280
|
+
const permissionOptions = [
|
|
281
|
+
{
|
|
282
|
+
value: "INTERNET",
|
|
283
|
+
label: { pt: "Internet", en: "Internet" },
|
|
284
|
+
detail: { pt: "Acesso a rede", en: "Network access" }
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
value: "POST_NOTIFICATIONS",
|
|
288
|
+
label: { pt: "Notificacoes", en: "Notifications" },
|
|
289
|
+
detail: { pt: "Android 13+", en: "Android 13+" }
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
value: "VIBRATE",
|
|
293
|
+
label: { pt: "Vibracao", en: "Vibration" },
|
|
294
|
+
detail: { pt: "Permite vibrar", en: "Allows vibration" }
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
value: "CAMERA",
|
|
298
|
+
label: { pt: "Camera", en: "Camera" },
|
|
299
|
+
detail: { pt: "Captura de imagem", en: "Image capture" }
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
value: "RECORD_AUDIO",
|
|
303
|
+
label: { pt: "Microfone", en: "Microphone" },
|
|
304
|
+
detail: { pt: "Captura de audio", en: "Audio capture" }
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
value: "ACCESS_FINE_LOCATION",
|
|
308
|
+
label: { pt: "Localizacao precisa", en: "Precise location" },
|
|
309
|
+
detail: { pt: "GPS e rede", en: "GPS and network" }
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
value: "ACCESS_COARSE_LOCATION",
|
|
313
|
+
label: { pt: "Localizacao aproximada", en: "Approximate location" },
|
|
314
|
+
detail: { pt: "Rede", en: "Network" }
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
value: "SYSTEM_ALERT_WINDOW",
|
|
318
|
+
label: { pt: "Sobrepor apps", en: "Draw over apps" },
|
|
319
|
+
detail: { pt: "Necessaria no modo flutuante", en: "Required for floating mode" }
|
|
320
|
+
}
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
const nativeCodeEntries = [
|
|
324
|
+
{ js: "toast('Mensagem')", java: "toast", description: { pt: "Mostra uma mensagem rapida nativa.", en: "Shows a native short message." } },
|
|
325
|
+
{ js: "vibrar(250)", java: "vibrate", description: { pt: "Aciona a vibracao do aparelho.", en: "Triggers device vibration." } },
|
|
326
|
+
{ js: "notificar({ titulo, texto })", java: "notify", description: { pt: "Cria uma notificacao Android imediata.", en: "Creates an immediate Android notification." } },
|
|
327
|
+
{ js: "agendarNotificacao({ titulo, texto, quando })", java: "scheduleNotification", description: { pt: "Agenda notificacao mesmo com o app fechado.", en: "Schedules a notification even when the app is closed." } },
|
|
328
|
+
{ js: "fullscreen(true)", java: "fullscreen", description: { pt: "Liga ou desliga o modo imersivo.", en: "Toggles immersive mode." } },
|
|
329
|
+
{ js: "solicitarPermissaoSobreposicao()", java: "requestOverlayPermission", description: { pt: "Abre a permissao para sobrepor outros apps.", en: "Opens draw-over-apps permission." } },
|
|
330
|
+
{ js: "iniciarIconeFlutuante()", java: "startFloatingIcon", description: { pt: "Mostra o icone flutuante nativo.", en: "Shows the native floating icon." } },
|
|
331
|
+
{ js: "pararIconeFlutuante()", java: "stopFloatingIcon", description: { pt: "Remove o icone flutuante.", en: "Removes the floating icon." } },
|
|
332
|
+
{ js: "manterTelaAcordada(true)", java: "keepScreenAwake", description: { pt: "Impede a tela de apagar enquanto o app esta aberto.", en: "Keeps the screen awake while the app is open." } },
|
|
333
|
+
{ js: "brilhoTela(0.8)", java: "setScreenBrightness", description: { pt: "Ajusta o brilho da janela do app.", en: "Adjusts this app window brightness." } },
|
|
334
|
+
{ js: "copiarTexto('texto')", java: "copyText", description: { pt: "Copia texto para a area de transferencia.", en: "Copies text to the clipboard." } },
|
|
335
|
+
{ js: "compartilharTexto('texto')", java: "shareText", description: { pt: "Abre o compartilhamento nativo do Android.", en: "Opens the native Android share sheet." } },
|
|
336
|
+
{ js: "abrirUrl('https://...')", java: "openUrl", description: { pt: "Abre um link em outro app Android.", en: "Opens a link in another Android app." } }
|
|
337
|
+
];
|
|
338
|
+
|
|
244
339
|
const state = {
|
|
245
340
|
language: localStorage.getItem("html2apk.language") || null,
|
|
246
341
|
theme: localStorage.getItem("html2apk.theme") || "light",
|
|
@@ -292,7 +387,12 @@ function collectElements() {
|
|
|
292
387
|
"packageIdInput",
|
|
293
388
|
"versionInput",
|
|
294
389
|
"modeInput",
|
|
390
|
+
"orientationInput",
|
|
391
|
+
"minSdkVersionInput",
|
|
295
392
|
"androidPlatformInput",
|
|
393
|
+
"themeColorInput",
|
|
394
|
+
"themeColorTextInput",
|
|
395
|
+
"permissionGrid",
|
|
296
396
|
"iconPathInput",
|
|
297
397
|
"iconPreview",
|
|
298
398
|
"selectIconButton",
|
|
@@ -308,6 +408,7 @@ function collectElements() {
|
|
|
308
408
|
"progressBar",
|
|
309
409
|
"progressPercent",
|
|
310
410
|
"reviewGrid",
|
|
411
|
+
"nativeCodeGrid",
|
|
311
412
|
"resultPanel",
|
|
312
413
|
"apkPath",
|
|
313
414
|
"openDistButton",
|
|
@@ -349,6 +450,8 @@ function applyLanguage() {
|
|
|
349
450
|
});
|
|
350
451
|
applyTheme();
|
|
351
452
|
applyLogBarVisibility();
|
|
453
|
+
renderPermissionOptions(selectedPermissions());
|
|
454
|
+
renderNativeCodeGrid();
|
|
352
455
|
if (state.project) {
|
|
353
456
|
validateSettings();
|
|
354
457
|
renderReview();
|
|
@@ -492,6 +595,16 @@ function packageSegment(value) {
|
|
|
492
595
|
.replace(/^[^a-z]+/, "") || "app";
|
|
493
596
|
}
|
|
494
597
|
|
|
598
|
+
function normalizeHexColor(value, fallback = "#126fff") {
|
|
599
|
+
const textValue = String(value || "").trim();
|
|
600
|
+
const normalized = textValue.startsWith("#") ? textValue : `#${textValue}`;
|
|
601
|
+
return /^#[0-9a-fA-F]{6}$/.test(normalized) ? normalized.toLowerCase() : fallback;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function currentLanguage() {
|
|
605
|
+
return state.language || "pt";
|
|
606
|
+
}
|
|
607
|
+
|
|
495
608
|
function toFileUrl(filePath) {
|
|
496
609
|
if (!filePath) {
|
|
497
610
|
return "../../../html2apk.png";
|
|
@@ -521,13 +634,106 @@ function escapeHtml(value) {
|
|
|
521
634
|
.replace(/"/g, """);
|
|
522
635
|
}
|
|
523
636
|
|
|
637
|
+
function selectedOptionText(select) {
|
|
638
|
+
if (!select || !select.selectedOptions || !select.selectedOptions.length) {
|
|
639
|
+
return select ? select.value : "";
|
|
640
|
+
}
|
|
641
|
+
return select.selectedOptions[0].textContent || select.value;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function selectedPermissions() {
|
|
645
|
+
if (!elements.permissionGrid) {
|
|
646
|
+
return DEFAULT_PERMISSIONS.slice();
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const inputs = Array.from(elements.permissionGrid.querySelectorAll("input[data-permission-option]"));
|
|
650
|
+
if (!inputs.length) {
|
|
651
|
+
return DEFAULT_PERMISSIONS.slice();
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const selected = inputs
|
|
655
|
+
.filter((input) => input.checked)
|
|
656
|
+
.map((input) => input.value);
|
|
657
|
+
|
|
658
|
+
if (elements.modeInput && elements.modeInput.value === "floating" && !selected.includes("SYSTEM_ALERT_WINDOW")) {
|
|
659
|
+
selected.push("SYSTEM_ALERT_WINDOW");
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
return Array.from(new Set(selected));
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function renderPermissionOptions(selected = DEFAULT_PERMISSIONS) {
|
|
666
|
+
if (!elements.permissionGrid) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const selectedSet = new Set(selected);
|
|
671
|
+
if (elements.modeInput && elements.modeInput.value === "floating") {
|
|
672
|
+
selectedSet.add("SYSTEM_ALERT_WINDOW");
|
|
673
|
+
}
|
|
674
|
+
const language = currentLanguage();
|
|
675
|
+
|
|
676
|
+
elements.permissionGrid.innerHTML = permissionOptions.map((permission) => {
|
|
677
|
+
const checked = selectedSet.has(permission.value) ? " checked" : "";
|
|
678
|
+
const disabled = elements.modeInput && elements.modeInput.value === "floating" && permission.value === "SYSTEM_ALERT_WINDOW"
|
|
679
|
+
? " disabled"
|
|
680
|
+
: "";
|
|
681
|
+
|
|
682
|
+
return `
|
|
683
|
+
<label class="permission-option">
|
|
684
|
+
<input type="checkbox" data-permission-option value="${escapeHtml(permission.value)}"${checked}${disabled}>
|
|
685
|
+
<span>
|
|
686
|
+
<strong>${escapeHtml(permission.label[language] || permission.label.pt)}</strong>
|
|
687
|
+
<small>${escapeHtml(permission.detail[language] || permission.detail.pt)}</small>
|
|
688
|
+
</span>
|
|
689
|
+
</label>
|
|
690
|
+
`;
|
|
691
|
+
}).join("");
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function normalizeOrientationInputValue(value) {
|
|
695
|
+
if (value === "vertical") {
|
|
696
|
+
return "portrait";
|
|
697
|
+
}
|
|
698
|
+
if (value === "horizontal") {
|
|
699
|
+
return "landscape";
|
|
700
|
+
}
|
|
701
|
+
return ["portrait", "landscape"].includes(value) ? value : "default";
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function normalizeMinSdkVersion(value) {
|
|
705
|
+
const parsed = Number.parseInt(value, 10);
|
|
706
|
+
return MIN_SDK_OPTIONS.includes(parsed) ? parsed : DEFAULT_MIN_SDK_VERSION;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function renderNativeCodeGrid() {
|
|
710
|
+
if (!elements.nativeCodeGrid) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const language = currentLanguage();
|
|
715
|
+
elements.nativeCodeGrid.innerHTML = nativeCodeEntries.map((entry) => `
|
|
716
|
+
<article class="code-card">
|
|
717
|
+
<code>${escapeHtml(entry.js)}</code>
|
|
718
|
+
<span>Java: ${escapeHtml(entry.java)}</span>
|
|
719
|
+
<p>${escapeHtml(entry.description[language] || entry.description.pt)}</p>
|
|
720
|
+
</article>
|
|
721
|
+
`).join("");
|
|
722
|
+
}
|
|
723
|
+
|
|
524
724
|
function populateSettings(config = {}, project = state.project) {
|
|
525
725
|
const projectName = project ? project.name : "MeuApp";
|
|
526
726
|
elements.appNameInput.value = config.appName || projectName || "";
|
|
527
727
|
elements.packageIdInput.value = config.packageId || `com.html2apk.${packageSegment(projectName)}`;
|
|
528
728
|
elements.versionInput.value = config.version || "1.0.0";
|
|
529
729
|
elements.modeInput.value = config.mode || "fullscreen";
|
|
730
|
+
elements.orientationInput.value = normalizeOrientationInputValue(config.orientation);
|
|
731
|
+
elements.minSdkVersionInput.value = String(normalizeMinSdkVersion(config.minSdkVersion || config.androidMinSdkVersion));
|
|
530
732
|
elements.androidPlatformInput.value = config.androidPlatform || "android@15.0.0";
|
|
733
|
+
const themeColor = normalizeHexColor(config.themeColor || config.splashBackgroundColor || config.backgroundColor);
|
|
734
|
+
elements.themeColorInput.value = themeColor;
|
|
735
|
+
elements.themeColorTextInput.value = themeColor;
|
|
736
|
+
renderPermissionOptions(Array.isArray(config.permissions) && config.permissions.length ? config.permissions : DEFAULT_PERMISSIONS);
|
|
531
737
|
elements.iconPathInput.value = config.icon || "";
|
|
532
738
|
elements.iconPreview.src = iconPreviewPath(config.icon || "");
|
|
533
739
|
elements.debugInput.checked = Boolean(config.debug);
|
|
@@ -554,6 +760,12 @@ function validateSettings() {
|
|
|
554
760
|
if (!elements.modeInput.value) {
|
|
555
761
|
errors.push(text("missingMode"));
|
|
556
762
|
}
|
|
763
|
+
if (!MIN_SDK_OPTIONS.includes(Number.parseInt(elements.minSdkVersionInput.value, 10))) {
|
|
764
|
+
errors.push(text("invalidMinSdkVersion"));
|
|
765
|
+
}
|
|
766
|
+
if (!/^#[0-9a-fA-F]{6}$/.test(elements.themeColorTextInput.value.trim())) {
|
|
767
|
+
errors.push(text("invalidThemeColor"));
|
|
768
|
+
}
|
|
557
769
|
if (!elements.iconPathInput.value.trim()) {
|
|
558
770
|
errors.push(text("missingIcon"));
|
|
559
771
|
} else if (!/\.png$/i.test(elements.iconPathInput.value.trim())) {
|
|
@@ -596,7 +808,11 @@ function renderReview() {
|
|
|
596
808
|
[text("appName"), elements.appNameInput.value.trim()],
|
|
597
809
|
[text("packageId"), elements.packageIdInput.value.trim()],
|
|
598
810
|
[text("appVersion"), elements.versionInput.value.trim()],
|
|
599
|
-
[text("mode"), elements.modeInput
|
|
811
|
+
[text("mode"), selectedOptionText(elements.modeInput)],
|
|
812
|
+
[text("orientation"), selectedOptionText(elements.orientationInput)],
|
|
813
|
+
[text("minSdkVersion"), selectedOptionText(elements.minSdkVersionInput)],
|
|
814
|
+
[text("appThemeColor"), elements.themeColorTextInput.value.trim()],
|
|
815
|
+
[text("androidPermissions"), selectedPermissions().join(", ")],
|
|
600
816
|
[text("appIcon"), elements.iconPathInput.value.trim()]
|
|
601
817
|
];
|
|
602
818
|
|
|
@@ -830,6 +1046,10 @@ function buildOptions() {
|
|
|
830
1046
|
packageId: elements.packageIdInput.value.trim(),
|
|
831
1047
|
version: elements.versionInput.value.trim(),
|
|
832
1048
|
mode: elements.modeInput.value,
|
|
1049
|
+
orientation: elements.orientationInput.value,
|
|
1050
|
+
minSdkVersion: normalizeMinSdkVersion(elements.minSdkVersionInput.value),
|
|
1051
|
+
themeColor: normalizeHexColor(elements.themeColorTextInput.value),
|
|
1052
|
+
permissions: selectedPermissions(),
|
|
833
1053
|
icon: elements.iconPathInput.value.trim(),
|
|
834
1054
|
androidPlatform: elements.androidPlatformInput.value.trim(),
|
|
835
1055
|
debug: elements.debugInput.checked,
|
|
@@ -980,11 +1200,32 @@ function bindEvents() {
|
|
|
980
1200
|
appendLog(`${text("iconSelected")}: ${iconPath}`, "system");
|
|
981
1201
|
validateSettings();
|
|
982
1202
|
});
|
|
1203
|
+
elements.themeColorInput.addEventListener("input", () => {
|
|
1204
|
+
elements.themeColorTextInput.value = elements.themeColorInput.value;
|
|
1205
|
+
validateSettings();
|
|
1206
|
+
});
|
|
1207
|
+
elements.themeColorTextInput.addEventListener("input", () => {
|
|
1208
|
+
if (/^#[0-9a-fA-F]{6}$/.test(elements.themeColorTextInput.value.trim())) {
|
|
1209
|
+
elements.themeColorInput.value = elements.themeColorTextInput.value.trim();
|
|
1210
|
+
}
|
|
1211
|
+
validateSettings();
|
|
1212
|
+
});
|
|
1213
|
+
elements.modeInput.addEventListener("change", () => {
|
|
1214
|
+
const overlayInput = elements.permissionGrid.querySelector("input[value='SYSTEM_ALERT_WINDOW']");
|
|
1215
|
+
const selected = selectedPermissions();
|
|
1216
|
+
const nextPermissions = elements.modeInput.value === "floating" || !overlayInput || !overlayInput.disabled
|
|
1217
|
+
? selected
|
|
1218
|
+
: selected.filter((permission) => permission !== "SYSTEM_ALERT_WINDOW");
|
|
1219
|
+
renderPermissionOptions(nextPermissions);
|
|
1220
|
+
validateSettings();
|
|
1221
|
+
});
|
|
1222
|
+
elements.permissionGrid.addEventListener("change", validateSettings);
|
|
983
1223
|
[
|
|
984
1224
|
elements.appNameInput,
|
|
985
1225
|
elements.packageIdInput,
|
|
986
1226
|
elements.versionInput,
|
|
987
|
-
elements.
|
|
1227
|
+
elements.orientationInput,
|
|
1228
|
+
elements.minSdkVersionInput,
|
|
988
1229
|
elements.androidPlatformInput,
|
|
989
1230
|
elements.debugInput,
|
|
990
1231
|
elements.releaseInput
|
|
@@ -381,7 +381,9 @@ h2 {
|
|
|
381
381
|
.toggle-grid,
|
|
382
382
|
.pipeline,
|
|
383
383
|
.review-grid,
|
|
384
|
-
.help-grid
|
|
384
|
+
.help-grid,
|
|
385
|
+
.permission-grid,
|
|
386
|
+
.code-grid {
|
|
385
387
|
display: grid;
|
|
386
388
|
gap: 16px;
|
|
387
389
|
}
|
|
@@ -489,10 +491,56 @@ h2 {
|
|
|
489
491
|
outline: none;
|
|
490
492
|
}
|
|
491
493
|
|
|
492
|
-
.icon-field
|
|
494
|
+
.icon-field,
|
|
495
|
+
.permissions-field {
|
|
493
496
|
grid-column: span 2;
|
|
494
497
|
}
|
|
495
498
|
|
|
499
|
+
.permission-grid {
|
|
500
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
501
|
+
gap: 10px;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.permission-option {
|
|
505
|
+
min-height: 74px;
|
|
506
|
+
border: 1px solid var(--line);
|
|
507
|
+
border-radius: 8px;
|
|
508
|
+
padding: 12px;
|
|
509
|
+
display: flex;
|
|
510
|
+
gap: 10px;
|
|
511
|
+
align-items: center;
|
|
512
|
+
background: var(--bg);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.permission-option input {
|
|
516
|
+
width: 20px;
|
|
517
|
+
height: 20px;
|
|
518
|
+
accent-color: var(--blue);
|
|
519
|
+
flex: 0 0 auto;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.permission-option strong,
|
|
523
|
+
.permission-option small {
|
|
524
|
+
display: block;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.permission-option small {
|
|
528
|
+
margin-top: 4px;
|
|
529
|
+
color: var(--muted);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.color-picker {
|
|
533
|
+
display: grid;
|
|
534
|
+
grid-template-columns: 64px minmax(0, 1fr);
|
|
535
|
+
gap: 10px;
|
|
536
|
+
align-items: center;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.field .color-picker input[type="color"] {
|
|
540
|
+
width: 64px;
|
|
541
|
+
padding: 4px;
|
|
542
|
+
}
|
|
543
|
+
|
|
496
544
|
.icon-picker {
|
|
497
545
|
display: grid;
|
|
498
546
|
grid-template-columns: 86px minmax(0, 1fr);
|
|
@@ -800,6 +848,44 @@ h2 {
|
|
|
800
848
|
padding: 20px;
|
|
801
849
|
}
|
|
802
850
|
|
|
851
|
+
.view-intro {
|
|
852
|
+
max-width: 760px;
|
|
853
|
+
color: var(--muted);
|
|
854
|
+
margin-bottom: 18px;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
.code-grid {
|
|
858
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
.code-card {
|
|
862
|
+
border: 1px solid var(--line);
|
|
863
|
+
border-radius: 8px;
|
|
864
|
+
background: var(--panel);
|
|
865
|
+
box-shadow: var(--shadow);
|
|
866
|
+
padding: 16px;
|
|
867
|
+
display: grid;
|
|
868
|
+
gap: 10px;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
.code-card code {
|
|
872
|
+
overflow-wrap: anywhere;
|
|
873
|
+
color: #d6e4f7;
|
|
874
|
+
background: #0c1117;
|
|
875
|
+
border-radius: 7px;
|
|
876
|
+
padding: 10px;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.code-card span {
|
|
880
|
+
color: var(--blue);
|
|
881
|
+
font-weight: 800;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
.code-card p {
|
|
885
|
+
margin: 0;
|
|
886
|
+
color: var(--muted);
|
|
887
|
+
}
|
|
888
|
+
|
|
803
889
|
.success-view {
|
|
804
890
|
min-height: 100%;
|
|
805
891
|
display: none;
|
|
@@ -1152,11 +1238,14 @@ body.logs-visible .bottom-log-bar {
|
|
|
1152
1238
|
.toggle-grid,
|
|
1153
1239
|
.pipeline,
|
|
1154
1240
|
.review-grid,
|
|
1155
|
-
.help-grid
|
|
1241
|
+
.help-grid,
|
|
1242
|
+
.permission-grid,
|
|
1243
|
+
.code-grid {
|
|
1156
1244
|
grid-template-columns: 1fr;
|
|
1157
1245
|
}
|
|
1158
1246
|
|
|
1159
|
-
.icon-field
|
|
1247
|
+
.icon-field,
|
|
1248
|
+
.permissions-field {
|
|
1160
1249
|
grid-column: span 1;
|
|
1161
1250
|
}
|
|
1162
1251
|
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
</config-file>
|
|
22
22
|
|
|
23
23
|
<config-file target="AndroidManifest.xml" parent="/manifest/application">
|
|
24
|
+
<service android:name="dev.html2apk.bridge.FloatingIconService" android:exported="false" />
|
|
24
25
|
<receiver android:name="dev.html2apk.bridge.NotificationReceiver" android:exported="false" />
|
|
25
26
|
<receiver android:name="dev.html2apk.bridge.BootReceiver" android:enabled="true" android:exported="true">
|
|
26
27
|
<intent-filter>
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
</config-file>
|
|
32
33
|
|
|
33
34
|
<source-file src="src/android/Html2ApkBridge.java" target-dir="app/src/main/java/dev/html2apk/bridge" />
|
|
35
|
+
<source-file src="src/android/FloatingIconService.java" target-dir="app/src/main/java/dev/html2apk/bridge" />
|
|
34
36
|
<source-file src="src/android/NotificationReceiver.java" target-dir="app/src/main/java/dev/html2apk/bridge" />
|
|
35
37
|
<source-file src="src/android/BootReceiver.java" target-dir="app/src/main/java/dev/html2apk/bridge" />
|
|
36
38
|
<source-file src="src/android/NotificationStore.java" target-dir="app/src/main/java/dev/html2apk/bridge" />
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
package dev.html2apk.bridge;
|
|
2
|
+
|
|
3
|
+
import android.app.Service;
|
|
4
|
+
import android.content.Intent;
|
|
5
|
+
import android.graphics.PixelFormat;
|
|
6
|
+
import android.graphics.drawable.GradientDrawable;
|
|
7
|
+
import android.os.Build;
|
|
8
|
+
import android.os.IBinder;
|
|
9
|
+
import android.provider.Settings;
|
|
10
|
+
import android.view.Gravity;
|
|
11
|
+
import android.view.MotionEvent;
|
|
12
|
+
import android.view.View;
|
|
13
|
+
import android.view.WindowManager;
|
|
14
|
+
import android.widget.ImageView;
|
|
15
|
+
|
|
16
|
+
public class FloatingIconService extends Service {
|
|
17
|
+
private WindowManager windowManager;
|
|
18
|
+
private View floatingView;
|
|
19
|
+
private WindowManager.LayoutParams params;
|
|
20
|
+
private int startX;
|
|
21
|
+
private int startY;
|
|
22
|
+
private float touchStartX;
|
|
23
|
+
private float touchStartY;
|
|
24
|
+
|
|
25
|
+
@Override
|
|
26
|
+
public IBinder onBind(Intent intent) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@Override
|
|
31
|
+
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
32
|
+
showFloatingIcon();
|
|
33
|
+
return START_STICKY;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@Override
|
|
37
|
+
public void onDestroy() {
|
|
38
|
+
removeFloatingIcon();
|
|
39
|
+
super.onDestroy();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private void showFloatingIcon() {
|
|
43
|
+
if (floatingView != null) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
|
|
48
|
+
stopSelf();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
|
53
|
+
if (windowManager == null) {
|
|
54
|
+
stopSelf();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
ImageView icon = new ImageView(this);
|
|
59
|
+
int size = dp(58);
|
|
60
|
+
int padding = dp(8);
|
|
61
|
+
icon.setImageDrawable(getApplicationInfo().loadIcon(getPackageManager()));
|
|
62
|
+
icon.setPadding(padding, padding, padding, padding);
|
|
63
|
+
|
|
64
|
+
GradientDrawable background = new GradientDrawable();
|
|
65
|
+
background.setShape(GradientDrawable.OVAL);
|
|
66
|
+
background.setColor(0xff126fff);
|
|
67
|
+
icon.setBackground(background);
|
|
68
|
+
|
|
69
|
+
int windowType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
|
70
|
+
? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
|
|
71
|
+
: WindowManager.LayoutParams.TYPE_PHONE;
|
|
72
|
+
|
|
73
|
+
params = new WindowManager.LayoutParams(
|
|
74
|
+
size,
|
|
75
|
+
size,
|
|
76
|
+
windowType,
|
|
77
|
+
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
|
78
|
+
PixelFormat.TRANSLUCENT
|
|
79
|
+
);
|
|
80
|
+
params.gravity = Gravity.TOP | Gravity.START;
|
|
81
|
+
params.x = dp(16);
|
|
82
|
+
params.y = dp(96);
|
|
83
|
+
|
|
84
|
+
icon.setOnTouchListener(new View.OnTouchListener() {
|
|
85
|
+
@Override
|
|
86
|
+
public boolean onTouch(View view, MotionEvent event) {
|
|
87
|
+
switch (event.getAction()) {
|
|
88
|
+
case MotionEvent.ACTION_DOWN:
|
|
89
|
+
startX = params.x;
|
|
90
|
+
startY = params.y;
|
|
91
|
+
touchStartX = event.getRawX();
|
|
92
|
+
touchStartY = event.getRawY();
|
|
93
|
+
return true;
|
|
94
|
+
case MotionEvent.ACTION_MOVE:
|
|
95
|
+
params.x = startX + Math.round(event.getRawX() - touchStartX);
|
|
96
|
+
params.y = startY + Math.round(event.getRawY() - touchStartY);
|
|
97
|
+
windowManager.updateViewLayout(floatingView, params);
|
|
98
|
+
return true;
|
|
99
|
+
case MotionEvent.ACTION_UP:
|
|
100
|
+
float deltaX = Math.abs(event.getRawX() - touchStartX);
|
|
101
|
+
float deltaY = Math.abs(event.getRawY() - touchStartY);
|
|
102
|
+
if (deltaX < dp(6) && deltaY < dp(6)) {
|
|
103
|
+
openApp();
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
default:
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
floatingView = icon;
|
|
113
|
+
try {
|
|
114
|
+
windowManager.addView(floatingView, params);
|
|
115
|
+
} catch (RuntimeException error) {
|
|
116
|
+
floatingView = null;
|
|
117
|
+
stopSelf();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private void removeFloatingIcon() {
|
|
122
|
+
if (windowManager != null && floatingView != null) {
|
|
123
|
+
windowManager.removeView(floatingView);
|
|
124
|
+
}
|
|
125
|
+
floatingView = null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private void openApp() {
|
|
129
|
+
Intent launchIntent = getPackageManager().getLaunchIntentForPackage(getPackageName());
|
|
130
|
+
if (launchIntent == null) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
135
|
+
startActivity(launchIntent);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private int dp(int value) {
|
|
139
|
+
return Math.round(value * getResources().getDisplayMetrics().density);
|
|
140
|
+
}
|
|
141
|
+
}
|