API da ponte JavaScript
Notificações no dispositivo
Publicar e agendar sem servidor, a permissão POST_NOTIFICATIONS, alarmes exatos e sons personalizados.
AppMint.enableNotifications helper#
AppMint.enableNotifications()
Exemplo
"Ativar notificações" em uma única chamada, para usuários que negaram ou pularam o aviso de notificação do Android: a caixa de permissão enquanto o Android ainda puder mostrá-la; caso contrário, as configurações de notificação deste app — e o novo estado quando o usuário volta.
Retorna: AppMint.enableNotifications() — uma Promise que sempre é resolvida com {state, changed, openedSettings}; state é 'granted' | 'prompt' | 'blocked' | 'unavailable'. AppMint.getNotificationState() — uma Promise desse estado. AppMint.openNotificationSettings() — uma Promise de true quando a tela de configurações abriu. Requer: chame enableNotifications() a partir de um botão que o usuário tocou; as notificações exigem Notificações (ou Push / Lembretes) na build.
// A banner that only shows while notifications are off, and turns them on in one tap.
async function setupNotifBanner() {
if (!(window.AppMint && AppMint.enableNotifications)) return; // a browser: no banner
var banner = document.getElementById('notif-banner');
var state = await AppMint.getNotificationState();
banner.hidden = !(state === 'prompt' || state === 'blocked');
document.getElementById('notif-turn-on').onclick = async function () {
var r = await AppMint.enableNotifications();
banner.hidden = r.state === 'granted';
if (r.state === 'blocked') showToast('Switch on "Allow notifications" to get updates.');
};
}
setupNotifBanner();Um simples link "Configurações de notificação" na sua própria página de configurações:
document.getElementById('notif-settings').onclick = async function () {
var opened = window.AppMint && AppMint.openNotificationSettings && await AppMint.openNotificationSettings();
if (!opened) showToast('Open Settings > Apps > this app > Notifications.');
};Observações: um botão de push ou de mensagem no app cujo link é appmint://notifications/enable executa o mesmo fluxo sem nenhum código na página (a página ouve appmint:notifications). O público de push do app ("Notificações ativadas / desativadas") é atualizado assim que o usuário as ativa.
AppMint.getNotificationState helper#
window.AppMint.getNotificationState
Exemplo
Documentado junto com AppMint.enableNotifications - o exemplo de lá mostra este também.
"Ativar notificações" em uma única chamada, para usuários que negaram ou pularam o aviso de notificação do Android: a caixa de permissão enquanto o Android ainda puder mostrá-la; caso contrário, as configurações de notificação deste app — e o novo estado quando o usuário volta.
Retorna: AppMint.enableNotifications() — uma Promise que sempre é resolvida com {state, changed, openedSettings}; state é 'granted' | 'prompt' | 'blocked' | 'unavailable'. AppMint.getNotificationState() — uma Promise desse estado. AppMint.openNotificationSettings() — uma Promise de true quando a tela de configurações abriu. Requer: chame enableNotifications() a partir de um botão que o usuário tocou; as notificações exigem Notificações (ou Push / Lembretes) na build.
// A banner that only shows while notifications are off, and turns them on in one tap.
async function setupNotifBanner() {
if (!(window.AppMint && AppMint.enableNotifications)) return; // a browser: no banner
var banner = document.getElementById('notif-banner');
var state = await AppMint.getNotificationState();
banner.hidden = !(state === 'prompt' || state === 'blocked');
document.getElementById('notif-turn-on').onclick = async function () {
var r = await AppMint.enableNotifications();
banner.hidden = r.state === 'granted';
if (r.state === 'blocked') showToast('Switch on "Allow notifications" to get updates.');
};
}
setupNotifBanner();Um simples link "Configurações de notificação" na sua própria página de configurações:
document.getElementById('notif-settings').onclick = async function () {
var opened = window.AppMint && AppMint.openNotificationSettings && await AppMint.openNotificationSettings();
if (!opened) showToast('Open Settings > Apps > this app > Notifications.');
};Observações: um botão de push ou de mensagem no app cujo link é appmint://notifications/enable executa o mesmo fluxo sem nenhum código na página (a página ouve appmint:notifications). O público de push do app ("Notificações ativadas / desativadas") é atualizado assim que o usuário as ativa.
AppMint.openNotificationSettings helper#
window.AppMint.openNotificationSettings
Exemplo
Documentado junto com AppMint.enableNotifications - o exemplo de lá mostra este também.
"Ativar notificações" em uma única chamada, para usuários que negaram ou pularam o aviso de notificação do Android: a caixa de permissão enquanto o Android ainda puder mostrá-la; caso contrário, as configurações de notificação deste app — e o novo estado quando o usuário volta.
Retorna: AppMint.enableNotifications() — uma Promise que sempre é resolvida com {state, changed, openedSettings}; state é 'granted' | 'prompt' | 'blocked' | 'unavailable'. AppMint.getNotificationState() — uma Promise desse estado. AppMint.openNotificationSettings() — uma Promise de true quando a tela de configurações abriu. Requer: chame enableNotifications() a partir de um botão que o usuário tocou; as notificações exigem Notificações (ou Push / Lembretes) na build.
// A banner that only shows while notifications are off, and turns them on in one tap.
async function setupNotifBanner() {
if (!(window.AppMint && AppMint.enableNotifications)) return; // a browser: no banner
var banner = document.getElementById('notif-banner');
var state = await AppMint.getNotificationState();
banner.hidden = !(state === 'prompt' || state === 'blocked');
document.getElementById('notif-turn-on').onclick = async function () {
var r = await AppMint.enableNotifications();
banner.hidden = r.state === 'granted';
if (r.state === 'blocked') showToast('Switch on "Allow notifications" to get updates.');
};
}
setupNotifBanner();Um simples link "Configurações de notificação" na sua própria página de configurações:
document.getElementById('notif-settings').onclick = async function () {
var opened = window.AppMint && AppMint.openNotificationSettings && await AppMint.openNotificationSettings();
if (!opened) showToast('Open Settings > Apps > this app > Notifications.');
};Observações: um botão de push ou de mensagem no app cujo link é appmint://notifications/enable executa o mesmo fluxo sem nenhum código na página (a página ouve appmint:notifications). O público de push do app ("Notificações ativadas / desativadas") é atualizado assim que o usuário as ativa.
Notification padrão web#
new Notification(title, options) / Notification.requestPermission()
Exemplo
A API padrão Web Notifications funciona dentro do app: new Notification() mostra uma notificação Android de verdade, close() a remove e Notification.requestPermission() mostra a caixa de permissão do Android e aguarda a resposta do usuário.
Retorna: Notification.permission é 'granted', 'denied' ou 'default', lido ao vivo do celular (uma mudança nas Configurações aparece sem recarregar). requestPermission() retorna uma Promise de 'granted' ou 'denied' (a forma com callback também funciona). Uma nova notificação dispara show quando publicada, ou error quando falta permissão. Requer: ative Notificações na etapa Acesso (Etapa 4) ao compilar.
async function notifyWhenSaved() {
if (!('Notification' in window)) return;
let perm = Notification.permission;
if (perm === 'default') perm = await Notification.requestPermission();
if (perm !== 'granted') {
showMessage('Turn on notifications for this app in Settings to get alerts.');
return;
}
const n = new Notification('Saved', {
body: 'Your note was saved',
icon: 'https://example.com/avatar.png', // shown as the large icon
image: 'https://example.com/note.jpg', // big picture
tag: 'note' // same tag replaces the old one
});
n.onshow = function () { console.log('posted'); };
n.onerror = function () { console.log('not posted'); };
setTimeout(function () { n.close(); }, 60000); // removes it from the shade
}
document.getElementById('save').addEventListener('click', notifyWhenSaved);Forma com callback (código mais antigo):
Notification.requestPermission(function (result) {
if (result === 'granted') new Notification('Welcome!', { body: 'Alerts are on', silent: true });
});Estilo service worker (código PWA) também publica pelo app, mas apenas onde a própria WebView não tem navigator.serviceWorker (por exemplo, páginas file:// ou http://):
if ('serviceWorker' in navigator && Notification.permission === 'granted') {
navigator.serviceWorker.ready.then(function (reg) {
return reg.showNotification('Backup done', { body: '12 files', tag: 'backup' });
});
}Um toque dispara click na notificação — o app vai para o primeiro plano e a página o recebe. Se o app estava fechado (o toque o iniciou), o objeto não existe mais, então a janela recebe um evento notificationclick, no formato de um service worker — event.notification.tag / title / body / data:
const n = new Notification('Order shipped', { body: '#7', tag: 'order-7', data: { orderId: 7 } });
n.onclick = () => openOrder(7); // the app was running
window.addEventListener('notificationclick', (e) => { // the tap started the app
if (e.notification.data && e.notification.data.orderId) openOrder(e.notification.data.orderId);
});Observações: opções usadas: body, icon, image, tag, silent, data (JSON, abaixo de 16 KB). O toque chega à página passando pelas telas de abertura e de PIN (evento por trás: appmint:notification-click). No Android 12 e anteriores não há permissão em tempo de execução, então é 'granted' a menos que o usuário tenha desativado as notificações. Web Push (pushManager.subscribe) não é suportado; use o AppMint Push. Para botões, progresso ou uma notificação fixa, use WebToApk.notify(...). O mesmo código funciona em um navegador normal.
__badgeSupported ponte#
window.WebToApk.__badgeSupported(): Boolean
Se este launcher consegue mostrar uma contagem de selo (navigator.setAppBadge só existe nesse caso).
Exemplo
Transporte interno por trás da presença de navigator.setAppBadge. As páginas usam a API padrão; nunca chame isto diretamente — os métodos __ podem mudar sem aviso.
Retorna: true quando o launcher deste celular mostra contagens de selo. Requer: nada para ativar.
Use a API pública:
if ('setAppBadge' in navigator) navigator.setAppBadge(unreadCount);Mostre a configuração "selo no ícone do app" apenas em celulares cujo launcher consegue mostrar uma contagem:
function renderBadgeSetting() {
const row = document.getElementById('badge-setting');
// setAppBadge exists only when this launcher shows counts
row.hidden = !('setAppBadge' in navigator);
}
renderBadgeSetting();Limpe a contagem quando o usuário abrir a caixa de entrada, em qualquer launcher que tenha uma:
function openInbox() {
showInbox();
if ('clearAppBadge' in navigator) {
navigator.clearAppBadge().catch(function (e) {
console.warn('badge not cleared', e.name); // NotAllowedError
});
}
}Observações: A API Badging só é instalada onde o launcher consegue mostrar uma contagem (Samsung, Huawei, Xiaomi, OPPO, Sony, …); em launchers estilo Pixel ela está ausente, como em um navegador sem badging.
__closeNotification ponte#
window.WebToApk.__closeNotification(tag: String): Boolean
Notification.close(): remove uma notificação que a página publicou, pela sua tag.
Exemplo
Estrutura interna por trás de Notification.prototype.close() — as páginas usam new Notification(...) e chamam close() nele. Remove uma notificação da bandeja pela sua tag.
Retorna: true quando o pedido de remoção foi feito (também quando nada tinha essa tag); false em caso de erro interno. Requer: nada.
Use a API pública:
let uploadNote = null;
function showUploading() {
if (!('Notification' in window) || Notification.permission !== 'granted') return;
uploadNote = new Notification('Uploading…', { body: 'Keep the app open', tag: 'upload', silent: true });
uploadNote.onclose = function () { uploadNote = null; };
}
function uploadFinished() {
if (uploadNote) uploadNote.close(); // removes it from the notification shade
}Abra o chat certo quando a notificação for tocada e depois remova-a da bandeja:
function notifyNewMessage(msg) {
if (!('Notification' in window) || Notification.permission !== 'granted') return;
const n = new Notification(msg.from, {
body: msg.text,
tag: 'chat-' + msg.threadId, // one notification per chat
data: { threadId: msg.threadId }
});
n.onclick = function () {
openThread(n.data.threadId);
n.close();
};
}Mostre uma breve confirmação que se remove sozinha após cinco segundos:
function confirmCopied() {
if (!('Notification' in window) || Notification.permission !== 'granted') return;
const n = new Notification('Link copied', { body: 'Paste it anywhere', tag: 'copied', silent: true });
setTimeout(function () { n.close(); }, 5000);
}Observações: não chame WebToApk.__closeNotification diretamente; os métodos __ podem mudar sem aviso. close() dispara o evento close da notificação.
__setAppBadge ponte#
window.WebToApk.__setAppBadge(contents: Long): Boolean
navigator.setAppBadge(n): n >= 0 é uma contagem (0 limpa), -1 o selo de sinalização da especificação.
Exemplo
Transporte interno por trás de navigator.setAppBadge(count) / clearAppBadge(). As páginas usam a API padrão; nunca chame isto diretamente — os métodos __ podem mudar sem aviso.
Retorna: true quando a contagem foi aplicada. Requer: nada para ativar.
Use a API pública:
if ('setAppBadge' in navigator) navigator.setAppBadge(unreadCount);Mantenha a contagem do ícone em sincronia com a contagem de não lidas e limpe-a em zero:
async function syncBadge(unread) {
if (!('setAppBadge' in navigator)) return;
try {
if (unread > 0) await navigator.setAppBadge(unread);
else await navigator.clearAppBadge();
} catch (e) {
console.warn('badge not applied', e.name); // NotAllowedError when the launcher refused it
}
}
syncBadge(messages.filter(function (m) { return !m.read; }).length);Coloque um selo de sinalização (sem número) no ícone enquanto houver trabalho não sincronizado:
function markPendingSync(hasPending) {
if (!('setAppBadge' in navigator)) return;
const p = hasPending
? navigator.setAppBadge() // no argument = flag badge; a count launcher shows 1
: navigator.clearAppBadge();
p.catch(function () {});
}Observações: -1 é o selo de sinalização da especificação (sem número), que um launcher com contagem mostra como 1.
cancelAllNotifications ponte#
window.WebToApk.cancelAllNotifications(): Boolean
Leitura e solicitação da permissão POST_NOTIFICATIONS e gerenciamento de notificações que a página já agendou. Publicar e agendar estão documentados nos próprios métodos.
Descrito pelo seu grupo, Permissão de notificação, cancelamento e listagem, e não individualmente.
Exemplo
Cancela todos os lembretes que este app agendou.
Retorna: true quando concluído; false apenas em erro interno. Requer: nada além disso.
document.getElementById('reset-reminders').addEventListener('click', function () {
if (!(window.WebToApk && window.WebToApk.cancelAllNotifications)) return;
if (!confirm('Remove all your reminders?')) return;
const ok = WebToApk.cancelAllNotifications();
showMessage(ok ? 'All reminders removed' : 'Could not remove reminders');
});Ao sair da conta, pare os lembretes do usuário anterior e limpe a contagem do ícone:
async function signOut() {
if (window.WebToApk && window.WebToApk.cancelAllNotifications) {
WebToApk.cancelAllNotifications();
}
if ('clearAppBadge' in navigator) navigator.clearAppBadge().catch(function () {});
await auth.signOut();
showLoginScreen();
}Reconstrua toda a agenda diária a partir dos horários salvos (cancele tudo e depois adicione cada um):
function applyMedicineTimes(times) { // e.g. ['08:00', '20:00']
const b = window.WebToApk;
if (!(b && b.cancelAllNotifications)) return;
b.cancelAllNotifications();
times.forEach(function (hhmm, i) {
const parts = hhmm.split(':');
const at = new Date();
at.setHours(Number(parts[0]), Number(parts[1]), 0, 0);
if (at.getTime() <= Date.now()) at.setDate(at.getDate() + 1); // next occurrence
b.scheduleNotification('med-' + i, 'Medicine', 'Time for your ' + hhmm + ' dose',
String(at.getTime()), 'daily');
});
}Observações: isto limpa a lista agendada, então nada dispara depois e nada é definido de novo após uma reinicialização. As notificações que já estão na bandeja continuam lá.
cancelNotification ponte#
window.WebToApk.cancelNotification(id: String): Boolean
Leitura e solicitação da permissão POST_NOTIFICATIONS e gerenciamento de notificações que a página já agendou. Publicar e agendar estão documentados nos próprios métodos.
Descrito pelo seu grupo, Permissão de notificação, cancelamento e listagem, e não individualmente.
Exemplo
Cancela um lembrete agendado pelo seu id, para que ele não dispare de novo.
Retorna: true (também quando nenhum lembrete tinha esse id); false apenas em erro interno. Requer: nada além disso (lembretes exigem ⏰ Lembretes na hora de compilar).
function stopWaterReminder() {
if (!(window.WebToApk && window.WebToApk.cancelNotification)) return;
WebToApk.cancelNotification('water'); // the id you gave scheduleNotification / scheduleNotificationEx
// Check it is gone:
const left = JSON.parse(WebToApk.getScheduledNotifications() || '[]');
const stillThere = left.some(function (r) { return r.id === 'water'; });
console.log('water reminder still scheduled?', stillThere); // false
}Um interruptor de lembrete nas configurações que agenda ou cancela pelo mesmo id:
document.getElementById('water-toggle').addEventListener('change', function (e) {
const b = window.WebToApk;
if (!(b && b.cancelNotification && b.scheduleNotification)) return;
if (e.target.checked) {
b.scheduleNotification('water', 'Drink water', 'Time for a glass of water',
String(Date.now() + 60 * 60 * 1000), 'hourly');
} else {
b.cancelNotification('water');
}
});Quando uma tarefa for excluída, cancele todos os lembretes que pertencem a ela:
function cancelTaskReminders(taskId) {
const b = window.WebToApk;
if (!(b && b.cancelNotification)) return;
let list = [];
try { list = JSON.parse(b.getScheduledNotifications() || '[]'); } catch (e) { list = []; }
list
.filter(function (r) { return r.id.indexOf('task-' + taskId + '-') === 0; })
.forEach(function (r) { b.cancelNotification(r.id); });
}Observações: isto cancela um lembrete que ainda não disparou (único ou recorrente). Não remove uma notificação que já está na bandeja e não afeta notificações publicadas com notify() / showNotification().
canOpenAppAtTime ponte#
window.WebToApk.canOpenAppAtTime(): String
Se uma notificação agendada pode colocar o PRÓPRIO APP na tela (`openApp`). "granted" — um alerta em tela cheia será exibido, sobre a tela de bloqueio. "denied" — o Android 14+ negou; chame requestOpenAppPermission(). Não existe uma terceira resposta em que o app se inicia sozinho em segundo plano: o Android bloqueia inícios de atividade em segundo plano desde o Android 10, e uma intent de tela cheia é tudo o que resta.
Exemplo
Informa se um lembrete com openApp: true pode colocar o próprio app na tela (um alerta em tela cheia, também sobre a tela de bloqueio).
Retorna: uma string, de forma síncrona: 'granted' ou 'denied'. Abaixo do Android 14 é sempre 'granted'. Requer: ative ⏰ Lembretes na etapa Acesso (Etapa 4) ao compilar.
function prepareAlarm() {
const b = window.WebToApk;
if (!(b && b.canOpenAppAtTime)) return;
if (b.canOpenAppAtTime() === 'denied') {
showMessage('To open the app at alarm time, allow "full-screen notifications" for this app.');
b.requestOpenAppPermission();
return;
}
const t = new Date();
t.setDate(t.getDate() + 1);
t.setHours(7, 0, 0, 0); // tomorrow 07:00
b.scheduleNotificationEx('wake-up', JSON.stringify({
at: t.getTime(), repeat: 'daily',
title: 'Wake up', body: 'Good morning!',
openApp: true
}));
}
window.addEventListener('appmint:reminder', function (e) {
// e.detail.overLockScreen is true when the app was shown as a full-screen alert
if (e.detail.overLockScreen) showBigAlarmScreen(e.detail.id);
});Uma tela de configurações que mostra os dois interruptores de alarme com seu estado atual:
function renderAlarmStatus() {
const b = window.WebToApk;
if (!(b && b.canOpenAppAtTime && b.getAlarmPrecision)) return;
const openApp = b.canOpenAppAtTime(); // 'granted' | 'denied'
const precision = b.getAlarmPrecision(); // 'exact' | 'inexact' | 'unavailable'
document.getElementById('open-app-state').textContent =
openApp === 'granted' ? 'Opens over the lock screen' : 'Notification only';
document.getElementById('open-app-fix').hidden = openApp === 'granted';
document.getElementById('timing-state').textContent =
precision === 'exact' ? 'On the minute' : 'May be late';
}
renderAlarmStatus();
document.addEventListener('visibilitychange', function () {
if (!document.hidden) renderAlarmStatus(); // back from Settings
});Avise quando alarmes salvos foram definidos para abrir o app, mas o Android não permite isso agora:
function checkSavedAlarms() {
const b = window.WebToApk;
if (!(b && b.canOpenAppAtTime && b.getScheduledNotifications)) return;
if (b.canOpenAppAtTime() === 'granted') return;
// [{ id, title, body, trigger, repeat, options }] - options is the JSON string you scheduled
const items = JSON.parse(b.getScheduledNotifications());
const affected = items.filter(function (r) {
try { return JSON.parse(r.options || '{}').openApp === true; } catch (e) { return false; }
});
if (affected.length) {
showMessage(affected.length + ' alarm(s) will only show a notification. Tap "Allow" to let them open the app.');
}
}Observações: quando é 'denied', um lembrete openApp ainda publica uma notificação em destaque, mas o app não abre sozinho. O Android não permite nenhuma outra forma de um app se iniciar sozinho em segundo plano.
clearNotificationSounds ponte#
window.WebToApk.clearNotificationSounds(): Int
Descarta os arquivos de som copiados e retorna quantos foram excluídos. Um arquivo é copiado de novo quando uma notificação (ou um lembrete agendado, quando dispara) usa esse som novamente — inclusive em um canal que já existe, que continua apontando para o mesmo nome de arquivo (NotificationBridge.restoreBundleSound).
Exemplo
Exclui as cópias dos seus sons de notificação personalizados que o app fez a partir do seu pacote.
Retorna: um número, de forma síncrona: quantos arquivos foram excluídos. Requer: nada.
Limpe e depois recoloque os sons que você ainda usa:
document.getElementById('reset-sounds').addEventListener('click', function () {
const b = window.WebToApk;
if (!(b && b.clearNotificationSounds)) return;
const removed = b.clearNotificationSounds();
// Sounds still in use must be copied again, or they play the default sound:
['/audio/alarm.mp3', '/audio/chime.mp3'].forEach(function (path) {
if (!b.prepareNotificationSound(path)) console.warn('missing sound', path);
});
showMessage(removed + ' sound file(s) refreshed');
});Um botão na tela de armazenamento que libera os arquivos de som copiados e informa quantos foram removidos:
document.getElementById('free-space').addEventListener('click', function () {
const b = window.WebToApk;
if (!(b && b.clearNotificationSounds)) return;
const removed = b.clearNotificationSounds();
showMessage(removed === 0 ? 'No sound files to remove.' : 'Removed ' + removed + ' sound file(s).');
});Ao sair da conta, cancele todos os lembretes e exclua as cópias de som que pertenciam a este usuário:
function signOutAndReset() {
const b = window.WebToApk;
if (b && b.cancelAllNotifications) b.cancelAllNotifications();
if (b && b.clearNotificationSounds) b.clearNotificationSounds();
localStorage.removeItem('alarmSound');
goToLoginScreen();
}Observações: você raramente precisa disto; as cópias são pequenas. Um som é copiado de volta na próxima vez que qualquer coisa o usar — notify(), um lembrete quando dispara, scheduleNotificationEx ou prepareNotificationSound(path) — inclusive em um canal de notificação que já existe, para que o som personalizado continue tocando.
enableNotifications ponte#
window.WebToApk.enableNotifications(callbackId: String)
Traz o usuário de volta às notificações: a caixa de diálogo do sistema enquanto ainda puder ser exibida; caso contrário, as configurações de notificação do app. Resolve `window.__appmintReplies[callbackId]` com {state, changed, openedSettings} (AppMint.enableNotifications() é o wrapper de promise).
Exemplo
Traz de volta um usuário que negou ou pulou as notificações: mostra a caixa de permissão do Android enquanto ela ainda puder ser exibida; caso contrário, abre as configurações de notificação deste app e responde quando o usuário volta.
Retorna: nada diretamente. A resposta {state, changed, openedSettings} é entregue em window.__appmintReplies[callbackId]; state é 'granted' | 'prompt' | 'blocked' | 'unavailable', changed é true quando difere de antes da chamada, openedSettings é true quando a tela de configurações foi usada. Se já estiver 'granted' (ou 'unavailable'), responde na hora. Requer: chame a partir de um botão que o usuário tocou; as notificações exigem Notificações (ou Push / Lembretes) na build.
Prefira o wrapper de promise AppMint.enableNotifications(). A chamada direta, para código que gerencia as próprias respostas:
function enableNotificationsRaw() {
return new Promise(function (resolve) {
if (!(window.WebToApk && WebToApk.enableNotifications)) return resolve({ state: 'unavailable', changed: false, openedSettings: false });
var replies = window.__appmintReplies = window.__appmintReplies || {};
var id = 'my_notif_' + Date.now();
replies[id] = resolve;
WebToApk.enableNotifications(id);
});
}
document.getElementById('turn-on-btn').onclick = async function () {
var r = await enableNotificationsRaw();
if (r.state === 'granted') document.getElementById('notif-banner').hidden = true;
};Todo fluxo concluído também é anunciado, inclusive um iniciado por um botão de push (appmint://notifications/enable):
window.addEventListener('appmint:notifications', function (e) {
// e.detail = { state, changed, openedSettings }
if (e.detail.state === 'granted') document.getElementById('notif-banner').hidden = true;
});Observações: um botão de push ou de mensagem no app cujo link é appmint://notifications/enable executa este mesmo fluxo; appmint://notifications/settings abre a tela de configurações. O público de push do app vê o novo estado na hora.
getAlarmPrecision ponte#
window.WebToApk.getAlarmPrecision(): String
"exact" — os lembretes disparam no minuto. "inexact" — o usuário (ou o fabricante) desativou os alarmes exatos para este app, então o Doze pode agrupá-los em minutos. Peça [requestExactAlarms] para corrigir. "unavailable" — Android 12+ e o serviço de alarmes do sistema não pôde ser acessado, então a precisão não pode ser lida (um lembrete agendado agora também pode não disparar). Uma página que precisa ser pontual deve verificar isto em vez de presumir. @return `"exact"`, `"inexact"` ou `"unavailable"`.
Exemplo
Informa se os lembretes vão disparar no minuto exato, ou podem atrasar porque o usuário (ou o fabricante do celular) desativou os alarmes exatos para este app.
Retorna: uma string, de forma síncrona: 'exact', 'inexact' (pode ser agrupado pela economia de bateria, muitas vezes em minutos) ou 'unavailable' (o celular não tem serviço de alarmes). Requer: ative ⏰ Lembretes na etapa Acesso (Etapa 4) ao compilar.
function checkReminderTiming() {
if (!(window.WebToApk && window.WebToApk.getAlarmPrecision)) return;
const precision = WebToApk.getAlarmPrecision();
const warn = document.getElementById('timing-warning');
warn.hidden = precision === 'exact';
if (precision === 'inexact') {
warn.textContent = 'Reminders may be a few minutes late. Tap to allow exact alarms.';
warn.onclick = function () { WebToApk.requestExactAlarms(); };
}
}
checkReminderTiming();
// Re-check when the user returns from Settings:
document.addEventListener('visibilitychange', function () {
if (!document.hidden) checkReminderTiming();
});Verifique antes de definir um alarme de despertar e avise o usuário se ele puder atrasar:
function setWakeUp(atMs) {
const b = window.WebToApk;
if (!(b && b.getAlarmPrecision && b.scheduleNotificationEx)) return;
const precision = b.getAlarmPrecision();
if (precision === 'unavailable') { showMessage('Alarms cannot be set on this phone right now.'); return; }
b.scheduleNotificationEx('wake', JSON.stringify({ at: atMs, title: 'Wake up', body: 'Good morning', openApp: true }));
if (precision === 'inexact') {
showMessage('Alarm set, but it may ring a few minutes late. Allow exact alarms for on-time alarms.');
}
}Mostre o horário do lembrete como uma linha em uma tela de configurações:
function renderTimingRow() {
if (!(window.WebToApk && window.WebToApk.getAlarmPrecision)) return;
const labels = {
exact: 'Reminders arrive on time',
inexact: 'Reminders may be a few minutes late',
unavailable: 'Reminders are not available on this phone'
};
document.getElementById('timing-row').textContent = labels[WebToApk.getAlarmPrecision()];
}
renderTimingRow();Observações: o Android 11 e anteriores sempre respondem 'exact'. Um lembrete 'inexact' ainda dispara, só que não no minuto.
getAlarmSupport ponte#
window.WebToApk.getAlarmSupport(): String
Se `scheduleNotificationEx(id, {alarm: true, ...})` pode ser agendado agora: "ready" | "not_enabled" (Despertador está desativado nesta build) | "needs_exact_alarm" (chame requestExactAlarms) | "needs_notification_permission".
Exemplo
Informa se um despertador (scheduleNotificationEx(id, { alarm: true, ... })) pode ser definido neste celular agora e, se não, o que o usuário precisa permitir primeiro.
Retorna: uma string, de forma síncrona: 'ready', 'not_enabled' (o app foi compilado sem 🔔 Despertador), 'needs_exact_alarm' (Android 12+: os alarmes exatos estão desativados para este app — chame requestExactAlarms()) ou 'needs_notification_permission' (Android 13+: sem permissão de notificação, então não haveria botão Dispensar — chame requestNotificationPermission()). Requer: ative 🔔 Despertador na etapa Acesso (Etapa 4) ao compilar.
function prepareAlarms() {
const b = window.WebToApk;
if (!(b && b.getAlarmSupport)) { showMessage('Alarms work in the installed app only.'); return false; }
switch (b.getAlarmSupport()) {
case 'ready': return true;
case 'needs_exact_alarm':
showMessage('Allow "Alarms & reminders" for this app, then come back.');
b.requestExactAlarms();
return false;
case 'needs_notification_permission':
b.requestNotificationPermission();
return false;
default:
showMessage('This app was built without alarm clocks.');
return false;
}
}Verifique de novo quando o usuário voltar das Configurações:
document.addEventListener('visibilitychange', function () {
if (document.hidden || !(window.WebToApk && window.WebToApk.getAlarmSupport)) return;
document.getElementById('alarm-setup').hidden = WebToApk.getAlarmSupport() === 'ready';
});Crie um alarme apenas quando o celular estiver pronto:
function setAlarmIn(hours) {
const b = window.WebToApk;
if (!(b && b.getAlarmSupport)) return false;
if (b.getAlarmSupport() !== 'ready') { prepareAlarms(); return false; } // asks for what is missing
return b.scheduleNotificationEx('wake-up', JSON.stringify({
at: Date.now() + hours * 3600 * 1000,
alarm: true,
title: 'Wake up',
body: 'Good morning'
}));
}Observações: scheduleNotificationEx com alarm: true retorna false sempre que isto não for 'ready' — um alarme que nunca poderia tocar nunca é armazenado.
getNotificationPermission ponte#
window.WebToApk.getNotificationPermission(): String
A permissão de notificação ATUAL, lida ao vivo do sistema. O polyfill antes fixava `Notification.permission = 'default'` em todo carregamento de página, então o padrão de página mais comum if (Notification.permission === 'granted') new Notification(...) nunca disparava — nem para um usuário que já tinha concedido, nem no Android 12 e anteriores, onde as notificações não precisam de nenhuma permissão em tempo de execução.
Exemplo
Lê, agora mesmo, se este app pode publicar notificações.
Retorna: uma string, de forma síncrona: 'granted'; 'denied' (o usuário recusou definitivamente, ou desativou as notificações deste app nas Configurações — uma publicação não apareceria); ou 'default' (ainda não decidido, o app ainda pode pedir). Requer: nada para chamá-lo; publicar exige Notificações ativadas na etapa Acesso (Etapa 4) ao compilar.
A API web padrão lê o mesmo valor ao vivo:
if ('Notification' in window && Notification.permission === 'granted') {
document.getElementById('alerts-on').hidden = false;
}A chamada direta, por exemplo depois que o usuário volta das Configurações:
function refreshAlertSetting() {
if (!(window.WebToApk && window.WebToApk.getNotificationPermission)) return;
const perm = WebToApk.getNotificationPermission(); // 'granted' | 'denied' | 'default'
document.getElementById('enable-alerts').hidden = perm !== 'default'; // can still ask
document.getElementById('open-settings-hint').hidden = perm !== 'denied';
}
document.addEventListener('visibilitychange', function () {
if (!document.hidden) refreshAlertSetting();
});
refreshAlertSetting();Decida antes de agendar se vai pedir, explicar ou seguir em frente:
async function ensureAlerts() {
const b = window.WebToApk;
if (!(b && b.getNotificationPermission)) return false;
const perm = b.getNotificationPermission();
if (perm === 'granted') return true;
if (perm === 'denied') {
showMessage('Notifications are off for this app. Turn them on in Settings.');
return false;
}
// 'default': the app can still ask
return (await Notification.requestPermission()) === 'granted';
}Observações: dá a mesma resposta que navigator.permissions.query({ name: 'notifications' }). Para perguntar ao usuário, use Notification.requestPermission().
getNotificationState ponte#
window.WebToApk.getNotificationState(): String
'granted' | 'prompt' (o Android ainda pode pedir) | 'blocked' (recusado definitivamente ou desativado nas Configurações — só as Configurações resolvem) | 'unavailable' (Android 13+ e esta build não declara a permissão de notificação). Veja NotificationOptIn.
Exemplo
Lê, agora mesmo, se as notificações deste app estão ativadas — e, se não, se o app ainda pode pedir ou se só as Configurações podem reativá-las.
Retorna: uma string, de forma síncrona: 'granted' (as notificações chegam ao celular); 'prompt' (o Android ainda pode mostrar a caixa de permissão); 'blocked' (o usuário recusou definitivamente, ou desativou as notificações deste app nas Configurações do sistema — só as Configurações resolvem); ou 'unavailable' (Android 13+ e o app foi compilado sem Notificações / Lembretes / Push, então nada pode ativá-las). Requer: nada para chamá-lo.
A chamada direta, para decidir se mostra um banner "Ativar notificações":
function refreshNotifBanner() {
if (!(window.WebToApk && WebToApk.getNotificationState)) return;
var state = WebToApk.getNotificationState(); // 'granted' | 'prompt' | 'blocked' | 'unavailable'
var banner = document.getElementById('notif-banner');
banner.hidden = !(state === 'prompt' || state === 'blocked');
banner.querySelector('.hint').textContent = state === 'blocked'
? 'Notifications are off. Tap to open Settings and switch them on.'
: 'Get order updates. Tap to turn on notifications.';
}
document.addEventListener('visibilitychange', function () {
if (!document.hidden) refreshNotifBanner();
});
refreshNotifBanner();Observações: getNotificationPermission() mantém as próprias palavras ('granted' / 'default' / 'denied') para páginas mais antigas. Para trazer o usuário de volta, chame AppMint.enableNotifications() (ele escolhe a caixa de diálogo ou as Configurações para você).
getScheduledNotifications ponte#
window.WebToApk.getScheduledNotifications(): String
Leitura e solicitação da permissão POST_NOTIFICATIONS e gerenciamento de notificações que a página já agendou. Publicar e agendar estão documentados nos próprios métodos.
Descrito pelo seu grupo, Permissão de notificação, cancelamento e listagem, e não individualmente.
Exemplo
Lista os lembretes que ainda estão agendados.
Retorna: uma string JSON, de forma síncrona. Faça o parse. Cada item: { id, title, body, trigger, repeat, options? } — trigger é o próximo horário de disparo em epoch ms, repeat é none|minutely|hourly|daily|weekly e options (apenas para scheduleNotificationEx) são as opções como uma string JSON. Os campos ao vivo dizem como ele será realmente entregue, lidos ao vivo: precision ("exact"; "inexact" = "Alarmes e lembretes" está desativado para o app, então o Android o entrega dentro de uma janela de 10 minutos enquanto o celular está ativo, e pode segurá-lo por mais tempo enquanto dorme; "alarm_clock" para alarm: true), windowMs + latestAt (a janela que o Android recebeu: 0 / trigger quando exato, 600000 / trigger + 600000 quando inexato) e notificationsEnabled (false = ele dispara mas o Android não mostra nada, porque as notificações do app estão desativadas). '[]' quando não há nenhum. Requer: nada além disso.
function renderReminders() {
if (!(window.WebToApk && window.WebToApk.getScheduledNotifications)) return;
let list = [];
try { list = JSON.parse(WebToApk.getScheduledNotifications() || '[]'); } catch (e) { list = []; }
const ul = document.getElementById('reminders');
ul.innerHTML = '';
list.sort(function (a, b) { return a.trigger - b.trigger; }).forEach(function (r) {
const li = document.createElement('li');
li.textContent = r.title + ' — ' + new Date(r.trigger).toLocaleString() +
(r.repeat !== 'none' ? ' (' + r.repeat + ')' : '');
ul.appendChild(li);
});
}Mostre se um lembrete está ativo e quando ele dispara a seguir:
function describeReminder(id) {
if (!(window.WebToApk && window.WebToApk.getScheduledNotifications)) return 'Not available';
let list = [];
try { list = JSON.parse(WebToApk.getScheduledNotifications() || '[]'); } catch (e) { list = []; }
const r = list.find(function (x) { return x.id === id; });
return r ? 'Next: ' + new Date(r.trigger).toLocaleString() : 'Off';
}
document.getElementById('water-status').textContent = describeReminder('water');Leia as opções extras de lembretes criados com scheduleNotificationEx:
function listAlarmStyleReminders() {
if (!(window.WebToApk && window.WebToApk.getScheduledNotifications)) return [];
let list = [];
try { list = JSON.parse(WebToApk.getScheduledNotifications() || '[]'); } catch (e) { list = []; }
return list.filter(function (r) {
if (!r.options) return false; // made with scheduleNotification
let o = {};
try { o = JSON.parse(r.options); } catch (e) { return false; }
return o.openApp === true; // puts the app on screen when it fires
}).map(function (r) { return r.id; });
}Observações: um lembrete único sai da lista quando dispara. Um recorrente permanece, com trigger movido para o próximo horário. Um lembrete cujo horário passou enquanto o celular estava desligado ou o app foi forçado a parar é mostrado uma vez, com atraso, na próxima vez que o app ou o celular iniciar; um alarm: true atrasado é informado como missed em vez de tocar. Um lembrete diário, semanal, mensal ou anual mantém o horário local quando o celular muda de fuso horário.
Avise quando os lembretes não vão aparecer no horário:
function reminderHealth() {
if (!(window.WebToApk && window.WebToApk.getScheduledNotifications)) return '';
let list = [];
try { list = JSON.parse(WebToApk.getScheduledNotifications() || '[]'); } catch (e) { list = []; }
if (!list.length) return '';
if (list.some(function (r) { return r.notificationsEnabled === false; })) return 'Notifications are off for this app: reminders will not appear.';
if (list.some(function (r) { return r.precision === 'inexact'; })) return 'Reminders may arrive late. Turn on Alarms & reminders.';
return '';
}isAlarmRinging ponte#
window.WebToApk.isAlarmRinging(): Boolean
True enquanto um despertador está tocando.
Exemplo
Informa se um dos alarmes do app está tocando agora, para que a página possa mostrar a própria tela de parar / adiar.
Retorna: true enquanto um alarme toca, caso contrário false (de forma síncrona). Requer: 🔔 Despertador na etapa Acesso (Etapa 4) ao compilar.
function showRingScreenIfNeeded() {
const b = window.WebToApk;
const ringing = !!(b && b.isAlarmRinging && b.isAlarmRinging());
document.getElementById('ring-screen').hidden = !ringing;
}
showRingScreenIfNeeded();
document.addEventListener('visibilitychange', showRingScreenIfNeeded);Pare o alarme que está tocando pela sua própria tela com as chamadas correspondentes.
document.getElementById('stop').addEventListener('click', function () {
const b = window.WebToApk;
if (b && b.isAlarmRinging && b.isAlarmRinging()) b.stopAlarm(''); // '' = whichever is ringing
});
document.getElementById('snooze').addEventListener('click', function () {
const b = window.WebToApk;
if (b && b.snoozeAlarm && !b.snoozeAlarm('', 10)) showMessage('Nothing is ringing');
});Reaja ao app ser aberto por um alarme que está tocando.
window.addEventListener('appmint:reminder', function (e) {
if (e.detail.action === 'open' && e.detail.ringing) {
document.getElementById('ring-screen').hidden = false;
}
});Observações: quando o alarme que está tocando abre o app, appmint:reminder também chega com { action: 'open', ringing: true }.
notify ponte#
window.WebToApk.notify(optionsJson: String): Boolean
Notificação rica. Veja NotificationBridge.show para a lista completa de opções: channel (urgent|default|quiet|ongoing), image, largeIcon, bigText, ongoing, actions, progress, sound, silent, group, color. A busca e a decodificação de imagens acontecem fora da thread principal, então uma imagem remota nunca pode travar a página.
Exemplo
Publica uma notificação agora, com qualquer combinação de opções: imagem, texto longo, botões, barra de progresso, fixa (ongoing), canal, som, cor e tag.
Retorna: true quando as opções eram JSON válido e a notificação foi entregue ao sistema (ela é montada em uma thread em segundo plano, então uma URL de imagem ruim não trava a página); false para JSON inválido. Requer: ative Notificações na etapa Acesso (Etapa 4) ao compilar e a permissão do usuário no Android 13+ (peça com Notification.requestPermission()).
Notificação simples. tag é um ID: publicar de novo com a mesma tag substitui a antiga.
async function notifyOrderReady() {
if (!(window.WebToApk && window.WebToApk.notify)) return;
if (window.Notification && Notification.permission !== 'granted') {
const p = await Notification.requestPermission();
if (p !== 'granted') { showMessage('Notifications are off for this app.'); return; }
}
WebToApk.notify(JSON.stringify({
title: 'Order ready',
body: 'Your order is ready to collect',
channel: 'default', // urgent | default | quiet | ongoing
tag: 'order-42'
}));
}Imagem, ícone grande e texto longo. image prevalece sobre bigText quando ambos são informados.
WebToApk.notify(JSON.stringify({
title: 'New photo',
body: 'Tap to see it',
image: 'https://example.com/photo.jpg', // expanded big picture
largeIcon: 'https://example.com/avatar.png', // round icon on the right
color: '#00C6FF',
tag: 'photo-7'
}));
WebToApk.notify(JSON.stringify({
title: 'Weekly summary',
body: '3 tasks done, 2 left',
bigText: 'Done: pay rent, call bank, water plants. Left: tax form, dentist.',
channel: 'quiet', // no sound, low priority
group: 'summaries'
}));Botões (no máximo 3). O toque volta como o evento appmint:notification-action.
WebToApk.notify(JSON.stringify({
title: 'Ali sent you a message',
body: 'Are we still on for 6pm?',
channel: 'urgent', // heads-up banner
tag: 'chat-ali',
actions: [
{ id: 'open', label: 'Open chat' },
{ id: 'done', label: 'Mark read' }
]
}));
window.addEventListener('appmint:notification-action', function (e) {
// e.detail = { actionId: 'done', tag: 'chat-ali' }
if (e.detail.actionId === 'open') openChat(e.detail.tag);
if (e.detail.actionId === 'done') markRead(e.detail.tag);
});Progresso fixo (ongoing). Publique de novo com a mesma tag para atualizá-lo. Quando o trabalho terminar, publique o estado final com ongoing: false para o usuário poder dispensá-lo deslizando.
function showUpload(percent) {
WebToApk.notify(JSON.stringify({
title: 'Uploading', body: percent + '%',
ongoing: true, // cannot be swiped away
progress: { current: percent, max: 100 }, // or { indeterminate: true }
silent: true,
tag: 'upload'
}));
}
showUpload(40);
// finished:
WebToApk.notify(JSON.stringify({ title: 'Upload complete', body: '3 photos saved', tag: 'upload' }));Outras opções: sound (um arquivo no seu pacote como '/audio/ding.mp3', uma URL data:audio/... ou 'silent'), smallIcon (um nome de drawable), when (epoch em ms mostrado como a hora), openApp: true (alerta em tela cheia; veja scheduleNotificationEx).
Observações: os botões não têm caixa de resposta embutida, e o toque em um botão chega à página apenas enquanto o app está em execução (ele não abre o app); o evento também chama window.onAppMintNotificationAction(detail) se você o definir. image e largeIcon aceitam uma URL https:// ou data:, ou uma imagem empacotada com o seu app, nomeada como a sua página a nomeia ('img/logo.png', '/img/logo.png' ou o endereço completo https://appassets.androidplatform.net/assets/…). Uma imagem inválida é ignorada, a notificação ainda aparece. ongoing: true sempre usa o canal discreto "Ongoing". notify() não tem um par público de "remover": cancelNotification só cancela lembretes agendados. Uma notificação que você queira remover depois pela página deve ser publicada com new Notification(...) e removida com seu close(); um cartão de progresso fixo com Capacitor.AppwrightLiveProgress (end). O Android fixa o som de um canal quando o canal é criado, então um novo valor de sound cria um novo canal.
openNotificationSettings ponte#
window.WebToApk.openNotificationSettings(): Boolean
Abre as configurações de notificação deste app; true quando a tela pôde ser aberta.
Exemplo
Abre a tela de configurações de notificação do próprio app, onde o usuário pode ativar ou desativar as notificações (e cada canal).
Retorna: true quando a tela foi aberta, de forma síncrona; false quando não pode ser (Android 13+ e a build não tem notificações). No Android 8+ é a página de notificações do app; em versões mais antigas, a tela de Informações do app. Requer: nada para ativar.
document.getElementById('notif-settings-link').onclick = function () {
if (!(window.WebToApk && WebToApk.openNotificationSettings && WebToApk.openNotificationSettings())) {
showMessage('Open Settings > Apps > this app > Notifications.');
}
};Observações: para trazer o usuário de volta com um toque, use AppMint.enableNotifications() em vez disso: ele mostra a caixa de permissão quando o Android ainda permite e abre esta tela apenas quando não permite.
prepareNotificationSound ponte#
window.WebToApk.prepareNotificationSound(spec: String): Boolean
Copia um arquivo de áudio incluído no app para o lugar certo com antecedência e informa se ele pode ser usado como som de notificação. Opcional — `notify()` e `scheduleNotificationEx()` fazem isso sozinhos — mas permite que uma tela de configurações verifique um som assim que o usuário o escolhe.
Exemplo
Verifica se um arquivo de áudio do seu app pode ser usado como som de notificação e o copia para o lugar agora. Opcional: notify() e scheduleNotificationEx() fazem o mesmo sozinhos; isto permite que uma tela de configurações verifique um som assim que o usuário o escolhe.
Retorna: true se o som pode ser usado; false se o arquivo não existe ou não é um tipo de áudio suportado. Requer: nada.
function chooseAlarmSound(path) { // e.g. '/audio/chime.mp3'
if (!(window.WebToApk && window.WebToApk.prepareNotificationSound)) return;
if (!WebToApk.prepareNotificationSound(path)) {
showMessage('This sound file is missing from the app: ' + path);
return;
}
localStorage.setItem('alarmSound', path);
// Use it:
WebToApk.notify(JSON.stringify({ title: 'Sound test', body: 'This is your alarm sound', sound: path, tag: 'sound-test' }));
}Preencha um seletor de sons apenas com os sons realmente utilizáveis:
const SOUNDS = [
{ label: 'Chime', path: '/audio/chime.mp3' },
{ label: 'Bell', path: '/audio/bell.ogg' },
{ label: 'Beep', path: '/audio/beep.wav' }
];
function renderSoundPicker(select) {
const b = window.WebToApk;
if (!(b && b.prepareNotificationSound)) return;
select.innerHTML = '';
SOUNDS.forEach(function (s) {
if (!b.prepareNotificationSound(s.path)) return; // missing or unsupported file
const opt = document.createElement('option');
opt.value = s.path; opt.textContent = s.label;
select.appendChild(opt);
});
}Deixe o usuário escolher o próprio arquivo de áudio e usá-lo em um lembrete diário, passado como uma URL data::
document.getElementById('own-sound').addEventListener('change', function (e) {
const file = e.target.files[0];
const b = window.WebToApk;
if (!file || !(b && b.prepareNotificationSound)) return;
const reader = new FileReader();
reader.onload = function () {
const dataUrl = reader.result; // 'data:audio/mpeg;base64,...'
if (!b.prepareNotificationSound(dataUrl)) {
showMessage('That file cannot be used as a notification sound.');
return;
}
b.scheduleNotificationEx('daily-walk', JSON.stringify({
at: nextEvening(), repeat: 'daily',
title: 'Walk time', body: 'Time for your evening walk', sound: dataUrl
}));
};
reader.readAsDataURL(file);
});Valores aceitos: um caminho no seu pacote web ('/audio/chime.mp3', 'audio/chime.mp3') ou uma URL data:audio/...;base64,... criada pela sua página. Tipos: mp3, wav, ogg, oga, m4a, aac, flac, opus, mid, midi.
Observações: um nome simples sem pasta ou extensão (um som de res/raw) não é verificado aqui e retorna false, mesmo que notify({sound: 'name'}) possa usá-lo. O Android mantém um som por canal, então mudar o som cria um novo canal e redefine as configurações do usuário para esse canal.
requestExactAlarms ponte#
window.WebToApk.requestExactAlarms(): Boolean
Abre a tela do sistema onde os alarmes exatos são concedidos. Android 12+.
Exemplo
Abre a tela do sistema do Android 12+ onde o usuário permite que este app use alarmes exatos, para que os lembretes disparem no minuto.
Retorna: true quando a tela de configurações foi aberta (e no Android 11 e anteriores, onde nada é necessário); false se a tela não pôde ser aberta. Não informa a escolha do usuário. Requer: ative ⏰ Lembretes na etapa Acesso (Etapa 4) ao compilar.
document.getElementById('allow-exact').addEventListener('click', function () {
if (!(window.WebToApk && window.WebToApk.requestExactAlarms)) return;
if (WebToApk.getAlarmPrecision() === 'exact') return; // already fine
WebToApk.requestExactAlarms();
});
// The user comes back from Settings: read the real answer.
document.addEventListener('visibilitychange', function () {
if (document.hidden || !window.WebToApk) return;
const exact = WebToApk.getAlarmPrecision() === 'exact';
document.getElementById('allow-exact').hidden = exact;
});Antes de salvar um lembrete crítico em horário, ofereça a tela de configurações apenas quando o horário realmente estiver comprometido:
function saveMedicineReminder(at) {
const b = window.WebToApk;
if (!(b && b.scheduleNotificationEx && b.getAlarmPrecision)) return;
b.scheduleNotificationEx('medicine', JSON.stringify({
at: at, repeat: 'daily', title: 'Medicine', body: 'Time for your tablet'
}));
const precision = b.getAlarmPrecision(); // 'exact' | 'inexact' | 'unavailable'
if (precision === 'inexact') {
showBanner('Reminders may arrive a few minutes late.', 'Fix timing', function () {
b.requestExactAlarms(); // runs from the banner button tap
});
} else if (precision === 'unavailable') {
showBanner('The phone\'s alarm service is not answering; this reminder may not fire.');
}
}Quando a tela de configurações não puder ser aberta (false), diga ao usuário onde encontrar o interruptor:
document.getElementById('fix-timing').addEventListener('click', function () {
const b = window.WebToApk;
if (!(b && b.requestExactAlarms)) return;
const opened = b.requestExactAlarms();
if (!opened) {
showMessage('Open Settings > Apps > this app > "Alarms & reminders" and turn it on.');
}
// When it did open, read the answer on return with getAlarmPrecision().
});Observações: chame a partir do toque em um botão e explique o motivo antes — isto leva o usuário para fora do seu app. Lembretes já agendados continuam funcionando de qualquer forma; só o horário muda.
requestNotificationPermission ponte#
window.WebToApk.requestNotificationPermission(): String
Leitura e solicitação da permissão POST_NOTIFICATIONS e gerenciamento de notificações que a página já agendou. Publicar e agendar estão documentados nos próprios métodos.
Descrito pelo seu grupo, Permissão de notificação, cancelamento e listagem, e não individualmente.
Exemplo
Mostra a caixa de diálogo "Permitir notificações?" do Android 13+. As páginas normalmente usam Notification.requestPermission(), que chama isto e aguarda a resposta do usuário.
Retorna: uma string, de forma síncrona: 'granted' se já estiver permitido (ou no Android 12 e anteriores), caso contrário 'default' enquanto a caixa de diálogo abre. Não consegue retornar a resposta do usuário. Requer: ative Notificações na etapa Acesso (Etapa 4) ao compilar.
Use a API de Promise — ela é resolvida com a resposta real ('granted' ou 'denied'):
document.getElementById('enable-alerts').addEventListener('click', async function () {
if (!('Notification' in window)) return;
const perm = await Notification.requestPermission();
if (perm === 'granted') {
showMessage('Alerts are on');
} else {
showMessage('Alerts are off. You can turn them on in Settings.');
}
});A chamada direta, apenas para abrir a caixa de diálogo sem esperar:
if (window.WebToApk && window.WebToApk.requestNotificationPermission) {
const now = WebToApk.requestNotificationPermission(); // 'granted' or 'default'
if (now === 'granted') enableAlertFeatures();
// otherwise re-check later with WebToApk.getNotificationPermission()
// ('granted' | 'denied' | 'default')
}Código mais antigo que passa um callback para Notification.requestPermission recebe a mesma resposta final:
document.getElementById('turn-on-reminders').addEventListener('click', function () {
if (!('Notification' in window)) return;
if (Notification.permission !== 'default') { updateReminderUi(Notification.permission); return; }
Notification.requestPermission(function (perm) {
updateReminderUi(perm); // 'granted' or 'denied', once the user answers
});
});Observações: peça a partir do toque em um botão, com um motivo claro. Depois que o usuário recusa duas vezes, o Android para de mostrar a caixa de diálogo e a resposta é 'denied' na hora; o usuário precisa então permitir nas Configurações do app.
requestOpenAppPermission ponte#
window.WebToApk.requestOpenAppPermission(): Boolean
Abre a tela do sistema onde o alerta em tela cheia é concedido. Android 14+.
Exemplo
Abre a tela do sistema do Android 14+ onde o usuário permite alertas em tela cheia para este app, para que um lembrete openApp: true possa colocar o app na tela.
Retorna: true quando a tela de configurações foi aberta (e abaixo do Android 14, onde nada é necessário); false se não pôde ser aberta. Não informa a escolha do usuário. Requer: ative ⏰ Lembretes na etapa Acesso (Etapa 4) ao compilar.
document.getElementById('allow-open-app').addEventListener('click', function () {
const b = window.WebToApk;
if (!(b && b.requestOpenAppPermission)) return;
if (b.canOpenAppAtTime() === 'granted') return;
b.requestOpenAppPermission();
});
// Read the real answer when the user comes back:
document.addEventListener('visibilitychange', function () {
if (document.hidden || !window.WebToApk) return;
const ok = WebToApk.canOpenAppAtTime() === 'granted';
document.getElementById('allow-open-app').hidden = ok;
});Lembre-se do alarme que o usuário estava definindo, envie-o às Configurações e conclua quando ele voltar:
let pendingAlarm = null;
function setWakeAlarm(at) {
const b = window.WebToApk;
if (!(b && b.requestOpenAppPermission)) return;
if (b.canOpenAppAtTime() === 'denied') {
pendingAlarm = at;
b.requestOpenAppPermission();
return;
}
b.scheduleNotificationEx('wake', JSON.stringify({ at: at, title: 'Alarm', body: 'Wake up', openApp: true }));
}
document.addEventListener('visibilitychange', function () {
if (document.hidden || pendingAlarm === null || !window.WebToApk) return;
if (WebToApk.canOpenAppAtTime() === 'granted') setWakeAlarm(pendingAlarm);
else showMessage('Without that setting the alarm cannot open the app by itself.');
pendingAlarm = null;
});Quando a tela de configurações não puder ser aberta (false), diga onde fica o interruptor:
document.getElementById('allow-fullscreen').addEventListener('click', function () {
const b = window.WebToApk;
if (!(b && b.requestOpenAppPermission)) return;
if (!b.requestOpenAppPermission()) {
showMessage('Open Settings > Apps > this app > Notifications and allow full-screen notifications.');
}
});Observações: chame a partir do toque em um botão, depois de explicar o motivo — isto leva o usuário para fora do seu app.
scheduleNotification ponte#
window.WebToApk.scheduleNotification(id: String, title: String, body: String, triggerAtMillis: String, repeat: String): Boolean
Leitura e solicitação da permissão POST_NOTIFICATIONS e gerenciamento de notificações que a página já agendou. Publicar e agendar estão documentados nos próprios métodos.
Descrito pelo seu grupo, Permissão de notificação, cancelamento e listagem, e não individualmente.
Exemplo
Agenda um lembrete simples (título e texto) para um horário futuro. Dispara mesmo com o app fechado e é definido de novo após a reinicialização do celular.
Retorna: true quando foi agendado; false quando id está vazio ou o horário não é um número. Requer: ative ⏰ Lembretes na etapa Acesso (Etapa 4) ao compilar (isso também ativa as Notificações) e a permissão de notificação do usuário no Android 13+.
function remindToDrinkWater() {
if (!(window.WebToApk && window.WebToApk.scheduleNotification)) {
showMessage('Reminders work in the installed app only.');
return;
}
const ok = WebToApk.scheduleNotification(
'water', // your own id; same id replaces the old reminder
'Drink water', // title
'Time for a glass of water', // body
String(Date.now() + 60 * 60 * 1000), // epoch milliseconds, AS A STRING
'daily' // none | minutely | hourly | daily | weekly
);
if (!ok) showMessage('Could not schedule the reminder');
}Saiba quando um lembrete abriu o app (o usuário o tocou):
window.addEventListener('appmint:reminder', function (e) {
// e.detail = { id: 'water', overLockScreen: false }
if (e.detail.id === 'water') openWaterLog();
});Um lembrete único às 8:00 do dia em que uma tarefa vence:
function remindTask(task) {
if (!(window.WebToApk && window.WebToApk.scheduleNotification)) return false;
const at = new Date(task.dueDate);
at.setHours(8, 0, 0, 0);
if (at.getTime() <= Date.now()) return false; // that morning has passed
return WebToApk.scheduleNotification(
'task-' + task.id,
'Due today',
task.title,
String(at.getTime()), // epoch ms as a string
'none'
);
}Observações: o lembrete usa o estilo "urgent" (heads-up) e sua tag é o id. Um lembrete recorrente perdido enquanto o celular estava desligado dispara no próximo passo. Para uma imagem, botões, seu próprio som ou abrir o app sobre a tela de bloqueio, use scheduleNotificationEx. Se os alarmes exatos estiverem desativados para o app, o horário pode variar em minutos; verifique getAlarmPrecision().
scheduleNotificationEx ponte#
window.WebToApk.scheduleNotificationEx(id: String, optionsJson: String): Boolean
A forma completa de [scheduleNotification]: tudo o que `notify()` aceita, disparado em um horário futuro com o app fechado. WebToApk.scheduleNotificationEx('dose-1', JSON.stringify({ at: Date.now() + 3600000, // epoch millis (alias: triggerAt) repeat: 'daily', // none|minutely|hourly|daily|weekly|weekdays|monthly|yearly // days: [1, 3, 5], // with 'weekdays': ISO Mon=1..Sun=7 (default Mon-Fri) // alarm: true, // ring like an alarm clock (getAlarmSupport()) title: 'Time for your dose', body: 'Vitamin D - 1 tablet', sound: '/audio/chime.mp3', // a file in YOUR bundle; 'silent' for none openApp: true // open the app itself, over the lock screen })); Um som do pacote é copiado para o lugar durante esta chamada, então um nome de arquivo errado é informado agora (false) em vez de virar o som padrão dias depois.
Exemplo
Agenda um lembrete com todas as opções que notify() tem (imagem, botões, som, canal, abrir o app), para um horário futuro. Dispara com o app fechado e sobrevive à reinicialização.
Retorna: true quando agendado. false quando nenhum horário é informado (at, triggerAt, triggerAtMillis ou inSeconds), o id está vazio, o JSON é inválido, ou sound nomeia um arquivo que não está no seu app. Requer: ative ⏰ Lembretes na etapa Acesso (Etapa 4) ao compilar e a permissão de notificação do usuário no Android 13+.
Um lembrete diário com seu próprio som que abre o app (sobre a tela de bloqueio):
function scheduleStandup(tomorrowAt9am) {
const b = window.WebToApk;
if (!(b && b.scheduleNotificationEx)) { showMessage('Reminders work in the installed app only.'); return; }
// Both are user settings: check, and ask if needed.
if (b.getAlarmPrecision() === 'inexact') b.requestExactAlarms();
if (b.canOpenAppAtTime() === 'denied') b.requestOpenAppPermission();
const ok = b.scheduleNotificationEx('standup', JSON.stringify({
at: tomorrowAt9am, // epoch ms (or triggerAt, or inSeconds: 3600)
repeat: 'daily', // none | minutely | hourly | daily | weekly | weekdays | monthly | yearly
title: 'Stand-up',
body: 'Daily sync starts now',
sound: '/audio/alarm.mp3', // a file in YOUR app; 'silent' for none
openApp: true // full-screen alert that puts the app on screen
}));
if (!ok) showMessage('Check the sound file path — nothing was scheduled.');
}
window.addEventListener('appmint:reminder', function (e) {
// e.detail = { id: 'standup', overLockScreen: true, action: 'open', ringing: false }
openMeetingScreen(e.detail.id);
});Só dias de academia — segunda, quarta e sexta às 18:00 (days são ISO: segunda = 1 … domingo = 7; sem days, 'weekdays' significa segunda a sexta):
WebToApk.scheduleNotificationEx('gym', JSON.stringify({
at: nextAt(18, 0), // epoch ms of the first time; moved to the next listed day if needed
repeat: 'weekdays',
days: [1, 3, 5],
title: 'Gym time',
body: 'Leg day'
}));
// Rent on the 31st: 'monthly' rings on the last day of shorter months (28/29/30) and on the 31st again when there is one.
WebToApk.scheduleNotificationEx('rent', JSON.stringify({ at: rentDueAt, repeat: 'monthly', title: 'Rent is due' }));
// A birthday: same date every year (29 February rings on the 28th in other years).
WebToApk.scheduleNotificationEx('bday-ana', JSON.stringify({ at: anaBirthdayAt9am, repeat: 'yearly', title: 'Ana\'s birthday' }));Um despertador de verdade (alarm: true): toca no volume de ALARME do celular — mesmo no modo silencioso e no Não Perturbe quando o usuário permite alarmes — e continua tocando até Dispensar / Adiar ou ringSeconds. Mostra o ícone de alarme na barra de status e aparece na tela sobre a tela de bloqueio:
function setWakeAlarm(atMs) {
const b = window.WebToApk;
if (!(b && b.getAlarmSupport)) { showMessage('Alarms work in the installed app only.'); return; }
const support = b.getAlarmSupport(); // 'ready' | 'not_enabled' | 'needs_exact_alarm' | 'needs_notification_permission'
if (support === 'needs_exact_alarm') { b.requestExactAlarms(); return; }
if (support !== 'ready') { showMessage('Alarms are not available: ' + support); return; }
const ok = b.scheduleNotificationEx('wake', JSON.stringify({
at: atMs,
repeat: 'weekdays', // daily/weekly/weekdays/monthly/yearly keep 07:00 across clock changes
title: 'Wake up',
body: 'Good morning',
alarm: true,
sound: '', // '' = the phone's alarm tone; or '/audio/rooster.mp3'; 'silent' = vibrate only
vibrate: true, // needs Vibration on in the build
ringSeconds: 300, // stop and count as missed after 5 minutes (default 600, max 3600)
snoozeMinutes: 9, // 0 hides the Snooze button (default 10)
snoozeLabel: 'Snooze', dismissLabel: 'Stop'
}));
if (!ok) showMessage('The alarm was not set.');
}
window.addEventListener('appmint:reminder', function (e) {
const d = e.detail;
// Opened by the ringing alarm: { id: 'wake', action: 'open', ringing: true, overLockScreen: true }
// How a ring ended (also delivered later if the app was closed):
// { id: 'wake', action: 'dismiss' | 'snooze' | 'missed' | 'error', alarm: true,
// source: 'notification' | 'page' | 'timeout' | 'replaced' | 'system',
// snoozeMinutes?, snoozedUntil?, error?, soundError? }
if (d.action === 'open' && d.ringing) showRingScreen(d.id); // your Stop / Snooze buttons call stopAlarm / snoozeAlarm
if (d.action === 'missed') logMissedAlarm(d.id);
});Um lembrete com botões, em 30 minutos:
WebToApk.scheduleNotificationEx('dose-1', JSON.stringify({
inSeconds: 30 * 60,
title: 'Time for your dose',
body: 'Vitamin D — 1 tablet',
actions: [{ id: 'taken', label: 'Taken' }, { id: 'snooze', label: 'Snooze' }]
}));
window.addEventListener('appmint:notification-action', function (e) {
// e.detail = { actionId: 'taken', tag: 'dose-1' }
if (e.detail.actionId === 'snooze') {
WebToApk.scheduleNotificationEx(e.detail.tag, JSON.stringify({ inSeconds: 600, title: 'Time for your dose', body: 'Snoozed reminder' }));
}
});Um lembrete único com uma imagem quando uma promoção começa, no canal normal:
function remindSale(sale) { // sale.startsAt = epoch ms
if (!(window.WebToApk && window.WebToApk.scheduleNotificationEx)) return false;
return WebToApk.scheduleNotificationEx('sale-' + sale.id, JSON.stringify({
at: sale.startsAt,
title: 'The sale starts now',
body: sale.name,
image: 'https://example.com/banners/' + sale.id + '.jpg', // expanded picture
channel: 'default' // not the heads-up style reminders use by default
}));
}Observações: o mesmo id substitui um lembrete mais antigo. Padrões: tag = o id, canal = urgent, bigText = o corpo. O toque em um botão chega à página apenas enquanto o app está em execução. O arquivo de som é copiado quando você agenda, então um caminho errado falha agora e não dias depois; mp3, wav, ogg, m4a, aac, flac e opus funcionam. openApp é a única forma de um app aparecer na tela sozinho em um horário definido; no Android 14+ o usuário precisa permitir (canOpenAppAtTime). repeat: 'weekdays' com days fora de 1-7 retorna false. alarm: true exige 🔔 Despertador na etapa Acesso (Etapa 4) e retorna false a menos que getAlarmSupport() seja 'ready'; apenas um alarme toca por vez (um novo encerra o anterior como missed). Se o arquivo de som de um alarme tiver desaparecido quando ele tocar (uma Atualização Remota o removeu), ele toca com o toque de alarme do celular e o evento carrega soundError: true.
showNotification ponte#
window.WebToApk.showNotification(title: String, body: String, icon: String, tag: String): Boolean
A forma ORIGINAL de quatro argumentos, mantida intacta como adaptador para que todo app já em uso e todo trecho de guia continuem funcionando. Agora ela encaminha para o construtor completo — o que significa que o argumento `icon`, descartado em silêncio pela implementação antiga, finalmente faz alguma coisa.
Exemplo
A chamada de notificação original com quatro argumentos. Mostra uma notificação com título, texto, um ícone grande opcional e uma tag. Código novo deve usar notify(optionsJson) ou new Notification(...), que fazem mais.
Retorna: true quando a notificação foi entregue ao sistema. Requer: ative Notificações na etapa Acesso (Etapa 4) ao compilar e a permissão do usuário no Android 13+.
A API web padrão (ela chama este método por você):
async function tellUser() {
if (!('Notification' in window)) return;
const p = Notification.permission === 'granted' ? 'granted' : await Notification.requestPermission();
if (p === 'granted') new Notification('Saved', { body: 'Your note was saved', tag: 'note' });
}A chamada direta. Argumentos: título, corpo, ícone, tag. Passe '' para nenhum ícone ou nenhuma tag.
if (window.WebToApk && window.WebToApk.showNotification) {
WebToApk.showNotification(
'Saved', // title
'Your note was saved', // body
'https://example.com/avatar.png', // large icon (https://, data:, or 'img/x.png' in your app), or ''
'note' // tag: same tag replaces the old notification
);
}Atualize uma notificação de status no mesmo lugar reutilizando sua tag:
function showSyncStatus(done, total) {
const b = window.WebToApk;
if (!(b && b.showNotification)) return;
if (b.getNotificationPermission() !== 'granted') return;
b.showNotification(
'Syncing photos',
done + ' of ' + total + ' uploaded',
'', // no large icon
'sync' // same tag every time: the text updates, no new notification
);
}Observações: ele encaminha para notify({title, body, largeIcon: icon, tag}) no canal padrão. Sem uma tag, cada chamada adiciona uma nova notificação. Para imagens, botões, progresso ou uma notificação fixa, use notify.
snoozeAlarm ponte#
window.WebToApk.snoozeAlarm(id: String, minutes: Int): Boolean
Para o alarme [id] que está tocando ("" = o que estiver tocando) e o faz tocar de novo em [minutes] (1-1440; 0 = o snoozeMinutes do próprio alarme). False quando não está tocando.
Exemplo
Para um alarme que está tocando e o faz tocar de novo após alguns minutos, como seu botão Adiar. Só este toque muda: um alarme diário ainda toca no horário de sempre amanhã.
Retorna: true quando o alarme estava tocando e foi adiado; false quando não estava tocando. id '' = o que estiver tocando; minutes 1-1440, ou 0 para o snoozeMinutes do próprio alarme (padrão 10). Requer: 🔔 Despertador na etapa Acesso (Etapa 4) ao compilar.
document.getElementById('snooze').onclick = function () {
if (!(window.WebToApk && window.WebToApk.snoozeAlarm)) return;
if (WebToApk.snoozeAlarm('', 5)) showMessage('Ringing again in 5 minutes');
hideRingScreen();
};
window.addEventListener('appmint:reminder', function (e) {
// { id: 'wake', action: 'snooze', alarm: true, source: 'page', snoozeMinutes: 5, snoozedUntil: 1767340800000 }
if (e.detail.action === 'snooze') updateNextAlarmLabel(e.detail.snoozedUntil);
});Agende um alarme e depois ligue um botão de dispensar:
const b = window.WebToApk;
if (b && b.snoozeAlarm && b.getAlarmSupport() === 'ready') {
b.scheduleNotificationEx('wake', JSON.stringify({
at: Date.now() + 8 * 3600000, repeat: 'daily', alarm: true,
title: 'Wake up', body: 'Good morning'
}));
}
document.getElementById('dismiss').onclick = () => {
if (WebToApk.isAlarmRinging()) WebToApk.stopAlarm('');
};Adie pela duração do próprio alarme e trate "nada está tocando":
document.getElementById('snooze').onclick = () => {
if (!(window.WebToApk && WebToApk.snoozeAlarm)) return;
const ok = WebToApk.snoozeAlarm('', 0); // 0 = the alarm's own snoozeMinutes (default 10)
if (!ok) showMessage('No alarm is ringing right now.');
};Observações: o toque adiado é listado por getScheduledNotifications() como <id>#snooze; cancelNotification(id) também o remove.
stopAlarm ponte#
window.WebToApk.stopAlarm(id: String): Boolean
Para o alarme [id] que está tocando ("" = o que estiver tocando), como seu botão Dispensar. Um alarme recorrente continua agendado para o próximo horário. False quando não está tocando.
Exemplo
Para um alarme que está tocando, exatamente como o botão Dispensar. Um alarme recorrente continua definido para o próximo horário; use cancelNotification(id) para removê-lo.
Retorna: true quando esse alarme estava tocando e agora está parado; false quando não estava tocando. Passe '' para parar o alarme que estiver tocando. Requer: 🔔 Despertador na etapa Acesso (Etapa 4) ao compilar.
window.addEventListener('appmint:reminder', function (e) {
const d = e.detail; // { id, action: 'open', ringing: true, overLockScreen }
if (d.action === 'open' && d.ringing) showRingScreen(d.id);
});
document.getElementById('stop').onclick = function () {
if (window.WebToApk && window.WebToApk.stopAlarm) WebToApk.stopAlarm(currentAlarmId);
hideRingScreen();
};Ofereça o botão de parar apenas onde a ponte existe e leia a resposta:
const stopBtn = document.getElementById('stop');
if (window.WebToApk && typeof WebToApk.stopAlarm === 'function') {
stopBtn.onclick = function () {
const wasRinging = WebToApk.stopAlarm(''); // '' = whichever alarm is ringing
if (!wasRinging) showMessage('Nothing was ringing.');
};
} else {
stopBtn.hidden = true; // plain browser: no alarm clock
}Combine com isAlarmRinging() para que a página possa mostrar a tela de toque de um alarme que começou enquanto ela estava ausente:
document.addEventListener('visibilitychange', function () {
if (document.hidden || !window.WebToApk) return;
if (WebToApk.isAlarmRinging && WebToApk.isAlarmRinging()) showRingScreen();
else hideRingScreen();
});Observações: a página então também recebe appmint:reminder com { id, action: 'dismiss', alarm: true, source: 'page' }.