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 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
- "permissions": ["INTERNET", "CAMERA", "POST_NOTIFICATIONS", "VIBRATE"],
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
- if (!status.granted) {
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/consulta `POST_NOTIFICATIONS` no Android 13+, abre o app com payload quando a notificacao e clicada, persiste notificacoes agendadas e tenta reagendar apos reboot ou update do app.
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, modo e icone PNG.
446
- 5. Revise, clique em `Gerar APK` e acompanhe a barra de progresso.
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
 
@@ -7,6 +7,8 @@
7
7
  "orientation": "default",
8
8
  "minSdkVersion": 24,
9
9
  "themeColor": "#126fff",
10
+ "themeMode": "fixed",
11
+ "oneSignalAppId": "",
10
12
  "icon": "",
11
13
  "splash": "",
12
14
  "permissions": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "html2apk",
3
- "version": "0.2.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 platformItems = [permissions, icon, splashPreferences].filter(Boolean).join("\n");
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">
@@ -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
  };