html2apk 0.9.0 → 12.0.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 +253 -12
- package/package.json +16 -4
- package/src/desktop/main.js +561 -29
- package/src/desktop/renderer/index.html +1 -1
- package/src/desktop/renderer/renderer.js +629 -35
- package/src/desktop/renderer/styles.css +13 -3
- package/src/runtime-manager/index.js +79 -17
- package/src/templates/cordova-plugin-html2apk-bridge/plugin.xml +34 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/BootReceiver.java +13 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/FloatingIconService.java +34 -7
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/Html2ApkBridge.java +5365 -1641
- package/src/templates/cordova-plugin-html2apk-bridge/www/html2apk-bridge.js +528 -12
- package/src/templates/html2apk-early-bridge.js +528 -12
- package/src/templates/html2apk-runtime-console.js +169 -29
- package/examples/minimal/dist/MeuApp-1.0.0-debug.apk +0 -0
- package/examples/minimal/dist/MeuApp-1.0.0-release.aab +0 -0
package/README.md
CHANGED
|
@@ -4,6 +4,50 @@
|
|
|
4
4
|
|
|
5
5
|
Use ele quando voce ja tem um app web, por exemplo uma pasta com `index.html`, `style.css`, `app.js` e imagens, e quer gerar um `.apk` instalavel no Android ou um `.aab` para loja.
|
|
6
6
|
|
|
7
|
+
## Como contribuir sem quebrar o padrão
|
|
8
|
+
|
|
9
|
+
O html2apk agora é um projeto aberto, mas a regra mais importante para novas features é simples: antes de implementar, entenda como o código atual trabalha. Evite criar uma segunda arquitetura para resolver algo que a ponte existente já resolve.
|
|
10
|
+
|
|
11
|
+
Antes de mandar qualquer feature nova, o contribuinte precisa estudar o fluxo atual da aplicação e confirmar que a solução segue a mesma estratégia das funções existentes. Não envie código que cria atalhos, caminhos paralelos, outro padrão de bridge, outro jeito de tratar permissão ou outra forma de comunicar JavaScript com Java sem uma justificativa técnica muito clara. Primeiro adapte a ideia ao desenho que já funciona no projeto; só proponha uma abordagem nova se o padrão atual realmente não atender.
|
|
12
|
+
|
|
13
|
+
Exemplo de postura esperada: "quero colocar uma nova função no projeto; antes de escrever, vou ver como as outras funções foram colocadas, como o código lida com elas, qual é o fluxo completo e por que esse fluxo existe". Essa investigação vem antes da implementação. Ela mostra onde a nova função deve nascer, como deve normalizar argumentos, qual action deve chamar, como o Java deve responder, onde documentar e como testar. É esse cuidado que permite criar algo novo sem quebrar a aplicação.
|
|
14
|
+
|
|
15
|
+
As funções interpretadas seguem um caminho bem definido:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
JavaScript do app
|
|
19
|
+
-> função global em português
|
|
20
|
+
-> aliases quando fizer sentido
|
|
21
|
+
-> normalização de argumentos
|
|
22
|
+
-> cordova.exec(action)
|
|
23
|
+
-> dispatcher Java
|
|
24
|
+
-> permissão/thread/subsistema Android
|
|
25
|
+
-> CallbackContext
|
|
26
|
+
-> Promise no JavaScript
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Quando uma feature nova entra, ela deve atravessar esse caminho em vez de criar uma forma paralela de comunicação. Isso evita duplicação, bugs difíceis de testar e diferença de comportamento entre o early bridge, o plugin Cordova, a interface desktop e o app final.
|
|
30
|
+
|
|
31
|
+
Checklist antes de abrir PR ou commit:
|
|
32
|
+
|
|
33
|
+
- Entenda a arquitetura existente antes de alterar arquivos. Se ainda não sabe por onde uma função interpretada nasce, passa pela bridge e chega ao Java, pare e leia o código antes de implementar.
|
|
34
|
+
- Confirme que a feature nova não está criando conflito com APIs, helpers, normalizadores, actions ou eventos que já existem.
|
|
35
|
+
- Evite estratégias novas desnecessárias. Se uma função parecida já usa `cordova.exec`, dispatcher Java, `CallbackContext`, permissão pendente ou evento `CustomEvent`, a nova feature deve seguir esse caminho.
|
|
36
|
+
- Leia funções parecidas antes de escrever código novo. Se for arquivo, veja `salvarArquivo`, `baixarArquivo` e `FileProvider`. Se for permissão, veja câmera, microfone, notificação e localização. Se for evento, veja `aoEvento`, notificação e compartilhamento recebido.
|
|
37
|
+
- Mantenha nomes em PT-BR como API principal e aliases em inglês apenas quando combinarem com o padrão existente.
|
|
38
|
+
- Adicione a função no early bridge e no plugin JS com a mesma assinatura pública.
|
|
39
|
+
- Reuse normalizadores e helpers existentes; não trate payload com string solta se o projeto já usa objeto estruturado.
|
|
40
|
+
- No Java, entre pelo dispatcher de `action` existente e retorne JSON consistente.
|
|
41
|
+
- Se precisar de permissão runtime, preserve callback pendente, busy state e abertura de configurações quando o Android exigir.
|
|
42
|
+
- Se tocar arquivos, preserve sanitização de nome, armazenamento interno, MediaStore quando aplicável e FileProvider.
|
|
43
|
+
- Se tocar operações longas, use a thread adequada e não trave a WebView.
|
|
44
|
+
- Atualize `plugin.xml` apenas com permissões, intent filters ou dependências realmente necessárias.
|
|
45
|
+
- Atualize runtime console, aba "Códigos interpretados", laboratório USB, README/SOBRE quando a feature for pública.
|
|
46
|
+
- Adicione ou ajuste testes em `test/config.test.js` para provar que JS, Java, UI e docs continuam alinhados.
|
|
47
|
+
- Rode `npm test` antes de enviar.
|
|
48
|
+
|
|
49
|
+
O objetivo não é impedir criatividade; é proteger o comportamento previsível da ferramenta. Uma contribuição boa parece nativa no projeto: usa os mesmos nomes, passa pelos mesmos pontos, retorna no mesmo formato e respeita as mesmas fronteiras entre build, desktop, bridge JS e Java Android.
|
|
50
|
+
|
|
7
51
|
## O Mais Importante
|
|
8
52
|
|
|
9
53
|
Na pratica, voce vai usar estes comandos:
|
|
@@ -303,6 +347,13 @@ O icone flutuante usa a permissao especial `SYSTEM_ALERT_WINDOW`. A bridge nativ
|
|
|
303
347
|
|
|
304
348
|
Mesmo com a permissao declarada no manifesto, o usuario ainda precisa liberar manualmente nas configuracoes do Android. Depois de liberar, volte ao app e chame `iniciarIconeFlutuante()` novamente.
|
|
305
349
|
|
|
350
|
+
O icone tambem aceita opacidade:
|
|
351
|
+
|
|
352
|
+
```js
|
|
353
|
+
await iniciarIconeFlutuante({ opacidade: 0.85 });
|
|
354
|
+
await definirOpacidadeIconeFlutuante(0.55);
|
|
355
|
+
```
|
|
356
|
+
|
|
306
357
|
### Tema Automatico Do APK
|
|
307
358
|
|
|
308
359
|
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.
|
|
@@ -386,7 +437,7 @@ Retorno:
|
|
|
386
437
|
|
|
387
438
|
## Bridge Nativa
|
|
388
439
|
|
|
389
|
-
A v0.1 instala um plugin Cordova local com uma API global simples para recursos Android. Todas as
|
|
440
|
+
A v0.1 instala um plugin Cordova local com uma API global simples para recursos Android. Todas as funções retornam `Promise`, exceto os ouvintes `aoEvento`/atalhos, que retornam uma função para cancelar a escuta.
|
|
390
441
|
|
|
391
442
|
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.
|
|
392
443
|
|
|
@@ -417,6 +468,8 @@ Exemplos de aliases:
|
|
|
417
468
|
| `escanearQRCode()` | `scanQRCode()` |
|
|
418
469
|
| `escolherArquivo()` | `pickFile()` |
|
|
419
470
|
| `escolherImagem()` | `pickImage()` |
|
|
471
|
+
| `escolherImagens()` | `pickImages()` |
|
|
472
|
+
| `escolherPasta()` | `pickFolder()` |
|
|
420
473
|
| `salvarArquivo()` | `saveFile()` |
|
|
421
474
|
| `lerArquivo()` | `readFile()` |
|
|
422
475
|
| `listarArquivos()` | `listFiles()` |
|
|
@@ -429,7 +482,28 @@ Exemplos de aliases:
|
|
|
429
482
|
| `definirPapelParede()` | `setWallpaper()` |
|
|
430
483
|
| `infoPapelParede()` | `wallpaperInfo()` |
|
|
431
484
|
| `abrirConfiguracaoPapelParede()` | `openWallpaperSettings()` |
|
|
485
|
+
| `capturarTela()` / `tirarPrint()` | `captureScreen()` / `takeScreenshot()` |
|
|
432
486
|
| `compartilhar()` | `share()` |
|
|
487
|
+
| `compartilharApp()` / `share_me()` | `shareApp()` |
|
|
488
|
+
| `aoReceberCompartilhamento()` | `onShareReceived()` |
|
|
489
|
+
| `obterCompartilhamentoInicial()` | `getInitialShare()` |
|
|
490
|
+
| `procurarBT()` | `scanBluetooth()` |
|
|
491
|
+
| `conectarBT()` | `connectBluetooth()` |
|
|
492
|
+
| `enviarBT()` | `sendBluetooth()` |
|
|
493
|
+
| `aoConectarBT()` | `onBluetoothConnect()` |
|
|
494
|
+
| `aoReceberDadosBT()` | `onBluetoothData()` |
|
|
495
|
+
| `aoDarErroBT()` | `onBluetoothError()` |
|
|
496
|
+
| `procurarWiFi()` | `scanWiFi()` |
|
|
497
|
+
| `conectarWiFi()` | `connectWiFi()` |
|
|
498
|
+
| `enviarWiFi()` | `sendWiFi()` |
|
|
499
|
+
| `aoConectarWiFi()` | `onWiFiConnect()` |
|
|
500
|
+
| `aoReceberDadosWiFi()` | `onWiFiData()` |
|
|
501
|
+
| `aoDarErroWiFi()` | `onWiFiError()` |
|
|
502
|
+
| `ocr()` | `recognizeText()` / `textFromImage()` |
|
|
503
|
+
| `falar()` | `speak()` / `textToSpeech()` |
|
|
504
|
+
| `pararFala()` | `stopSpeaking()` |
|
|
505
|
+
| `ouvir()` | `recognizeSpeech()` / `speechToText()` |
|
|
506
|
+
| `aguardar()` | `loading()` |
|
|
433
507
|
| `copiarTexto()` | `copyText()` |
|
|
434
508
|
| `lerTextoCopiado()` | `readText()` |
|
|
435
509
|
| `abrirNoApp()` | `openInApp()` |
|
|
@@ -438,22 +512,47 @@ Exemplos de aliases:
|
|
|
438
512
|
| `manterTelaLigada()` | `keepScreenOn()` |
|
|
439
513
|
| `brilhoTela()` | `setScreenBrightness()` |
|
|
440
514
|
| `definirCorTema()` | `setThemeColor()` |
|
|
515
|
+
| `volumeAtual()` | `getVolume()` |
|
|
516
|
+
| `definirVolume()` | `setVolume()` |
|
|
517
|
+
| `aumentarVolume()` | `increaseVolume()` |
|
|
518
|
+
| `diminuirVolume()` | `decreaseVolume()` |
|
|
441
519
|
| `infoMemoria()` | `memoryInfo()` |
|
|
442
520
|
| `infoArmazenamento()` | `storageInfo()` |
|
|
443
521
|
| `infoDesempenho()` | `performanceInfo()` |
|
|
444
522
|
| `appsAbertos()` | `openAppsMemory()` |
|
|
523
|
+
| `configurarIconeFlutuante()` | `configureFloatingIcon()` |
|
|
524
|
+
| `definirOpacidadeIconeFlutuante()` | `setFloatingIconOpacity()` |
|
|
525
|
+
| `minimizarApp()` | `minimizeApp()` |
|
|
526
|
+
| `fecharApp()` | `closeApp()` / `exitApp()` |
|
|
445
527
|
| `obterLocalizacao()` | `getLocation()` |
|
|
446
528
|
| `acompanharLocalizacao()` | `watchLocation()` |
|
|
447
529
|
| `pararLocalizacao()` | `stopLocationWatch()` |
|
|
448
530
|
| `autenticarBiometria()` | `authenticateBiometric()` |
|
|
531
|
+
| `solicitarBloqueio()` | `requestDeviceLock()` |
|
|
532
|
+
| `solicitarSegundoPlano()` | `requestBackgroundExecution()` |
|
|
533
|
+
| `configurarInicioAutomatico()` | `setAutoStartOnBoot()` |
|
|
449
534
|
| `salvarSeguro()` | `saveSecure()` |
|
|
450
535
|
| `lerSeguro()` | `readSecure()` |
|
|
451
536
|
| `removerSeguro()` | `deleteSecure()` |
|
|
452
537
|
| `aoEvento()` | `onEvent()` |
|
|
453
538
|
| `aoMinimizar()` | `onMinimize()` |
|
|
539
|
+
| `aoConectarUSB()` | `onUSBConnect()` |
|
|
540
|
+
| `aoDesconectarUSB()` | `onUSBDisconnect()` |
|
|
541
|
+
| `aoConectarFone()` | `onHeadphoneConnect()` |
|
|
542
|
+
| `aoDesconectarFone()` | `onHeadphoneDisconnect()` |
|
|
543
|
+
| `aoMudarVolume()` | `onVolumeChange()` |
|
|
544
|
+
| `aoAbrirTeclado()` | `onKeyboardOpen()` |
|
|
545
|
+
| `aoFecharTeclado()` | `onKeyboardClose()` |
|
|
546
|
+
| `aoSacudirCelular()` | `onPhoneShake()` |
|
|
547
|
+
| `aoVirarCelularParaBaixo()` | `onPhoneFaceDown()` |
|
|
548
|
+
| `aoAproximarObjeto()` | `onProximityNear()` |
|
|
549
|
+
| `aoTirarPrint()` | `onScreenshot()` |
|
|
550
|
+
| `aoMudarOrientacao()` | `onOrientationChange()` |
|
|
551
|
+
| `aoNFC()` | `onNFC()` |
|
|
552
|
+
| `aoReceberNotificacao()` | `onNotificationReceived()` |
|
|
454
553
|
| `obterLinkInicial()` | `getInitialLink()` |
|
|
455
554
|
|
|
456
|
-
Os eventos tambem aceitam aliases em ingles em `onEvent()`: `app:ready`, `app:background`, `app:resumed`, `back:button`, `link:opened`, `network:changed`, `battery:changed`, `location:changed`, `notification:received` e `notification:clicked`.
|
|
555
|
+
Os eventos tambem aceitam aliases em ingles em `onEvent()`: `app:ready`, `app:background`, `app:resumed`, `back:button`, `link:opened`, `share:received`, `sharing:received`, `bluetooth:connected`, `bluetooth:data`, `bluetooth:error`, `wifi:connected`, `wifi:data`, `wifi:error`, `usb:connected`, `usb:disconnected`, `headphone:connected`, `headphone:disconnected`, `volume:changed`, `keyboard:opened`, `keyboard:closed`, `phone:shaken`, `phone:faceDown`, `proximity:near`, `screenshot:taken`, `orientation:changed`, `nfc:received`, `network:changed`, `battery:changed`, `location:changed`, `notification:received` e `notification:clicked`.
|
|
457
556
|
|
|
458
557
|
Como tratar retornos:
|
|
459
558
|
|
|
@@ -470,6 +569,7 @@ No seu JavaScript do app:
|
|
|
470
569
|
```js
|
|
471
570
|
toast("Mensagem");
|
|
472
571
|
vibrar(250);
|
|
572
|
+
await aguardar(5000);
|
|
473
573
|
|
|
474
574
|
await notificar({
|
|
475
575
|
titulo: "Pedido aprovado",
|
|
@@ -532,6 +632,14 @@ fullscreen(true);
|
|
|
532
632
|
|
|
533
633
|
`notificar()` nao obriga clique, botao nem funcao. So `titulo` e `texto` ja geram uma notificacao normal. `aoClicar`, `acoes`/`actions` e `open` sao opcionais.
|
|
534
634
|
|
|
635
|
+
`aguardar(ms)` e `loading(ms)` pausam o fluxo com Promise, sem travar a WebView:
|
|
636
|
+
|
|
637
|
+
```js
|
|
638
|
+
await toast("Comecando");
|
|
639
|
+
await aguardar(5000);
|
|
640
|
+
await toast("Continuando");
|
|
641
|
+
```
|
|
642
|
+
|
|
535
643
|
`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.
|
|
536
644
|
|
|
537
645
|
Em `aoClicar`, voce pode passar uma funcao diretamente:
|
|
@@ -636,10 +744,11 @@ Use OneSignal para pushes enviados remotamente pelo painel/API do OneSignal. Use
|
|
|
636
744
|
Arquivos, galeria e compartilhamento:
|
|
637
745
|
|
|
638
746
|
```js
|
|
639
|
-
const imagem = await escolherImagem();
|
|
640
|
-
const imagens = await escolherImagens({
|
|
747
|
+
const imagem = await escolherImagem(); // Retorna: { uri, nome, tamanho, mimeType }
|
|
748
|
+
const imagens = await escolherImagens({ multiplas: true }); // Array de objetos
|
|
641
749
|
const pdf = await escolherArquivo({ tipos: ["application/pdf"] });
|
|
642
750
|
const arquivos = await escolherArquivos({ multiplo: true });
|
|
751
|
+
const pasta = await escolherPasta(); // Retorna: { uri, nome, treeUri }
|
|
643
752
|
|
|
644
753
|
await salvarArquivo({
|
|
645
754
|
nome: "relatorio.txt",
|
|
@@ -647,9 +756,75 @@ await salvarArquivo({
|
|
|
647
756
|
conteudo: "Conteudo salvo pelo app"
|
|
648
757
|
});
|
|
649
758
|
|
|
650
|
-
await compartilhar({
|
|
759
|
+
await compartilhar({
|
|
760
|
+
titulo: "Material",
|
|
761
|
+
texto: "Veja isso",
|
|
762
|
+
url: "https://exemplo.com",
|
|
763
|
+
arquivo: imagem
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
aoReceberCompartilhamento((dados) => {
|
|
767
|
+
console.log("Compartilhamento recebido", dados.tipo, dados.uri || dados.texto);
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
const texto = await ocr(imagem);
|
|
771
|
+
console.log(texto.texto); // Retorna: { texto, blocos: [...] }
|
|
772
|
+
|
|
773
|
+
await falar("Ola mundo", { idioma: "pt-BR", velocidade: 1 });
|
|
774
|
+
const voz = await ouvir({ idioma: "pt-BR" });
|
|
775
|
+
console.log(voz.texto); // Retorna: { texto, error }
|
|
776
|
+
|
|
777
|
+
aoConectarBT((dispositivo) => {
|
|
778
|
+
console.log("Bluetooth conectado", dispositivo.nome);
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
aoReceberDadosBT((dados) => {
|
|
782
|
+
console.log("Dados Bluetooth", dados);
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
aoDarErroBT((erro) => {
|
|
786
|
+
console.log("Erro Bluetooth", erro.mensagem || erro.message);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
const dispositivos = await procurarBT(); // Retorna: [{ id, nome, host }, ...]
|
|
790
|
+
if (dispositivos[0]) {
|
|
791
|
+
await conectarBT(dispositivos[0].id);
|
|
792
|
+
await enviarBT({ mensagem: "Ola por Bluetooth" });
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
aoConectarWiFi((dispositivo) => {
|
|
796
|
+
console.log("Wi-Fi conectado", dispositivo.nome || dispositivo.host);
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
aoReceberDadosWiFi((dados) => {
|
|
800
|
+
console.log("Dados Wi-Fi", dados);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
aoDarErroWiFi((erro) => {
|
|
804
|
+
console.log("Erro Wi-Fi", erro.mensagem || erro.message);
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
const dispositivosWifi = await procurarWiFi(); // Retorna: [{ id, nome, host, porta }, ...]
|
|
808
|
+
if (dispositivosWifi[0]) {
|
|
809
|
+
await conectarWiFi(dispositivosWifi[0].id);
|
|
810
|
+
await enviarWiFi({ mensagem: "Ola por Wi-Fi" });
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
await share_me(); // compartilha o APK do proprio app aberto
|
|
651
814
|
```
|
|
652
815
|
|
|
816
|
+
`escolherImagem()` e `escolherImagens()` usam o Android Photo Picker no Android 13+ e caem automaticamente para o Storage Access Framework em Android antigo. Quando o Photo Picker esta disponivel, o html2apk nao solicita permissao ampla de armazenamento. O retorno segue o mesmo formato dos seletores: `{ uri, nome, mimeType, tamanho }`.
|
|
817
|
+
|
|
818
|
+
`compartilhar()` aceita texto, link, imagem, video, PDF, arquivo salvo pelo app, URI `content://` e lista de arquivos em `arquivo`/`arquivos`. O retorno confirma a abertura do share sheet com `{ ok: true }`.
|
|
819
|
+
|
|
820
|
+
Apps gerados tambem entram no menu Compartilhar do Android para `text/plain`, `image/*`, `video/*`, `application/pdf` e `*/*`. Use `obterCompartilhamentoInicial()` no boot se o app foi aberto por compartilhamento, e `aoReceberCompartilhamento()` para receber novos intents enquanto ele ja esta aberto.
|
|
821
|
+
|
|
822
|
+
`ocr()` usa ML Kit local para reconhecimento de texto em imagens. O processamento fica offline no aparelho. `falar()` usa o TextToSpeech do Android e `ouvir()` usa o reconhecedor de voz do sistema; ambos aceitam `idioma: "pt-BR"` ou `idioma: "auto"`.
|
|
823
|
+
|
|
824
|
+
Bluetooth usa RFCOMM classico entre apps html2apk. O aparelho que vai receber registra `aoConectarBT()`, `aoReceberDadosBT()` e, se quiser tratar falhas, `aoDarErroBT()`; isso inicia o servidor interno. O outro chama `procurarBT()`, escolhe um `id`, chama `conectarBT(id)` e envia JSON com `enviarBT(objeto)`. Para aparecer na busca, o aparelho precisa estar pareado ou visivel nas configuracoes Bluetooth do Android.
|
|
825
|
+
|
|
826
|
+
Wi-Fi local usa descoberta NSD e socket TCP entre apps html2apk na mesma rede ou hotspot. O aparelho que vai receber registra `aoConectarWiFi()`, `aoReceberDadosWiFi()` e `aoDarErroWiFi()`; isso inicia o servidor interno e anuncia o app na rede local. O outro chama `procurarWiFi()`, escolhe um `id`, chama `conectarWiFi(id)` e envia JSON com `enviarWiFi(objeto)`. Nao e Wi-Fi Direct: os dois aparelhos precisam estar conectados na mesma rede local, ou um deles precisa estar no hotspot do outro.
|
|
827
|
+
|
|
653
828
|
`salvarArquivo()` tem dois modos:
|
|
654
829
|
|
|
655
830
|
- `salvarArquivo({ nome, conteudo })` abre o seletor nativo para o usuario escolher onde salvar, como ja acontecia.
|
|
@@ -684,7 +859,8 @@ await baixarArquivo("https://exemplo.com/relatorio.pdf", "relatorio.pdf");
|
|
|
684
859
|
await abrirArquivo("relatorio.pdf");
|
|
685
860
|
|
|
686
861
|
await baixarBase64("foto.png", base64DaImagem, {
|
|
687
|
-
mimeType: "image/png"
|
|
862
|
+
mimeType: "image/png",
|
|
863
|
+
galeria: true
|
|
688
864
|
});
|
|
689
865
|
|
|
690
866
|
const arquivo = await escolherArquivo();
|
|
@@ -695,6 +871,8 @@ if (arquivo) {
|
|
|
695
871
|
|
|
696
872
|
Durante `baixarArquivo()`, `baixarBase64()` e `baixarArquivoLocal()`, o Android mostra uma notificacao de progresso quando a permissao `POST_NOTIFICATIONS` estiver liberada. No Android 13+, o html2apk pede essa permissao automaticamente; se o usuario negar, o download continua e o retorno vem com `notificationShown: false`.
|
|
697
873
|
|
|
874
|
+
Por padrao, o arquivo baixado fica no armazenamento do app para funcionar com `abrirArquivo()` e `compartilharArquivo()`. Para imagem ou video aparecer na galeria, passe `{ galeria: true }`; no Android 10+ o html2apk publica uma copia em `Pictures/html2apk` ou `Movies/html2apk` e retorna `publicUri`.
|
|
875
|
+
|
|
698
876
|
Papel de parede:
|
|
699
877
|
|
|
700
878
|
```js
|
|
@@ -707,7 +885,7 @@ await salvarArquivo("wallpaper.jpg", foto.base64, {
|
|
|
707
885
|
|
|
708
886
|
const resultado = await definirPapelParede("wallpaper.jpg", {
|
|
709
887
|
alvo: "inicio" // "inicio", "bloqueio" ou "ambos"
|
|
710
|
-
});
|
|
888
|
+
}); // Retorna: { applied, systemApplied, lockApplied, error }
|
|
711
889
|
|
|
712
890
|
console.log(resultado.applied, resultado.systemApplied, resultado.lockApplied);
|
|
713
891
|
```
|
|
@@ -717,17 +895,19 @@ console.log(resultado.applied, resultado.systemApplied, resultado.lockApplied);
|
|
|
717
895
|
Camera, QR Code, localizacao, biometria e storage seguro:
|
|
718
896
|
|
|
719
897
|
```js
|
|
720
|
-
const foto = await tirarFoto({ base64: true });
|
|
898
|
+
const foto = await tirarFoto({ base64: true }); // Retorna: { base64, mimeType, uri }
|
|
721
899
|
|
|
722
|
-
const qr = await escanearQRCode();
|
|
900
|
+
const qr = await escanearQRCode(); // Retorna: { text, format, cancelled }
|
|
723
901
|
if (qr) {
|
|
724
902
|
console.log(qr.text);
|
|
725
903
|
}
|
|
726
904
|
|
|
727
905
|
const local = await obterLocalizacao({ altaPrecisao: true, timeoutMs: 10000 });
|
|
906
|
+
// Retorna: { latitude, longitude, precisao, altitude, error }
|
|
728
907
|
console.log(local.latitude, local.longitude);
|
|
729
908
|
|
|
730
|
-
const watch = await acompanharLocalizacao({ intervaloMs: 5000 });
|
|
909
|
+
const watch = await acompanharLocalizacao({ intervaloMs: 5000 });
|
|
910
|
+
// Retorna: { watchId, error }
|
|
731
911
|
const pararEvento = aoMudarLocalizacao((evento) => {
|
|
732
912
|
console.log(evento.latitude, evento.longitude);
|
|
733
913
|
});
|
|
@@ -738,13 +918,36 @@ pararEvento();
|
|
|
738
918
|
const bio = await autenticarBiometria({
|
|
739
919
|
titulo: "Confirmar acesso",
|
|
740
920
|
descricao: "Use a biometria do aparelho"
|
|
741
|
-
});
|
|
921
|
+
}); // Retorna: { authenticated, supported, canceled, message }
|
|
742
922
|
|
|
743
923
|
if (bio.authenticated) {
|
|
744
924
|
await salvarSeguro("token", "abc123");
|
|
745
925
|
const token = await lerSeguro("token");
|
|
746
926
|
await removerSeguro("token");
|
|
747
927
|
}
|
|
928
|
+
|
|
929
|
+
const auth = await solicitarBloqueio({
|
|
930
|
+
titulo: "Acesso Restrito",
|
|
931
|
+
descricao: "Confirme sua senha de tela"
|
|
932
|
+
}); // Retorna: { autenticado, suportado, cancelado, mensagem }
|
|
933
|
+
|
|
934
|
+
if (auth.autenticado) {
|
|
935
|
+
// Acesso permitido
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
const bg = await solicitarSegundoPlano();
|
|
939
|
+
// Retorna: { ok, abriuInicioAutomatico, abriuOtimizacaoBateria }
|
|
940
|
+
if (bg.ok) {
|
|
941
|
+
toast("Obrigado por permitir rodar em segundo plano!");
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// Para abrir o aplicativo sozinho quando o aparelho for ligado:
|
|
945
|
+
await configurarInicioAutomatico(true); // Retorna: { ok, enabled }
|
|
946
|
+
|
|
947
|
+
const inicio = await obterLinkInicial(); // Retorna string: "html2apk://boot" ou "https://..."
|
|
948
|
+
if (inicio === "html2apk://boot") {
|
|
949
|
+
console.log("App abriu sozinho pelo boot do aparelho");
|
|
950
|
+
}
|
|
748
951
|
```
|
|
749
952
|
|
|
750
953
|
O retorno de arquivos tem este formato:
|
|
@@ -763,7 +966,8 @@ O retorno de arquivos tem este formato:
|
|
|
763
966
|
Microfone:
|
|
764
967
|
|
|
765
968
|
```js
|
|
766
|
-
const inicio = await ouvirMic();
|
|
969
|
+
const inicio = await ouvirMic();
|
|
970
|
+
// Retorna: { recording: true, settingsOpened: boolean, error: string }
|
|
767
971
|
if (inicio.settingsOpened) {
|
|
768
972
|
console.log("Libere Microfone nas configuracoes e tente novamente");
|
|
769
973
|
} else {
|
|
@@ -853,6 +1057,27 @@ const abertos = await appsAbertos();
|
|
|
853
1057
|
|
|
854
1058
|
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.
|
|
855
1059
|
|
|
1060
|
+
Tela, volume e ciclo do app:
|
|
1061
|
+
|
|
1062
|
+
```js
|
|
1063
|
+
const volume = await volumeAtual();
|
|
1064
|
+
console.log(volume.midia.atual, volume.midia.maximo);
|
|
1065
|
+
|
|
1066
|
+
await definirVolume("midia", 0.5, { mostrarUI: true });
|
|
1067
|
+
await aumentarVolume("midia", 1);
|
|
1068
|
+
await diminuirVolume("midia", 1);
|
|
1069
|
+
|
|
1070
|
+
const imagem = await capturarTela();
|
|
1071
|
+
document.querySelector("img.preview").src = imagem.dataUrl;
|
|
1072
|
+
|
|
1073
|
+
await minimizarApp();
|
|
1074
|
+
|
|
1075
|
+
// Use somente depois de salvar estado importante:
|
|
1076
|
+
// await fecharApp();
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
`capturarTela()` captura a tela do proprio APK/WebView, nao outros apps ou areas protegidas do sistema. `definirVolume()` aceita porcentagem entre 0 e 1 ou passos absolutos do stream.
|
|
1080
|
+
|
|
856
1081
|
Eventos nativos:
|
|
857
1082
|
|
|
858
1083
|
```js
|
|
@@ -865,11 +1090,27 @@ aoEvento("botao:voltar", console.log);
|
|
|
865
1090
|
aoEvento("link:aberto", (evento) => console.log(evento.url));
|
|
866
1091
|
aoEvento("rede:mudou", console.log);
|
|
867
1092
|
aoEvento("bateria:mudou", console.log);
|
|
1093
|
+
aoConectarUSB((dados) => console.log("USB conectado", dados));
|
|
1094
|
+
aoDesconectarUSB(() => console.log("USB desconectado"));
|
|
1095
|
+
aoReceberNotificacao((dados) => console.log("Notificação recebida", dados));
|
|
868
1096
|
aoEvento("notificacao:clicada", console.log);
|
|
1097
|
+
aoConectarFone((dados) => console.log("Fone conectado", dados.dispositivo));
|
|
1098
|
+
aoDesconectarFone(() => console.log("Fone desconectado"));
|
|
1099
|
+
aoMudarVolume((dados) => console.log("Volume de mídia", dados.midia.atual));
|
|
1100
|
+
aoAbrirTeclado((dados) => console.log("Teclado abriu", dados.alturaTeclado));
|
|
1101
|
+
aoFecharTeclado(() => console.log("Teclado fechou"));
|
|
1102
|
+
aoMudarOrientacao((dados) => console.log("Orientação", dados.orientacao));
|
|
1103
|
+
aoSacudirCelular((dados) => console.log("Sacudiu", dados.forca));
|
|
1104
|
+
aoVirarCelularParaBaixo(() => console.log("Tela para baixo"));
|
|
1105
|
+
aoAproximarObjeto((dados) => console.log("Objeto perto", dados.distancia));
|
|
1106
|
+
aoTirarPrint((dados) => console.log("Print detectado", dados.uri));
|
|
1107
|
+
aoNFC((dados) => console.log("Tag NFC", dados.id, dados.mensagens));
|
|
869
1108
|
|
|
870
1109
|
parar();
|
|
871
1110
|
```
|
|
872
1111
|
|
|
1112
|
+
Eventos de sensor e sistema seguem limites do Android/fabricante. `aoTirarPrint()` observa a MediaStore e depende do nome/pasta da captura; pode não disparar em alguns aparelhos. `aoNFC()` escuta tags enquanto o app está aberto em primeiro plano e exige NFC ligado no aparelho.
|
|
1113
|
+
|
|
873
1114
|
Deep links:
|
|
874
1115
|
|
|
875
1116
|
```js
|
package/package.json
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "html2apk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "12.0.0",
|
|
4
|
+
"author": {
|
|
5
|
+
"name": "Dev Caio Multiversando",
|
|
6
|
+
"email": "dev@caio.local"
|
|
7
|
+
},
|
|
4
8
|
"description": "Node CLI and library to turn an HTML/CSS/JS folder into an Android APK through Cordova.",
|
|
5
9
|
"main": "index.js",
|
|
6
10
|
"bin": {
|
|
7
|
-
"html2apk": "
|
|
11
|
+
"html2apk": "bin/html2apk.js"
|
|
8
12
|
},
|
|
9
13
|
"type": "commonjs",
|
|
10
14
|
"license": "MIT",
|
|
11
15
|
"files": [
|
|
12
16
|
"bin",
|
|
13
17
|
"src",
|
|
14
|
-
"examples",
|
|
18
|
+
"examples/minimal/app.json",
|
|
19
|
+
"examples/minimal/index.html",
|
|
15
20
|
"index.js",
|
|
16
21
|
"html2apk.png",
|
|
17
22
|
"README.md"
|
|
@@ -21,7 +26,8 @@
|
|
|
21
26
|
"doctor": "node bin/html2apk.js doctor",
|
|
22
27
|
"desktop": "node bin/html2apk-desktop.js",
|
|
23
28
|
"build-desktop-win": "node scripts/build-desktop-portable.js",
|
|
24
|
-
"
|
|
29
|
+
"build-desktop-deb": "npx electron-builder --linux deb",
|
|
30
|
+
"test": "node --test test/config.test.js",
|
|
25
31
|
"build-win": "pkg . --targets node20-win-x64 --output dist/html2apk.exe",
|
|
26
32
|
"build-linux": "pkg . --targets node20-linux-x64 --output dist/html2apk-linux",
|
|
27
33
|
"build-mac": "pkg . --targets node20-macos-x64 --output dist/html2apk-macos"
|
|
@@ -60,6 +66,12 @@
|
|
|
60
66
|
"portable"
|
|
61
67
|
],
|
|
62
68
|
"icon": "html2apk.png"
|
|
69
|
+
},
|
|
70
|
+
"linux": {
|
|
71
|
+
"target": [
|
|
72
|
+
"deb"
|
|
73
|
+
],
|
|
74
|
+
"icon": "html2apk.png"
|
|
63
75
|
}
|
|
64
76
|
},
|
|
65
77
|
"devDependencies": {
|