html2apk 0.4.0 → 0.5.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 +88 -10
- package/examples/minimal/dist/MeuApp-1.0.0-debug.apk +0 -0
- package/package.json +1 -1
- package/src/core/build-apk.js +18 -7
- package/src/desktop/renderer/renderer.js +93 -8
- package/src/templates/cordova-plugin-html2apk-bridge/plugin.xml +2 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/Html2ApkBridge.java +176 -32
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/NotificationClickReceiver.java +28 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/NotificationReceiver.java +1 -1
- package/src/templates/cordova-plugin-html2apk-bridge/www/html2apk-bridge.js +278 -11
- package/src/templates/html2apk-early-bridge.js +860 -0
package/README.md
CHANGED
|
@@ -356,7 +356,7 @@ Retorno:
|
|
|
356
356
|
|
|
357
357
|
A v0.1 instala um plugin Cordova local com uma API global simples para recursos Android. Todas as funcoes retornam `Promise`, exceto os ouvintes `aoEvento`/atalhos, que retornam uma funcao para cancelar a escuta.
|
|
358
358
|
|
|
359
|
-
O html2apk injeta `cordova.js` automaticamente no HTML inicial do APK.
|
|
359
|
+
O html2apk injeta `html2apk-early-bridge.js` e `cordova.js` automaticamente no HTML inicial do APK. A bridge inicial cria as funcoes interpretadas antes dos scripts do seu projeto; se uma funcao nativa for chamada antes do `deviceready`, ela espera o Android ficar pronto antes de executar.
|
|
360
360
|
|
|
361
361
|
Cada funcao em portugues tambem tem alias em ingles. A interface grafica mostra a sintaxe PT-BR quando o idioma esta em portugues e mostra a sintaxe em ingles quando o usuario troca o idioma para English.
|
|
362
362
|
|
|
@@ -418,18 +418,29 @@ No seu JavaScript do app:
|
|
|
418
418
|
toast("Mensagem");
|
|
419
419
|
vibrar(250);
|
|
420
420
|
|
|
421
|
+
await notificar({
|
|
422
|
+
titulo: "Pedido aprovado",
|
|
423
|
+
texto: "Toque para abrir o app"
|
|
424
|
+
});
|
|
425
|
+
|
|
421
426
|
notificar({
|
|
422
427
|
titulo: "Pedido aprovado",
|
|
423
428
|
texto: "Toque para abrir os detalhes",
|
|
424
429
|
acoes: [
|
|
425
|
-
{
|
|
426
|
-
|
|
430
|
+
{
|
|
431
|
+
id: "abrir",
|
|
432
|
+
titulo: "Abrir",
|
|
433
|
+
open: true,
|
|
434
|
+
aoClicar: { funcao: "abrirNoApp", argumentos: ["#/pedido/123"] }
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
id: "site",
|
|
438
|
+
titulo: "Ver site",
|
|
439
|
+
open: false,
|
|
440
|
+
aoClicar: { funcao: "abrirForaDoApp", argumentos: ["https://exemplo.com/pedido/123"], open: false }
|
|
441
|
+
}
|
|
427
442
|
],
|
|
428
|
-
aoClicar:
|
|
429
|
-
acao: "abrir-rota",
|
|
430
|
-
rota: "/pedido/123",
|
|
431
|
-
dados: { id: 123 }
|
|
432
|
-
}
|
|
443
|
+
aoClicar: () => abrirForaDoApp("https://exemplo.com/pedido/123")
|
|
433
444
|
});
|
|
434
445
|
|
|
435
446
|
agendarNotificacao({
|
|
@@ -437,8 +448,8 @@ agendarNotificacao({
|
|
|
437
448
|
texto: "Hora de abrir o app",
|
|
438
449
|
quando: Date.now() + 60000,
|
|
439
450
|
aoClicar: {
|
|
440
|
-
|
|
441
|
-
|
|
451
|
+
funcao: "abrirNoApp",
|
|
452
|
+
argumentos: ["#/lembretes"]
|
|
442
453
|
}
|
|
443
454
|
});
|
|
444
455
|
|
|
@@ -466,8 +477,75 @@ const loop = await agendarLoopNotificacoes({
|
|
|
466
477
|
fullscreen(true);
|
|
467
478
|
```
|
|
468
479
|
|
|
480
|
+
`notificar()` nao obriga clique, botao nem funcao. So `titulo` e `texto` ja geram uma notificacao normal. `aoClicar`, `acoes`/`actions` e `open` sao opcionais.
|
|
481
|
+
|
|
469
482
|
`agendarNotificacao()` agenda uma notificacao. Se voce passar um array para ela, ou usar `agendarNotificacoes()`, o html2apk agenda varias em sequencia. Cada item recebe `id` automatico se voce nao informar um.
|
|
470
483
|
|
|
484
|
+
Em `aoClicar`, voce pode passar uma funcao diretamente:
|
|
485
|
+
|
|
486
|
+
```js
|
|
487
|
+
await notificar({
|
|
488
|
+
titulo: "Pedido aprovado",
|
|
489
|
+
texto: "Toque para abrir os detalhes",
|
|
490
|
+
aoClicar: () => abrirForaDoApp("https://exemplo.com/pedido/123")
|
|
491
|
+
});
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Nao use `aoClicar: { acao: abrirForaDoApp("https://...") }`, porque os parenteses executam a funcao na hora em que a notificacao e criada. Para notificacao agendada, loop ou app fechado, prefira o formato serializavel:
|
|
495
|
+
|
|
496
|
+
```js
|
|
497
|
+
await agendarNotificacao({
|
|
498
|
+
titulo: "Pedido aprovado",
|
|
499
|
+
texto: "Toque para abrir os detalhes",
|
|
500
|
+
quando: Date.now() + 60000,
|
|
501
|
+
aoClicar: {
|
|
502
|
+
funcao: "abrirForaDoApp",
|
|
503
|
+
argumentos: ["https://exemplo.com/pedido/123"]
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Esse formato tambem aceita funcoes suas, desde que elas existam em `window` quando o app abrir:
|
|
509
|
+
|
|
510
|
+
```js
|
|
511
|
+
window.abrirPedido = (id) => abrirNoApp("#/pedido/" + id);
|
|
512
|
+
|
|
513
|
+
await notificar({
|
|
514
|
+
titulo: "Pedido aprovado",
|
|
515
|
+
texto: "Toque para abrir os detalhes",
|
|
516
|
+
aoClicar: { funcao: "abrirPedido", argumentos: [123] }
|
|
517
|
+
});
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Para botoes na notificacao, use `acoes` ou `actions`. Cada botao tambem aceita `aoClicar`/`onClick`:
|
|
521
|
+
|
|
522
|
+
```js
|
|
523
|
+
window.marcarPedidoLido = (id) => {
|
|
524
|
+
localStorage.setItem("pedido:" + id, "lido");
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
await notificar({
|
|
528
|
+
titulo: "Pedido aprovado",
|
|
529
|
+
texto: "Escolha uma acao",
|
|
530
|
+
acoes: [
|
|
531
|
+
{
|
|
532
|
+
id: "abrir",
|
|
533
|
+
titulo: "Abrir",
|
|
534
|
+
open: true,
|
|
535
|
+
aoClicar: { funcao: "abrirNoApp", argumentos: ["#/pedido/123"] }
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
id: "lido",
|
|
539
|
+
titulo: "Marcar lido",
|
|
540
|
+
open: false,
|
|
541
|
+
aoClicar: { funcao: "marcarPedidoLido", argumentos: [123], open: false }
|
|
542
|
+
}
|
|
543
|
+
]
|
|
544
|
+
});
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
`open: false` evita trazer a tela do app para frente. Se o app ainda estiver vivo em segundo plano, o html2apk dispara o evento e executa a funcao JavaScript. Se o Android tiver matado o app, JavaScript de WebView nao consegue rodar sem abrir o app; nesse caso, acoes externas como `{ funcao: "abrirForaDoApp", argumentos: ["https://..."], open: false }` ainda funcionam por fallback nativo.
|
|
548
|
+
|
|
471
549
|
`agendarLoopNotificacoes()` cria um loop recorrente que funciona com o app fechado. Use `aCada`, `intervalo`, `every` ou `interval` em milissegundos ou como texto (`"30min"`, `"12h"`, `"1d"`). A cada disparo, o Android mostra a proxima notificacao da lista. Para parar:
|
|
472
550
|
|
|
473
551
|
```js
|
|
Binary file
|
package/package.json
CHANGED
package/src/core/build-apk.js
CHANGED
|
@@ -14,6 +14,7 @@ const { installBridgePlugin } = require("../bridge/install-bridge");
|
|
|
14
14
|
const { getRuntimeEnvironment } = require("../runtime-manager");
|
|
15
15
|
|
|
16
16
|
const AUTO_THEME_SCRIPT_NAME = "html2apk-auto-theme.js";
|
|
17
|
+
const EARLY_BRIDGE_SCRIPT_NAME = "html2apk-early-bridge.js";
|
|
17
18
|
const ONESIGNAL_SCRIPT_NAME = "html2apk-onesignal.js";
|
|
18
19
|
const ONESIGNAL_PLUGIN_PACKAGE = "onesignal-cordova-plugin";
|
|
19
20
|
const DEFAULT_APP_ICON_NAME = "html2apk.png";
|
|
@@ -63,19 +64,25 @@ function scriptTag(scriptPath) {
|
|
|
63
64
|
return `<script src="${scriptPath}"></script>`;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
async function injectCordovaRuntimeIntoHtml(htmlPath, scriptPath = "cordova.js") {
|
|
67
|
+
async function injectCordovaRuntimeIntoHtml(htmlPath, scriptPath = "cordova.js", earlyBridgePath = EARLY_BRIDGE_SCRIPT_NAME) {
|
|
67
68
|
let html = await fs.readFile(htmlPath, "utf8");
|
|
68
|
-
|
|
69
|
+
const hasCordova = /<script\b[^>]*\bsrc=["'][^"']*cordova\.js["'][^>]*>/i.test(html);
|
|
70
|
+
const hasEarlyBridge = /<script\b[^>]*\bsrc=["'][^"']*html2apk-early-bridge\.js["'][^>]*>/i.test(html);
|
|
71
|
+
if (hasCordova && hasEarlyBridge) {
|
|
69
72
|
return false;
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
const
|
|
75
|
+
const tags = [
|
|
76
|
+
hasEarlyBridge ? null : scriptTag(earlyBridgePath),
|
|
77
|
+
hasCordova ? null : scriptTag(scriptPath)
|
|
78
|
+
].filter(Boolean).join("\n ");
|
|
79
|
+
|
|
73
80
|
if (/<head\b[^>]*>/i.test(html)) {
|
|
74
|
-
html = html.replace(/<head\b[^>]*>/i, (match) => `${match}\n ${
|
|
81
|
+
html = html.replace(/<head\b[^>]*>/i, (match) => `${match}\n ${tags}`);
|
|
75
82
|
} else if (/<html\b[^>]*>/i.test(html)) {
|
|
76
|
-
html = html.replace(/<html\b[^>]*>/i, (match) => `${match}\n ${
|
|
83
|
+
html = html.replace(/<html\b[^>]*>/i, (match) => `${match}\n ${tags}`);
|
|
77
84
|
} else {
|
|
78
|
-
html = `${
|
|
85
|
+
html = `${tags}\n${html}`;
|
|
79
86
|
}
|
|
80
87
|
|
|
81
88
|
await fs.writeFile(htmlPath, html, "utf8");
|
|
@@ -86,12 +93,16 @@ async function installCordovaRuntimeScript(buildDir, options) {
|
|
|
86
93
|
const wwwDir = path.join(buildDir, "www");
|
|
87
94
|
const entryHtmlPath = path.resolve(wwwDir, options.entryFile || "index.html");
|
|
88
95
|
const scriptPath = toCordovaPath(path.relative(path.dirname(entryHtmlPath), path.join(wwwDir, "cordova.js"))) || "cordova.js";
|
|
96
|
+
const earlyBridgeSource = path.resolve(__dirname, "..", "templates", EARLY_BRIDGE_SCRIPT_NAME);
|
|
97
|
+
const earlyBridgeTarget = path.join(wwwDir, EARLY_BRIDGE_SCRIPT_NAME);
|
|
98
|
+
const earlyBridgePath = toCordovaPath(path.relative(path.dirname(entryHtmlPath), earlyBridgeTarget)) || EARLY_BRIDGE_SCRIPT_NAME;
|
|
89
99
|
|
|
90
100
|
if (!isInside(wwwDir, entryHtmlPath)) {
|
|
91
101
|
throw new Error(`Entry file must stay inside the Cordova www folder: ${options.entryFile}`);
|
|
92
102
|
}
|
|
93
103
|
|
|
94
|
-
|
|
104
|
+
await copyFile(earlyBridgeSource, earlyBridgeTarget);
|
|
105
|
+
return injectCordovaRuntimeIntoHtml(entryHtmlPath, scriptPath, earlyBridgePath);
|
|
95
106
|
}
|
|
96
107
|
|
|
97
108
|
async function injectScriptIntoHtml(htmlPath, scriptPath) {
|
|
@@ -391,11 +391,11 @@ const nativeCodeEntries = [
|
|
|
391
391
|
handling: { pt: "Limite duracoes longas e deixe a permissao `VIBRATE` no app; se falhar, siga sem bloquear o fluxo principal.", en: "Keep long durations under control and include the `VIBRATE` permission; if it fails, continue without blocking the main flow." }
|
|
392
392
|
},
|
|
393
393
|
{
|
|
394
|
-
syntax: { pt: "notificar({ titulo, texto, acoes })", en: "notify({ title, text, actions })" },
|
|
394
|
+
syntax: { pt: "notificar({ titulo, texto, aoClicar?, acoes?, open? })", en: "notify({ title, text, onClick?, actions?, open? })" },
|
|
395
395
|
java: "notify",
|
|
396
|
-
description: { pt: "Cria notificacao Android imediata. `acoes`
|
|
397
|
-
returns: { pt: "Promise<void>; cliques chegam em `aoEvento('notificacao:clicada')` ou `aoClicarNotificacao()
|
|
398
|
-
handling: { pt: "
|
|
396
|
+
description: { pt: "Cria notificacao Android imediata. So `titulo` e `texto` ja bastam; `aoClicar`, `acoes` e `open` sao opcionais.", en: "Creates an immediate Android notification. `title` and `text` are enough; `onClick`, `actions` and `open` are optional." },
|
|
397
|
+
returns: { pt: "Promise<void>; cliques chegam em `aoEvento('notificacao:clicada')` ou `aoClicarNotificacao()` e tambem podem executar `aoClicar` automaticamente.", en: "Promise<void>; clicks arrive through `onEvent('notification:clicked')` or `onNotificationClick()` and can also run `onClick` automatically." },
|
|
398
|
+
handling: { pt: "Use `aoClicar: () => funcao()` enquanto o app esta vivo. Para agendada ou app fechado, prefira `aoClicar: { funcao, argumentos }`. `open:false` evita abrir a tela; JavaScript so roda assim se o app ainda estiver vivo, mas acoes externas como `abrirForaDoApp` funcionam por fallback nativo.", en: "Use `onClick: () => functionName()` while the app process is alive. For scheduled notifications or a closed app, prefer `onClick: { functionName, args }`. `open:false` avoids opening the screen; JavaScript only runs this way if the app is still alive, but external actions like `openOutsideApp` work through a native fallback." }
|
|
399
399
|
},
|
|
400
400
|
{
|
|
401
401
|
syntax: { pt: "agendarNotificacao({ titulo, texto, quando }) / agendarNotificacoes([...])", en: "scheduleNotification({ title, text, when }) / scheduleNotifications([...])" },
|
|
@@ -409,7 +409,7 @@ const nativeCodeEntries = [
|
|
|
409
409
|
java: "AlarmManager + NotificationStore",
|
|
410
410
|
description: { pt: "Cria um loop de notificacoes que continua funcionando com o app fechado e alterna os itens da lista.", en: "Creates a notification loop that keeps working with the app closed and rotates through the list items." },
|
|
411
411
|
returns: { pt: "{ id, when, repeating, loop }. Guarde o `id` para cancelar depois.", en: "{ id, when, repeating, loop }. Store the `id` to cancel later." },
|
|
412
|
-
handling: { pt: "
|
|
412
|
+
handling: { pt: "Cancele com `cancelarNotificacao(id)`. Se o clique precisar chamar funcao automaticamente mesmo apos o Android reabrir o app, use `funcao` + `argumentos` no `aoClicar`.", en: "Cancel with `cancelNotification(id)`. If the click must call a function automatically after Android reopens the app, use `functionName` + `args` in `onClick`." }
|
|
413
413
|
},
|
|
414
414
|
{
|
|
415
415
|
syntax: { pt: "solicitarPermissaoNotificacoes()", en: "requestNotificationPermission()" },
|
|
@@ -668,17 +668,102 @@ const nativeCodeRecipes = [
|
|
|
668
668
|
}
|
|
669
669
|
},
|
|
670
670
|
{
|
|
671
|
-
when: { pt: "Para mostrar uma notificacao agora.
|
|
671
|
+
when: { pt: "Para mostrar uma notificacao simples agora. `aoClicar`, `acoes` e `open` sao opcionais.", en: "To show a simple notification now. `onClick`, `actions` and `open` are optional." },
|
|
672
|
+
example: {
|
|
673
|
+
pt: `await notificar({
|
|
674
|
+
titulo: "Pedido aprovado",
|
|
675
|
+
texto: "Toque para abrir o app"
|
|
676
|
+
});`,
|
|
677
|
+
en: `await notify({
|
|
678
|
+
title: "Order approved",
|
|
679
|
+
text: "Tap to open the app"
|
|
680
|
+
});`
|
|
681
|
+
}
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
when: { pt: "Para executar algo quando a notificacao for clicada.", en: "To run something when the notification is clicked." },
|
|
672
685
|
example: {
|
|
673
686
|
pt: `await notificar({
|
|
674
687
|
titulo: "Pedido aprovado",
|
|
675
688
|
texto: "Toque para abrir os detalhes",
|
|
676
|
-
aoClicar:
|
|
689
|
+
aoClicar: () => abrirForaDoApp("https://exemplo.com/pedidos/123")
|
|
677
690
|
});`,
|
|
678
691
|
en: `await notify({
|
|
679
692
|
title: "Order approved",
|
|
680
693
|
text: "Tap to open details",
|
|
681
|
-
onClick:
|
|
694
|
+
onClick: () => openOutsideApp("https://example.com/orders/123")
|
|
695
|
+
});`
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
when: { pt: "Para colocar botoes na notificacao, cada um chamando uma funcao.", en: "To add buttons to the notification, each one calling a function." },
|
|
700
|
+
example: {
|
|
701
|
+
pt: `window.marcarPedidoLido = (id) => {
|
|
702
|
+
localStorage.setItem("pedido:" + id, "lido");
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
await notificar({
|
|
706
|
+
titulo: "Pedido aprovado",
|
|
707
|
+
texto: "Escolha uma acao",
|
|
708
|
+
acoes: [
|
|
709
|
+
{
|
|
710
|
+
id: "abrir",
|
|
711
|
+
titulo: "Abrir",
|
|
712
|
+
open: true,
|
|
713
|
+
aoClicar: { funcao: "abrirNoApp", argumentos: ["#/pedido/123"] }
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
id: "lido",
|
|
717
|
+
titulo: "Marcar lido",
|
|
718
|
+
open: false,
|
|
719
|
+
aoClicar: { funcao: "marcarPedidoLido", argumentos: [123], open: false }
|
|
720
|
+
}
|
|
721
|
+
]
|
|
722
|
+
});`,
|
|
723
|
+
en: `window.markOrderRead = (id) => {
|
|
724
|
+
localStorage.setItem("order:" + id, "read");
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
await notify({
|
|
728
|
+
title: "Order approved",
|
|
729
|
+
text: "Choose an action",
|
|
730
|
+
actions: [
|
|
731
|
+
{
|
|
732
|
+
id: "open",
|
|
733
|
+
title: "Open",
|
|
734
|
+
open: true,
|
|
735
|
+
onClick: { functionName: "openInApp", args: ["#/order/123"] }
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
id: "read",
|
|
739
|
+
title: "Mark read",
|
|
740
|
+
open: false,
|
|
741
|
+
onClick: { functionName: "markOrderRead", args: [123], open: false }
|
|
742
|
+
}
|
|
743
|
+
]
|
|
744
|
+
});`
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
when: { pt: "Para clique de notificacao agendada ou com app fechado.", en: "For scheduled notification clicks or when the app is closed." },
|
|
749
|
+
example: {
|
|
750
|
+
pt: `await agendarNotificacao({
|
|
751
|
+
titulo: "Pedido aprovado",
|
|
752
|
+
texto: "Toque para abrir os detalhes",
|
|
753
|
+
quando: Date.now() + 60 * 1000,
|
|
754
|
+
aoClicar: {
|
|
755
|
+
funcao: "abrirForaDoApp",
|
|
756
|
+
argumentos: ["https://exemplo.com/pedidos/123"]
|
|
757
|
+
}
|
|
758
|
+
});`,
|
|
759
|
+
en: `await scheduleNotification({
|
|
760
|
+
title: "Order approved",
|
|
761
|
+
text: "Tap to open details",
|
|
762
|
+
when: Date.now() + 60 * 1000,
|
|
763
|
+
onClick: {
|
|
764
|
+
functionName: "openOutsideApp",
|
|
765
|
+
args: ["https://example.com/orders/123"]
|
|
766
|
+
}
|
|
682
767
|
});`
|
|
683
768
|
}
|
|
684
769
|
},
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
<config-file target="AndroidManifest.xml" parent="/manifest/application">
|
|
28
28
|
<service android:name="dev.html2apk.bridge.FloatingIconService" android:exported="false" />
|
|
29
29
|
<receiver android:name="dev.html2apk.bridge.NotificationReceiver" android:exported="false" />
|
|
30
|
+
<receiver android:name="dev.html2apk.bridge.NotificationClickReceiver" android:exported="false" />
|
|
30
31
|
<receiver android:name="dev.html2apk.bridge.BootReceiver" android:enabled="true" android:exported="true">
|
|
31
32
|
<intent-filter>
|
|
32
33
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
<source-file src="src/android/Html2ApkBridge.java" target-dir="app/src/main/java/dev/html2apk/bridge" />
|
|
39
40
|
<source-file src="src/android/FloatingIconService.java" target-dir="app/src/main/java/dev/html2apk/bridge" />
|
|
40
41
|
<source-file src="src/android/NotificationReceiver.java" target-dir="app/src/main/java/dev/html2apk/bridge" />
|
|
42
|
+
<source-file src="src/android/NotificationClickReceiver.java" target-dir="app/src/main/java/dev/html2apk/bridge" />
|
|
41
43
|
<source-file src="src/android/BootReceiver.java" target-dir="app/src/main/java/dev/html2apk/bridge" />
|
|
42
44
|
<source-file src="src/android/NotificationStore.java" target-dir="app/src/main/java/dev/html2apk/bridge" />
|
|
43
45
|
<framework src="androidx.core:core:1.12.0" />
|
|
@@ -73,6 +73,7 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
73
73
|
private static final int REQUEST_PICK_FOLDER = 7413;
|
|
74
74
|
private static final String PREFS_NAME = "html2apk_bridge";
|
|
75
75
|
private static final String PREF_PERMISSION_PREFIX = "permission_requested_";
|
|
76
|
+
private static Html2ApkBridge activeBridge;
|
|
76
77
|
|
|
77
78
|
private CallbackContext notificationPermissionCallback;
|
|
78
79
|
private CallbackContext cameraPermissionCallback;
|
|
@@ -100,6 +101,7 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
100
101
|
@Override
|
|
101
102
|
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
|
102
103
|
super.initialize(cordova, webView);
|
|
104
|
+
activeBridge = this;
|
|
103
105
|
handleNotificationIntent(cordova.getActivity().getIntent(), false);
|
|
104
106
|
handleLinkIntent(cordova.getActivity().getIntent(), false);
|
|
105
107
|
registerSystemReceiver();
|
|
@@ -131,6 +133,9 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
131
133
|
stopMicRecorderSilently();
|
|
132
134
|
unregisterSystemReceiver();
|
|
133
135
|
dispatchEvent("app:fechado", baseEvent("app:fechado"));
|
|
136
|
+
if (activeBridge == this) {
|
|
137
|
+
activeBridge = null;
|
|
138
|
+
}
|
|
134
139
|
super.onDestroy();
|
|
135
140
|
}
|
|
136
141
|
|
|
@@ -757,11 +762,6 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
757
762
|
return;
|
|
758
763
|
}
|
|
759
764
|
|
|
760
|
-
if (shouldOpenSettingsForRuntimePermission(Manifest.permission.POST_NOTIFICATIONS)) {
|
|
761
|
-
callbackContext.success(openSettingsForRuntimePermission(Manifest.permission.POST_NOTIFICATIONS, true, false));
|
|
762
|
-
return;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
765
|
notificationPermissionCallback = callbackContext;
|
|
766
766
|
rememberRuntimePermissionRequest(Manifest.permission.POST_NOTIFICATIONS);
|
|
767
767
|
cordova.requestPermission(this, REQUEST_POST_NOTIFICATIONS, Manifest.permission.POST_NOTIFICATIONS);
|
|
@@ -777,11 +777,6 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
777
777
|
return false;
|
|
778
778
|
}
|
|
779
779
|
|
|
780
|
-
if (shouldOpenSettingsForRuntimePermission(Manifest.permission.POST_NOTIFICATIONS)) {
|
|
781
|
-
callbackContext.success(openSettingsForRuntimePermission(Manifest.permission.POST_NOTIFICATIONS, true, false));
|
|
782
|
-
return true;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
780
|
pendingNotificationOptions = options;
|
|
786
781
|
pendingNotificationSchedule = schedule;
|
|
787
782
|
pendingNotificationCallback = callbackContext;
|
|
@@ -806,7 +801,7 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
806
801
|
.setContentText(text)
|
|
807
802
|
.setStyle(new NotificationCompat.BigTextStyle().bigText(text))
|
|
808
803
|
.setAutoCancel(true)
|
|
809
|
-
.setContentIntent(
|
|
804
|
+
.setContentIntent(createNotificationClickIntent(context(), id, detailPayload(options)))
|
|
810
805
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
|
|
811
806
|
|
|
812
807
|
addNotificationActions(builder, context(), id, options);
|
|
@@ -1069,11 +1064,6 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
1069
1064
|
return;
|
|
1070
1065
|
}
|
|
1071
1066
|
|
|
1072
|
-
if (shouldOpenSettingsForRuntimePermission(Manifest.permission.CAMERA)) {
|
|
1073
|
-
callbackContext.success(openSettingsForRuntimePermission(Manifest.permission.CAMERA, true, false));
|
|
1074
|
-
return;
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
1067
|
cameraPermissionCallback = callbackContext;
|
|
1078
1068
|
rememberRuntimePermissionRequest(Manifest.permission.CAMERA);
|
|
1079
1069
|
cordova.requestPermission(this, REQUEST_CAMERA, Manifest.permission.CAMERA);
|
|
@@ -1092,11 +1082,6 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
1092
1082
|
return;
|
|
1093
1083
|
}
|
|
1094
1084
|
|
|
1095
|
-
if (shouldOpenSettingsForRuntimePermission(Manifest.permission.RECORD_AUDIO)) {
|
|
1096
|
-
callbackContext.success(openSettingsForRuntimePermission(Manifest.permission.RECORD_AUDIO, true, false));
|
|
1097
|
-
return;
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
1085
|
microphonePermissionCallback = callbackContext;
|
|
1101
1086
|
rememberRuntimePermissionRequest(Manifest.permission.RECORD_AUDIO);
|
|
1102
1087
|
cordova.requestPermission(this, REQUEST_RECORD_AUDIO, Manifest.permission.RECORD_AUDIO);
|
|
@@ -1131,11 +1116,6 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
1131
1116
|
}
|
|
1132
1117
|
|
|
1133
1118
|
if (!hasMicrophonePermission()) {
|
|
1134
|
-
if (shouldOpenSettingsForRuntimePermission(Manifest.permission.RECORD_AUDIO)) {
|
|
1135
|
-
callbackContext.success(openSettingsForRuntimePermission(Manifest.permission.RECORD_AUDIO, true, false));
|
|
1136
|
-
return;
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
1119
|
pendingMicStartCallback = callbackContext;
|
|
1140
1120
|
rememberRuntimePermissionRequest(Manifest.permission.RECORD_AUDIO);
|
|
1141
1121
|
cordova.requestPermission(this, REQUEST_RECORD_AUDIO, Manifest.permission.RECORD_AUDIO);
|
|
@@ -1319,11 +1299,6 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
1319
1299
|
|
|
1320
1300
|
private void setFlashlightWithPermission(boolean enabled, boolean toggle, CallbackContext callbackContext) throws Exception {
|
|
1321
1301
|
if (!hasCameraPermission()) {
|
|
1322
|
-
if (shouldOpenSettingsForRuntimePermission(Manifest.permission.CAMERA)) {
|
|
1323
|
-
callbackContext.success(openSettingsForRuntimePermission(Manifest.permission.CAMERA, true, false));
|
|
1324
|
-
return;
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
1302
|
pendingFlashlightCallback = callbackContext;
|
|
1328
1303
|
pendingFlashlightEnabled = enabled;
|
|
1329
1304
|
pendingFlashlightToggle = toggle;
|
|
@@ -2016,6 +1991,167 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
2016
1991
|
);
|
|
2017
1992
|
}
|
|
2018
1993
|
|
|
1994
|
+
static PendingIntent createNotificationClickIntent(Context context, int id, JSONObject detail) {
|
|
1995
|
+
if (shouldOpenAppForNotificationClick(detail)) {
|
|
1996
|
+
return createContentIntent(context, id, detail);
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
Intent intent = new Intent(context, NotificationClickReceiver.class);
|
|
2000
|
+
intent.putExtra(EXTRA_NOTIFICATION_CLICKED, true);
|
|
2001
|
+
intent.putExtra(EXTRA_NOTIFICATION_DETAIL, detail == null ? "{}" : detail.toString());
|
|
2002
|
+
|
|
2003
|
+
return PendingIntent.getBroadcast(
|
|
2004
|
+
context,
|
|
2005
|
+
id,
|
|
2006
|
+
intent,
|
|
2007
|
+
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
static void handleNotificationClickBroadcast(Context context, JSONObject detail) {
|
|
2012
|
+
if (detail == null) {
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
if (activeBridge != null && activeBridge.webView != null) {
|
|
2017
|
+
activeBridge.dispatchNotificationClick(detail);
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
handleNativeNotificationAction(context, detail);
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
private static void handleNativeNotificationAction(Context context, JSONObject detail) {
|
|
2025
|
+
JSONObject action = notificationClickAction(detail);
|
|
2026
|
+
if (action == null) {
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
String functionName = action.optString("funcao",
|
|
2031
|
+
action.optString("functionName",
|
|
2032
|
+
action.optString("function",
|
|
2033
|
+
action.optString("fn", action.optString("nomeFuncao", "")))));
|
|
2034
|
+
|
|
2035
|
+
if (!isExternalUrlFunction(functionName)) {
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
String url = firstActionArgument(action);
|
|
2040
|
+
if (url.length() == 0) {
|
|
2041
|
+
url = action.optString("url", action.optString("href", ""));
|
|
2042
|
+
}
|
|
2043
|
+
if (url.length() == 0) {
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
|
2048
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
2049
|
+
try {
|
|
2050
|
+
context.startActivity(intent);
|
|
2051
|
+
} catch (Exception ignored) {
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
private static boolean isExternalUrlFunction(String functionName) {
|
|
2056
|
+
return "abrirForaDoApp".equals(functionName)
|
|
2057
|
+
|| "abrirUrlExterno".equals(functionName)
|
|
2058
|
+
|| "abrirUrl".equals(functionName)
|
|
2059
|
+
|| "openOutsideApp".equals(functionName)
|
|
2060
|
+
|| "openExternalUrl".equals(functionName)
|
|
2061
|
+
|| "openUrl".equals(functionName);
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
private static String firstActionArgument(JSONObject action) {
|
|
2065
|
+
JSONArray args = action.optJSONArray("argumentos");
|
|
2066
|
+
if (args == null) {
|
|
2067
|
+
args = action.optJSONArray("args");
|
|
2068
|
+
}
|
|
2069
|
+
if (args == null) {
|
|
2070
|
+
args = action.optJSONArray("parametros");
|
|
2071
|
+
}
|
|
2072
|
+
if (args == null) {
|
|
2073
|
+
args = action.optJSONArray("params");
|
|
2074
|
+
}
|
|
2075
|
+
if (args == null || args.length() == 0) {
|
|
2076
|
+
return "";
|
|
2077
|
+
}
|
|
2078
|
+
return args.optString(0, "");
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
private static JSONObject notificationClickAction(JSONObject detail) {
|
|
2082
|
+
if (detail == null) {
|
|
2083
|
+
return null;
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
JSONObject action = detail.optJSONObject("action");
|
|
2087
|
+
if (action != null) {
|
|
2088
|
+
JSONObject nested = action.optJSONObject("aoClicar");
|
|
2089
|
+
if (nested == null) {
|
|
2090
|
+
nested = action.optJSONObject("onClick");
|
|
2091
|
+
}
|
|
2092
|
+
return nested == null ? action : nested;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
action = detail.optJSONObject("acao");
|
|
2096
|
+
if (action != null) {
|
|
2097
|
+
JSONObject nested = action.optJSONObject("aoClicar");
|
|
2098
|
+
if (nested == null) {
|
|
2099
|
+
nested = action.optJSONObject("onClick");
|
|
2100
|
+
}
|
|
2101
|
+
return nested == null ? action : nested;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
action = detail.optJSONObject("aoClicar");
|
|
2105
|
+
if (action == null) {
|
|
2106
|
+
action = detail.optJSONObject("onClick");
|
|
2107
|
+
}
|
|
2108
|
+
return action;
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
private static boolean shouldOpenAppForNotificationClick(JSONObject detail) {
|
|
2112
|
+
Boolean open = openFlag(detail);
|
|
2113
|
+
JSONObject action;
|
|
2114
|
+
|
|
2115
|
+
if (open != null) {
|
|
2116
|
+
return open.booleanValue();
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
action = detail == null ? null : detail.optJSONObject("action");
|
|
2120
|
+
open = openFlag(action);
|
|
2121
|
+
if (open != null) {
|
|
2122
|
+
return open.booleanValue();
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
action = detail == null ? null : detail.optJSONObject("acao");
|
|
2126
|
+
open = openFlag(action);
|
|
2127
|
+
if (open != null) {
|
|
2128
|
+
return open.booleanValue();
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
action = notificationClickAction(detail);
|
|
2132
|
+
open = openFlag(action);
|
|
2133
|
+
return open == null || open.booleanValue();
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
private static Boolean openFlag(JSONObject object) {
|
|
2137
|
+
if (object == null) {
|
|
2138
|
+
return null;
|
|
2139
|
+
}
|
|
2140
|
+
if (object.has("open")) {
|
|
2141
|
+
return Boolean.valueOf(object.optBoolean("open", true));
|
|
2142
|
+
}
|
|
2143
|
+
if (object.has("abrir")) {
|
|
2144
|
+
return Boolean.valueOf(object.optBoolean("abrir", true));
|
|
2145
|
+
}
|
|
2146
|
+
if (object.has("openApp")) {
|
|
2147
|
+
return Boolean.valueOf(object.optBoolean("openApp", true));
|
|
2148
|
+
}
|
|
2149
|
+
if (object.has("abrirApp")) {
|
|
2150
|
+
return Boolean.valueOf(object.optBoolean("abrirApp", true));
|
|
2151
|
+
}
|
|
2152
|
+
return null;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2019
2155
|
static boolean canScheduleExactAlarms(Context context) {
|
|
2020
2156
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
2021
2157
|
return true;
|
|
@@ -2219,9 +2355,13 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
2219
2355
|
static JSONObject detailPayload(JSONObject options) throws Exception {
|
|
2220
2356
|
JSONObject detail = new JSONObject();
|
|
2221
2357
|
JSONObject click = options.optJSONObject("aoClicar");
|
|
2358
|
+
Boolean open = openFlag(options);
|
|
2222
2359
|
if (click == null) {
|
|
2223
2360
|
click = options.optJSONObject("onClick");
|
|
2224
2361
|
}
|
|
2362
|
+
if (open == null) {
|
|
2363
|
+
open = openFlag(click);
|
|
2364
|
+
}
|
|
2225
2365
|
|
|
2226
2366
|
detail.put("id", notificationId(options));
|
|
2227
2367
|
detail.put("title", title(options));
|
|
@@ -2232,6 +2372,10 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
2232
2372
|
detail.put("clickedAt", System.currentTimeMillis());
|
|
2233
2373
|
detail.put("onClick", click == null ? new JSONObject().put("action", "open-app") : click);
|
|
2234
2374
|
detail.put("aoClicar", click == null ? new JSONObject().put("acao", "abrir-app") : click);
|
|
2375
|
+
if (open != null) {
|
|
2376
|
+
detail.put("open", open.booleanValue());
|
|
2377
|
+
detail.put("abrir", open.booleanValue());
|
|
2378
|
+
}
|
|
2235
2379
|
return detail;
|
|
2236
2380
|
}
|
|
2237
2381
|
|
|
@@ -2259,7 +2403,7 @@ public class Html2ApkBridge extends CordovaPlugin {
|
|
|
2259
2403
|
builder.addAction(
|
|
2260
2404
|
context.getApplicationInfo().icon,
|
|
2261
2405
|
title,
|
|
2262
|
-
|
|
2406
|
+
createNotificationClickIntent(context, notificationId * 100 + index + 1, detail)
|
|
2263
2407
|
);
|
|
2264
2408
|
} catch (Exception ignored) {
|
|
2265
2409
|
}
|