Pour commencer
Écrire du HTML pour votre application
Ce que le runtime de l'application expose à votre page (contacts, SMS, notifications, infos de l'appareil, biométrie) et les règles pour l'appeler correctement
1Règle 1 : vérifiez toujours d'abord#
window.WebToApk n'existe que dans votre application compilée. Dans un navigateur, il est absent.
Vérifiez donc toujours avant d'appeler. Sinon, votre page ne fonctionne nulle part ailleurs que dans l'application.
Certaines choses ne demandent aucune vérification : navigator.vibrate(), speechSynthesis et Notification fonctionnent déjà, l'application les relie pour vous. Utilisez simplement le code web habituel.
// GOOD — safe everywhere
if (window.WebToApk) {
WebToApk.playClick();
}
// BAD — this breaks your page in a browser
WebToApk.playClick();2Règle 2 : certaines réponses arrivent plus tard#
Lire les contacts, les SMS ou le journal d'appels prend du temps. Ces appels ne répondent donc pas immédiatement. S'ils le faisaient, votre application se figerait.
Vous donnez un nom (un id) à l'appel. La réponse arrive plus tard sous forme d'événement. Vous écoutez cet événement.
Noms des événements : 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);3Règle 3 : deux interrupteurs doivent être activés#
Une fonctionnalité du téléphone nécessite DEUX choses :
- Vous l'avez activé à l'étape 4 (Accès) en compilant l'application. Sinon, vous obtenez l'erreur 'not_enabled' et l'utilisateur ne voit rien.
- L'utilisateur a dit Oui sur le téléphone. S'il a dit Non, vous obtenez l'erreur 'permission_denied'.
Gérez toujours les deux erreurs pour que l'utilisateur sache ce qui s'est passé.
Plus simple : pickContact() et composeSms() ne nécessitent AUCUNE autorisation. Utilisez-les quand ils conviennent : ils font le même travail avec moins de soucis.
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);
}
});4Notifications avec images et boutons#
notify() accepte une liste d'options. Vous pouvez ajouter une grande image, une icône, des boutons et une barre de progression.
channel peut être : urgent, default, quiet ou ongoing.
Utilisez ongoing:true pour une notification que l'utilisateur ne peut pas balayer. Attention : il peut toujours la supprimer depuis les paramètres d'Android. Aucune application ne peut en créer une impossible à supprimer.
Utilisez tag pour lui donner un nom. Envoyer à nouveau avec le même tag remplace l'ancienne notification au lieu d'en ajouter une nouvelle.
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();
});5Informations sur le téléphone et empreinte digitale#
getDeviceInfo() ne nécessite aucune autorisation. Il vous donne la version d'Android, l'identifiant de version (buildId), le modèle du téléphone, la taille de l'écran, le niveau de batterie, et plus encore.
À propos de l'ID de l'appareil : il n'y a PAS d'IMEI ni de numéro de série. Android l'a bloqué pour toutes les applications depuis Android 10. Utilisez plutôt getInstallId() : un identifiant fixe pour cette installation, qui reste le même après les mises à jour.
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
}));6Faites remplir tout l'écran à votre page#
Quand vous activez Plein écran, l'application masque pour vous les barres d'Android. Mais ajoutez ce CSS pour que votre contenu ne soit pas caché sous l'encoche du téléphone.
Important : Plein écran masque les barres d'ANDROID (heure, batterie, boutons de retour). La barre colorée de votre application en haut est un autre interrupteur, appelé « Afficher la barre supérieure ». Si vous voyez encore une barre après avoir activé Plein écran, c'est celle-là qu'il faut désactiver.
Testez aussi votre application en rotation. L'encoche passe sur le côté en mode paysage.
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 */
}7Les téléchargements conservent le nom de fichier que vous avez choisi#
Votre code de téléchargement habituel fonctionne déjà. Quand votre page enregistre un fichier (rapport PDF, export CSV, sauvegarde), l'application l'intercepte et ouvre la feuille « Enregistrer sous… » d'Android avec VOTRE nom de fichier déjà rempli.
Cela couvre toutes les façons habituelles de faire : <a download="...">, a.click() depuis le code, html2pdf / jsPDF, saveAs() de FileSaver.js, et window.open() sur une URL blob. Un objet File apporte son propre nom.
Si vous préférez demander directement plutôt que de construire une balise <a>, utilisez AppMint.downloadFile().
À savoir : le nom n'existe que dans le JavaScript de votre page ; Android ne le voit jamais de lui-même. Renseignez donc download= (ou passez un nom) à chaque fois, sinon le fichier est enregistré sous le nom « 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');8Faites faire au bouton Retour ce que VOTRE application attend#
Si votre application change d'écran en affichant et masquant des éléments (sans changement d'URL, sans history.pushState), le bouton Retour d'Android ne voit pas ces écrans : son historique est vide, donc Retour quitte l'application depuis n'importe où.
Enregistrez un gestionnaire de retour et décidez vous-même : renvoyez true quand vous avez géré l'appui (fermé un menu, revenu à un écran), renvoyez false pour laisser le comportement normal s'exécuter : retour dans l'historique de la page, puis confirmation de sortie, puis sortie.
Les applications qui utilisent history.pushState pour chaque écran n'ont pas besoin de cela : Retour parcourt déjà leur historique. Et pas d'inquiétude pour un gel de l'application : si votre gestionnaire se bloque, le bouton Retour du téléphone refonctionne nativement après un court instant.
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();
});9Recevoir ce que les gens partagent vers votre application : liens, photos, vidéos, fichiers#
Activez « Receive shares from other apps » dans l'assistant de compilation et cochez ce que votre application accepte : texte et liens, fichiers de données (JSON, CSV, TXT, XML, Markdown, GPX, TCX, FIT), photos, vidéos, audio, documents (PDF, Word, Excel, PowerPoint). Votre application apparaît alors dans le menu Partager de toutes les autres applications pour exactement ces types : une application de notes pour le texte, un lecteur pour les vidéos.
Un lien ou un texte (partager une vidéo YouTube depuis l'application YouTube envoie son lien) est lu avec WebToApk.getSharedText() et annoncé comme appmint:shared. Un fichier se demande avec AppMint.getOpenedFile() : demandez quand vous êtes prêt, le fichier vous attend. C'est important pour les applications React, Vue et Angular : elles finissent de démarrer APRÈS le chargement de la page, si bien qu'une application qui n'écoutait que l'événement ratait le fichier et affichait simplement son écran d'accueil.
f.kind vous indique ce qui est arrivé : image, video, audio, document ou data. Les photos, vidéos et audio arrivent sous forme de f.url : une adresse diffusable et navigable que vous mettez directement dans <video src>, <img src> ou <audio src>, afin qu'un clip de 300 Mo n'ait jamais à tenir en mémoire ; f.getFile() récupère le File quand vous avez vraiment besoin des octets. Les fichiers de données et documents arrivent aussi avec f.file, plus f.text pour les formats texte et f.base64 pour les petits binaires comme raccourcis. Une photo partagée avec une légende livre aussi la légende comme texte partagé.
// 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));10Capteurs Bluetooth : Web Bluetooth standard#
Sangles de fréquence cardiaque, capteurs de cadence et de vitesse, capteurs de puissance, home trainers connectés, balances : tout ce qui parle Bluetooth LE.
Utilisez navigator.bluetooth, exactement le même code que vous écririez pour Chrome sur ordinateur. AppMint le fournit dans l'application ; une WebView Android ordinaire n'en a pas, c'est pourquoi ce code ne fait rien dans les autres générateurs d'applications.
Le sélecteur d'appareil est affiché par l'application, comme celui du navigateur : votre page ne peut atteindre que l'appareil choisi par l'utilisateur.
Activez l'autorisation Bluetooth à l'étape 4 (Accès). Si votre code mentionne navigator.bluetooth, AppMint l'active pour vous quand vous choisissez votre ZIP.
Non disponibles : getDevices(), watchAdvertisements() et requestLEScan() : ils sont rejetés avec NotSupportedError, vérifiez donc avant de les utiliser.
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());11Envoi de photos : le sélecteur de photos d'Android, sans autorisation#
Utilisez un champ de fichier normal. Rien d'autre à ajouter.
Dans l'application, <input type="file" accept="image/*"> ouvre le sélecteur de photos d'Android : les photos de l'utilisateur dans une grille, et votre page ne reçoit que celles qu'il choisit. Aucune autorisation de stockage, aucune fenêtre « Autoriser l'accès aux photos ? ». Cela fonctionne aussi sur les vieux téléphones : Google Play services y installe le sélecteur.
- accept="image/*" : photos uniquement
- accept="video/*" : vidéos uniquement
- accept="image/*,video/*" : les deux
- ajoutez multiple : l'utilisateur peut en choisir plusieurs
- ajoutez capture="environment" : ouvre la caméra à la place (nécessite Caméra cochée à l'étape 4 (Accès))
Mélangez d'autres fichiers (accept="image/*,.pdf") et le sélecteur de fichiers normal s'ouvre à la place, afin que le PDF puisse aussi être choisi.
<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>12Scanner un code QR ou un code-barres : une seule ligne#
AppMint.scanCode() ouvre l'écran du scanner de Google. L'utilisateur pointe la caméra, et vous récupérez le code.
Aucune autorisation caméra. Aucun élément video. Rien à dessiner. Le scanner zoome tout seul pour les petits codes. La première fois sur un téléphone, Google Play services télécharge le scanner (quelques secondes).
La réponse contient value (le texte du code) et format (qr_code, ean_13, …). Pour les codes QR courants, vous obtenez aussi les éléments prêts à l'emploi : url, wifi (ssid, password), phone, email, sms, geo, contact.
Si ok vaut false, code vous dit pourquoi : cancelled (l'utilisateur l'a fermé), unavailable (pas de Google Play services, ou échec du téléchargement), busy (un scan est déjà ouvert).
AppMint.scanCode n'existe que dans l'application : vérifiez d'abord sa présence.
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 })13Passkeys : se connecter avec l'empreinte ou le visage, sans mot de passe#
Si votre site web a déjà des passkeys, elles fonctionnent désormais aussi dans votre application. Une WebView Android normale ne gère pas les passkeys ; AppMint relie navigator.credentials au gestionnaire de passkeys d'Android, si bien que des bibliothèques comme SimpleWebAuthn, Hanko, Corbado ou Auth0 fonctionnent sans modification.
Il vous faut un serveur qui crée les options de passkey et vérifie la réponse (par exemple SimpleWebAuthn sur votre backend). Une passkey ne peut pas être vérifiée dans la page.
Deux choses à configurer une seule fois :
- Votre site web doit déclarer qu'il fait confiance à l'application. Placez le fichier fourni par
AppMint.passkey.assetLinks() à l'adresse https://YOUR-DOMAIN/.well-known/assetlinks.json. Prenez-le depuis l'application installée DEPUIS Google Play : Play signe votre application avec sa propre clé, donc le fichier d'un APK de test ne correspondra pas aux utilisateurs de Play. Le domaine doit être exactement le rpID de votre serveur (www et sans-www sont différents).
- Votre serveur doit accepter l'origine de l'application. La réponse de l'application indique l'origine 'android:apk-key-hash:…' au lieu de https://… ; récupérez cette valeur avec
AppMint.passkey.origin() et ajoutez-la aux origines autorisées de votre serveur.
Erreurs : NotAllowedError = l'utilisateur a annulé ou n'a pas de passkey sur ce téléphone. InvalidStateError = cette passkey existe déjà. SecurityError = assetlinks.json ne liste pas l'application.
Les passkeys nécessitent Android 9 ou plus récent avec Google Play services : AppMint.passkey.isAvailable() vous le dit. Le remplissage automatique de passkey dans un champ texte (mediation: 'conditional') n'est pas disponible dans les applications ; lancez la connexion depuis un bouton.
// 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 d'écran d'accueil : affichez vos données sur l'écran d'accueil#
Votre application a un widget d'écran d'accueil. L'utilisateur l'ajoute depuis la liste des widgets du téléphone (appui long sur l'écran d'accueil, puis Widgets), ou vous ouvrez la feuille d'ajout avec AppMint.widget.requestPin().
Le widget reste vide (« Nothing to show yet ») tant que votre page ne le remplit pas. C'est une petite carte Android, pas une page web, il ne peut donc pas afficher votre HTML. Vous lui donnez un titre, une grande valeur, jusqu'à six lignes courtes et des couleurs.
Appelez AppMint.widget.set() à l'ouverture de l'application, puis à chaque changement des données affichées. Le widget conserve la dernière carte, même après un redémarrage. route est la page que votre application ouvre quand on touche le 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();
}15Protection contre la copie et menu d'appui long#
Deux interrupteurs à l'étape 3 (Intégrer), sous Options d'affichage. Tous deux sont désactivés tant que vous ne les activez pas.
- Menu d'appui long sur les liens et images : maintenez appuyé sur un lien ou une image pour l'ouvrir, le copier, le partager ou l'enregistrer. Désactivé par défaut, donc un appui long sur un lien ne fait rien. Une page peut toujours refuser le menu sur un élément en annulant l'événement contextmenu.
- Protection contre la copie : personne ne peut sélectionner, copier ou couper le texte de votre application. La saisie dans les formulaires fonctionne toujours. Elle désactive aussi le menu d'appui long.
Votre page peut le savoir : AppMint.copyProtection vaut true quand la protection est active, et est absent quand elle est désactivée (et dans un navigateur). Il est en lecture seule. Masquez vos propres boutons Copier quand elle est active, car ils ne copieraient rien.
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} exemples prêts à l'emploi : ouvrez-les ici#
Du code fonctionnel à copier : envoyer un SMS, lire les contacts, afficher une notification avec une image, verrouillage par empreinte, informations sur le téléphone, enregistrement de fichiers, capteurs Bluetooth, et plus. Chacun est court et expliqué en mots simples.
S'ouvre dans AppMint. Vous pouvez y faire des recherches, et chaque bloc de code a un bouton Copier.
Le même écran propose aussi « Toutes les méthodes » : les {methodCount} choses que votre application peut appeler, chacune avec un exemple fonctionnel à copier. Cette liste est construite à partir du runtime de l'application lui-même, elle correspond donc toujours exactement à ce que votre application sait faire.
17Enregistrez les exemples sur votre téléphone ou votre ordinateur#
Enregistre trois fichiers dans Downloads/AppMint/Guides :
- Exemples : les {exampleCount} exemples ci-dessus
- Liste des méthodes : toutes les {methodCount} méthodes
- index.html : une page de test prête à l'emploi
La page de test a des boutons pour les infos de l'appareil, la vibration, les notifications et le sélecteur de contacts. Compilez-la en application pour tout voir fonctionner, puis transformez-la en votre propre application. Elle s'ouvre aussi très bien dans un navigateur normal : rien ne casse, elle indique simplement qu'elle n'est pas dans l'application.
Dans l'application, le bouton de cette étape les enregistre sur votre téléphone. Ici, ce sont des téléchargements directs.
AppMint-examples.md AppMint-all-methods.md index.html18Réveil qui sonne même sur l'écran de verrouillage#
Cochez Réveil à l'étape Accès. scheduleNotificationEx avec alarm: true sonne avec votre son jusqu'à ce que l'utilisateur l'arrête ou le reporte, même application fermée. La répétition peut être quotidienne, hebdomadaire, en semaine, mensuelle ou annuelle, ou choisir des jours de la semaine.
Avant de programmer, interrogez getAlarmSupport(). Il répond 'ready', 'not_enabled', 'needs_exact_alarm' ou 'needs_notification_permission'. Puis stopAlarm(id), snoozeAlarm(id, minutes) et isAlarmRinging(id) contrôlent un réveil en train de sonner.
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 système#
getVolume(stream) lit le volume média, alarme, sonnerie, notification ou appel en JSON. setVolume(stream, percent, showUi) le modifie. watchVolume(true) envoie un événement appmint:volume à chaque changement. Couper le volume de la sonnerie nécessite l'accès Ne pas déranger : 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);
}20Service en arrière-plan, localisation en arrière-plan, geofences et tâches#
startBackgroundService garde votre page, l'audio et les minuteurs actifs pendant que l'utilisateur est ailleurs, avec une notification. requestBackgroundLocation demande « Toujours autoriser ». addGeofence déclenche entrée, sortie ou séjour application fermée. AppMint.background.register exécute le code de votre propre page toutes les 15 minutes ou plus. Tous redémarrent après un redémarrage du téléphone.
Celles-ci nécessitent leur interrupteur dans l'assistant (étape Accès) ; sans lui, un appel répond { 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 });
}21Rapports de plantage#
Cochez Rapports de plantage à l'étape Intégrer. Les erreurs non interceptées sont enregistrées pour vous. logError(message, stack) en enregistre une que vous avez interceptée, setCrashUserId(id) associe les rapports à votre propre identifiant utilisateur (jamais un e-mail), et getLastCrash() renvoie le plantage le plus récent en JSON, ou une chaîne vide. Consultez les rapports dans Mettre à jour l'application, puis votre application, puis Rapports de plantage.
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
}22Intégrité de l'appareil et Play Integrity#
Cochez Contrôles d'intégrité de l'appareil à l'étape Intégrer. getDeviceIntegrity() renvoie en JSON les signes de root, d'émulateur et de débogueur ; c'est un avertissement, pas une preuve. Pour un verdict fiable, appelez requestIntegrityToken(nonce, '') et envoyez le jeton de l'événement appmint:integrity à VOTRE serveur, qui demande le verdict à 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, '');23Icônes d'application alternatives et TV#
Ajoutez jusqu'à 4 icônes alternatives à l'étape Basique. setAppIcon(name) change l'icône du lanceur ('default' revient à l'icône d'origine), getAppIcon() nomme l'icône actuelle et getAvailableIcons() les liste. isTv() vous indique que l'application tourne sur une télévision, pour afficher une disposition adaptée à la télécommande.
if (window.WebToApk && WebToApk.setAppIcon) WebToApk.setAppIcon('dark');
if (window.WebToApk && WebToApk.isTv && WebToApk.isTv()) {
document.documentElement.classList.add('tv');
}24Connexion Facebook, analytique et RevenueCat#
La connexion Facebook est prise en charge. À l'étape Intégrer, saisissez votre Facebook App ID et votre Client Token ; la connexion s'ouvre dans un onglet du navigateur et revient à l'application. Appelez AppMint.facebook.login(['public_profile', 'email']) et envoyez le jeton à votre serveur. AppMint.analytics (Firebase) et AppMint.facebook.logEvent (Meta App Events) n'enregistrent qu'une fois que l'utilisateur a accepté. AppMint.revenuecat gère les abonnements avec votre clé Android publique.
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();25Texte à partir de photos, de PDF et de widgets#
AppMint.textRecognition.recognize(image, { script }) lit le texte en caractères latins, devanagari, chinois, japonais ou coréens. AppMint.pdf.fromPage() et fromHtml(html, options) créent un vrai PDF sans boîte de dialogue d'impression. AppMint.widget.set(card) remplit le widget de l'écran d'accueil.
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' });26Données de santé et balises Bluetooth#
AppMint.health lit et écrit les pas, la fréquence cardiaque, le sommeil, le poids et l'exercice via Health Connect ; cochez Données de santé et les types à l'étape Accès. AppMint.beacons.startScan() trouve les balises iBeacon et Eddystone et envoie des événements 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();27Plugins prêts à l'emploi : SQLite, notifications, push, scanner, contacts#
Sur les pages servies par l'application elle-même, window.Capacitor.Plugins contient CapacitorSQLite (une base de données privée), LocalNotifications, PushNotifications, CapacitorBarcodeScanner et Contacts. Vérifiez d'abord que le plugin existe : une application en mode Site web n'a window.Capacitor que sur les pages de son propre site, jamais sur d'autres domaines.
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();