API del puente de JavaScript
Widget de pantalla de inicio
Una pequeña tarjeta de Android en la pantalla de inicio que tu página llena con un título, un valor y algunas líneas - AppMint.widget.
AppMint.widget ayudante#
AppMint.widget.set(card) / update(partial) / clear() / placedCount() / requestPin()
Ejemplo
Llena el widget de pantalla de inicio de tu app: una tarjeta nativa con un título, un valor grande, un pie, hasta doce líneas (cada una con su propio color, marcador y ruta al tocar si quieres), tus colores y una ruta que se abre al tocar. Cuando el usuario agranda el widget, muestra más líneas, y con textSize: 'auto' el texto crece para llenarlo, mientras cada línea siga cabiendo; una línea larga ocupa dos filas en lugar de cortarse. Un widget no puede mostrar HTML: tú le envías el texto.
Devuelve: cada método devuelve una Promise. set(card) → {ok: true, placed} (placed = cuántas copias hay en la pantalla de inicio) o {ok: false, error}. clear() → undefined. placedCount() → un número. requestPin() → true si se abrió la hoja "Añadir" del launcher. Requiere: nada: todas las compilaciones incluyen el widget.
Define la tarjeta una vez al inicio, y de nuevo cada vez que cambien los datos que muestra:
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);
}Una agenda que llena el widget: fondo transparente, texto que crece al cambiar el tamaño del widget, líneas de colores con marcador, una línea vencida en rojo y un toque en una línea que abre ese día:
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
});Una línea es una cadena o { text, color?, marker?, bold?, wrap?, route? }; marker pone un ● de color delante del texto, wrap: true en una línea (o en la tarjeta) permite que una línea larga ocupe dos filas con cualquier tamaño de texto; con textSize: 'auto' las líneas ya lo hacen, y wrap: false mantiene una línea en una sola fila. Siempre se muestran hasta seis líneas; las líneas de la siete a la doce se muestran cuando el widget es lo bastante alto.
Widgets más completos: una lista, un número grande con barra de progreso, una imagen de fondo y botones. Una tarjeta se dibuja así solo cuando tiene una de estas claves; una tarjeta sin ellas se ve exactamente como antes:
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 keysTu propia fuente: pon el archivo de la fuente en tu app (.ttf o .otf, p. ej. desde "Download family" de Google Fonts) e indica su ruta, como harías con un <img>; todo el texto de la tarjeta se dibuja con ella:
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 NOMBRE de fuente como 'RocknRoll One' no puede funcionar: la pantalla de inicio dibuja los widgets y solo tiene las fuentes propias del teléfono; set() responde {ok: false, error} indicándolo. Estas familias integradas no necesitan archivo: 'serif', 'monospace', 'sans-serif-condensed', 'sans-serif-light', 'sans-serif-medium', 'sans-serif-black', 'casual', 'cursive', 'sans-serif-smallcaps'. Los archivos WOFF / WOFF2 se rechazan (Android no puede leerlos): usa el .ttf. Con un archivo de fuente, la tarjeta se dibuja como una imagen al tamaño del widget y se redibuja al cambiar de tamaño; los toques en líneas, filas y botones y los lectores de pantalla funcionan como en cualquier tarjeta.
Actualizar con la app cerrada: indica una dirección https que devuelva el mismo JSON; Android la consulta cada refreshMinutes (30 o más, 60 por defecto) mientras haya un widget en la pantalla de inicio, incluso después de reiniciar:
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"}]}Imágenes: una ruta dentro de tu app (resuelta como un <img src> en la página), una dirección https o una URL data:image/…; se reducen de tamaño y se guardan en el teléfono. En el JSON de actualización, una ruta relativa significa un archivo de la app. refreshUrl: '' detiene la actualización; clear() también la detiene. Android decide el momento exacto de cada actualización (agrupa el trabajo en segundo plano para ahorrar batería).
Un botón "Añadir a la pantalla de inicio":
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.');
}Vacíalo (por ejemplo, cuando el usuario cierra sesión):
if (window.AppMint && AppMint.widget) AppMint.widget.clear(); // shows "Nothing to show yet"Un toque en el widget abre la app con appmint-shortcut://<package><route> como enlace de inicio: léelo con WebToApk.getLaunchUrl() al arrancar y con el evento appmint:deep-link mientras se ejecuta:
window.addEventListener('appmint:deep-link', function (e) {
var m = /^appmint-shortcut:\/\/[^\/?#]*([^#]*)/i.exec(e.detail.url);
if (m) goTo(m[1] || '/');
});Colores: #RRGGBB, #RGB, rgb(20, 38, 74), rgba(20, 38, 74, 0.6) (también rgb(20 38 74 / 60%)), transparent y nombres simples como red. El hexadecimal de ocho dígitos usa el orden de Android, con el alfa PRIMERO: #AARRGGBB. #9914264A es 60 % opaco; el #14264A99 de CSS se lee aquí como alfa 14 (8 %), una tarjeta casi invisible. Para fondos transparentes, rgba() evita la confusión. Los colores de línea (color, marker) admiten las mismas formas; un color de línea no válido se rechaza con {ok: false, error: 'lines[2].color is not a colour'}, y un color de tarjeta no válido usa el predeterminado.
Notas: Todos los campos de la tarjeta son opcionales: un title vacío muestra el nombre de la app; value, caption o líneas vacíos se ocultan; las líneas después de la duodécima se descartan. textSize y align se aplican a todo el texto de la tarjeta; un valor desconocido se rechaza con {ok: false, error}. Una tarjeta sin las claves nuevas (caption, textSize, align, wrap, líneas con estilo) se ve exactamente como antes. El widget conserva la última tarjeta, incluso después de reiniciar, y cambia solo cuando tu página vuelve a llamar a set(). requestPin() requiere Android 8+ y un launcher compatible. Las apps creadas con IA pueden usar widget de @/lib/appmintNative. Las líneas con estilo, caption, textSize, align, wrap y rgba() requieren una app compilada con AppMint 1.11.38 o posterior: vuelve a compilar la app antes de enviar objetos de línea, porque una compilación anterior los mostraría como texto sin procesar. font requiere una app compilada con AppMint 1.11.41 o posterior (una compilación anterior lo ignora). Apps en modo sitio web: un toque abre la ruta en tu sitio web: /#/today carga https://<your site>/#/today, y getLaunchUrl() sigue devolviendo el enlace appmint-shortcut://.
widgetClear puente#
window.WebToApk.widgetClear()
Elimina la tarjeta; los widgets colocados vuelven a su estado vacío.
Ejemplo
Mecanismo interno detrás de AppMint.widget.clear(): elimina la tarjeta del widget; cada widget colocado vuelve a "Nada que mostrar todavía".
Devuelve: nada. Requiere: nada.
Primero la API pública:
async function onSignOut() {
if (window.AppMint && AppMint.widget) await AppMint.widget.clear();
}La llamada en bruto:
if (window.WebToApk && typeof window.WebToApk.widgetClear === 'function') {
window.WebToApk.widgetClear();
}Vincula el widget a un interruptor "Mostrar en la pantalla de inicio" en tus ajustes: al apagarlo se vacía, al encenderlo se vuelve a llenar:
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();
}
};Después de eliminar la cuenta, vacía el widget e informa al usuario si todavía queda una copia en su pantalla de inicio:
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.');
}
}Notas: El widget permanece en la pantalla de inicio; solo se elimina su contenido. Llama a AppMint.widget.set() para volver a llenarlo.
widgetPlacedCount puente#
window.WebToApk.widgetPlacedCount(): Int
Cuántas copias del widget ha colocado el usuario en la pantalla de inicio.
Ejemplo
Mecanismo interno detrás de AppMint.widget.placedCount(): cuántas copias de tu widget ha puesto el usuario en la pantalla de inicio.
Devuelve: un número, de forma síncrona (0 si no hay ninguno). Requiere: nada.
Primero la API pública: muestra un botón "Añadir widget" solo cuando el widget aún no está colocado:
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;
}La llamada en bruto:
if (window.WebToApk && typeof window.WebToApk.widgetPlacedCount === 'function') {
var n = window.WebToApk.widgetPlacedCount();
console.log('Widgets on the home screen: ' + n);
}Vuelve a comprobarlo cada vez que el usuario regrese del launcher y dale las gracias cuando el widget ya esté ahí:
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;
});Muestra el estado del widget en una página de ajustes con la llamada sin procesar síncrona:
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';
}Notas: Vuelve a comprobarlo cuando la app regrese al primer plano (visibilitychange), ya que el usuario añade widgets desde el launcher, fuera de tu app.
widgetRequestPin puente#
window.WebToApk.widgetRequestPin(): Boolean
Abre la hoja "Añadir a la pantalla de inicio" del launcher para el widget (Android 8+). Devuelve false donde el launcher no puede.
Ejemplo
Mecanismo interno detrás de AppMint.widget.requestPin(): abre la hoja "Añadir a la pantalla de inicio" del launcher para tu widget.
Devuelve: true si se abrió la hoja, false donde el launcher no puede (de forma síncrona). Requiere: Android 8+ y un launcher que admita fijar widgets.
Primero la API pública, desde un toque en un botón:
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.');
}
};La llamada en bruto:
if (window.WebToApk && typeof window.WebToApk.widgetRequestPin === 'function') {
if (!window.WebToApk.widgetRequestPin()) showManualSteps();
}Llena primero la tarjeta y luego abre la hoja, para que el widget muestre contenido real en cuanto aparezca:
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
}
};Averigua si el usuario aceptó: compara la cantidad de widgets colocados antes de abrir la hoja y después de que regrese:
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');
});
};Notas: true solo significa que la hoja se abrió: el usuario aún puede cancelarla. Usa AppMint.widget.placedCount() más tarde para ver si se añadió. Llena primero la tarjeta con AppMint.widget.set(), para que el widget nuevo no esté vacío.
widgetSet puente#
window.WebToApk.widgetSet(cardJson: String): String
Guarda la tarjeta del widget [cardJson] `{title?, value?, lines?, route?, bg?, ink?, accent?}` y vuelve a dibujar cada widget colocado. Devuelve `{ok, placed}`.
Ejemplo
Mecanismo interno detrás de AppMint.widget.set(card): las páginas usan esa función para llenar el widget de pantalla de inicio.
Devuelve: una cadena JSON {"ok":true,"placed":<number>} o {"ok":false,"error":"…"} (de forma síncrona). Requiere: nada: todas las compilaciones incluyen el widget.
Primero la API pública:
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); });
}La llamada sin procesar recibe la tarjeta como una cadena JSON y devuelve texto 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);
}Mantén el widget al día con una lista de tareas: el número de pendientes como valor, las primeras seis tareas como líneas, y un toque abre la pantalla de tareas:
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);
}Escribe el último estado cuando el usuario salga de la app; la llamada sin procesar es síncrona, así que termina antes de que la página se 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'
}));
});Notas: Claves de la tarjeta: title, value, caption, lines (hasta doce; una línea es una cadena o {text, color, marker, bold, wrap, route}), route (se añade un / si falta), bg, ink, accent, textSize, align, wrap, font (un archivo .ttf/.otf en tu app o una familia del sistema como serif), además de las claves del diseño enriquecido: consulta AppMint.widget. Colores: #RRGGBB, #AARRGGBB (alfa primero), rgba(). Todos los widgets colocados se redibujan a la vez. placed es 0 cuando el usuario aún no ha añadido el widget: la tarjeta se guarda igualmente y aparece cuando lo haga.
widgetUpdate puente#
window.WebToApk.widgetUpdate(partialJson: String): String
Combina [partialJson] con la tarjeta del widget guardada (las claves indicadas reemplazan, el resto se mantiene) y vuelve a dibujar. Devuelve `{ok, placed}`.
Ejemplo
Cambia una parte del widget de pantalla de inicio: las claves que pasas reemplazan a las guardadas, todo lo demás se mantiene. Las páginas normalmente llaman a AppMint.widget.update(partial).
Devuelve: una cadena JSON {"ok":true,"placed":<n>} o {"ok":false,"error":"…"}. Requiere: nada: todas las compilaciones incluyen el 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);
}Borra la tarjeta al cerrar sesión e informa de un error desde 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);
}Combínalo con la comprobación de colocación: actualiza la tarjeta solo mientras haya una copia en la pantalla de inicio.
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() });
}Notas: Sin una tarjeta guardada, update() se comporta como set(). Consulta AppMint.widget para ver todas las claves de la tarjeta.