API da ponte JavaScript
Widget da tela inicial
Um pequeno cartão Android na tela inicial que sua página preenche com um título, um valor e algumas linhas - AppMint.widget.
AppMint.widget helper#
AppMint.widget.set(card) / update(partial) / clear() / placedCount() / requestPin()
Exemplo
Preenche o widget da tela inicial do seu app: um cartão nativo com um título, um valor grande, uma legenda, até doze linhas (cada uma com sua própria cor, marcador e rota de toque, se quiser), suas cores e uma rota que um toque abre. Quando o usuário aumenta o widget, ele mostra mais linhas e, com textSize: 'auto', o texto cresce para preenchê-lo - enquanto todas as linhas ainda couberem, com uma linha longa ocupando duas linhas em vez de ser cortada. Um widget não pode mostrar HTML - você envia o texto.
Retorna: todo método retorna uma Promise. set(card) → {ok: true, placed} (placed = quantas cópias estão na tela inicial) ou {ok: false, error}. clear() → undefined. placedCount() → um número. requestPin() → true se a janela "Adicionar" do launcher abriu. Requer: nada - toda build tem o widget.
Defina o cartão - uma vez no início e de novo toda vez que os dados que ele mostra mudarem:
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);
}Uma agenda que preenche o widget - fundo transparente, texto que cresce quando o widget é redimensionado, linhas coloridas com marcador, uma linha atrasada em vermelho e um toque em uma linha que abre aquele dia:
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
});Uma linha é uma string ou { text, color?, marker?, bold?, wrap?, route? }; marker coloca um ● colorido antes do texto, wrap: true em uma linha (ou no cartão) permite que uma linha longa ocupe duas linhas em qualquer tamanho de texto; com textSize: 'auto' as linhas já fazem isso, e wrap: false mantém uma linha em uma só linha. Até seis linhas sempre aparecem; as linhas sete a doze aparecem quando o widget é alto o bastante.
Widgets mais ricos - uma lista, um número grande com barra de progresso, uma imagem de fundo e botões. Um cartão só é desenhado assim quando tem uma dessas chaves; um cartão sem elas fica exatamente 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 keysSua própria fonte - coloque o arquivo da fonte no seu app (.ttf ou .otf, por exemplo do "Download family" do Google Fonts) e informe o caminho dele, como faria com um <img>; todo o texto do cartão é desenhado com ela:
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: …"Um NOME de fonte como 'RocknRoll One' não funciona: a tela inicial desenha os widgets e só tem as fontes do próprio celular - set() responde {ok: false, error} dizendo isso. Estas famílias embutidas não precisam de arquivo: 'serif', 'monospace', 'sans-serif-condensed', 'sans-serif-light', 'sans-serif-medium', 'sans-serif-black', 'casual', 'cursive', 'sans-serif-smallcaps'. Arquivos WOFF / WOFF2 são recusados (o Android não consegue lê-los) - use o .ttf. Com um arquivo de fonte, o cartão é desenhado como uma imagem no tamanho do widget e redesenhado quando ele é redimensionado; toques em linhas, itens e botões e leitores de tela funcionam como em qualquer cartão.
Atualizar com o app fechado - informe um endereço https que retorne o mesmo JSON; o Android o busca a cada refreshMinutes (30 ou mais, padrão 60) enquanto um widget estiver na tela inicial, mesmo após 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"}]}Imagens: um caminho no seu app (resolvido como um <img src> na página), um endereço https ou uma URL data:image/…; elas são reduzidas e guardadas no celular. No JSON de atualização, um caminho relativo significa um arquivo do app. refreshUrl: '' interrompe a atualização; clear() também. O Android decide o momento exato de cada atualização (ele agrupa o trabalho em segundo plano para economizar bateria).
Um botão "Adicionar à tela inicial":
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.');
}Esvazie-o (por exemplo, depois que o usuário sair da conta):
if (window.AppMint && AppMint.widget) AppMint.widget.clear(); // shows "Nothing to show yet"Um toque no widget abre o app com appmint-shortcut://<package><route> como link de abertura - leia-o com WebToApk.getLaunchUrl() ao iniciar e com o evento appmint:deep-link durante a execução:
window.addEventListener('appmint:deep-link', function (e) {
var m = /^appmint-shortcut:\/\/[^\/?#]*([^#]*)/i.exec(e.detail.url);
if (m) goTo(m[1] || '/');
});Cores: #RRGGBB, #RGB, rgb(20, 38, 74), rgba(20, 38, 74, 0.6) (também rgb(20 38 74 / 60%)), transparent e nomes simples como red. Hex de oito dígitos segue a ordem do Android, com o alfa PRIMEIRO: #AARRGGBB. #9914264A tem 60 % de opacidade; o #14264A99 do CSS é lido aqui como alfa 14 (8 %), um cartão quase invisível. Para fundos transparentes, rgba() evita a confusão. As cores de linha (color, marker) aceitam as mesmas formas; uma cor de linha inválida é recusada com {ok: false, error: 'lines[2].color is not a colour'}, uma cor de cartão inválida usa o padrão.
Observações: Todos os campos do cartão são opcionais: um title vazio mostra o nome do app; value, caption ou linhas vazios ficam ocultos; linhas após a décima segunda são descartadas. textSize e align valem para todo o texto do cartão; um valor desconhecido é recusado com {ok: false, error}. Um cartão sem as chaves novas (caption, textSize, align, wrap, linhas estilizadas) fica exatamente como antes. O widget guarda o último cartão, mesmo após reiniciar, e só muda quando sua página chama set() de novo. requestPin() requer Android 8+ e um launcher compatível. Apps criados com IA podem usar widget de @/lib/appmintNative. Linhas estilizadas, caption, textSize, align, wrap e rgba() exigem um app compilado com AppMint 1.11.38 ou mais recente - recompile o app antes de enviar objetos de linha, pois uma build mais antiga os mostraria como texto bruto. font exige um app compilado com AppMint 1.11.41 ou mais recente (uma build mais antiga o ignora). Apps no modo site: um toque abre a rota no seu site - /#/today carrega https://<your site>/#/today - e getLaunchUrl() ainda retorna o link appmint-shortcut://.
widgetClear ponte#
window.WebToApk.widgetClear()
Remove o cartão; os widgets colocados voltam ao estado vazio.
Exemplo
Parte interna por trás de AppMint.widget.clear() - remove o cartão do widget; todo widget colocado volta a "Nada para mostrar ainda".
Retorna: nada. Requer: nada.
Primeiro, a API pública:
async function onSignOut() {
if (window.AppMint && AppMint.widget) await AppMint.widget.clear();
}A chamada bruta:
if (window.WebToApk && typeof window.WebToApk.widgetClear === 'function') {
window.WebToApk.widgetClear();
}Ligue o widget a uma chave "Mostrar na tela inicial" nas suas configurações - desligada esvazia o widget, ligada preenche de novo:
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();
}
};Depois de excluir a conta, esvazie o widget e avise o usuário se ainda houver uma cópia na tela inicial dele:
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.');
}
}Observações: O widget continua na tela inicial; só o conteúdo dele é removido. Chame AppMint.widget.set() para preenchê-lo de novo.
widgetPlacedCount ponte#
window.WebToApk.widgetPlacedCount(): Int
Quantas cópias do widget o usuário colocou na tela inicial.
Exemplo
Parte interna por trás de AppMint.widget.placedCount() - quantas cópias do seu widget o usuário colocou na tela inicial.
Retorna: um número, de forma síncrona (0 quando nenhum). Requer: nada.
Primeiro a API pública - mostre um botão "Adicionar widget" apenas quando o widget ainda não estiver 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;
}A chamada bruta:
if (window.WebToApk && typeof window.WebToApk.widgetPlacedCount === 'function') {
var n = window.WebToApk.widgetPlacedCount();
console.log('Widgets on the home screen: ' + n);
}Verifique de novo cada vez que o usuário voltar do launcher e agradeça a ele assim que o widget estiver lá:
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;
});Mostre o status do widget em uma página de configurações com a chamada bruta 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';
}Observações: Verifique de novo quando o app voltar para o primeiro plano (visibilitychange), já que o usuário adiciona widgets pelo launcher, fora do seu app.
widgetRequestPin ponte#
window.WebToApk.widgetRequestPin(): Boolean
Abre a janela "Adicionar à tela inicial" do launcher para o widget (Android 8+). False onde o launcher não consegue.
Exemplo
Parte interna por trás de AppMint.widget.requestPin() - abre a janela "Adicionar à tela inicial" do launcher para o seu widget.
Retorna: true se a janela abriu, false onde o launcher não consegue (de forma síncrona). Requer: Android 8+ e um launcher que aceite fixar widgets.
Primeiro a API pública, a partir de um toque em botão:
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.');
}
};A chamada bruta:
if (window.WebToApk && typeof window.WebToApk.widgetRequestPin === 'function') {
if (!window.WebToApk.widgetRequestPin()) showManualSteps();
}Preencha o cartão primeiro e depois abra a janela, para que o widget mostre conteúdo real assim que for colocado:
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
}
};Descubra se o usuário aceitou: compare a contagem de widgets colocados antes da janela e depois que ele voltar:
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');
});
};Observações: true só significa que a janela abriu - o usuário ainda pode cancelar. Use AppMint.widget.placedCount() depois para ver se foi adicionado. Preencha o cartão com AppMint.widget.set() antes, para que o novo widget não fique vazio.
widgetSet ponte#
window.WebToApk.widgetSet(cardJson: String): String
Salva o cartão do widget [cardJson] `{title?, value?, lines?, route?, bg?, ink?, accent?}` e redesenha todos os widgets colocados. Retorna `{ok, placed}`.
Exemplo
Parte interna por trás de AppMint.widget.set(card) - as páginas usam esse auxiliar para preencher o widget da tela inicial.
Retorna: uma string JSON {"ok":true,"placed":<number>} ou {"ok":false,"error":"…"} (de forma síncrona). Requer: nada - toda build tem o widget.
Primeiro, a 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); });
}A chamada bruta recebe o cartão como uma string JSON e retorna 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);
}Mantenha o widget em sincronia com uma lista de tarefas - a contagem de abertas como valor, as seis primeiras tarefas como linhas e um toque abre a tela de tarefas:
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);
}Grave o estado mais recente quando o usuário sair do app; a chamada bruta é síncrona, então termina antes de a página ser pausada:
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'
}));
});Observações: Chaves do cartão: title, value, caption, lines (até doze; uma linha é uma string ou {text, color, marker, bold, wrap, route}), route (uma / é adicionada se faltar), bg, ink, accent, textSize, align, wrap, font (um arquivo .ttf/.otf no seu app ou uma família do sistema como serif), além das chaves de layout rico - veja AppMint.widget. Cores: #RRGGBB, #AARRGGBB (alfa primeiro), rgba(). Todo widget colocado é redesenhado de uma vez. placed é 0 quando o usuário ainda não adicionou o widget - o cartão é salvo mesmo assim e aparece quando ele adicionar.
widgetUpdate ponte#
window.WebToApk.widgetUpdate(partialJson: String): String
Mescla [partialJson] no cartão salvo do widget (as chaves informadas substituem, o resto permanece) e redesenha. Retorna `{ok, placed}`.
Exemplo
Altera parte do widget da tela inicial: as chaves que você passa substituem as salvas, todo o resto permanece. As páginas normalmente chamam AppMint.widget.update(partial).
Retorna: uma string JSON {"ok":true,"placed":<n>} ou {"ok":false,"error":"…"}. Requer: nada - toda build tem o 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);
}Limpe o cartão ao sair da conta e informe um erro de 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);
}Combine com a verificação de posicionamento: só atualize o cartão enquanto houver uma cópia na tela inicial.
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() });
}Observações: Sem um cartão salvo, update() se comporta como set(). Veja AppMint.widget para todas as chaves do cartão.