Crea APK gratis

Inizia da qui

Scrivere HTML per la tua app

Cosa il runtime dell'app mette a disposizione della tua pagina (contatti, SMS, notifiche, informazioni sul dispositivo, biometria) e le regole per chiamarlo correttamente

1Regola 1: controlla sempre prima#

window.WebToApk esiste solo dentro la tua app compilata. In un browser non c'è.

Quindi controlla sempre prima di chiamare. Se non lo fai, la tua pagina si rompe ovunque tranne che dentro l'app.

Alcune cose non richiedono alcun controllo: navigator.vibrate(), speechSynthesis e Notification funzionano già, l'app li collega per te. Usa semplicemente il normale codice web.

// GOOD — safe everywhere
if (window.WebToApk) {
  WebToApk.playClick();
}

// BAD — this breaks your page in a browser
WebToApk.playClick();

2Regola 2: alcune risposte arrivano più tardi#

Leggere contatti, SMS o registro chiamate richiede tempo. Perciò non rispondono subito. Se lo facessero, la tua app si bloccherebbe.

Dai un nome alla chiamata (un id). La risposta arriva più tardi come evento. Tu ascolti quell'evento.

Nomi degli eventi: appmint:contacts, appmint:calllog, appmint:sms, appmint:sms-received, appmint:notification-action.

// 1. Listen for the answer
window.addEventListener('appmint:contacts', function (e) {
  if (e.detail.error) {
    alert('Problem: ' + e.detail.error);
    return;
  }
  console.log(e.detail.contacts);
});

// 2. Ask the question ('my1' is any name you choose)
WebToApk.listContacts('my1', 50, 0);

3Regola 3: due interruttori devono essere ATTIVI#

Una funzione del telefono richiede DUE cose:

  1. L'hai attivato nel passaggio 4 (Access) quando hai creato l'app. Altrimenti ricevi l'errore 'not_enabled' e l'utente non vede nulla.
  1. L'utente ha detto Sì sul telefono. Se ha detto No, ricevi l'errore 'permission_denied'.

Gestisci sempre entrambi gli errori così l'utente sa cosa è successo.

Via più semplice: pickContact() e composeSms() NON richiedono alcuna autorizzazione. Usali quando vanno bene: fanno lo stesso lavoro con meno complicazioni.

window.addEventListener('appmint:contacts', function (e) {
  if (e.detail.error === 'not_enabled') {
    alert('Turn on Contacts when you build the app');
  } else if (e.detail.error === 'permission_denied') {
    alert('Please allow contacts access');
  } else {
    showContacts(e.detail.contacts);
  }
});

4Notifiche con immagini e pulsanti#

notify() accetta un elenco di opzioni. Puoi aggiungere un'immagine grande, un'icona, dei pulsanti e una barra di avanzamento.

channel può essere: urgent, default, quiet oppure ongoing.

Usa ongoing:true per una notifica che l'utente non può eliminare con uno swipe. Nota: può comunque rimuoverla dalle impostazioni di Android. Nessuna app può crearne una impossibile da rimuovere.

Usa tag per dargli un nome. Se invii di nuovo con lo stesso tag, sostituisce quella vecchia invece di aggiungerne una nuova.

WebToApk.notify(JSON.stringify({
  title: 'Order shipped',
  body: 'Arriving Tuesday',
  channel: 'urgent',
  image: 'https://mysite.com/box.jpg',
  actions: [{ id: 'track', label: 'Track' }],
  tag: 'order-482'
}));

// Know which button the user pressed
window.addEventListener('appmint:notification-action', function (e) {
  if (e.detail.actionId === 'track') showTracking();
});

5Informazioni sul telefono e impronta digitale#

getDeviceInfo() non richiede autorizzazioni. Ti dà la versione di Android, l'ID della versione (buildId), il modello del telefono, la dimensione dello schermo, il livello della batteria e altro.

A proposito dell'ID del dispositivo: NON esiste IMEI o numero di serie. Android li ha bloccati per tutte le app da Android 10. Usa invece getInstallId(): un ID fisso per questa installazione che resta uguale dopo gli aggiornamenti.

var info = JSON.parse(WebToApk.getDeviceInfo());
info.android.release;      // "14"
info.android.buildId;      // "TQ3A.230805.001"  <- version ID
info.hardware.model;       // "Pixel 7"
info.runtime.batteryLevel; // 82

// Fingerprint
window.__webToApkAuth = window.__webToApkAuth || {};
window.__webToApkAuth['unlock'] = function (json) {
  var r = JSON.parse(json);
  if (r.ok) showMyApp();
};
WebToApk.authenticateBiometricEx('unlock', JSON.stringify({
  title: 'Unlock',
  allowDeviceCredential: true
}));

6Fai riempire lo schermo alla tua pagina#

Quando attivi Fullscreen, l'app nasconde per te le barre di Android. Ma aggiungi questo CSS in modo che il contenuto non finisca nascosto sotto il notch del telefono.

Importante: Fullscreen nasconde le barre di ANDROID (orologio, batteria, pulsanti indietro). La barra colorata in alto della tua app è un altro interruttore chiamato 'Show top bar'. Se vedi ancora una barra dopo aver attivato Fullscreen, è quella da disattivare.

Inoltre: prova la tua app ruotata. Il notch si sposta di lato in orizzontale.

body {
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  min-height: 100vh;   /* old phones */
  min-height: 100dvh;  /* correct when bars are hidden */
}

7I download mantengono il nome file che hai scelto#

Il tuo normale codice di download funziona già. Quando la tua pagina salva un file (un report PDF, un'esportazione CSV, un backup) l'app lo intercetta e apre il foglio Android 'Salva come…' con il TUO nome file già compilato.

Questo copre tutti i modi consueti di farlo: <a download="...">, a.click() da codice, html2pdf / jsPDF, saveAs() di FileSaver.js e window.open() su un URL blob. Un oggetto File porta con sé il suo nome.

Se preferisci chiedere direttamente invece di creare un tag <a>, usa AppMint.downloadFile().

Una cosa da sapere: il nome vive solo nel JavaScript della tua pagina, Android non lo vede da solo. Quindi imposta download= (o passa un nome) ogni volta, altrimenti il file viene salvato come 'download'.

// The usual way — saves as Site_Report_2026-08.pdf
const blob = await html2pdf().from(el).outputPdf('blob');
const a = document.createElement('a');
a.download = 'Site_Report_2026-08.pdf';
a.href = URL.createObjectURL(blob);
a.click();

// Or ask directly
AppMint.downloadFile(base64String, 'Ledger_Q3.csv', 'text/csv');
AppMint.saveBlob(myBlob, 'Backup.json');

8Fai fare al pulsante Indietro ciò che si aspetta la TUA app#

Se la tua app cambia schermata mostrando e nascondendo elementi (nessun cambio di URL, nessun history.pushState), il tasto Indietro di Android non vede quelle schermate: la sua cronologia è vuota, quindi Indietro esce dall'app da qualsiasi punto.

Registra un gestore del tasto Indietro e decidi tu: restituisci true quando hai gestito la pressione (chiuso un menu, tornato a una schermata), restituisci false per lasciare eseguire il comportamento normale: indietro nella cronologia della pagina, poi la conferma di uscita, poi l'uscita.

Le app che usano history.pushState per ogni schermata non ne hanno bisogno: Indietro percorre già la loro cronologia. E non preoccuparti di bloccare l'app: se il tuo gestore dovesse bloccarsi, il tasto Indietro del telefono torna a funzionare in modo nativo dopo un breve momento.

AppMint.setBackHandler(() => {
  if (isMenuOpen)   { closeMenu();  return true; }   // consumed
  if (screen !== 'home') { goTo('home'); return true; }
  return false;   // nothing open — normal exit behaviour
});

// Or listen instead: e.preventDefault() consumes the press
window.addEventListener('appmint:back', e => {
  if (closeTopmost()) e.preventDefault();
});

Attiva 'Receive shares from other apps' nella procedura guidata e seleziona cosa accetta la tua app: testo e link, file di dati (JSON, CSV, TXT, XML, Markdown, GPX, TCX, FIT), foto, video, audio, documenti (PDF, Word, Excel, PowerPoint). La tua app compare quindi nel menu Condividi di ogni altra app per esattamente quei tipi: un'app di note per il testo, un player per i video.

Un link o un testo (condividere un video di YouTube dall'app YouTube ne invia il link) viene letto con WebToApk.getSharedText() e annunciato come appmint:shared. Un file si richiede con AppMint.getOpenedFile(): chiedilo quando sei pronto, il file ti aspetta. Questo conta per le app React, Vue e Angular: finiscono di avviarsi DOPO il caricamento della pagina, quindi un'app che ascoltava solo l'evento perdeva il file e mostrava solo la schermata iniziale.

f.kind ti dice cosa è arrivato: image, video, audio, document o data. Foto, video e audio arrivano come f.url, un indirizzo riproducibile e navigabile che metti direttamente in <video src>, <img src> o <audio src>, così un clip da 300 MB non deve mai stare in memoria; f.getFile() recupera il File quando ti servono davvero i byte. File di dati e documenti arrivano anche con f.file, più f.text per i formati testuali e f.base64 per i piccoli binari come scorciatoie. Una foto condivisa con didascalia consegna la didascalia anche come testo condiviso.

// A link or text someone shared
const shared = JSON.parse(WebToApk.getSharedText() || 'null');
if (shared) saveLink(shared.url || shared.text);
window.addEventListener('appmint:shared', e => saveLink(e.detail.url || e.detail.text));

// A photo, video, audio clip, document or data file — ask on startup, however late
const f = await AppMint.getOpenedFile();   // null if opened normally
if (f) {
  if (f.kind === 'video')         player.src = f.url;                 // streams, seekable
  else if (f.kind === 'image')    img.src = f.url;
  else if (f.kind === 'document') await upload(await f.getFile());   // a real File
  else importActivity(f.text ?? await f.file.text());               // data files
}

// Or listen, if you prefer
window.addEventListener('appmint:fileopen', e => handle(e.detail));

10Sensori Bluetooth: Web Bluetooth standard#

Fasce cardio, sensori di cadenza e velocità, misuratori di potenza, rulli intelligenti, bilance: tutto ciò che usa il Bluetooth LE.

Usa navigator.bluetooth, esattamente lo stesso codice che scriveresti per Chrome su desktop. AppMint lo fornisce dentro l'app; una normale WebView di Android non lo ha, ed è per questo che questo codice non fa nulla in altri creatori di app.

Il selettore dei dispositivi è disegnato dall'app, come quello del browser: la tua pagina può raggiungere solo il dispositivo scelto dall'utente.

Attiva l'autorizzazione Bluetooth nel passaggio 4 (Access). Se il tuo codice cita navigator.bluetooth, AppMint la attiva per te quando scegli il tuo ZIP.

Non disponibili: getDevices(), watchAdvertisements() e requestLEScan() - vengono rifiutati con NotSupportedError, quindi controlla prima di usarli.

const device = await navigator.bluetooth.requestDevice({
  filters: [{ services: ['heart_rate'] }]
});
const server  = await device.gatt.connect();
const service = await server.getPrimaryService('heart_rate');
const chr     = await service.getCharacteristic('heart_rate_measurement');

chr.addEventListener('characteristicvaluechanged', e => {
  const v = e.target.value;   // a DataView
  const bpm = (v.getUint8(0) & 1) ? v.getUint16(1, true) : v.getUint8(1);
  document.getElementById('bpm').textContent = bpm;
});
await chr.startNotifications();

device.addEventListener('gattserverdisconnected', () => showReconnect());

11Caricamento foto: il selettore foto di Android, senza autorizzazioni#

Usa un normale input file. Non serve aggiungere altro.

Nell'app, <input type="file" accept="image/*"> apre il selettore foto di Android: le foto dell'utente in una griglia, e la tua pagina riceve solo quelle scelte. Nessuna autorizzazione di archiviazione, nessun popup 'Consentire l'accesso alle foto?'. Funziona anche su telefoni vecchi: Google Play services vi installa il selettore.

  • accept="image/*": solo foto
  • accept="video/*": solo video
  • accept="image/*,video/*": entrambi
  • aggiungi multiple: l'utente può sceglierne più di uno
  • aggiungi capture="environment": apre invece la fotocamera (richiede Camera attivata nel passaggio 4 (Access))

Se aggiungi altri file (accept="image/*,.pdf") si apre invece il normale selettore di file, così si può scegliere anche il PDF.

<input type="file" id="photos" accept="image/*" multiple>

<script>
document.getElementById('photos').onchange = function (e) {
  for (const file of e.target.files) {
    const img = document.createElement('img');
    img.src = URL.createObjectURL(file);
    document.body.appendChild(img);
  }
};
</script>

12Scansiona un codice QR o un codice a barre: una riga#

AppMint.scanCode() apre la schermata dello scanner di Google. L'utente inquadra con la fotocamera e ricevi il codice.

Nessuna autorizzazione per la fotocamera. Nessun elemento video. Niente da disegnare. Lo scanner zooma da solo per i codici piccoli. La prima volta su un telefono, Google Play services scarica lo scanner (qualche secondo).

La risposta contiene value (il testo nel codice) e format (qr_code, ean_13, …). Per i codici QR più comuni ottieni anche le parti già pronte: url, wifi (ssid, password), phone, email, sms, geo, contact.

Se ok è false, code ti dice perché: cancelled (l'utente l'ha chiuso), unavailable (niente Google Play services, o download non riuscito), busy (una scansione è già aperta).

AppMint.scanCode esiste solo dentro l'app: controllalo prima.

async function scan() {
  if (!window.AppMint || !AppMint.scanCode) return;   // in a browser

  const r = await AppMint.scanCode({ formats: ['qr_code'] });
  if (!r.ok) return;                                  // closed, or no scanner

  console.log(r.value);          // the text inside the code
  if (r.url) location.href = r.url.url;
}

// Any code type: AppMint.scanCode()
// Let the user type it when it will not scan: AppMint.scanCode({ manualInput: true })

13Passkey: accedi con impronta o volto, senza password#

Se il tuo sito ha già le passkey, ora funzionano anche dentro la tua app. Una normale WebView di Android non gestisce le passkey; AppMint collega navigator.credentials al gestore di passkey di Android, quindi librerie come SimpleWebAuthn, Hanko, Corbado o Auth0 funzionano senza modifiche.

Ti serve un server che crei le opzioni della passkey e verifichi la risposta (ad esempio SimpleWebAuthn sul tuo backend). Una passkey non può essere verificata dentro la pagina.

Due cose da configurare una volta sola:

  1. Il tuo sito deve dichiarare di fidarsi dell'app. Metti il file ottenuto da AppMint.passkey.assetLinks() in https://YOUR-DOMAIN/.well-known/assetlinks.json. Prendilo dall'app installata DA Google Play: Play firma la tua app con una chiave propria, quindi il file ricavato da un APK di prova non corrisponderà agli utenti di Play. Il dominio deve essere esattamente l'rpID del tuo server (www e senza www sono diversi).
  1. Il tuo server deve accettare l'origine dell'app. La risposta dell'app riporta l'origine 'android:apk-key-hash:…' invece di https://…: ricava quel valore da AppMint.passkey.origin() e aggiungilo alle origini consentite del tuo server.

Errori: NotAllowedError = l'utente ha annullato o non ha una passkey su questo telefono. InvalidStateError = questa passkey esiste già. SecurityError = assetlinks.json non elenca l'app.

Le passkey richiedono Android 9 o successivo con Google Play services: AppMint.passkey.isAvailable() te lo dice. Il riempimento automatico della passkey in un campo di testo (mediation: 'conditional') non è disponibile nelle app; avvia l'accesso da un pulsante.

// Your normal passkey code works as it is:
const cred = await navigator.credentials.get({ publicKey: options });

// Or with your server's JSON options (SimpleWebAuthn style):
const options = await (await fetch('/passkey/login-options')).json();
const answer = await AppMint.passkey.get(options);
await fetch('/passkey/login-verify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(answer)
});

// Once, on the Play-installed app — copy these to your server setup:
console.log(JSON.stringify(AppMint.passkey.assetLinks(), null, 2));
console.log(AppMint.passkey.origin());

14Widget della schermata Home: mostra i tuoi dati nella Home#

La tua app ha un widget per la schermata Home. L'utente lo aggiunge dall'elenco widget del telefono (tieni premuta la Home, poi Widget), oppure apri il foglio Aggiungi con AppMint.widget.requestPin().

Il widget resta vuoto ("Nothing to show yet") finché la tua pagina non lo riempie. È una piccola scheda Android, non una pagina web, quindi non può mostrare il tuo HTML. Gli dai un titolo, un valore grande, fino a sei righe brevi e dei colori.

Chiama AppMint.widget.set() quando l'app si apre, e di nuovo ogni volta che cambiano i dati mostrati. Il widget conserva l'ultima scheda, anche dopo un riavvio. route è dove si apre la tua app quando si tocca il widget.

function updateWidget() {
  if (!window.AppMint || !AppMint.widget) return;   // in a browser

  AppMint.widget.set({
    title: 'Today',
    value: '3 events',
    lines: ['09:00 Dentist', '13:00 Lunch with Sam', '18:30 Gym'],
    route: '/',
    bg: '#0E7C86', ink: '#FFFFFF', accent: '#FFE08A'
  });
}

updateWidget();   // on start-up, and after every change

// An "Add to home screen" button:
async function addWidget() {
  if ((await AppMint.widget.placedCount()) > 0) return;
  await AppMint.widget.requestPin();
}

15Protezione dalla copia e menu di pressione prolungata#

Due interruttori nel passaggio 3 (Integrate), sotto Display Options. Entrambi sono disattivati finché non li attivi.

  • Menu di pressione prolungata su link e immagini: tieni premuto un link o un'immagine per aprirla, copiarla, condividerla o salvarla. Disattivato per impostazione predefinita, quindi una pressione prolungata su un link non fa nulla. Una pagina può comunque rifiutare il menu su un elemento annullando l'evento contextmenu.
  • Protezione dalla copia: nessuno può selezionare, copiare o tagliare il testo nella tua app. Scrivere nei moduli funziona comunque. Tiene disattivato anche il menu di pressione prolungata.

La tua pagina può saperlo: AppMint.copyProtection è true quando la protezione è attiva, e mancante quando è disattivata (e in un browser). È di sola lettura. Nascondi i tuoi pulsanti Copia quando è attiva, perché non copierebbero nulla.

const protectedApp = !!(window.AppMint && window.AppMint.copyProtection === true);

// A Copy button would copy nothing, so hide it
document.querySelectorAll('[data-copy-button]').forEach(btn => {
  btn.hidden = protectedApp;
});

16{exampleCount} esempi pronti: aprili qui#

Codice funzionante da copiare: inviare un SMS, leggere i contatti, mostrare una notifica con immagine, blocco con impronta, informazioni sul telefono, salvataggio di file, sensori Bluetooth e altro. Ognuno è breve e spiegato con parole semplici.

Si apre dentro AppMint. Puoi cercarci dentro, e ogni blocco di codice ha un pulsante Copia.

La stessa schermata ha anche 'All methods': tutte le {methodCount} cose che la tua app può chiamare, ognuna con un esempio funzionante da copiare. Quell'elenco è generato dal runtime stesso dell'app, quindi corrisponde sempre esattamente a ciò che la tua app sa fare.

17Salva gli esempi sul telefono o sul computer#

Salva tre file in Downloads/AppMint/Guides:

  • Esempi: i {exampleCount} esempi qui sopra
  • Elenco dei metodi: tutti i {methodCount} metodi
  • index.html: una pagina di prova già pronta

La pagina di prova ha pulsanti per informazioni sul dispositivo, vibrazione, notifiche e selettore dei contatti. Creala come app per vedere tutto funzionare, poi trasformala nella tua app. Si apre senza problemi anche in un normale browser: non si rompe nulla, dice solo che non è dentro l'app.

Nell'app, il pulsante di questo passaggio li salva sul tuo telefono. Qui sono download diretti.

AppMint-examples.md MD · 48 KB AppMint-all-methods.md MD · 1.4 MB index.html HTML · 6 KB

18Sveglia che suona sopra la schermata di blocco#

Attiva Alarm clock nel passaggio Access. scheduleNotificationEx con alarm: true suona con il tuo suono finché l'utente non lo ferma o lo rimanda, anche con l'app chiusa. La ripetizione può essere giornaliera, settimanale, nei giorni feriali, mensile o annuale, oppure puoi scegliere i giorni della settimana.

Prima di programmare, chiedi a getAlarmSupport(). Risponde 'ready', 'not_enabled', 'needs_exact_alarm' oppure 'needs_notification_permission'. Poi stopAlarm(id), snoozeAlarm(id, minutes) e isAlarmRinging(id) controllano una sveglia che sta suonando.

const B = window.WebToApk;
if (B && B.getAlarmSupport && B.getAlarmSupport() === 'ready') {
  B.scheduleNotificationEx('wake', JSON.stringify({
    at: firstAtMs, alarm: true, title: 'Wake up', repeat: 'weekdays', sound: '/audio/alarm.mp3'
  }));
}
document.getElementById('snooze').onclick = () => B.snoozeAlarm('wake', 10);

19Volume di sistema#

getVolume(stream) legge in JSON il volume di media, sveglia, suoneria, notifiche o chiamata. setVolume(stream, percent, showUi) lo cambia. watchVolume(true) invia un evento appmint:volume a ogni modifica. Silenziare il volume della suoneria richiede l'accesso a Non disturbare: requestDndAccess().

if (window.WebToApk && WebToApk.getVolume) {
  const v = JSON.parse(WebToApk.getVolume('alarm'));
  if (v.percent === 0) alert('Alarm volume is off');
  window.addEventListener('appmint:volume', (e) => console.log(e.detail.stream, e.detail.percent));
  WebToApk.watchVolume(true);
}

20Servizio in background, posizione in background, geofence e attività#

startBackgroundService mantiene in esecuzione pagina, audio e timer mentre l'utente è altrove, con una notifica. requestBackgroundLocation chiede 'Allow all the time'. addGeofence scatta in ingresso, uscita o permanenza con l'app chiusa. AppMint.background.register esegue il codice della tua pagina ogni 15 minuti o più. Tutti ripartono dopo il riavvio del telefono.

Servono i loro interruttori nella procedura guidata (passaggio Access); senza di essi una chiamata risponde { ok: false, reason: 'not_enabled' }.

const r = JSON.parse(WebToApk.startBackgroundService(JSON.stringify({ title: 'Syncing', text: 'Keeping data fresh' })));
if (!r.ok) console.warn(r.reason);
if (AppMint.background.isBackgroundRun()) {
  window.addEventListener('appmint:background', async (e) => {
    await refreshData();
    AppMint.background.done(e.detail.runId, { ok: true });
  });
} else {
  AppMint.background.register('refresh', { intervalMinutes: 60 });
}

21Report dei crash#

Attiva Crash reports nel passaggio Integrate. Gli errori non gestiti vengono registrati per te. logError(message, stack) ne registra uno che hai intercettato, setCrashUserId(id) etichetta i report con il tuo ID utente (mai un'e-mail) e getLastCrash() restituisce in JSON il crash più recente, o una stringa vuota. Leggi i report in Update the App, poi la tua app, poi Crash reports.

if (window.WebToApk && WebToApk.logError) {
  try { risky(); } catch (err) { WebToApk.logError(err.message, err.stack || ''); }
  WebToApk.setCrashUserId('u_42');
  const last = WebToApk.getLastCrash();   // '' when none
}

22Integrità del dispositivo e Play Integrity#

Attiva Device integrity checks nel passaggio Integrate. getDeviceIntegrity() restituisce in JSON i segnali di root, emulatore e debugger; è un avviso, non una prova. Per un verdetto affidabile, chiama requestIntegrityToken(nonce, '') e invia il token dell'evento appmint:integrity al TUO server, che chiede il verdetto a Google Play.

const d = JSON.parse(WebToApk.getDeviceIntegrity());
if (d.rooted || d.emulator) alert('This phone is modified');
window.addEventListener('appmint:integrity', (e) => sendToServer(e.detail.token));
WebToApk.requestIntegrityToken(serverNonceBase64, '');

23Icone alternative dell'app e TV#

Aggiungi fino a 4 icone alternative nel passaggio Basic. setAppIcon(name) cambia l'icona nel launcher ('default' torna a quella originale), getAppIcon() indica quella attuale e getAvailableIcons() le elenca. isTv() ti dice se l'app è su un televisore, così puoi mostrare un layout adatto al telecomando.

if (window.WebToApk && WebToApk.setAppIcon) WebToApk.setAppIcon('dark');
if (window.WebToApk && WebToApk.isTv && WebToApk.isTv()) {
  document.documentElement.classList.add('tv');
}

24Accesso con Facebook, analytics e RevenueCat#

L'accesso con Facebook è supportato. Nel passaggio Integrate inserisci il tuo Facebook App ID e Client Token; il login si apre in una scheda del browser e torna all'app. Chiama AppMint.facebook.login(['public_profile', 'email']) e invia il token al tuo server. AppMint.analytics (Firebase) e AppMint.facebook.logEvent (Meta App Events) registrano solo dopo il consenso dell'utente. AppMint.revenuecat gestisce gli abbonamenti con la tua chiave Android pubblica.

const r = await AppMint.facebook.login(['public_profile', 'email']);
if (r.ok) await fetch('/api/login/facebook', { method: 'POST', body: JSON.stringify({ token: r.accessToken }) });
AppMint.analytics.log('share', { method: 'whatsapp' });
const offerings = await AppMint.revenuecat.getOfferings();

25Testo da foto, PDF e widget#

AppMint.textRecognition.recognize(image, { script }) legge il testo in caratteri latini, devanagari, cinesi, giapponesi o coreani. AppMint.pdf.fromPage() e fromHtml(html, options) creano un vero PDF senza finestra di stampa. AppMint.widget.set(card) compila il widget della schermata Home.

const r = await AppMint.textRecognition.recognize(file, { script: 'devanagari' });
const pdf = await AppMint.pdf.fromHtml('<h1>Invoice</h1>', { fileName: 'invoice', save: 'share' });
await AppMint.widget.set({ title: 'Today', value: '3 events', route: '/calendar' });

26Dati sulla salute e beacon Bluetooth#

AppMint.health legge e scrive passi, frequenza cardiaca, sonno, peso ed esercizio tramite Health Connect; attiva Health data e i tipi nel passaggio Access. AppMint.beacons.startScan() trova i beacon iBeacon ed Eddystone e invia eventi appmint:beacon.

const a = await AppMint.health.isAvailable();
if (a.available) {
  await AppMint.health.requestPermissions({ read: ['steps'] });
  const steps = await AppMint.health.read('steps', { start: new Date(Date.now() - 864e5), end: new Date(), aggregate: true });
}
window.addEventListener('appmint:beacon', (e) => console.log(e.detail));
await AppMint.beacons.startScan();

27Plugin pronti: SQLite, notifiche, push, scanner, contatti#

Nelle pagine che l'app serve da sé, window.Capacitor.Plugins contiene CapacitorSQLite (un database privato), LocalNotifications, PushNotifications, CapacitorBarcodeScanner e Contacts. Controlla prima che il plugin esista: un'app in modalità Website ha window.Capacitor solo sulle pagine del proprio sito, mai su altri domini.

const P = window.Capacitor && window.Capacitor.Plugins;
if (P && P.CapacitorBarcodeScanner) {
  const s = await P.CapacitorBarcodeScanner.scanBarcode({ hint: 17 });
  console.log(s.ScanResult);
}
if (P && P.LocalNotifications) await P.LocalNotifications.requestPermissions();

Questa pagina è generata dalla Guida all'integrazione all'interno dell'app stessa, quindi dice esattamente ciò che dice la versione attuale. Leggila in Markdown.

Verificato rispetto alla guida distribuita il 22/10/2026.