html2apk 0.2.0 → 0.4.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 +323 -12
- package/examples/minimal/app.json +2 -0
- package/examples/minimal/dist/MeuApp-1.0.0-debug.apk +0 -0
- package/package.json +3 -2
- package/src/cli/index.js +10 -1
- package/src/cordova/config-xml.js +59 -1
- package/src/cordova/project.js +19 -1
- package/src/core/build-apk.js +399 -23
- package/src/core/config.js +47 -1
- package/src/core/defaults.js +6 -0
- package/src/desktop/main.js +152 -2
- package/src/desktop/preload.js +14 -0
- package/src/desktop/renderer/index.html +20 -2
- package/src/desktop/renderer/renderer.js +1254 -41
- package/src/desktop/renderer/styles.css +98 -2
- package/src/templates/cordova-plugin-html2apk-bridge/plugin.xml +4 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/Html2ApkBridge.java +1770 -38
- 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 +516 -3
- package/src/templates/html2apk-auto-theme.js +144 -0
- package/src/templates/html2apk-onesignal.js +155 -0
- package/src/utils/command-runner.js +3 -0
package/README.md
CHANGED
|
@@ -186,6 +186,7 @@ html2apk build --entry-file index.html
|
|
|
186
186
|
html2apk build --web-root .
|
|
187
187
|
html2apk build --app-name MeuApp
|
|
188
188
|
html2apk build --package-id com.seuapp.meuapp
|
|
189
|
+
html2apk build --theme auto
|
|
189
190
|
html2apk build --android-platform android@15.0.0
|
|
190
191
|
```
|
|
191
192
|
|
|
@@ -209,9 +210,21 @@ Exemplo completo:
|
|
|
209
210
|
"orientation": "default",
|
|
210
211
|
"minSdkVersion": 24,
|
|
211
212
|
"themeColor": "#126fff",
|
|
213
|
+
"themeMode": "fixed",
|
|
214
|
+
"oneSignalAppId": "",
|
|
212
215
|
"icon": "",
|
|
213
216
|
"splash": "",
|
|
214
|
-
"
|
|
217
|
+
"deepLinks": {
|
|
218
|
+
"schemes": ["meuapp"],
|
|
219
|
+
"appLinks": [
|
|
220
|
+
{
|
|
221
|
+
"host": "meusite.com",
|
|
222
|
+
"paths": ["/produto/*", "/pedido/*"],
|
|
223
|
+
"autoVerify": false
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
},
|
|
227
|
+
"permissions": ["INTERNET", "CAMERA", "RECORD_AUDIO", "POST_NOTIFICATIONS", "VIBRATE"],
|
|
215
228
|
"plugins": ["cordova-plugin-camera"],
|
|
216
229
|
"release": false,
|
|
217
230
|
"androidPlatform": "android@15.0.0",
|
|
@@ -238,7 +251,11 @@ Campos principais:
|
|
|
238
251
|
| `mode` | `fullscreen` para tela cheia, `standalone` para modo normal ou `floating` para icone flutuante. |
|
|
239
252
|
| `orientation` | `default`, `vertical`, `horizontal`, `portrait` ou `landscape`. |
|
|
240
253
|
| `minSdkVersion` | Versao minima do Android em API level. Padrao: `24`. |
|
|
241
|
-
| `themeColor` | Cor base do tema/splash Android, em hexadecimal. |
|
|
254
|
+
| `themeColor` | Cor base do tema/splash Android, em hexadecimal. Tambem vira fallback do modo automatico. |
|
|
255
|
+
| `icon` | Icone PNG do app. Se ficar vazio, o html2apk usa automaticamente o icone padrao da ferramenta. |
|
|
256
|
+
| `themeMode` | `fixed` usa a cor fixa. `auto` adapta as barras Android a cor visivel na tela do APK. Tambem aceita `theme: "auto"`. |
|
|
257
|
+
| `oneSignalAppId` | Opcional. App ID publico do OneSignal para push remoto. Nao coloque REST API Key no APK. |
|
|
258
|
+
| `deepLinks` | URLs que podem abrir o APK, como `meuapp://rota` ou `https://meusite.com/produto/1`. |
|
|
242
259
|
| `entryFile` | Arquivo HTML inicial. Normalmente `index.html`. |
|
|
243
260
|
| `webRoot` | Pasta onde estao os arquivos web. Normalmente `"."`. |
|
|
244
261
|
| `permissions` | Permissoes Android adicionadas ao app. |
|
|
@@ -254,6 +271,33 @@ Prioridade de configuracao:
|
|
|
254
271
|
2. `app.json` ou `config.json`.
|
|
255
272
|
3. Valores padrao do html2apk.
|
|
256
273
|
|
|
274
|
+
### Tema Automatico Do APK
|
|
275
|
+
|
|
276
|
+
Use `themeMode: "auto"` ou `theme: "auto"` para o APK adaptar as barras nativas do Android a cor que esta visivel na tela. O html2apk observa a tela do WebView e ajusta status bar/navigation bar em tempo real.
|
|
277
|
+
|
|
278
|
+
```json
|
|
279
|
+
{
|
|
280
|
+
"themeMode": "auto",
|
|
281
|
+
"themeColor": "#126fff"
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
`themeColor` continua importante: ela e usada no splash e como fallback quando o app ainda nao conseguiu detectar uma cor visivel.
|
|
286
|
+
|
|
287
|
+
### OneSignal Opcional
|
|
288
|
+
|
|
289
|
+
Se quiser receber notificacoes remotas pelo OneSignal, preencha `oneSignalAppId` no `app.json` ou no campo OneSignal App ID da interface grafica:
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{
|
|
293
|
+
"oneSignalAppId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Quando esse campo esta vazio, nada do OneSignal e instalado no APK. Quando ele esta preenchido, o html2apk instala `onesignal-cordova-plugin`, injeta a inicializacao no HTML e mantem as notificacoes locais existentes (`notificar`, `agendarNotificacao`, loops etc.).
|
|
298
|
+
|
|
299
|
+
Importante: o App ID do OneSignal pode ficar no APK. A REST API Key nao deve ir no app, porque e segredo de servidor. Envie pushes pelo painel do OneSignal ou por um backend seu.
|
|
300
|
+
|
|
257
301
|
## Exemplo Minimo
|
|
258
302
|
|
|
259
303
|
Este repositorio ja inclui um exemplo em:
|
|
@@ -310,19 +354,77 @@ Retorno:
|
|
|
310
354
|
|
|
311
355
|
## Bridge Nativa
|
|
312
356
|
|
|
313
|
-
A v0.1 instala um plugin Cordova local com uma API global simples para recursos Android.
|
|
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
|
+
|
|
359
|
+
O html2apk injeta `cordova.js` automaticamente no HTML inicial do APK. Se uma funcao nativa for chamada antes do `deviceready`, a bridge espera o Android ficar pronto antes de executar.
|
|
360
|
+
|
|
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
|
+
|
|
363
|
+
Exemplos de aliases:
|
|
364
|
+
|
|
365
|
+
| PT-BR | English |
|
|
366
|
+
| --- | --- |
|
|
367
|
+
| `notificar()` | `notify()` |
|
|
368
|
+
| `agendarNotificacao()` | `scheduleNotification()` |
|
|
369
|
+
| `agendarNotificacoes()` | `scheduleNotifications()` |
|
|
370
|
+
| `agendarLoopNotificacoes()` | `scheduleNotificationLoop()` |
|
|
371
|
+
| `cancelarNotificacao()` | `cancelNotification()` |
|
|
372
|
+
| `solicitarPermissaoNotificacoes()` | `requestNotificationPermission()` |
|
|
373
|
+
| `solicitarPermissaoPush()` | `requestPushPermission()` |
|
|
374
|
+
| `aoClicarPush()` | `onPushClick()` |
|
|
375
|
+
| `identificarUsuarioPush()` | `loginPushUser()` |
|
|
376
|
+
| `adicionarTagPush()` | `addPushTag()` |
|
|
377
|
+
| `solicitarPermissaoMicrofone()` | `requestMicrophonePermission()` |
|
|
378
|
+
| `statusMicrofone()` | `microphoneStatus()` |
|
|
379
|
+
| `ouvirMic()` | `listenMic()` / `startMic()` |
|
|
380
|
+
| `pararMic()` | `stopMic()` |
|
|
381
|
+
| `lanterna()` | `flashlight()` |
|
|
382
|
+
| `alternarLanterna()` | `toggleFlashlight()` |
|
|
383
|
+
| `escolherArquivo()` | `pickFile()` |
|
|
384
|
+
| `escolherImagem()` | `pickImage()` |
|
|
385
|
+
| `salvarArquivo()` | `saveFile()` |
|
|
386
|
+
| `compartilhar()` | `share()` |
|
|
387
|
+
| `copiarTexto()` | `copyText()` |
|
|
388
|
+
| `lerTextoCopiado()` | `readText()` |
|
|
389
|
+
| `abrirNoApp()` | `openInApp()` |
|
|
390
|
+
| `abrirForaDoApp()` | `openOutsideApp()` |
|
|
391
|
+
| `abrirUrlExterno()` | `openExternalUrl()` |
|
|
392
|
+
| `manterTelaLigada()` | `keepScreenOn()` |
|
|
393
|
+
| `brilhoTela()` | `setScreenBrightness()` |
|
|
394
|
+
| `definirCorTema()` | `setThemeColor()` |
|
|
395
|
+
| `infoMemoria()` | `memoryInfo()` |
|
|
396
|
+
| `infoArmazenamento()` | `storageInfo()` |
|
|
397
|
+
| `infoDesempenho()` | `performanceInfo()` |
|
|
398
|
+
| `appsAbertos()` | `openAppsMemory()` |
|
|
399
|
+
| `aoEvento()` | `onEvent()` |
|
|
400
|
+
| `aoMinimizar()` | `onMinimize()` |
|
|
401
|
+
| `obterLinkInicial()` | `getInitialLink()` |
|
|
402
|
+
|
|
403
|
+
Os eventos tambem aceitam aliases em ingles em `onEvent()`: `app:ready`, `app:background`, `app:resumed`, `back:button`, `link:opened`, `network:changed`, `battery:changed`, `notification:received` e `notification:clicked`.
|
|
404
|
+
|
|
405
|
+
Como tratar retornos:
|
|
406
|
+
|
|
407
|
+
- Use `await` dentro de `try/catch` quando a acao depender de Android, permissao ou outro app instalado.
|
|
408
|
+
- Seletores de arquivo podem retornar `null`, array vazio ou objeto vazio quando o usuario cancela.
|
|
409
|
+
- Funcoes que precisam de permissao pedem automaticamente quando sao chamadas, como `lanterna()`, `notificar()` e `ouvirMic()`.
|
|
410
|
+
- Se o Android nao puder mostrar o pop-up de permissao, o html2apk abre a tela correta de configuracoes e retorna `settingsOpened: true`.
|
|
411
|
+
- APIs manuais de permissao continuam existindo para quem quer explicar antes; elas retornam objetos com `granted`, `requiresSettings` e `settingsOpened`.
|
|
412
|
+
- Eventos retornam uma funcao de cancelamento; guarde essa funcao e chame quando a tela/componente sair.
|
|
413
|
+
- Medidas de memoria, armazenamento e apps abertos sao diagnosticas. Android moderno pode limitar dados de outros apps por privacidade.
|
|
314
414
|
|
|
315
415
|
No seu JavaScript do app:
|
|
316
416
|
|
|
317
417
|
```js
|
|
318
|
-
await solicitarPermissaoNotificacoes();
|
|
319
|
-
|
|
320
418
|
toast("Mensagem");
|
|
321
419
|
vibrar(250);
|
|
322
420
|
|
|
323
421
|
notificar({
|
|
324
422
|
titulo: "Pedido aprovado",
|
|
325
423
|
texto: "Toque para abrir os detalhes",
|
|
424
|
+
acoes: [
|
|
425
|
+
{ id: "abrir", titulo: "Abrir" },
|
|
426
|
+
{ id: "cancelar", titulo: "Cancelar" }
|
|
427
|
+
],
|
|
326
428
|
aoClicar: {
|
|
327
429
|
acao: "abrir-rota",
|
|
328
430
|
rota: "/pedido/123",
|
|
@@ -340,9 +442,216 @@ agendarNotificacao({
|
|
|
340
442
|
}
|
|
341
443
|
});
|
|
342
444
|
|
|
445
|
+
await agendarNotificacoes([
|
|
446
|
+
{ titulo: "Primeiro aviso", texto: "Daqui 1 minuto", quando: Date.now() + 60000 },
|
|
447
|
+
{ titulo: "Segundo aviso", texto: "Daqui 2 minutos", quando: Date.now() + 120000 }
|
|
448
|
+
]);
|
|
449
|
+
|
|
450
|
+
const loop = await agendarLoopNotificacoes({
|
|
451
|
+
aCada: "12h",
|
|
452
|
+
notificacoes: [
|
|
453
|
+
{
|
|
454
|
+
titulo: "Hidrate-se",
|
|
455
|
+
texto: "Beba agua agora",
|
|
456
|
+
aoClicar: { acao: "abrir-hidratacao" }
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
titulo: "Alongamento",
|
|
460
|
+
texto: "Pausa rapida para alongar",
|
|
461
|
+
aoClicar: { acao: "abrir-alongamento" }
|
|
462
|
+
}
|
|
463
|
+
]
|
|
464
|
+
});
|
|
465
|
+
|
|
343
466
|
fullscreen(true);
|
|
344
467
|
```
|
|
345
468
|
|
|
469
|
+
`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
|
+
|
|
471
|
+
`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
|
+
|
|
473
|
+
```js
|
|
474
|
+
await cancelarNotificacao(loop.id);
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
Cliques continuam chegando normalmente:
|
|
478
|
+
|
|
479
|
+
```js
|
|
480
|
+
aoClicarNotificacao((evento) => {
|
|
481
|
+
if ((evento.aoClicar || evento.onClick).acao === "abrir-hidratacao") {
|
|
482
|
+
abrirNoApp("#/hidratacao");
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
As notificacoes usam o icone do APK gerado como icone nativo. No Android, esse icone pode aparecer monocromatico na barra de notificacoes por regra do sistema.
|
|
488
|
+
|
|
489
|
+
OneSignal, quando `oneSignalAppId` esta preenchido:
|
|
490
|
+
|
|
491
|
+
```js
|
|
492
|
+
const permitido = await solicitarPermissaoPush();
|
|
493
|
+
|
|
494
|
+
identificarUsuarioPush("user-123");
|
|
495
|
+
adicionarTagPush("plano", "premium");
|
|
496
|
+
|
|
497
|
+
const pararPush = aoClicarPush((evento) => {
|
|
498
|
+
console.log("Push clicado", evento);
|
|
499
|
+
abrirNoApp("#/notificacoes");
|
|
500
|
+
});
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Use OneSignal para pushes enviados remotamente pelo painel/API do OneSignal. Use `notificar()` e `agendarNotificacao()` para notificacoes locais criadas dentro do APK.
|
|
504
|
+
|
|
505
|
+
Arquivos, galeria e compartilhamento:
|
|
506
|
+
|
|
507
|
+
```js
|
|
508
|
+
const imagem = await escolherImagem();
|
|
509
|
+
const imagens = await escolherImagens({ multiplo: true });
|
|
510
|
+
const pdf = await escolherArquivo({ tipos: ["application/pdf"] });
|
|
511
|
+
const arquivos = await escolherArquivos({ multiplo: true });
|
|
512
|
+
|
|
513
|
+
await salvarArquivo({
|
|
514
|
+
nome: "relatorio.txt",
|
|
515
|
+
mimeType: "text/plain",
|
|
516
|
+
conteudo: "Conteudo salvo pelo app"
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
await compartilhar({ texto: "Veja isso", url: "https://exemplo.com" });
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
O retorno de arquivos tem este formato:
|
|
523
|
+
|
|
524
|
+
```json
|
|
525
|
+
{
|
|
526
|
+
"uri": "content://...",
|
|
527
|
+
"name": "foto.png",
|
|
528
|
+
"nome": "foto.png",
|
|
529
|
+
"size": 12345,
|
|
530
|
+
"tamanho": 12345,
|
|
531
|
+
"mimeType": "image/png"
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Microfone:
|
|
536
|
+
|
|
537
|
+
```js
|
|
538
|
+
const inicio = await ouvirMic();
|
|
539
|
+
if (inicio.settingsOpened) {
|
|
540
|
+
console.log("Libere Microfone nas configuracoes e tente novamente");
|
|
541
|
+
} else {
|
|
542
|
+
// ... depois, quando quiser parar
|
|
543
|
+
const audio = await pararMic();
|
|
544
|
+
const audioUrl = `data:${audio.mimeType};base64,${audio.base64}`;
|
|
545
|
+
|
|
546
|
+
const player = new Audio(audioUrl);
|
|
547
|
+
player.play();
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
`ouvirMic()` comeca a gravar e tambem pede permissao se ela ainda nao foi concedida. Se a permissao estiver bloqueada pelo Android, a tela de configuracoes do app e aberta automaticamente e o retorno traz `settingsOpened: true`.
|
|
552
|
+
|
|
553
|
+
`pararMic()` encerra a gravacao e retorna:
|
|
554
|
+
|
|
555
|
+
```json
|
|
556
|
+
{
|
|
557
|
+
"base64": "AAAA...",
|
|
558
|
+
"mimeType": "audio/mp4",
|
|
559
|
+
"extension": "m4a",
|
|
560
|
+
"size": 12345,
|
|
561
|
+
"durationMs": 3200
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Para tratar o retorno, use `mimeType` e `base64` juntos em um Data URL quando quiser tocar, baixar ou enviar o audio. Se `pararMic()` for chamado rapido demais, o Android pode nao conseguir finalizar o arquivo; aguarde alguns instantes apos `ouvirMic()`.
|
|
566
|
+
|
|
567
|
+
Lanterna, tela, clipboard e intents:
|
|
568
|
+
|
|
569
|
+
```js
|
|
570
|
+
const lanternaStatus = await lanterna(true);
|
|
571
|
+
await alternarLanterna();
|
|
572
|
+
|
|
573
|
+
await manterTelaLigada(true);
|
|
574
|
+
await brilhoTela(0.8);
|
|
575
|
+
|
|
576
|
+
await copiarTexto("codigo123");
|
|
577
|
+
const texto = await lerTextoCopiado();
|
|
578
|
+
|
|
579
|
+
await abrirNoApp("/sobre.html");
|
|
580
|
+
await abrirNoApp("#/pedido/123", { substituir: true });
|
|
581
|
+
await abrirForaDoApp("https://exemplo.com");
|
|
582
|
+
await abrirWhatsapp("559999999999", "Oi");
|
|
583
|
+
await discar("11999999999");
|
|
584
|
+
await abrirMapa("Avenida Paulista, Sao Paulo");
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
Use `abrirNoApp()`/`openInApp()` quando a navegacao deve acontecer dentro do proprio APK/WebView. Use `abrirForaDoApp()`/`openOutsideApp()` ou `abrirUrlExterno()`/`openExternalUrl()` quando quer mandar o usuario para navegador, WhatsApp, Maps ou outro app Android.
|
|
588
|
+
|
|
589
|
+
Informacoes e desempenho:
|
|
590
|
+
|
|
591
|
+
```js
|
|
592
|
+
const aparelho = await infoDispositivo();
|
|
593
|
+
const rede = await infoRede();
|
|
594
|
+
const bateria = await infoBateria();
|
|
595
|
+
const memoria = await infoMemoria();
|
|
596
|
+
const armazenamento = await infoArmazenamento();
|
|
597
|
+
const desempenho = await infoDesempenho();
|
|
598
|
+
const abertos = await appsAbertos();
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
`infoDesempenho()` agrupa memoria, armazenamento, bateria, rede e `timestamp`. Valores de memoria e armazenamento retornam bytes.
|
|
602
|
+
|
|
603
|
+
`appsAbertos()` retorna os processos/apps que o Android permite o APK enxergar:
|
|
604
|
+
|
|
605
|
+
```json
|
|
606
|
+
{
|
|
607
|
+
"apps": [
|
|
608
|
+
{
|
|
609
|
+
"name": "MeuApp",
|
|
610
|
+
"packageName": "com.seuapp.meuapp",
|
|
611
|
+
"ramBytes": 12345678,
|
|
612
|
+
"ramMb": 11.77,
|
|
613
|
+
"importanceName": "foreground"
|
|
614
|
+
}
|
|
615
|
+
],
|
|
616
|
+
"porNome": {
|
|
617
|
+
"MeuApp": {
|
|
618
|
+
"ramBytes": 12345678,
|
|
619
|
+
"ramMb": 11.77
|
|
620
|
+
}
|
|
621
|
+
},
|
|
622
|
+
"limited": true
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
Por privacidade, Android moderno pode limitar essa lista ao proprio app e alguns processos visiveis ao APK. Entao essa funcao nao deve ser tratada como gerenciador completo de tarefas do sistema.
|
|
627
|
+
|
|
628
|
+
Eventos nativos:
|
|
629
|
+
|
|
630
|
+
```js
|
|
631
|
+
const parar = aoEvento("app:background", (evento) => {
|
|
632
|
+
console.log("App saiu da frente", evento.timestamp);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
aoEvento("app:voltou", console.log);
|
|
636
|
+
aoEvento("botao:voltar", console.log);
|
|
637
|
+
aoEvento("link:aberto", (evento) => console.log(evento.url));
|
|
638
|
+
aoEvento("rede:mudou", console.log);
|
|
639
|
+
aoEvento("bateria:mudou", console.log);
|
|
640
|
+
aoEvento("notificacao:clicada", console.log);
|
|
641
|
+
|
|
642
|
+
parar();
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
Deep links:
|
|
646
|
+
|
|
647
|
+
```js
|
|
648
|
+
const linkInicial = await obterLinkInicial();
|
|
649
|
+
|
|
650
|
+
aoAbrirLink((evento) => {
|
|
651
|
+
console.log(evento.url, evento.path, evento.query);
|
|
652
|
+
});
|
|
653
|
+
```
|
|
654
|
+
|
|
346
655
|
Clique em notificacao:
|
|
347
656
|
|
|
348
657
|
```js
|
|
@@ -361,9 +670,7 @@ Permissoes e alarmes:
|
|
|
361
670
|
|
|
362
671
|
```js
|
|
363
672
|
const status = await statusPermissaoNotificacoes();
|
|
364
|
-
|
|
365
|
-
await solicitarPermissaoNotificacoes();
|
|
366
|
-
}
|
|
673
|
+
console.log(status.granted);
|
|
367
674
|
|
|
368
675
|
const podeUsarAlarmeExato = await podeAgendarNotificacaoExata();
|
|
369
676
|
if (!podeUsarAlarmeExato) {
|
|
@@ -371,7 +678,7 @@ if (!podeUsarAlarmeExato) {
|
|
|
371
678
|
}
|
|
372
679
|
```
|
|
373
680
|
|
|
374
|
-
A bridge cria canal de notificacao, solicita
|
|
681
|
+
A bridge cria canal de notificacao, solicita `POST_NOTIFICATIONS` automaticamente quando `notificar()`/`agendarNotificacao()` precisam, abre configuracoes se o Android bloquear o pop-up, abre o app com payload quando a notificacao e clicada, persiste notificacoes agendadas e tenta reagendar apos reboot ou update do app. Se voce usar `exato: true`/`exact: true` em uma notificacao agendada e o Android exigir liberacao manual de alarme exato, o html2apk abre essa tela automaticamente.
|
|
375
682
|
|
|
376
683
|
## Problemas Comuns
|
|
377
684
|
|
|
@@ -442,11 +749,15 @@ Fluxo da interface:
|
|
|
442
749
|
1. Arraste ou escolha a pasta do projeto.
|
|
443
750
|
2. O app mostra `verificando ambiente` antes de liberar as proximas etapas.
|
|
444
751
|
3. Se faltarem pacotes do Android SDK, ele pede permissao e tenta baixar/instalar mostrando logs.
|
|
445
|
-
4. Preencha as configuracoes obrigatorias: nome do app, Package ID, versao
|
|
446
|
-
5. Revise
|
|
752
|
+
4. Preencha as configuracoes obrigatorias: nome do app, Package ID, versao e modo. Se voce nao escolher icone, o html2apk usa o icone padrao da ferramenta.
|
|
753
|
+
5. Revise e clique em `Gerar APK` para salvar o APK em `dist`, ou `Testar no USB` para gerar debug, instalar e abrir direto em um celular conectado.
|
|
447
754
|
6. Ao concluir, a tela final mostra o APK gerado e botoes para abrir a pasta `dist` ou localizar o arquivo.
|
|
448
755
|
|
|
449
|
-
O PNG escolhido e usado como icone do aplicativo e tambem como imagem da tela inicial do Android, evitando o splash padrao do Cordova.
|
|
756
|
+
O PNG escolhido e usado como icone do aplicativo e tambem como imagem da tela inicial do Android, evitando o splash padrao do Cordova. Quando nenhum PNG e escolhido, o `html2apk.png` da propria ferramenta entra como fallback.
|
|
757
|
+
|
|
758
|
+
Depois que a pasta foi escolhida, a interface acompanha mudancas nela automaticamente. Edicoes em HTML, CSS, JS e assets entram no proximo build sem arrastar a pasta de novo. Se `app.json` ou `config.json` mudar, os campos da tela de configuracoes sao recarregados.
|
|
759
|
+
|
|
760
|
+
Para `Testar no USB`, o celular precisa estar com `Opcoes do desenvolvedor > Depuracao USB` ativa. Ao conectar, desbloqueie o aparelho e aceite a chave RSA. Se o Android aparecer como `unauthorized` ou `offline`, a interface mostra o que fazer nos logs.
|
|
450
761
|
|
|
451
762
|
Os logs podem ser abertos em uma barra inferior durante qualquer etapa pelo botao `Mostrar logs`. Se atrapalhar a visualizacao, use `Ocultar logs` e a area principal volta a ocupar a altura da janela.
|
|
452
763
|
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "html2apk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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": {
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"node": ">=18"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"cordova": "^13.0.0"
|
|
74
|
+
"cordova": "^13.0.0",
|
|
75
|
+
"onesignal-cordova-plugin": "^5.3.10"
|
|
75
76
|
}
|
|
76
77
|
}
|
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|floating] [--orientation vertical|horizontal] [--min-sdk 24] [--android-platform android@15.0.0]
|
|
13
|
+
html2apk build [--release] [--debug] [--mode fullscreen|standalone|floating] [--theme fixed|auto] [--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.`);
|
|
@@ -34,6 +34,9 @@ function parseBuildArgs(args) {
|
|
|
34
34
|
} else if (arg === "--theme-color") {
|
|
35
35
|
options.themeColor = args[index + 1];
|
|
36
36
|
index += 1;
|
|
37
|
+
} else if (arg === "--theme" || arg === "--theme-mode") {
|
|
38
|
+
options.themeMode = args[index + 1];
|
|
39
|
+
index += 1;
|
|
37
40
|
} else if (arg === "--min-sdk" || arg === "--min-sdk-version" || arg === "--minSdkVersion") {
|
|
38
41
|
options.minSdkVersion = args[index + 1];
|
|
39
42
|
index += 1;
|
|
@@ -76,6 +79,12 @@ function createPlaceholderConfig(projectName = "MeuApp") {
|
|
|
76
79
|
orientation: "default",
|
|
77
80
|
minSdkVersion: 24,
|
|
78
81
|
themeColor: "#126fff",
|
|
82
|
+
themeMode: "fixed",
|
|
83
|
+
oneSignalAppId: "",
|
|
84
|
+
deepLinks: {
|
|
85
|
+
schemes: [],
|
|
86
|
+
appLinks: []
|
|
87
|
+
},
|
|
79
88
|
icon: "",
|
|
80
89
|
splash: "",
|
|
81
90
|
permissions: [
|
|
@@ -77,6 +77,59 @@ function renderAndroidSplashPreferences(options) {
|
|
|
77
77
|
].filter(Boolean).join("\n");
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function renderDeepLinkData(pathItem) {
|
|
81
|
+
if (!pathItem || pathItem === "*" || pathItem === "/*") {
|
|
82
|
+
return "";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (pathItem.includes("*")) {
|
|
86
|
+
return ` android:pathPattern="${xmlEscape(pathItem.replace(/\*/g, ".*"))}"`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return ` android:pathPrefix="${xmlEscape(pathItem)}"`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function renderDeepLinkIntentFilters(deepLinks = {}) {
|
|
93
|
+
const filters = [];
|
|
94
|
+
const schemes = Array.isArray(deepLinks.schemes) ? deepLinks.schemes : [];
|
|
95
|
+
const appLinks = Array.isArray(deepLinks.appLinks) ? deepLinks.appLinks : [];
|
|
96
|
+
|
|
97
|
+
for (const scheme of schemes) {
|
|
98
|
+
filters.push(` <config-file target="AndroidManifest.xml" parent="/manifest/application/activity">
|
|
99
|
+
<intent-filter>
|
|
100
|
+
<action android:name="android.intent.action.VIEW" />
|
|
101
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
102
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
103
|
+
<data android:scheme="${xmlEscape(scheme)}" />
|
|
104
|
+
</intent-filter>
|
|
105
|
+
</config-file>`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const link of appLinks) {
|
|
109
|
+
if (!link || !link.host) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const scheme = link.scheme || "https";
|
|
114
|
+
const paths = Array.isArray(link.paths) && link.paths.length ? link.paths : [""];
|
|
115
|
+
const dataItems = paths
|
|
116
|
+
.map((pathItem) => ` <data android:scheme="${xmlEscape(scheme)}" android:host="${xmlEscape(link.host)}"${renderDeepLinkData(pathItem)} />`)
|
|
117
|
+
.join("\n");
|
|
118
|
+
const verify = link.autoVerify ? " android:autoVerify=\"true\"" : "";
|
|
119
|
+
|
|
120
|
+
filters.push(` <config-file target="AndroidManifest.xml" parent="/manifest/application/activity">
|
|
121
|
+
<intent-filter${verify}>
|
|
122
|
+
<action android:name="android.intent.action.VIEW" />
|
|
123
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
124
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
125
|
+
${dataItems}
|
|
126
|
+
</intent-filter>
|
|
127
|
+
</config-file>`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return filters.join("\n");
|
|
131
|
+
}
|
|
132
|
+
|
|
80
133
|
function renderConfigXml(options) {
|
|
81
134
|
const fullscreen = options.mode === "fullscreen" ? "true" : "false";
|
|
82
135
|
const permissionsList = options.mode === "floating"
|
|
@@ -85,8 +138,11 @@ function renderConfigXml(options) {
|
|
|
85
138
|
const permissions = renderPermissions(permissionsList);
|
|
86
139
|
const icon = renderIcon(options.icon);
|
|
87
140
|
const splashPreferences = renderAndroidSplashPreferences(options);
|
|
88
|
-
const
|
|
141
|
+
const deepLinkIntentFilters = renderDeepLinkIntentFilters(options.deepLinks);
|
|
142
|
+
const platformItems = [permissions, deepLinkIntentFilters, icon, splashPreferences].filter(Boolean).join("\n");
|
|
89
143
|
const backgroundPreference = renderPreference("BackgroundColor", options.themeColor || options.backgroundColor);
|
|
144
|
+
const themeModePreference = renderPreference("Html2ApkThemeMode", options.themeMode || options.theme || "fixed");
|
|
145
|
+
const oneSignalPreference = renderPreference("Html2ApkOneSignalAppId", options.oneSignalAppId);
|
|
90
146
|
const modePreference = renderPreference("Html2ApkMode", options.mode || "standalone");
|
|
91
147
|
const minSdkPreference = renderPreference(
|
|
92
148
|
"android-minSdkVersion",
|
|
@@ -124,6 +180,8 @@ function renderConfigXml(options) {
|
|
|
124
180
|
<preference name="GradlePluginKotlinEnabled" value="true" />
|
|
125
181
|
${minSdkPreference}
|
|
126
182
|
${modePreference}
|
|
183
|
+
${themeModePreference}
|
|
184
|
+
${oneSignalPreference}
|
|
127
185
|
${orientationPreference ? `${orientationPreference}\n` : ""}${backgroundPreference ? `${backgroundPreference}\n` : ""}
|
|
128
186
|
|
|
129
187
|
<platform name="android">
|
package/src/cordova/project.js
CHANGED
|
@@ -47,10 +47,28 @@ async function buildAndroid(buildDir, options, buildJsonPath, runner) {
|
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
async function runAndroidDevice(buildDir, options, buildJsonPath, runner, deviceId) {
|
|
51
|
+
const args = ["run", "android", "--device", "--debug", "--no-telemetry"];
|
|
52
|
+
|
|
53
|
+
if (deviceId) {
|
|
54
|
+
args.push(`--target=${deviceId}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (buildJsonPath) {
|
|
58
|
+
args.push("--buildConfig", buildJsonPath);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await runner.run("cordova", args, {
|
|
62
|
+
cwd: buildDir,
|
|
63
|
+
pipeOutput: options.debug
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
50
67
|
module.exports = {
|
|
51
68
|
DEFAULT_ANDROID_PLATFORM,
|
|
52
69
|
createCordovaProject,
|
|
53
70
|
addCordovaPlugin,
|
|
54
71
|
addAndroidPlatform,
|
|
55
|
-
buildAndroid
|
|
72
|
+
buildAndroid,
|
|
73
|
+
runAndroidDevice
|
|
56
74
|
};
|