С чего начать
Как писать HTML для приложения
Что среда выполнения приложения предоставляет вашей странице — контакты, SMS, уведомления, информация об устройстве, биометрия — и правила их правильного вызова
1Правило 1 — всегда проверяйте сначала#
window.WebToApk существует только внутри собранного приложения. В браузере его нет.
Поэтому всегда проверяйте перед вызовом. Иначе ваша страница сломается везде, кроме как внутри приложения.
Кое-что вообще не требует проверки: navigator.vibrate(), speechSynthesis и Notification уже работают — приложение подключает их за вас. Просто используйте обычный веб-код.
// GOOD — safe everywhere
if (window.WebToApk) {
WebToApk.playClick();
}
// BAD — this breaks your page in a browser
WebToApk.playClick();2Правило 2 — некоторые ответы приходят позже#
Чтение контактов, SMS или журнала звонков требует времени. Поэтому эти вызовы не отвечают сразу. Если бы отвечали, ваше приложение зависло бы.
Вы даёте вызову имя (id). Ответ приходит позже в виде события. Вы слушаете это событие.
Названия событий: appmint:contacts, appmint:calllog, appmint:sms, appmint:sms-received, appmint:notification-action.
// 1. Listen for the answer
window.addEventListener('appmint:contacts', function (e) {
if (e.detail.error) {
alert('Problem: ' + e.detail.error);
return;
}
console.log(e.detail.contacts);
});
// 2. Ask the question ('my1' is any name you choose)
WebToApk.listContacts('my1', 50, 0);3Правило 3 — должны быть включены два переключателя#
Для функции телефона нужны ДВЕ вещи:
- Вы включили это на шаге 4 (Доступ) при сборке приложения. Если нет, вы получите ошибку 'not_enabled', а пользователь ничего не увидит.
- Пользователь сказал «Да» на телефоне. Если он сказал «Нет», вы получите ошибку 'permission_denied'.
Всегда обрабатывайте обе ошибки, чтобы пользователь понимал, что произошло.
Способ проще: pickContact() и composeSms() НЕ требуют никаких разрешений. Используйте их, когда они подходят, — они выполняют ту же задачу с меньшими хлопотами.
window.addEventListener('appmint:contacts', function (e) {
if (e.detail.error === 'not_enabled') {
alert('Turn on Contacts when you build the app');
} else if (e.detail.error === 'permission_denied') {
alert('Please allow contacts access');
} else {
showContacts(e.detail.contacts);
}
});4Уведомления с картинками и кнопками#
notify() принимает список параметров. Можно добавить большую картинку, значок, кнопки и индикатор выполнения.
channel может быть: urgent, default, quiet или ongoing.
Используйте ongoing:true для уведомления, которое пользователь не может смахнуть. Учтите: его всё равно можно убрать в настройках Android. Ни одно приложение не может сделать уведомление, которое невозможно убрать.
Используйте tag, чтобы дать ему имя. Повторная отправка с тем же tag заменяет старое уведомление, а не добавляет новое.
WebToApk.notify(JSON.stringify({
title: 'Order shipped',
body: 'Arriving Tuesday',
channel: 'urgent',
image: 'https://mysite.com/box.jpg',
actions: [{ id: 'track', label: 'Track' }],
tag: 'order-482'
}));
// Know which button the user pressed
window.addEventListener('appmint:notification-action', function (e) {
if (e.detail.actionId === 'track') showTracking();
});5Информация о телефоне и отпечаток#
getDeviceInfo() не требует разрешений. Он возвращает версию Android, идентификатор сборки (buildId), модель телефона, размер экрана, уровень заряда батареи и многое другое.
Об ID устройства: IMEI или серийного номера НЕТ. Android заблокировал их для всех приложений начиная с Android 10. Используйте вместо этого getInstallId() — постоянный ID этой установки, который не меняется после обновлений.
var info = JSON.parse(WebToApk.getDeviceInfo());
info.android.release; // "14"
info.android.buildId; // "TQ3A.230805.001" <- version ID
info.hardware.model; // "Pixel 7"
info.runtime.batteryLevel; // 82
// Fingerprint
window.__webToApkAuth = window.__webToApkAuth || {};
window.__webToApkAuth['unlock'] = function (json) {
var r = JSON.parse(json);
if (r.ok) showMyApp();
};
WebToApk.authenticateBiometricEx('unlock', JSON.stringify({
title: 'Unlock',
allowDeviceCredential: true
}));6Сделайте так, чтобы страница заполняла экран#
Когда вы включаете полноэкранный режим, приложение само скрывает панели Android. Но добавьте этот CSS, чтобы ваш контент не скрывался под вырезом телефона.
Важно: полноэкранный режим скрывает панели ANDROID (часы, батарея, кнопки навигации). Собственная цветная панель вашего приложения вверху — это другой переключатель, называемый «Показывать верхнюю панель». Если после включения полноэкранного режима вы всё ещё видите панель, нужно отключить именно её.
Также проверьте приложение в повёрнутом виде. В альбомной ориентации вырез смещается набок.
body {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
min-height: 100vh; /* old phones */
min-height: 100dvh; /* correct when bars are hidden */
}7Загрузки сохраняют выбранное вами имя файла#
Ваш обычный код скачивания уже работает. Когда ваша страница сохраняет файл — отчёт в PDF, экспорт в CSV, резервную копию, — приложение перехватывает его и открывает окно Android «Сохранить как…» с уже подставленным ВАШИМ именем файла.
Это охватывает все обычные способы: <a download="...">, a.click() из кода, html2pdf / jsPDF, FileSaver.js saveAs() и window.open() для blob-URL. Объект File приносит своё имя с собой.
Если вы хотите запросить напрямую, а не создавать тег <a>, используйте AppMint.downloadFile().
Важно знать: имя существует только в JavaScript вашей страницы — Android само его не видит. Поэтому каждый раз задавайте download= (или передавайте имя), иначе файл сохранится как «download».
// The usual way — saves as Site_Report_2026-08.pdf
const blob = await html2pdf().from(el).outputPdf('blob');
const a = document.createElement('a');
a.download = 'Site_Report_2026-08.pdf';
a.href = URL.createObjectURL(blob);
a.click();
// Or ask directly
AppMint.downloadFile(base64String, 'Ledger_Q3.csv', 'text/csv');
AppMint.saveBlob(myBlob, 'Backup.json');8Сделайте так, чтобы кнопка «Назад» делала то, что ожидает ВАШЕ приложение#
Если ваше приложение меняет экраны, показывая и скрывая элементы (без смены URL, без history.pushState), кнопка «Назад» Android не видит эти экраны — её история пуста, поэтому «Назад» закрывает приложение с любого экрана.
Зарегистрируйте обработчик кнопки «Назад» и решайте сами: верните true, если вы обработали нажатие (закрыли меню, вернулись на экран назад), верните false, чтобы выполнилось обычное поведение — возврат по истории страницы, затем подтверждение выхода, затем выход.
Приложениям, которые используют history.pushState для каждого экрана, это не нужно: «Назад» уже проходит по их истории. И не бойтесь, что приложение зависнет — если ваш обработчик когда-нибудь повиснет, кнопка «Назад» телефона через короткое время снова будет работать как обычно.
AppMint.setBackHandler(() => {
if (isMenuOpen) { closeMenu(); return true; } // consumed
if (screen !== 'home') { goTo('home'); return true; }
return false; // nothing open — normal exit behaviour
});
// Or listen instead: e.preventDefault() consumes the press
window.addEventListener('appmint:back', e => {
if (closeTopmost()) e.preventDefault();
});9Получайте то, чем люди делятся с вашим приложением, — ссылки, фото, видео, файлы#
Включите «Получать данные из других приложений» в мастере сборки и отметьте, что принимает ваше приложение: текст и ссылки, файлы данных (JSON, CSV, TXT, XML, Markdown, GPX, TCX, FIT), фото, видео, аудио, документы (PDF, Word, Excel, PowerPoint). Тогда ваше приложение появится в меню «Поделиться» каждого другого приложения ровно для этих типов — приложение для заметок для текста, плеер для видео.
Ссылка или текст (при отправке видео YouTube из приложения YouTube передаётся его ссылка) читается с помощью WebToApk.getSharedText() и объявляется как appmint:shared. Файл запрашивается через AppMint.getOpenedFile() — запрашивайте, когда будете готовы, файл подождёт. Это важно для приложений на React, Vue и Angular: они завершают запуск ПОСЛЕ загрузки страницы, поэтому приложение, слушавшее только событие, раньше пропускало файл и просто показывало главный экран.
f.kind сообщает, что пришло: image, video, audio, document или data. Фото, видео и аудио приходят как f.url — адрес, поддерживающий потоковую передачу и перемотку, который вы вставляете прямо в <video src>, <img src> или <audio src>, так что клипу на 300 МБ не нужно помещаться в память; f.getFile() получает File, когда вам действительно нужны байты. Файлы данных и документы также приходят с f.file, а также f.text для текстовых форматов и f.base64 для небольших бинарных файлов в качестве сокращений. Фото, отправленное с подписью, передаёт подпись и как общий текст.
// A link or text someone shared
const shared = JSON.parse(WebToApk.getSharedText() || 'null');
if (shared) saveLink(shared.url || shared.text);
window.addEventListener('appmint:shared', e => saveLink(e.detail.url || e.detail.text));
// A photo, video, audio clip, document or data file — ask on startup, however late
const f = await AppMint.getOpenedFile(); // null if opened normally
if (f) {
if (f.kind === 'video') player.src = f.url; // streams, seekable
else if (f.kind === 'image') img.src = f.url;
else if (f.kind === 'document') await upload(await f.getFile()); // a real File
else importActivity(f.text ?? await f.file.text()); // data files
}
// Or listen, if you prefer
window.addEventListener('appmint:fileopen', e => handle(e.detail));10Датчики Bluetooth — стандартный Web Bluetooth#
Нагрудные пульсометры, датчики каденса и скорости, измерители мощности, умные велостанки, весы — всё, что работает по Bluetooth LE.
Используйте navigator.bluetooth, тот же код, что вы написали бы для Chrome на компьютере. AppMint предоставляет его внутри приложения; в обычном Android WebView его нет, поэтому этот код не работает в других конструкторах приложений.
Окно выбора устройства рисуется приложением, как в браузере, — ваша страница получает доступ только к устройству, выбранному пользователем.
Включите разрешение Bluetooth на шаге 4 (Доступ). Если в вашем коде упоминается navigator.bluetooth, AppMint включает его за вас, когда вы выбираете свой ZIP.
Недоступно: getDevices(), watchAdvertisements() и requestLEScan() — они завершаются с ошибкой NotSupportedError, поэтому проверяйте их перед использованием.
const device = await navigator.bluetooth.requestDevice({
filters: [{ services: ['heart_rate'] }]
});
const server = await device.gatt.connect();
const service = await server.getPrimaryService('heart_rate');
const chr = await service.getCharacteristic('heart_rate_measurement');
chr.addEventListener('characteristicvaluechanged', e => {
const v = e.target.value; // a DataView
const bpm = (v.getUint8(0) & 1) ? v.getUint16(1, true) : v.getUint8(1);
document.getElementById('bpm').textContent = bpm;
});
await chr.startNotifications();
device.addEventListener('gattserverdisconnected', () => showReconnect());11Загрузка фото — выбор фото Android, без разрешений#
Используйте обычное поле выбора файла. Больше ничего добавлять не нужно.
В приложении <input type="file" accept="image/*"> открывает средство выбора фото Android: фото пользователя в сетке, и ваша страница получает только те, что он выбрал. Без разрешения на хранилище, без запроса «Разрешить доступ к фото?». Работает и на старых телефонах — там средство выбора устанавливают службы Google Play.
- accept="image/*" — только фото
- accept="video/*" — только видео
- accept="image/*,video/*" — и то и другое
- добавьте multiple — пользователь сможет выбрать несколько файлов
- добавьте capture="environment" — вместо этого открывается камера (нужна отметка «Камера» на шаге 4 (Доступ))
Добавьте другие файлы (accept="image/*,.pdf"), и откроется обычное окно выбора файлов, чтобы можно было выбрать и PDF.
<input type="file" id="photos" accept="image/*" multiple>
<script>
document.getElementById('photos').onchange = function (e) {
for (const file of e.target.files) {
const img = document.createElement('img');
img.src = URL.createObjectURL(file);
document.body.appendChild(img);
}
};
</script>12Сканирование QR-кода или штрихкода — одной строкой#
AppMint.scanCode() открывает экран сканера Google. Пользователь наводит камеру, и вы получаете код обратно.
Без разрешения на камеру. Без элемента video. Ничего не нужно рисовать. Сканер сам приближает изображение для мелких кодов. При первом запуске на телефоне службы Google Play загружают сканер (несколько секунд).
Ответ содержит value (текст в коде) и format (qr_code, ean_13, …). Для распространённых QR-кодов вы также получаете готовые части: url, wifi (ssid, password), phone, email, sms, geo, contact.
Если ok равно false, code объясняет причину: cancelled (пользователь закрыл), unavailable (нет служб Google Play или загрузка не удалась), busy (сканирование уже открыто).
AppMint.scanCode существует только внутри приложения — сначала проверяйте его наличие.
async function scan() {
if (!window.AppMint || !AppMint.scanCode) return; // in a browser
const r = await AppMint.scanCode({ formats: ['qr_code'] });
if (!r.ok) return; // closed, or no scanner
console.log(r.value); // the text inside the code
if (r.url) location.href = r.url.url;
}
// Any code type: AppMint.scanCode()
// Let the user type it when it will not scan: AppMint.scanCode({ manualInput: true })13Passkey — вход по отпечатку или лицу, без пароля#
Если на вашем сайте уже есть passkey, теперь они работают и внутри приложения. Обычный Android WebView не умеет работать с passkey; AppMint связывает navigator.credentials с менеджером passkey Android, поэтому библиотеки вроде SimpleWebAuthn, Hanko, Corbado или Auth0 работают без изменений.
Вам нужен сервер, который создаёт параметры passkey и проверяет ответ (например, SimpleWebAuthn на вашем бэкенде). Проверить passkey внутри страницы нельзя.
Две вещи нужно настроить один раз:
- Ваш сайт должен заявить, что доверяет приложению. Разместите файл из
AppMint.passkey.assetLinks() по адресу https://YOUR-DOMAIN/.well-known/assetlinks.json. Берите его из приложения, установленного ИЗ Google Play — Play подписывает ваше приложение собственным ключом, поэтому файл из тестового APK не подойдёт пользователям Play. Домен должен быть точно rpID вашего сервера (www и без www — это разные домены).
- Ваш сервер должен принимать источник (origin) приложения. В ответе от приложения origin выглядит как 'android:apk-key-hash:…', а не https://… — получите это значение через
AppMint.passkey.origin() и добавьте его в разрешённые источники вашего сервера.
Ошибки: NotAllowedError = пользователь отменил действие или на этом телефоне нет passkey. InvalidStateError = такой passkey уже существует. SecurityError = assetlinks.json не содержит приложение.
Passkey требуют Android 9 или новее со службами Google Play — AppMint.passkey.isAvailable() сообщит об этом. Автозаполнение passkey в текстовом поле (mediation: 'conditional') в приложениях недоступно; запускайте вход по кнопке.
// Your normal passkey code works as it is:
const cred = await navigator.credentials.get({ publicKey: options });
// Or with your server's JSON options (SimpleWebAuthn style):
const options = await (await fetch('/passkey/login-options')).json();
const answer = await AppMint.passkey.get(options);
await fetch('/passkey/login-verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(answer)
});
// Once, on the Play-installed app — copy these to your server setup:
console.log(JSON.stringify(AppMint.passkey.assetLinks(), null, 2));
console.log(AppMint.passkey.origin());14Виджет на домашнем экране — показывайте свои данные на главном экране#
У вашего приложения есть виджет на домашнем экране. Пользователь добавляет его из списка виджетов телефона (нажмите и удерживайте домашний экран, затем «Виджеты»), либо вы открываете окно добавления через AppMint.widget.requestPin().
Виджет остаётся пустым («Пока нечего показывать»), пока ваша страница его не заполнит. Это небольшая карточка Android, а не веб-страница, поэтому она не может показать ваш HTML. Вы задаёте заголовок, одно крупное значение, до шести коротких строк и цвета.
Вызывайте AppMint.widget.set() при открытии приложения и снова при каждом изменении показываемых данных. Виджет сохраняет последнюю карточку даже после перезапуска. route — это то место, куда нажатие на виджет открывает ваше приложение.
function updateWidget() {
if (!window.AppMint || !AppMint.widget) return; // in a browser
AppMint.widget.set({
title: 'Today',
value: '3 events',
lines: ['09:00 Dentist', '13:00 Lunch with Sam', '18:30 Gym'],
route: '/',
bg: '#0E7C86', ink: '#FFFFFF', accent: '#FFE08A'
});
}
updateWidget(); // on start-up, and after every change
// An "Add to home screen" button:
async function addWidget() {
if ((await AppMint.widget.placedCount()) > 0) return;
await AppMint.widget.requestPin();
}15Защита от копирования и меню долгого нажатия#
Два переключателя на шаге 3 (Интеграция), в разделе «Параметры отображения». Оба выключены, пока вы их не включите.
- Меню долгого нажатия на ссылках и изображениях: нажмите и удерживайте ссылку или картинку, чтобы открыть, скопировать, отправить или сохранить её. По умолчанию выключено, поэтому долгое нажатие на ссылку ничего не делает. Страница всё равно может отказаться от меню для отдельного элемента, отменив событие contextmenu.
- Защита от копирования: никто не может выделить, скопировать или вырезать текст в вашем приложении. Ввод в формах по-прежнему работает. Она также отключает меню долгого нажатия.
Ваша страница может это определить: AppMint.copyProtection равно true, когда защита включена, и отсутствует, когда она выключена (и в браузере). Только для чтения. Скройте собственные кнопки «Копировать», когда защита включена, потому что они ничего не скопируют.
const protectedApp = !!(window.AppMint && window.AppMint.copyProtection === true);
// A Copy button would copy nothing, so hide it
document.querySelectorAll('[data-copy-button]').forEach(btn => {
btn.hidden = protectedApp;
});16{exampleCount} готовых примеров — откройте их здесь#
Рабочий код, который можно скопировать: отправка SMS, чтение контактов, уведомление с картинкой, блокировка по отпечатку, информация о телефоне, сохранение файлов, датчики Bluetooth и многое другое. Каждый пример короткий и объяснён простыми словами.
Открывается внутри AppMint. Можно искать по тексту, а у каждого блока кода есть кнопка «Копировать».
На том же экране есть «Все методы» — все {methodCount} вещей, которые может вызвать ваше приложение, и у каждой есть рабочий пример, который можно скопировать. Этот список строится из самой среды выполнения приложения, поэтому всегда точно соответствует тому, что умеет ваше приложение.
17Сохраните примеры на телефон или компьютер#
Сохраняет три файла в Downloads/AppMint/Guides:
- Примеры — {exampleCount} примеров выше
- Список методов — все {methodCount} методов
- index.html — готовая тестовая страница
На тестовой странице есть кнопки для информации об устройстве, вибрации, уведомлений и выбора контакта. Соберите её как приложение, чтобы увидеть, как всё работает, затем превратите в своё приложение. Она также нормально открывается в обычном браузере — ничего не ломается, просто пишет, что она не внутри приложения.
В приложении кнопка этого шага сохраняет их на телефон. Здесь они доступны для прямого скачивания.
AppMint-examples.md AppMint-all-methods.md index.html18Будильник, который звонит поверх экрана блокировки#
Отметьте «Будильник» на шаге «Доступ». scheduleNotificationEx с alarm: true звонит с вашим звуком, пока пользователь не отключит или не отложит его, даже когда приложение закрыто. Повтор может быть ежедневным, еженедельным, по будням, ежемесячным или ежегодным, либо можно выбрать дни недели.
Перед планированием спросите getAlarmSupport(). Он отвечает 'ready', 'not_enabled', 'needs_exact_alarm' или 'needs_notification_permission'. Затем stopAlarm(id), snoozeAlarm(id, minutes) и isAlarmRinging(id) управляют звонящим будильником.
const B = window.WebToApk;
if (B && B.getAlarmSupport && B.getAlarmSupport() === 'ready') {
B.scheduleNotificationEx('wake', JSON.stringify({
at: firstAtMs, alarm: true, title: 'Wake up', repeat: 'weekdays', sound: '/audio/alarm.mp3'
}));
}
document.getElementById('snooze').onclick = () => B.snoozeAlarm('wake', 10);19Системная громкость#
getVolume(stream) читает громкость мультимедиа, будильника, звонка, уведомлений или вызова в виде JSON. setVolume(stream, percent, showUi) меняет её. watchVolume(true) отправляет событие appmint:volume при каждом изменении. Для отключения громкости звонка нужен доступ к режиму «Не беспокоить»: requestDndAccess().
if (window.WebToApk && WebToApk.getVolume) {
const v = JSON.parse(WebToApk.getVolume('alarm'));
if (v.percent === 0) alert('Alarm volume is off');
window.addEventListener('appmint:volume', (e) => console.log(e.detail.stream, e.detail.percent));
WebToApk.watchVolume(true);
}20Фоновая служба, фоновая геолокация, геозоны и задачи#
startBackgroundService сохраняет вашу страницу, звук и таймеры работающими, пока пользователь отсутствует, с уведомлением. requestBackgroundLocation запрашивает «Разрешить всегда». addGeofence срабатывает при входе, выходе или нахождении в зоне при закрытом приложении. AppMint.background.register запускает ваш собственный код страницы каждые 15 минут или реже. Всё это снова запускается после перезагрузки телефона.
Для них нужен переключатель в мастере (шаг «Доступ»); без него вызов отвечает { ok: false, reason: 'not_enabled' }.
const r = JSON.parse(WebToApk.startBackgroundService(JSON.stringify({ title: 'Syncing', text: 'Keeping data fresh' })));
if (!r.ok) console.warn(r.reason);
if (AppMint.background.isBackgroundRun()) {
window.addEventListener('appmint:background', async (e) => {
await refreshData();
AppMint.background.done(e.detail.runId, { ok: true });
});
} else {
AppMint.background.register('refresh', { intervalMinutes: 60 });
}21Отчёты о сбоях#
Отметьте «Отчёты о сбоях» на шаге «Интеграция». Необработанные ошибки записываются автоматически. logError(message, stack) записывает перехваченную вами ошибку, setCrashUserId(id) помечает отчёты вашим собственным идентификатором пользователя (никогда не e-mail), а getLastCrash() возвращает последний сбой в виде JSON или пустую строку. Читайте отчёты в «Обновить приложение», затем выберите приложение, затем «Отчёты о сбоях».
if (window.WebToApk && WebToApk.logError) {
try { risky(); } catch (err) { WebToApk.logError(err.message, err.stack || ''); }
WebToApk.setCrashUserId('u_42');
const last = WebToApk.getLastCrash(); // '' when none
}22Целостность устройства и Play Integrity#
Отметьте «Проверки целостности устройства» на шаге «Интеграция». getDeviceIntegrity() возвращает признаки root, эмулятора и отладчика в виде JSON; это предупреждение, а не доказательство. Для надёжного вердикта вызовите requestIntegrityToken(nonce, '') и отправьте токен из события appmint:integrity на ВАШ сервер, который запросит вердикт у Google Play.
const d = JSON.parse(WebToApk.getDeviceIntegrity());
if (d.rooted || d.emulator) alert('This phone is modified');
window.addEventListener('appmint:integrity', (e) => sendToServer(e.detail.token));
WebToApk.requestIntegrityToken(serverNonceBase64, '');23Альтернативные значки приложения и ТВ#
Добавьте до 4 альтернативных значков на шаге «Основное». setAppIcon(name) переключает значок на рабочем столе ('default' возвращает исходный), getAppIcon() называет текущий, а getAvailableIcons() перечисляет их. isTv() сообщает, что приложение работает на телевизоре, чтобы вы могли показать макет, удобный для пульта.
if (window.WebToApk && WebToApk.setAppIcon) WebToApk.setAppIcon('dark');
if (window.WebToApk && WebToApk.isTv && WebToApk.isTv()) {
document.documentElement.classList.add('tv');
}24Вход через Facebook, аналитика и RevenueCat#
Вход через Facebook поддерживается. На шаге «Интеграция» введите Facebook App ID и Client Token; вход открывается во вкладке браузера и возвращается в приложение. Вызовите AppMint.facebook.login(['public_profile', 'email']) и отправьте токен на свой сервер. AppMint.analytics (Firebase) и AppMint.facebook.logEvent (Meta App Events) записывают данные только после согласия пользователя. AppMint.revenuecat обрабатывает подписки с вашим публичным ключом Android.
const r = await AppMint.facebook.login(['public_profile', 'email']);
if (r.ok) await fetch('/api/login/facebook', { method: 'POST', body: JSON.stringify({ token: r.accessToken }) });
AppMint.analytics.log('share', { method: 'whatsapp' });
const offerings = await AppMint.revenuecat.getOfferings();25Текст с фото, PDF и виджеты#
AppMint.textRecognition.recognize(image, { script }) читает текст на латинице, деванагари, китайском, японском или корейском. AppMint.pdf.fromPage() и fromHtml(html, options) создают настоящий PDF без окна печати. AppMint.widget.set(card) заполняет виджет на домашнем экране.
const r = await AppMint.textRecognition.recognize(file, { script: 'devanagari' });
const pdf = await AppMint.pdf.fromHtml('<h1>Invoice</h1>', { fileName: 'invoice', save: 'share' });
await AppMint.widget.set({ title: 'Today', value: '3 events', route: '/calendar' });26Данные о здоровье и маяки Bluetooth#
AppMint.health читает и записывает шаги, пульс, сон, вес и тренировки через Health Connect; отметьте «Данные о здоровье» и нужные типы на шаге «Доступ». AppMint.beacons.startScan() находит маяки iBeacon и Eddystone и отправляет события appmint:beacon.
const a = await AppMint.health.isAvailable();
if (a.available) {
await AppMint.health.requestPermissions({ read: ['steps'] });
const steps = await AppMint.health.read('steps', { start: new Date(Date.now() - 864e5), end: new Date(), aggregate: true });
}
window.addEventListener('appmint:beacon', (e) => console.log(e.detail));
await AppMint.beacons.startScan();27Готовые плагины: SQLite, уведомления, пуш, сканер, контакты#
На страницах, которые приложение отдаёт само, window.Capacitor.Plugins содержит CapacitorSQLite (приватная база данных), LocalNotifications, PushNotifications, CapacitorBarcodeScanner и Contacts. Сначала проверьте, что плагин существует: в приложении в режиме «Сайт» window.Capacitor есть только на страницах его собственного сайта, но никогда на других доменах.
const P = window.Capacitor && window.Capacitor.Plugins;
if (P && P.CapacitorBarcodeScanner) {
const s = await P.CapacitorBarcodeScanner.scanBarcode({ hint: 17 });
console.log(s.ScanResult);
}
if (P && P.LocalNotifications) await P.LocalNotifications.requestPermissions();