API du pont JavaScript
Widget d'écran d'accueil
Une petite carte Android sur l'écran d'accueil que votre page remplit avec un titre, une valeur et quelques lignes - AppMint.widget.
AppMint.widget assistant#
AppMint.widget.set(card) / update(partial) / clear() / placedCount() / requestPin()
Exemple
Remplit le widget d'écran d'accueil de votre application : une carte native avec un titre, une grande valeur, une légende, jusqu'à douze lignes (chacune avec sa propre couleur, son marqueur et sa route d'appui si vous le souhaitez), vos couleurs et une route qu'un appui ouvre. Lorsque l'utilisateur agrandit le widget, il affiche plus de lignes, et avec textSize: 'auto' le texte grandit pour le remplir, tant que chaque ligne tient encore, une longue ligne prenant deux rangées plutôt que d'être coupée. Un widget ne peut pas afficher de HTML : vous lui envoyez le texte.
Renvoie : chaque méthode renvoie une Promise. set(card) → {ok: true, placed} (placed = nombre d'exemplaires sur l'écran d'accueil) ou {ok: false, error}. clear() → undefined. placedCount() → un nombre. requestPin() → true si la fenêtre « Ajouter » du lanceur s'est ouverte. Nécessite : rien : chaque build inclut le widget.
Définir la carte : une fois au démarrage, puis à chaque changement des données qu'elle affiche :
async function updateWidget(events) {
if (!(window.AppMint && AppMint.widget)) return; // in a browser
var r = await AppMint.widget.set({
title: 'Today',
value: events.length + ' events',
lines: events.slice(0, 6).map(function (ev) { return ev.time + ' ' + ev.name; }),
route: '/calendar', // a tap opens the app at this route
bg: '#0E7C86', // background
ink: '#FFFFFF', // title and lines
accent: '#FFE08A' // the big value
});
if (!r.ok) console.log('Widget not updated: ' + r.error);
}Un agenda qui remplit le widget : fond transparent, texte qui grandit quand le widget est redimensionné, lignes colorées avec un marqueur, une ligne en retard en rouge, et un appui sur une ligne qui ouvre ce jour :
AppMint.widget.set({
title: 'Agenda',
value: 'Sun 18 Oct',
caption: '5 events, 1 overdue', // small text under the value
bg: 'rgba(20, 38, 74, 0.6)', // 60 % opaque — the wallpaper shows through
ink: '#FFFFFF', accent: '#FFE08A',
textSize: 'auto', // 'auto' | 'small' | 'normal' | 'large' | 'xl' | a number 0.6–3
align: 'start', // 'start' (left) | 'center' | 'end'
lines: [
{ text: '09:00 Stand-up', marker: '#29B6F6', route: '/#/day/2026-10-18' },
{ text: '10:30 Dentist, Dr Rao', marker: '#43A047', route: '/#/day/2026-10-18' },
{ text: 'Overdue: renew the car insurance', color: '#FF5252', marker: '#FF5252', bold: true, route: '/#/tasks/overdue' },
{ text: 'Tue 20: Team lunch', route: '/#/day/2026-10-20' },
'Wed 21: Gym' // a plain string is still a line
],
route: '/#/today' // a tap anywhere else on the card
});Une ligne est une chaîne ou { text, color?, marker?, bold?, wrap?, route? } ; marker place un ● coloré devant le texte, wrap: true sur une ligne (ou sur la carte) permet à une longue ligne de prendre deux rangées quelle que soit la taille du texte ; avec textSize: 'auto', c'est déjà le cas des lignes, et wrap: false garde une ligne sur une seule rangée. Jusqu'à six lignes s'affichent toujours ; les lignes sept à douze s'affichent quand le widget est assez haut.
Widgets enrichis : une liste, un grand nombre avec une barre de progression, une image de fond et des boutons. Une carte n'est dessinée ainsi que si elle contient l'une de ces clés ; une carte sans elles s'affiche exactement comme avant :
AppMint.widget.set({
layout: 'list', // 'list' | 'progress' | 'card'
title: 'Today', subtitle: 'Tue 30 Sep',
items: [ // up to 5 rows; a row tap opens its own route
{ text: '09:00 Dentist', secondary: 'Dr Rao, 2nd floor', image: 'img/tooth.png', route: '/event/41' },
{ text: '13:30 Lunch with Sam', image: 'https://example.com/sam.jpg', route: '/event/42' }
],
buttons: [{ label: 'Add', route: '/new' }, { label: 'Week', route: '/week' }], // up to 2
bgImage: 'img/widget-bg.jpg', bg: '#101820', ink: '#FFFFFF', accent: '#FFB000'
});
AppMint.widget.set({
layout: 'progress', title: 'Steps', value: '8 214',
progress: { value: 8214, max: 10000, label: '82% of today's goal' }
});
AppMint.widget.update({ value: '8 950', progress: { value: 8950, max: 10000 } }); // change only these keysVotre propre police : placez le fichier de police dans votre application (.ttf ou .otf, par exemple depuis « Download family » de Google Fonts) et indiquez son chemin, comme pour un <img> ; tous les textes de la carte sont dessinés avec elle :
AppMint.widget.set({
title: 'Score', value: '1 250',
font: 'fonts/RocknRollOne-Regular.ttf', // a file in your app, an https address, or a data: URL
lines: ['Level 7', { text: 'New record!', bold: true }]
}).then(function (r) { if (!r.ok) console.log(r.error); }); // e.g. "font file not found in the app: …"Un NOM de police comme 'RocknRoll One' ne peut pas fonctionner : l'écran d'accueil dessine les widgets et ne dispose que des polices du téléphone ; set() répond {ok: false, error} pour le signaler. Ces familles intégrées n'ont besoin d'aucun fichier : 'serif', 'monospace', 'sans-serif-condensed', 'sans-serif-light', 'sans-serif-medium', 'sans-serif-black', 'casual', 'cursive', 'sans-serif-smallcaps'. Les fichiers WOFF / WOFF2 sont refusés (Android ne sait pas les lire) : utilisez le .ttf. Avec un fichier de police, la carte est dessinée comme une image à la taille du widget et redessinée lors du redimensionnement ; les appuis sur les lignes, les rangées et les boutons ainsi que les lecteurs d'écran fonctionnent comme sur n'importe quelle carte.
Actualiser quand l'application est fermée : donnez une adresse https qui renvoie le même JSON ; Android la récupère toutes les refreshMinutes (30 ou plus, 60 par défaut) tant qu'un widget est sur l'écran d'accueil, même après un redémarrage :
AppMint.widget.set({
title: 'Orders', value: '…',
refreshUrl: 'https://api.example.com/widget.json?user=42',
refreshMinutes: 30
}).then(function (r) { console.log('refreshing every ' + r.refreshMinutes + ' min'); });
// widget.json answers e.g. {"title":"Orders","value":"12 new","layout":"list","items":[{"text":"#1042 paid"}]}Images : un chemin dans votre application (résolu comme un <img src> dans la page), une adresse https ou une URL data:image/… ; elles sont réduites et conservées sur le téléphone. Dans le JSON d'actualisation, un chemin relatif désigne un fichier de l'application. refreshUrl: '' arrête l'actualisation ; clear() aussi. Android décide du moment exact de chaque actualisation (il regroupe les tâches en arrière-plan pour économiser la batterie).
Un bouton « Ajouter à l'écran d'accueil » :
async function addWidget() {
if (!(window.AppMint && AppMint.widget)) return;
if ((await AppMint.widget.placedCount()) > 0) { alert('The widget is already on your home screen'); return; }
var opened = await AppMint.widget.requestPin();
if (!opened) alert('Touch and hold your home screen, tap Widgets, and pick this app.');
}Videz-la (par exemple après la déconnexion de l'utilisateur) :
if (window.AppMint && AppMint.widget) AppMint.widget.clear(); // shows "Nothing to show yet"Un appui sur le widget ouvre l'application avec appmint-shortcut://<package><route> comme lien de lancement : lisez-le avec WebToApk.getLaunchUrl() au démarrage et avec l'événement appmint:deep-link pendant l'exécution :
window.addEventListener('appmint:deep-link', function (e) {
var m = /^appmint-shortcut:\/\/[^\/?#]*([^#]*)/i.exec(e.detail.url);
if (m) goTo(m[1] || '/');
});Couleurs : #RRGGBB, #RGB, rgb(20, 38, 74), rgba(20, 38, 74, 0.6) (aussi rgb(20 38 74 / 60%)), transparent et des noms simples comme red. L'hexadécimal à huit chiffres suit l'ordre Android, alpha EN PREMIER : #AARRGGBB. #9914264A est opaque à 60 % ; le #14264A99 de CSS se lit ici comme un alpha de 14 (8 %), une carte presque invisible. Pour des fonds transparents, rgba() évite la confusion. Les couleurs de ligne (color, marker) acceptent les mêmes formes ; une mauvaise couleur de ligne est refusée avec {ok: false, error: 'lines[2].color is not a colour'}, une mauvaise couleur de carte utilise la valeur par défaut.
Remarques : tous les champs de la carte sont facultatifs : un title vide affiche le nom de l'application ; un value, un caption ou des lignes vides sont masqués ; les lignes au-delà de la douzième sont ignorées. textSize et align s'appliquent à tous les textes de la carte ; une valeur inconnue est refusée avec {ok: false, error}. Une carte sans les nouvelles clés (caption, textSize, align, wrap, lignes stylées) s'affiche exactement comme avant. Le widget conserve la dernière carte, même après un redémarrage, et ne change que lorsque votre page rappelle set(). requestPin() nécessite Android 8+ et un lanceur qui le prend en charge. Les applications créées par IA peuvent utiliser widget depuis @/lib/appmintNative. Les lignes stylées, caption, textSize, align, wrap et rgba() nécessitent une application compilée avec AppMint 1.11.38 ou plus récent : recompilez l'application avant d'envoyer des objets de ligne, car une ancienne version les afficherait comme du texte brut. font nécessite une application compilée avec AppMint 1.11.41 ou plus récent (une ancienne version l'ignore). Applications en mode site web : un appui ouvre la route sur votre site : /#/today charge https://<your site>/#/today ; et getLaunchUrl() renvoie toujours le lien appmint-shortcut://.
widgetClear pont#
window.WebToApk.widgetClear()
Supprime la carte ; les widgets placés reviennent à leur état vide.
Exemple
Mécanisme interne derrière AppMint.widget.clear() : supprime la carte du widget ; chaque widget placé revient à « Rien à afficher pour l'instant ».
Retourne : rien. Nécessite : rien.
D'abord l'API publique :
async function onSignOut() {
if (window.AppMint && AppMint.widget) await AppMint.widget.clear();
}L'appel brut :
if (window.WebToApk && typeof window.WebToApk.widgetClear === 'function') {
window.WebToApk.widgetClear();
}Reliez le widget à un interrupteur « Afficher sur l'écran d'accueil » dans vos paramètres : désactivé, il le vide ; activé, il le remplit à nouveau :
document.getElementById('widget-toggle').onchange = async function (e) {
if (!(window.AppMint && AppMint.widget)) return;
if (e.target.checked) {
await AppMint.widget.set(buildWidgetCard()); // your own card builder
} else {
await AppMint.widget.clear();
}
};Après la suppression du compte, videz le widget et prévenez l'utilisateur si un exemplaire est encore sur son écran d'accueil :
function afterAccountDeleted() {
var W = window.WebToApk;
if (!W || typeof W.widgetClear !== 'function') return;
W.widgetClear();
if (W.widgetPlacedCount() > 0) {
showMessage('Your widget is now empty. You can remove it from your home screen.');
}
}Remarques : le widget reste sur l'écran d'accueil ; seul son contenu est supprimé. Appelez AppMint.widget.set() pour le remplir à nouveau.
widgetPlacedCount pont#
window.WebToApk.widgetPlacedCount(): Int
Combien d'exemplaires du widget l'utilisateur a placés sur l'écran d'accueil.
Exemple
Mécanisme interne derrière AppMint.widget.placedCount() : combien d'exemplaires de votre widget l'utilisateur a placés sur l'écran d'accueil.
Renvoie : un nombre, de façon synchrone (0 s'il n'y en a aucun). Nécessite : rien.
L'API publique d'abord : affichez un bouton « Ajouter le widget » uniquement lorsque le widget n'est pas encore placé :
async function showAddWidgetButton() {
var btn = document.getElementById('add-widget');
if (!(window.AppMint && AppMint.widget)) { btn.hidden = true; return; }
btn.hidden = (await AppMint.widget.placedCount()) > 0;
}L'appel brut :
if (window.WebToApk && typeof window.WebToApk.widgetPlacedCount === 'function') {
var n = window.WebToApk.widgetPlacedCount();
console.log('Widgets on the home screen: ' + n);
}Vérifiez à nouveau chaque fois que l'utilisateur revient du lanceur, et remerciez-le une fois le widget en place :
document.addEventListener('visibilitychange', async function () {
if (document.visibilityState !== 'visible') return;
if (!(window.AppMint && AppMint.widget)) return;
var n = await AppMint.widget.placedCount();
document.getElementById('widget-promo').hidden = n > 0;
if (n > 0) document.getElementById('widget-thanks').hidden = false;
});Affichez l'état du widget sur une page de paramètres avec l'appel brut synchrone :
var status = document.getElementById('widget-status');
if (window.WebToApk && typeof window.WebToApk.widgetPlacedCount === 'function') {
var n = window.WebToApk.widgetPlacedCount();
status.textContent = n === 0 ? 'Not on your home screen'
: n === 1 ? 'On your home screen' : 'On your home screen ' + n + ' times';
} else {
status.textContent = 'Available in the installed app';
}Remarques : vérifiez à nouveau quand l'application revient au premier plan (visibilitychange), car l'utilisateur ajoute les widgets depuis le lanceur, en dehors de votre application.
widgetRequestPin pont#
window.WebToApk.widgetRequestPin(): Boolean
Ouvre la fenêtre « Ajouter à l'écran d'accueil » du lanceur pour le widget (Android 8+). Renvoie false là où le lanceur ne le permet pas.
Exemple
Mécanisme interne derrière AppMint.widget.requestPin() : ouvre la fenêtre « Ajouter à l'écran d'accueil » du lanceur pour votre widget.
Renvoie : true si la fenêtre s'est ouverte, false là où le lanceur ne le permet pas (de façon synchrone). Nécessite : Android 8+ et un lanceur qui prend en charge l'épinglage de widgets.
L'API publique d'abord, depuis un appui sur un bouton :
document.getElementById('add-widget').onclick = async function () {
if (!(window.AppMint && AppMint.widget)) return;
var opened = await AppMint.widget.requestPin();
if (!opened) {
alert('Touch and hold your home screen, tap Widgets, then pick this app.');
}
};L'appel brut :
if (window.WebToApk && typeof window.WebToApk.widgetRequestPin === 'function') {
if (!window.WebToApk.widgetRequestPin()) showManualSteps();
}Remplissez d'abord la carte, puis ouvrez la fenêtre, pour que le widget affiche un vrai contenu dès son arrivée :
document.getElementById('add-widget').onclick = async function () {
if (!(window.AppMint && AppMint.widget)) return;
await AppMint.widget.set({ title: 'Streak', value: '12 days', lines: ['Best 30'], route: '/streak' });
if (!(await AppMint.widget.requestPin())) {
document.getElementById('widget-help').hidden = false; // manual steps
}
};Savoir si l'utilisateur a accepté : comparez le nombre de widgets placés avant la fenêtre et après son retour :
document.getElementById('add-widget').onclick = function () {
var W = window.WebToApk;
if (!W || typeof W.widgetRequestPin !== 'function') return;
var before = W.widgetPlacedCount();
if (!W.widgetRequestPin()) { showManualSteps(); return; }
document.addEventListener('visibilitychange', function check() {
if (document.visibilityState !== 'visible') return;
document.removeEventListener('visibilitychange', check);
if (W.widgetPlacedCount() > before) showToast('Widget added');
});
};Remarques : true signifie seulement que la fenêtre s'est ouverte : l'utilisateur peut encore l'annuler. Utilisez AppMint.widget.placedCount() plus tard pour savoir si le widget a été ajouté. Remplissez d'abord la carte avec AppMint.widget.set(), pour que le nouveau widget ne soit pas vide.
widgetSet pont#
window.WebToApk.widgetSet(cardJson: String): String
Enregistre la carte du widget [cardJson] `{title?, value?, lines?, route?, bg?, ink?, accent?}` et redessine chaque widget placé. Renvoie `{ok, placed}`.
Exemple
Mécanisme interne derrière AppMint.widget.set(card) : les pages utilisent cette fonction d'aide pour remplir le widget d'écran d'accueil.
Renvoie : une chaîne JSON {"ok":true,"placed":<number>} ou {"ok":false,"error":"…"} (de façon synchrone). Nécessite : rien : chaque build inclut le widget.
D'abord l'API publique :
if (window.AppMint && AppMint.widget) {
AppMint.widget.set({ title: 'Steps', value: '8 214', lines: ['Goal 10 000'], route: '/' })
.then(function (r) { if (!r.ok) console.log(r.error); });
}L'appel brut prend la carte sous forme de chaîne JSON et renvoie du texte JSON :
if (window.WebToApk && typeof window.WebToApk.widgetSet === 'function') {
var card = { title: 'Steps', value: '8 214', lines: ['Goal 10 000'], route: '/',
bg: '#15161A', ink: '#FFFFFF', accent: '#22D3EE' };
var r = JSON.parse(window.WebToApk.widgetSet(JSON.stringify(card)));
console.log(r.ok ? 'on ' + r.placed + ' home screen(s)' : r.error);
}Gardez le widget synchronisé avec une liste de tâches : le nombre de tâches ouvertes comme valeur, les six premières tâches comme lignes, et un appui ouvre l'écran des tâches :
async function syncTaskWidget(tasks) {
if (!(window.AppMint && AppMint.widget)) return;
var open = tasks.filter(function (t) { return !t.done; });
var r = await AppMint.widget.set({
title: 'To do',
value: open.length ? open.length + ' left' : 'All done',
lines: open.slice(0, 6).map(function (t) { return '• ' + t.title; }),
route: '/tasks'
});
if (!r.ok) console.warn('Widget not updated: ' + r.error);
}Écrivez le dernier état quand l'utilisateur quitte l'application ; l'appel brut est synchrone, il se termine donc avant que la page soit mise en pause :
document.addEventListener('visibilitychange', function () {
if (document.visibilityState !== 'hidden') return;
if (!window.WebToApk || typeof window.WebToApk.widgetSet !== 'function') return;
var s = getTodayStats(); // your app's own numbers
window.WebToApk.widgetSet(JSON.stringify({
title: 'Today', value: s.spent + ' spent',
lines: ['Budget ' + s.budget, 'Left ' + (s.budget - s.spent)], route: '/budget'
}));
});Remarques : clés de la carte : title, value, caption, lines (jusqu'à douze ; une ligne est une chaîne ou {text, color, marker, bold, wrap, route}), route (un / est ajouté s'il manque), bg, ink, accent, textSize, align, wrap, font (un fichier .ttf/.otf de votre application ou une famille système comme serif), plus les clés de mise en page riche, voir AppMint.widget. Couleurs : #RRGGBB, #AARRGGBB (alpha en premier), rgba(). Chaque widget placé est redessiné immédiatement. placed vaut 0 lorsque l'utilisateur n'a pas encore ajouté le widget : la carte est tout de même enregistrée et apparaît dès qu'il l'ajoute.
widgetUpdate pont#
window.WebToApk.widgetUpdate(partialJson: String): String
Fusionne [partialJson] dans la carte du widget enregistrée (les clés nommées remplacent, les autres restent) et redessine. Renvoie `{ok, placed}`.
Exemple
Modifie une partie du widget d'écran d'accueil : les clés que vous passez remplacent celles enregistrées, tout le reste est conservé. Les pages appellent normalement AppMint.widget.update(partial).
Renvoie : une chaîne JSON {"ok":true,"placed":<n>} ou {"ok":false,"error":"…"}. Nécessite : rien : chaque build inclut le widget.
// Only the number and the progress bar change; title, rows and buttons stay as they were.
if (window.AppMint && AppMint.widget) {
AppMint.widget.update({ value: '7 420', progress: { value: 7420, max: 10000, label: '74% of goal' } });
}
// Raw form:
if (window.WebToApk && typeof window.WebToApk.widgetUpdate === 'function') {
var r = JSON.parse(window.WebToApk.widgetUpdate(JSON.stringify({ subtitle: 'Updated ' + new Date().toLocaleTimeString() })));
if (!r.ok) console.warn(r.error);
}Videz la carte à la déconnexion et signalez une erreur depuis update:
async function onSignOut() {
if (!(window.AppMint && AppMint.widget)) return;
await AppMint.widget.clear();
}
async function bumpCount(n) {
if (!(window.AppMint && AppMint.widget)) return;
const r = await AppMint.widget.update({ value: String(n) });
if (!r.ok) console.warn('Widget not updated: ' + r.error);
}Combinez-le avec la vérification de placement : n'actualisez la carte que lorsqu'un exemplaire est sur l'écran d'accueil.
async function refreshWidget(stats) {
if (!(window.AppMint && AppMint.widget)) return;
if (await AppMint.widget.placedCount() === 0) return;
AppMint.widget.update({ value: stats.total + ' today', subtitle: 'Updated ' + new Date().toLocaleTimeString() });
}Remarques : sans carte enregistrée, update() se comporte comme set(). Voir AppMint.widget pour toutes les clés de carte.