Создать APK бесплатно

JavaScript bridge API

Плагины Capacitor

Плагины Capacitor, которые есть в приложении. Страница, написанная вручную, обращается к ним как к window.Capacitor.Plugins.<Name>; приложение, созданное с помощью ИИ, использует обёртку @/lib/appmintNative, указанную в каждом примере.

Capacitor.ActionSheet Capacitor#

Capacitor.Plugins.ActionSheet — showActions({ title, message, options })

Пример

Показывает нативное нижнее меню со списком вариантов и сообщает, какой из них выбран (@capacitor/action-sheet 8.1.1).

Возвращает: Promise { index, canceled } — index — это выбранный вариант; если меню закрыто без выбора, canceled равно true, а index лежит вне списка (-1). Требуется: ничего включать не нужно — плагин есть в каждом приложении. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

Каждый вариант — это { title, style? } со значением style: 'DEFAULT', 'DESTRUCTIVE' или 'CANCEL'.

var ActionSheet = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ActionSheet;

async function photoMenu() {
  if (!ActionSheet) return;
  var opts = [
    { title: 'Share', style: 'DEFAULT' },
    { title: 'Edit', style: 'DEFAULT' },
    { title: 'Delete', style: 'DESTRUCTIVE' }
  ];
  var r = await ActionSheet.showActions({ title: 'Photo', message: 'Choose an action', options: opts });
  if (r.canceled || r.index < 0 || r.index >= opts.length) return;   // dismissed
  if (opts[r.index].title === 'Delete') deletePhoto();
}

В приложении, созданном с помощью ИИ, обёртка в стиле Capacitor при закрытии возвращает { index: -1 }; краткая форма actionSheet() возвращает null:

import { ActionSheet, actionSheet } from '@/lib/appmintNative';

const { index } = await ActionSheet.showActions({
  title: 'Photo',
  options: [{ title: 'Share' }, { title: 'Delete', destructive: true }],
});
if (index === 1) deletePhoto();

const i = await actionSheet('Sort by', [{ title: 'Name' }, { title: 'Date' }]);   // number | null

Подтверждение удаления, которое работает и в обычном браузере:

async function confirmDelete(name) {
  const AS = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ActionSheet;
  if (!AS) return window.confirm('Delete ' + name + '?');
  const r = await AS.showActions({
    title: 'Delete ' + name + '?',
    options: [{ title: 'Delete', style: 'DESTRUCTIVE' }, { title: 'Keep it', style: 'CANCEL' }]
  });
  return !r.canceled && r.index === 0;
}

Примечания: Всегда проверяйте границы индекса — никогда не считайте любой индекс настоящим выбором. Форма параметра в обёртке — { title, destructive? }; она сопоставляет destructive со стилем плагина 'DESTRUCTIVE'.

Capacitor.App Capacitor#

Capacitor.Plugins.App — getInfo(), getState(), exitApp(), minimizeApp(), addListener(...)

Пример

Идентификация и жизненный цикл приложения: название, id пакета, версия, находится ли приложение на переднем плане, а также события паузы и возобновления (@capacitor/app 8.1.0).

Возвращает: getInfo() → { name, id, build, version }; getState() → { isActive }; getLaunchUrl() → { url } или ничего; getAppLanguage() → { value }; exitApp() / minimizeApp() → void. addListener() → Promise с дескриптором с remove(), как в собственном API Capacitor — используйте await или цепочку .then(); возвращаемое значение также имеет собственный remove(). Требуется: ничего включать не нужно — каждое приложение содержит плагин. window.Capacitor существует на страницах, которые приложение отдаёт само (HTML, ZIP и приложения, созданные ИИ), а в приложении режима Сайт — на вашем собственном сайте (его домене и поддоменах), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие.

var App = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.App;
if (App) {
  App.getInfo().then(function (i) {
    document.getElementById('about').textContent = i.name + ' ' + i.version + ' (' + i.build + ')';
  });
}

Пауза и возобновление — сохраните черновик, когда пользователь уходит, и обновите данные, когда он возвращается:

if (App) {
  App.addListener('appStateChange', function (s) {
    if (!s.isActive) saveDraft();      // went to the background
    else refreshInbox();               // back in front
  });
  App.addListener('pause', function () { console.log('paused'); });
  App.addListener('resume', function () { console.log('resumed'); });
}

В приложении, созданном с помощью ИИ:

import { App } from '@/lib/appmintNative';

const info = await App.getInfo();   // { name, id, build, version }
App.exitApp();                      // closes the app on Android

Примечания: Событие backButton никогда не срабатывает: оболочка сохраняет за собой кнопку «Назад» (плагин работает с disableBackButtonHandler). Обрабатывайте «Назад» через AppMint.setBackHandler(fn) или обёртку onBackGesture. Для глубоких ссылок используйте WebToApk.getLaunchUrl() / событие appmint:deep-link (или launchUrl() / onDeepLink() в приложениях, созданных с помощью ИИ). minimizeApp() работает только на Android. Никогда не делайте кнопку «Выход» единственным путём вперёд.

Capacitor.AppLauncher Capacitor#

Capacitor.Plugins.AppLauncher — canOpenUrl({ url }), openUrl({ url })

Пример

Открывает другое приложение по ссылке (whatsapp://send?text=…, tel:, geo:, ссылка https) и проверяет, может ли какое-то приложение её обработать (@capacitor/app-launcher 8.0.1).

Возвращает: canOpenUrl() → Promise { value: boolean }. openUrl() → Promise { completed: boolean } (false, если ни одно приложение не приняло ссылку). Требуется: ничего включать не нужно — плагин есть в каждом приложении. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var AL = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppLauncher;

async function chatOnWhatsApp(text) {
  if (!AL) return;
  var url = 'whatsapp://send?text=' + encodeURIComponent(text);
  var can = await AL.canOpenUrl({ url: url });
  if (!can.value) { alert('WhatsApp is not installed'); return; }
  var r = await AL.openUrl({ url: url });
  if (!r.completed) alert('Could not open WhatsApp');
}

В приложении, созданном с помощью ИИ:

import { appLauncher } from '@/lib/appmintNative';

if (await appLauncher.canOpen('tel:+15551234567')) await appLauncher.open('tel:+15551234567');

Откройте ссылку на карту без предварительного вопроса:

async function openMap(query) {
  const AL = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppLauncher;
  if (!AL) { window.open('https://maps.google.com/?q=' + encodeURIComponent(query)); return; }
  const r = await AL.openUrl({ url: 'geo:0,0?q=' + encodeURIComponent(query) });
  if (!r.completed) alert('No maps app could open this');
}

Примечания: На Android 11+ приложение «видит» только те другие приложения, которые оно объявило. Созданные приложения объявляют ссылки https, geo, google.navigation, tel, mailto, sms, whatsapp, intent и upi, поэтому canOpenUrl отвечает false для других схем (например, spotify:), даже если приложение установлено. openUrl для них всё равно работает — вызовите его и проверьте completed. На Android canOpenUrl также принимает имя пакета ('com.whatsapp') с тем же ограничением видимости.

Capacitor.AppUpdate Capacitor#

Capacitor.Plugins.AppUpdate — getAppUpdateInfo(), performImmediateUpdate(), startFlexibleUpdate(), completeFlexibleUpdate(), openAppStore()

Пример

Проверяет в Google Play наличие более новой версии приложения и запускает обновление в приложении средствами Play. Включён в сборку @capawesome/capacitor-app-update (template/plugins/app-update).

Возвращает: getAppUpdateInfo() → { currentVersionName, currentVersionCode, availableVersionCode, updateAvailability, updatePriority, immediateUpdateAllowed, flexibleUpdateAllowed, clientVersionStalenessDays?, installStatus } (коды версий — строки; updateAvailability 2 = доступно обновление). performImmediateUpdate() / startFlexibleUpdate() → { code }: 0 — успешно, 1 — отменено, 2 — ошибка, 3 — недоступно, 4 — не разрешено, 5 — сначала вызовите getAppUpdateInfo(). openAppStore() → void. Требуется: ничего включать не нужно; приложение должно быть установлено из Google Play. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var AU = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppUpdate;

async function checkForUpdate() {
  if (!AU) return;
  try {
    var info = await AU.getAppUpdateInfo();          // must come first
    if (info.updateAvailability !== 2) return;       // up to date
    if (info.immediateUpdateAllowed) {
      var r = await AU.performImmediateUpdate();     // Play's full-screen update
      if (r.code === 1) console.log('user said not now');
    } else {
      await AU.openAppStore();                       // the Play listing
    }
  } catch (e) {
    console.log('Play update check failed:', e.message);  // e.g. no Play services
  }
}

Гибкое обновление — загрузка в фоне, затем перезапуск, когда пользователь согласится:

async function flexibleUpdate() {
  if (!AU) return;
  var info = await AU.getAppUpdateInfo();
  if (info.updateAvailability !== 2 || !info.flexibleUpdateAllowed) return;
  await AU.addListener('onFlexibleUpdateStateChange', function (s) {
    if (s.installStatus === 2) console.log(s.bytesDownloaded + ' / ' + s.totalBytesToDownload);
    if (s.installStatus === 11) showRestartButton();   // 11 = DOWNLOADED
  });
  await AU.startFlexibleUpdate();
}
function restartNow() { if (AU) AU.completeFlexibleUpdate(); }

В приложении, созданном с помощью ИИ:

import { appUpdate } from '@/lib/appmintNative';

const info = await appUpdate.check();          // null on the web / without Play
if (info?.inPlace) await appUpdate.start();    // Android: immediate update

Примечания: Используйте updateAvailability === 2 как единственную проверку «доступно обновление». Метод check() в обёртке сравнивает currentVersionName с availableVersionCode, которые на Android никогда не совпадают, поэтому её updateAvailable там ненадёжен — проверяйте inPlace. start() завершается true, даже если пользователь отменил.

Capacitor.AppwrightDocScanner Capacitor#

Capacitor.Plugins.AppwrightDocScanner — scan({ pageLimit, pdf, quality }), isSupported()

Пример

Открывает сканер документов Google ML Kit: пользователь фотографирует одну или несколько страниц (или импортирует их), края находятся и выравниваются, а страницы возвращаются в виде JPEG-изображений. Собственный плагин Appwright (AppwrightDocScannerPlugin.kt).

Возвращает: scan() → Promise, который всегда ЗАВЕРШАЕТСЯ успешно: { ok: true, pages: string[] (JPEG data URLs), pdfDataUrl?, pdfPath?, pdfError? } при успехе, { ok: false, cancelled: true }, когда пользователь вышел, { ok: false, error }, когда сканер не удалось запустить. isSupported() → { value: boolean } (сервисы Google Play установлены). Требуется: ничего включать не нужно, разрешение на камеру не требуется (сканер работает внутри сервисов Play, которые загружают свою модель при первом использовании).

var Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightDocScanner;

async function scanReceipt() {
  if (!Scanner) { alert('Scanning is not available here'); return; }
  var s = await Scanner.isSupported();
  if (!s.value) { alert('This phone has no Google Play services'); return; }
  var r = await Scanner.scan({ pageLimit: 1, pdf: false, quality: 0.8 });
  if (r.cancelled) return;
  if (!r.ok) { alert('Scanner error: ' + r.error); return; }
  document.getElementById('preview').src = r.pages[0];
}

pageLimit 0 = без ограничений. quality — от 0,1 до 1 для JPEG. С pdf: true сканер также записывает PDF: pdfDataUrl — это он в виде URL data:application/pdf;base64,…, который страница может скачать, загрузить или показать, а pdfPath — его копия в папке кэша приложения. Если PDF прочитать не удалось, страницы всё равно возвращаются, а pdfError объясняет причину.

В приложении, созданном с помощью ИИ (null при отмене, ошибке или в вебе):

import { docScanner, can } from '@/lib/appmintNative';

if (can('docScanner') !== 'none') {
  const res = await docScanner.scan({ pageLimit: 5, pdf: true });
  if (res) setPages(res.pages);          // res.pdfPath when pdf: true
}

Отсканируйте несколько страниц и предложите PDF:

async function scanContract() {
  const Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightDocScanner;
  if (!Scanner) return;
  const r = await Scanner.scan({ pageLimit: 0, pdf: true, quality: 0.9 });
  if (!r.ok) { if (!r.cancelled) alert('Scanner error: ' + r.error); return; }
  if (r.pdfDataUrl) {
    const a = document.createElement('a');
    a.href = r.pdfDataUrl; a.download = 'contract.pdf'; a.click();
  } else if (r.pdfError) console.warn('PDF not made:', r.pdfError);
  console.log(r.pages.length + ' pages scanned');
}

Примечания: Страницы — это полные data URL и могут быть большими: уменьшайте их перед сохранением многих в localStorage. Используйте pdfDataUrl, а не pdfPath — путь к файлу на устройстве не является URL, который страница может загрузить. Страница, которую не удалось прочитать, пропускается; остальные возвращаются.

Capacitor.AppwrightGallery Capacitor#

Capacitor.Plugins.AppwrightGallery — save({ source, fileName, album, kind })

Пример

Сохраняет фото или видео в галерею телефона (Pictures/<album> или Movies/<album>). Собственный плагин оболочки приложения (AppwrightGalleryPlugin.kt).

Возвращает: Promise с { uri } сохранённого элемента. Отклоняется с code: bad-source, download-failed, permission-denied (Android 7–9, доступ к хранилищу отклонён) или failed. Требуется: ничего включать не нужно; на Android 10+ разрешение не требуется (Android 7–9 запрашивает доступ к хранилищу при первом использовании).

const Gallery = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightGallery;

async function saveCard(canvas) {
  const dataUrl = canvas.toDataURL('image/png');
  if (!Gallery) { const a = document.createElement('a'); a.href = dataUrl; a.download = 'card.png'; a.click(); return; }
  try {
    await Gallery.save({ source: dataUrl, fileName: 'birthday-card.png', album: 'Cards' });
    toast('Saved to your gallery');
  } catch (e) {
    toast('Could not save: ' + e.message);
  }
}

Сохраните видео с вашего сервера в отдельный альбом с сообщением для каждого кода ошибки:

const Gallery = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightGallery;

async function saveClip(url) {
  if (!Gallery) { window.open(url, '_blank'); return; }
  try {
    await Gallery.save({ source: url, fileName: 'highlight.mp4', album: 'Highlights', kind: 'video' });
    toast('Video saved to Movies/Highlights');
  } catch (e) {
    if (e.code === 'download-failed') toast('Could not download the video. Check your connection.');
    else if (e.code === 'permission-denied') toast('Allow storage access to save videos.');
    else toast('Could not save: ' + e.message);
  }
}

Сохраните фото, которое пользователь отредактировал, как Blob (например, из canvas.toBlob), предварительно превратив его в URL data::

const Gallery = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightGallery;

function blobToDataUrl(blob) {
  return new Promise((resolve, reject) => {
    const r = new FileReader();
    r.onload = () => resolve(r.result);
    r.onerror = () => reject(r.error);
    r.readAsDataURL(blob);
  });
}

async function saveEditedPhoto(canvas) {
  if (!Gallery) return;
  const blob = await new Promise((res) => canvas.toBlob(res, 'image/jpeg', 0.92));
  const { uri } = await Gallery.save({ source: await blobToDataUrl(blob), fileName: 'edit-' + Date.now() + '.jpg', album: 'My Edits' });
  console.log('saved as', uri);
}

Примечания: source — это URL data:, URL http(s) или URI content:///file://; kind ('image'/'video') определяется по MIME-типу, если опущен. Приложения, созданные с помощью ИИ, используют saveToGallery() из @/lib/appmintNative.

Capacitor.AppwrightHaptics Capacitor#

Capacitor.Plugins.AppwrightHaptics

Пример

Собственный плагин AppMint для сложных шаблонов вибрации: список ударов, у каждого своё время начала, длительность и сила.

Возвращает: play({ events }) завершается { ok: true } или { ok: false, error } ('unsupported' = нет вибромотора, 'empty' = нет событий или сообщение системы). Он никогда не отклоняется. isSupported() завершается { value: true/false }. Требуется: ничего включать не нужно — каждое приложение объявляет разрешение на вибрацию, независимо от того, отмечено ли Вибрация на шаге 4 (Доступ). window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

Каждое событие — это { at, duration, intensity, sharpness }: at = начало в мс, duration = длительность в мс (меньше 20 превращается в касание 30 мс), intensity = 0–1 (по умолчанию 0,8). sharpness принимается, но на Android игнорируется.

var AH = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.AppwrightHaptics : null;

async function heartbeat() {
  if (!AH) return false;
  var r = await AH.play({
    events: [
      { at: 0,   intensity: 0.8 },
      { at: 140, intensity: 0.5 },
      { at: 700, intensity: 0.8 },
      { at: 840, intensity: 0.5 }
    ]
  });
  if (!r.ok) console.log('pattern not played:', r.error);
  return r.ok;
}

document.getElementById('like').addEventListener('click', heartbeat);

Скройте кнопку на телефонах без вибромотора:

(async function () {
  var btn = document.getElementById('feel-it');
  var ok = false;
  if (AH) { try { ok = (await AH.isSupported()).value; } catch (e) {} }
  btn.hidden = !ok;
})();

Воспроизведите короткое двойное касание при успешном действии и сообщите, если воспроизвести не удалось:

async function successBuzz() {
  const AH = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightHaptics;
  if (!AH) return;
  const r = await AH.play({ events: [{ at: 0, duration: 40, intensity: 1 }, { at: 120, duration: 40, intensity: 0.6 }] });
  if (!r.ok && r.error === 'unsupported') console.log('this phone has no vibrator');
}

Примечания: Сила работает только на телефонах с управлением амплитудой; остальные телефоны сохраняют тайминг с одной фиксированной силой. Если система отказывает в вибрации, play отвечает { ok: false, error: '<the system message>' }. В приложениях, созданных с помощью ИИ, используйте hapticPlay из @/lib/appmintNative, где есть и именованные шаблоны.

Capacitor.AppwrightLiveProgress Capacitor#

Capacitor.Plugins.AppwrightLiveProgress — start / update / end

Пример

Собственный Android-плагин AppMint (в JS он называется AppwrightLiveProgress) для закреплённого, беззвучного, обновляемого в реальном времени уведомления о статусе (индикатор выполнения, спиннер или идущие часы), которое по нажатию открывает нужный маршрут.

Возвращает: start(opts) / update(opts) → Promise<{ ok: true, id }> или { ok: false, error }, если ничего не опубликовано — 'permission', когда уведомления не разрешены (Android 13+: разрешение ещё не выдано или отключено для приложения), 'channel_disabled', когда пользователь отключил категорию «Live status»; end({ id }) → Promise<void>. Требуется: Android-приложение и разрешение на уведомления (Android 13+). В вебе недоступно.

В приложении, созданном с помощью ИИ, используйте обёртку liveProgress из @/lib/appmintNative:

import { liveProgress } from '@/lib/appmintNative';
await liveProgress.start({ id: 'order', title: 'Order #42', text: 'Being prepared', indeterminate: true });
await liveProgress.update({ id: 'order', title: 'Order #42', text: 'On the way', progress: 0.6, route: '/orders/42' });
await liveProgress.end('order');

На странице, написанной вручную:

async function showDelivery(step, total) {
  const P = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightLiveProgress;
  if (!P) return;                                   // not in the Android app
  if (window.Notification && Notification.permission !== 'granted') {
    if (await Notification.requestPermission() !== 'granted') return;
  }
  const r = await P.update({
    id: 'delivery',
    title: 'Your delivery',
    text: 'Stop ' + step + ' of ' + total,
    progress: step / total,                         // 0–1; or indeterminate: true
    route: '/track'                                 // opened when the card is tapped
  });
  if (!r.ok) console.warn('live card not posted:', r.error);
}

async function deliveryDone() {
  const P = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightLiveProgress;
  if (P) await P.end({ id: 'delivery' });
}

Таймер тренировки: startedAt заставляет карточку считать вверх самой, поэтому её публикуют один раз и завершают, когда тренировка заканчивается:

const Live = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightLiveProgress;
let workoutStart = 0;

async function startWorkout(name) {
  workoutStart = Date.now();
  if (!Live) return;
  const r = await Live.start({ id: 'workout', title: name, text: 'Workout in progress', startedAt: workoutStart, route: '/workout' });
  if (!r.ok && r.error === 'channel_disabled') {
    showHint('Turn on "Live status" in this app\'s notification settings to see the timer.');
  }
}

async function stopWorkout() {
  if (Live) await Live.end({ id: 'workout' });
  saveWorkout({ durationMs: Date.now() - workoutStart });
}

Параметры: id (по умолчанию 'live'), title, text, progress (0–1, пропустите, чтобы не было полосы), indeterminate, startedAt (мс эпохи, показывает идущие часы), route.

Примечания: start и update делают одно и то же (публикуют карточку или заменяют карточку с тем же id). Карточку нельзя смахнуть — всегда вызывайте end. Сначала запросите разрешение: на Android 13+ без него start отвечает error: 'permission' и ничего не публикует.

Capacitor.AppwrightSystemPalette Capacitor#

Capacitor.Plugins.AppwrightSystemPalette — get()

Пример

Возвращает цвета Material You телефона (палитру, которую Android 12+ строит по обоям), чтобы приложение могло подстроиться под системные. Собственный плагин AppMint (AppwrightSystemPalettePlugin.kt).

Возвращает: Promise { available: false } на Android ниже 12, иначе { available: true, accent1, accent2, accent3, neutral1, neutral2 } — каждый как объект «тон → hex-цвет», с тонами '0', '10', '50', '100' … '900', '1000' (например, accent1['600'] === '#3F5F90'). Требуется: ничего включать не нужно — плагин есть в каждом приложении. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var Palette = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightSystemPalette;

async function useWallpaperColours() {
  if (!Palette) return;
  var p = await Palette.get();
  if (!p.available) return;                 // keep the app's own colours
  var root = document.documentElement.style;
  root.setProperty('--accent', p.accent1['600']);
  root.setProperty('--surface', p.neutral1['50']);
}
useWallpaperColours();

В приложении, созданном с помощью ИИ:

import { systemPalette } from '@/lib/appmintNative';

const p = await systemPalette();
if (p.available && p.accent1) document.documentElement.style.setProperty('--accent', p.accent1['600']);

Выбирайте светлые или тёмные тона в соответствии с системной темой и заново считывайте палитру, когда пользователь возвращается после смены обоев:

var Palette = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightSystemPalette;
var darkQuery = window.matchMedia('(prefers-color-scheme: dark)');

async function applyPalette() {
  if (!Palette) return;
  var p = await Palette.get();
  if (!p.available) return;
  var dark = darkQuery.matches;
  var s = document.documentElement.style;
  s.setProperty('--accent', p.accent1[dark ? '200' : '600']);
  s.setProperty('--on-accent', p.accent1[dark ? '800' : '0']);
  s.setProperty('--bg', p.neutral1[dark ? '900' : '10']);
  s.setProperty('--text', p.neutral1[dark ? '100' : '900']);
}

darkQuery.addEventListener('change', applyPalette);
document.addEventListener('visibilitychange', function () {
  if (document.visibilityState === 'visible') applyPalette();
});
applyPalette();

Примечания: Проверяйте контрастность текста, прежде чем применять цвета палитры, — тон обоев не гарантированно хорошо читается на вашем фоне. Браузеры не предоставляют цвета обоев: в вебе обёртка отвечает { available: false }. Цвета меняются, когда пользователь меняет обои; вызывайте get() снова при возобновлении.

Capacitor.AppwrightWidget Capacitor#

Capacitor.Plugins.AppwrightWidget — set(card), clear(), placedCount(), requestPin()

Пример

Заполняет виджет приложения на главном экране: одна карточка с заголовком, крупным значением, до двенадцати строк, вашими цветами и маршрутом по нажатию — та же карточка, что принимает AppMint.widget.set(), со всеми ключами. Карточка сохраняется на телефоне и отображается даже после перезагрузки. Собственный плагин AppMint (AppwrightWidgetPlugin.kt).

Возвращает: set({ title?, value?, caption?, lines?, route?, bg?, ink?, accent?, textSize?, align?, wrap?, … }) → Promise { ok: true, placed }, отклоняется с причиной для неверной карточки (placed = виджеты, которые сейчас есть на главном экране). clear() → void. placedCount() → { value }. requestPin() → { value: boolean } (true, когда открыто окно лаунчера «Добавить виджет»). Требуется: ничего включать не нужно — каждое приложение объявляет виджет. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var Widget = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightWidget;

async function updateWidget(tasks) {
  if (!Widget) return;
  await Widget.set({
    title: 'Today',
    value: tasks.length + ' tasks',
    lines: tasks.slice(0, 6).map(function (t) { return t.title; }),
    route: '/today',                 // opened when the widget is tapped
    bg: '#0B0F1A', ink: '#FFFFFF', accent: '#22D3EE'
  });
}

Предложите разместить его (только если он ещё не размещён):

async function offerWidget() {
  if (!Widget) return;
  var n = (await Widget.placedCount()).value;
  if (n > 0) return;
  var r = await Widget.requestPin();
  if (!r.value) alert('Long-press the home screen, tap Widgets and pick this app.');
}

В приложении, созданном с помощью ИИ:

import { widget } from '@/lib/appmintNative';

await widget.set({ title: 'Today', value: '3 tasks', lines: ['Gym', 'Call mum'], route: '/today' });
if ((await widget.placedCount()) === 0) await widget.requestPin();

Примечания: Цвета — это #RRGGBB, #AARRGGBB (сначала альфа) или rgba(); пустые используют тёмную карточку с голубым значением. О стилизованных строках см. AppMint.widget, а также caption, textSize, align, wrap и font (ваш собственный файл .ttf/.otf, заданный путём в вашем приложении). Нажатие открывает приложение по адресу route как глубокую ссылку (читайте её через WebToApk.getLaunchUrl() или событие appmint:deep-link). До первого set() виджет показывает «Nothing to show yet»; clear() возвращает его в это состояние. В браузере can('widget') — 'none'.

Capacitor.Badge Capacitor#

Capacitor.Plugins.Badge — the launcher-icon count

Пример

Плагин Badge (capawesome), который выводит число на значке приложения в лаунчере.

Возвращает: Promise: set({ count }), clear(), increase(), decrease() → void; get() → { count }; isSupported() → { isSupported }. В Android-плагине нет методов разрешений. Требуется: на Android ничего. В вебе недоступно.

В приложении, созданном с помощью ИИ, используйте обёртку badge из @/lib/appmintNative:

import { badge } from '@/lib/appmintNative';
await badge.set(3);
await badge.clear();

На странице, написанной вручную:

async function setUnread(count) {
  const B = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Badge;
  if (!B) return false;                       // not inside the app
  try {
    const s = await B.isSupported();
    if (!s.isSupported) return false;         // this launcher shows no badges
    if (count > 0) await B.set({ count: count });
    else await B.clear();
    return true;
  } catch (e) {
    return false;
  }
}

window.addEventListener('appmint:push', function () {
  const inbox = JSON.parse(window.WebToApk ? WebToApk.getPushInbox() : '[]');
  setUnread(inbox.filter(function (m) { return !m.read; }).length);
});

Считайте вверх и вниз по мере поступления и прочтения элементов:

const B = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Badge;
async function onNewMessage() { if (B) await B.increase(); }
async function onMessageRead() { if (B) await B.decrease(); }
async function showCurrent() {
  if (!B) return;
  const { count } = await B.get();
  console.log('badge shows', count);
}

Примечания: на Android внешний вид зависит от лаунчера (число, точка или ничего). navigator.setAppBadge(n) в приложении пишет в тот же значок (он есть только в лаунчерах, которые умеют показывать счётчик).

Capacitor.BarcodeScanner Capacitor#

Capacitor.Plugins.BarcodeScanner — scan, startScan, readBarcodesFromImage, isSupported

Пример

Сканирование QR-кодов и штрихкодов через API @capacitor-mlkit/barcode-scanning — собственный плагин оболочки приложения (BarcodeScannerPlugin.kt) поверх того же сканера кодов Google, что и AppMint.scanCode(). Старые вызовы @capacitor-community/barcode-scanner (startScan() с результатом { hasContent, content }, checkPermission, prepare, hideBackground) используют то же имя и тоже работают.

Возвращает: scan({ formats?, autoZoom? }) → { barcodes: [{ rawValue, displayValue, format, valueType, bytes?, cornerPoints?, urlBookmark?, wifi?, email?, phone?, sms?, geoPoint?, contactInfo?, calendarEvent?, driverLicense? }] } (отклоняется с "scan canceled.", когда пользователь вышел); readBarcodesFromImage({ path, formats? }) → { barcodes }; isSupported() → { supported }; isGoogleBarcodeScannerModuleAvailable() → { available }; installGoogleBarcodeScannerModule() (прогресс как googleBarcodeScannerModuleInstallProgress); checkPermissions() / requestPermissions() → { camera }; openSettings(). format — это 'QR_CODE', 'EAN_13', 'CODE_128'… Требуется: ничего включать не нужно, разрешение на камеру не требуется — сканер Google рисует собственный экран (сервисы Play загружают его при первом использовании). window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

const Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.BarcodeScanner;

scanBtn.onclick = async () => {
  if (!Scanner) return;
  try {
    const { barcodes } = await Scanner.scan({ formats: ['QR_CODE'] });
    const code = barcodes[0];
    if (code.valueType === 'URL') location.href = code.urlBookmark.url;
    else resultBox.textContent = code.displayValue;
  } catch (e) {
    if (e.message !== 'scan canceled.') alert(e.message);
  }
};

Поиск цены по штрихкоду товара со стилем слушателя (startScan + barcodesScanned), который уже используют приложения с ML Kit:

const Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.BarcodeScanner;

async function scanProduct() {
  const listener = await Scanner.addListener('barcodesScanned', async ({ barcodes }) => {
    await listener.remove();
    await Scanner.stopScan();
    const ean = barcodes[0].rawValue;
    const res = await fetch('https://api.example.com/products/' + encodeURIComponent(ean));
    priceBox.textContent = res.ok ? (await res.json()).price : 'Not found';
  });
  await Scanner.startScan({ formats: ['EAN_13', 'EAN_8', 'UPC_A', 'UPC_E'] });
}

Считайте QR-код с фотографии, которая уже есть у пользователя:

const Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.BarcodeScanner;
const Camera = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Camera;

async function qrFromGallery() {
  const photo = await Camera.getPhoto({ source: 'PHOTOS', resultType: 'uri', quality: 90 });
  const { barcodes } = await Scanner.readBarcodesFromImage({ path: photo.path, formats: ['QR_CODE'] });
  return barcodes.length ? barcodes[0].rawValue : null;
}

Примечания: startScan() из оригинала показывает живой предпросмотр камеры за прозрачной страницей и продолжает сканировать до stopScan(); здесь он открывает экран сканера, первый код вызывает barcodeScanned / barcodesScanned, и сеанс завершается (вызов тогда выполняется с { hasContent, content, format } для кода сообщества; закрытый сканер выполняется с { hasContent: false }). Фонарик и зум относятся к этому предпросмотру, поэтому isTorchAvailable() равен false, а установщики фонарика/зума отклоняются — у экрана сканера есть собственная кнопка фонарика. checkPermissions() отвечает granted, когда у приложения вообще нет разрешения на камеру (сканеру оно не нужно); приложение, которое также использует камеру, получает реальное состояние. Приложения, созданные ИИ, используют scanCode() из @/lib/appmintNative.

Capacitor.Browser Capacitor#

Capacitor.Plugins.Browser — open({ url, toolbarColor }), close(), addListener('browserFinished')

Пример

Открывает веб-страницу во вкладке встроенного браузера (Chrome Custom Tabs), чтобы пользователь прочитал условия, документацию или страницу входа и вернулся одним жестом (@capacitor/browser 8.0.3).

Возвращает: open() / close() → Promise void. События: browserFinished (пользователь закрыл вкладку) и browserPageLoaded. Требуется: ничего — плагин есть в каждом приложении (должен быть установлен браузер с поддержкой Custom Tabs, например Chrome). window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var Browser = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Browser;

async function openTerms() {
  if (!Browser) return;
  var h = await Browser.addListener('browserFinished', function () {
    console.log('user closed the terms');
    h.remove();
  });
  await Browser.open({ url: 'https://example.com/terms', toolbarColor: '#0B0F1A' });
}

В приложении, созданном с помощью ИИ (новая вкладка в вебе):

import { browser } from '@/lib/appmintNative';

const off = browser.onClosed(() => refreshAccount());
await browser.open('https://example.com/terms', { toolbarColor: '#0B0F1A' });
// call off() when you no longer care

Откройте ссылку и закройте вкладку, когда возврат после входа дойдёт до страницы:

var Browser = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Browser;

async function openHelp() {
  if (!Browser) { window.open('https://example.com/help', '_blank'); return; }
  try {
    await Browser.open({ url: 'https://example.com/help' });
  } catch (e) {
    alert('Could not open the page: ' + e.message);
  }
}

window.addEventListener('appmint:deep-link', function () {
  if (Browser) Browser.close().catch(function () {});
});

Примечания: url не должен быть пустым. toolbarColor — hex-цвет; недопустимый игнорируется. close() в этой версии плагина тоже работает на Android (он завершает активность-хост вкладки) — это полезно, когда ссылка входа возвращает в приложение. Вкладка — это отдельный браузер: он не использует cookie приложения и localStorage.

Capacitor.Calendar Capacitor#

Capacitor.Plugins.Calendar — createEvent, findEvents, deleteEvent, listCalendars, openCalendar

Пример

Добавляет, находит и удаляет события в собственном календаре телефона (Android CalendarContract) и открывает приложение календаря на нужной дате. Включён в сборку @capacitor/calendar (template/plugins/calendar).

Возвращает: createEvent() → { id }; findEvents() → { events: [{ id, title, location?, notes?, startDate, endDate, isAllDay, calendarId, calendarName?, attendees? }] }; listCalendars() → { calendars: [{ id, name, displayName?, isPrimary }] }; deleteEvent(), openCalendar() → void. Даты — в миллисекундах эпохи. Требуется: при сборке отметьте Календарь на шаге 4 (Доступ). Для приложений из HTML, ZIP и созданных с помощью ИИ сборка сама добавляет разрешение на календарь, если в ваших файлах есть вызов Calendar.<method>(, записанный именно так (как ниже), или calendar.createEvent( из обёртки. Плагин запрашивает у пользователя доступ к календарю при первом использовании. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var Calendar = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Calendar;

async function addDentist() {
  if (!Calendar) return;
  var start = new Date('2026-10-02T10:00:00').getTime();
  try {
    var r = await Calendar.createEvent({
      title: 'Dentist', startDate: start, endDate: start + 60 * 60 * 1000,
      location: 'Main St 5', notes: 'Bring the card', isAllDay: false,
      firstReminderMinutes: 30
    });
    console.log('event id', r.id);
  } catch (e) {
    // e.code: OS-PLUG-CLDR-0020 permission denied, -0001 bad arguments
    alert('Could not add the event: ' + e.message);
  }
}

Прочитайте события этой недели, удалите одно, откройте календарь:

async function thisWeek() {
  if (!Calendar) return;
  var now = Date.now();
  var found = await Calendar.findEvents({ startDate: now, endDate: now + 7 * 864e5 });
  found.events.forEach(function (ev) { console.log(ev.title, new Date(ev.startDate)); });
  if (found.events.length) await Calendar.deleteEvent({ id: found.events[0].id });
  await Calendar.openCalendar({ date: now });
}

В приложении, созданном с помощью ИИ (в вебе createEvent скачивает файл .ics; чтение возвращает []):

import { calendar } from '@/lib/appmintNative';

if ((await calendar.requestPermission()) === 'granted') {
  const id = await calendar.createEvent({ title: 'Dentist', start: new Date('2026-10-02T10:00'), end: new Date('2026-10-02T11:00') });
  const week = await calendar.listEvents({ from: Date.now(), to: Date.now() + 7 * 864e5 });
  if (id) await calendar.deleteEvent(id);
  await calendar.openCalendar(new Date());
}

Примечания: Без разрешения на календарь в приложении диалог не появляется, а каждый вызов события отклоняется (нет разрешения); пользователь, отказавший в доступе, получает OS-PLUG-CLDR-0020. recurrence: { frequency: 'daily' | 'weekly' | 'monthly' | 'yearly', interval?, count?, endDate? } создаёт повторяющееся событие. createEventInteractively({ title, startDate, endDate }) вместо этого открывает собственный редактор приложения календаря: он возвращает { id } сохранённого события и отклоняется с OS-PLUG-CLDR-0006, если пользователь закрыл редактор без сохранения (чтобы отличить эти случаи, нужен доступ и на чтение, и на запись). deleteEvent({ id, fromDate }) завершает повторяющееся событие до fromDate и сохраняет более ранние повторения. Обёртка возвращает null / [] / false вместо выбрасывания ошибки.

Capacitor.Camera Capacitor#

Capacitor.Plugins.Camera

Пример

Официальный плагин Capacitor Camera, встроенный в приложение: сделайте фото камерой или выберите фотографии из галереи.

Возвращает: Promise. getPhoto(opts) → { dataUrl | base64String | webPath, format, saved } (какое поле — зависит от resultType) и ОТКЛОНЯЕТСЯ, если пользователь отменил; pickImages(opts) → { photos: [{ webPath, format }] }; checkPermissions() / requestPermissions({ permissions }) → { camera, photos }. Требуется: отметьте Камера на шаге 4 (Доступ). Для приложений из HTML, ZIP и созданных с помощью ИИ сборка сама добавляет разрешение на камеру, если в ваших файлах есть Camera.getPhoto(, записанный именно так (как ниже), getUserMedia с видео или <input capture>. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

Сделайте фото и покажите его.

async function takePhoto() {
  var Camera = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Camera;
  if (!Camera) { showMessage('Open the app to use the camera.'); return; }
  try {
    var perm = await Camera.requestPermissions({ permissions: ['camera'] });
    if (perm.camera !== 'granted') { showMessage('Camera permission was refused.'); return; }

    var photo = await Camera.getPhoto({
      source: 'CAMERA',          // 'CAMERA' | 'PHOTOS' | 'PROMPT'
      resultType: 'dataUrl',     // 'dataUrl' | 'base64' | 'uri'
      quality: 80,
      direction: 'REAR',         // 'REAR' | 'FRONT'
      correctOrientation: true,
      saveToGallery: false
    });
    document.getElementById('photo').src = photo.dataUrl;
  } catch (err) {
    // The user closed the camera (or no camera app). Nothing to do.
  }
}

Выберите до 5 фотографий из галереи.

async function choosePhotos() {
  var Camera = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Camera;
  if (!Camera) return;
  try {
    var result = await Camera.pickImages({ quality: 80, limit: 5 });
    result.photos.forEach(function (p) {
      var img = document.createElement('img');
      img.src = p.webPath;       // a URL the page can load
      document.getElementById('gallery').appendChild(img);
    });
  } catch (err) { /* cancelled */ }
}

В приложении, созданном с помощью ИИ, используйте обёртку, которая работает и в вебе (и при отмене возвращает null, а не выбрасывает ошибку):

import { camera } from '@/lib/appmintNative';
const photo = await camera.take({ source: 'camera' });

Примечания: pickImages не требует разрешения на камеру. Если разрешения на камеру в приложении нет (флажок был выключен, а сборка не нашла вызовов камеры), в разрешении отказывается без запроса. <input type="file" accept="image/*" capture> тоже открывает камеру. Камера — чувствительная функция: укажите её в форме Play Data safety.

Capacitor.CapacitorBarcodeScanner Capacitor#

Capacitor.Plugins.CapacitorBarcodeScanner — scanBarcode({ hint })

Пример

Сканирование QR-кодов и штрихкодов одним вызовом через официальный API @capacitor/barcode-scanner — собственный плагин оболочки приложения (CapacitorBarcodeScannerPlugin.kt) поверх того же сканера кодов Google, что и AppMint.scanCode(). Код, написанный для npm-пакета (CapacitorBarcodeScanner.scanBarcode({ hint: CapacitorBarcodeScannerTypeHint.ALL })), работает без изменений.

Возвращает: scanBarcode({ hint, scanInstructions?, cameraDirection?, scanOrientation? }) → { ScanResult, format }. hint / format — это числа пакета: 0 QR_CODE, 1 AZTEC, 2 CODABAR, 3 CODE_39, 4 CODE_93, 5 CODE_128, 6 DATA_MATRIX, 8 ITF, 9 EAN_13, 10 EAN_8, 11 PDF_417, 14 UPC_A, 15 UPC_E, 17 ALL. Отклоняется с кодом OS-PLUG-BARC-0006, когда пользователь закрывает сканер. Требуется: ничего включать не нужно, разрешение на камеру не требуется — сканер Google рисует собственный экран (сервисы Play загружают его при первом использовании). window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

const Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.CapacitorBarcodeScanner;

ticketBtn.onclick = async () => {
  if (!Scanner) return;
  try {
    const { ScanResult } = await Scanner.scanBarcode({ hint: 0 });   // QR codes only
    const ok = await fetch('/api/tickets/check?code=' + encodeURIComponent(ScanResult)).then((r) => r.ok);
    statusBox.textContent = ok ? 'Valid ticket' : 'Unknown ticket';
  } catch (e) {
    if (e.code !== 'OS-PLUG-BARC-0006') alert(e.message);
  }
};

Принимайте любой код и сообщайте пользователю, какого он типа:

const Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.CapacitorBarcodeScanner;
const NAMES = { 0: 'QR code', 5: 'Code 128', 9: 'EAN-13', 10: 'EAN-8', 14: 'UPC-A', 15: 'UPC-E' };

async function scanAnything() {
  const { ScanResult, format } = await Scanner.scanBarcode({ hint: 17 });
  return (NAMES[format] || 'Barcode') + ': ' + ScanResult;
}

Обработайте отказ, не связанный с тем, что пользователь закрыл сканер:

const Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.CapacitorBarcodeScanner;

async function scanProduct() {
  if (!Scanner) { alert('Scanning works only inside the app'); return null; }
  try {
    const { ScanResult } = await Scanner.scanBarcode({ hint: 9 });   // EAN-13
    return ScanResult;
  } catch (e) {
    if (e.code === 'OS-PLUG-BARC-0006') return null;                 // user closed the scanner
    alert('Scan failed: ' + e.message);
    return null;
  }
}

Примечания: scanInstructions, scanButton, cameraDirection и scanOrientation оформляют собственный экран камеры из исходного проекта и не влияют на сканер Google (всегда основная камера). MAXICODE, RSS_14, RSS_EXPANDED и UPC_EAN_EXTENSION не поддерживаются как hint (отклоняется с OS-PLUG-BARC-0008); 17 (ALL) сканирует все поддерживаемые типы. Приложения, созданные с помощью ИИ, используют scanCode() из @/lib/appmintNative.

Capacitor.CapacitorCookies Capacitor#

Capacitor.Plugins.CapacitorCookies — getCookies / setCookie / deleteCookie / clearCookies / clearAllCookies

Пример

Читает и записывает cookie приложения, включая cookie другого хоста, с которым общается ваша страница (ядро Capacitor CapacitorCookies).

Возвращает: getCookies({ url? }) → { [name]: value } — с url куки этого URL (хост и путь), которые он отправил бы; без него — собственный document.cookie страницы. Отклоняет url, не являющийся http(s). setCookie({ key, value, url?, path?, expires? }), deleteCookie({ key, url? }), clearCookies({ url? }), clearAllCookies() → Promise void; setCookie / deleteCookie отклоняются без строки key (и value) и тогда ничего не записывают. Требуется: ничего включать не нужно — каждое приложение содержит плагин. window.Capacitor существует на страницах, которые приложение отдаёт само (HTML, ZIP и приложения, созданные ИИ), а в приложении режима Сайт — на вашем собственном сайте (его домене и поддоменах), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие.

var Cookies = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.CapacitorCookies;

// Read the session cookie your API host set — not the page's own cookies.
async function apiSession() {
  if (!Cookies) return null;          // not inside the app
  var jar = await Cookies.getCookies({ url: 'https://api.example.com/' });
  return jar.session || null;
}

// Remember a choice for the API host, for 30 days, on one path only.
async function rememberRegion(region) {
  if (!Cookies) return;
  var expires = new Date(Date.now() + 30 * 864e5).toUTCString();
  await Cookies.setCookie({ url: 'https://api.example.com', path: '/v2', key: 'region', value: region, expires: expires });
}

Выход из аккаунта: удалите cookie этого хоста или все cookie, которые хранит приложение.

async function signOut(everything) {
  if (!Cookies) return;
  if (everything) await Cookies.clearAllCookies();
  else await Cookies.clearCookies({ url: 'https://api.example.com' });
}

Примечания: Cookie, заданный с path, возвращается только для url по этому пути. Значения при чтении декодируются из URL. Cookie общие для всех страниц, которые показывает приложение, поэтому никогда не храните секреты там, где их может прочитать чужая страница, — им место в Preferences или в защищённом хранилище приложения.

Capacitor.CapacitorSQLite Capacitor#

Capacitor.Plugins.CapacitorSQLite — createConnection, open, execute, query, run, executeSet, close

Пример

Настоящая база данных SQLite на телефоне с API @capacitor-community/sqlite. Включён в сборку @capacitor-community/sqlite (template/plugins/sqlite), работает на собственном SQLite Android. Код, написанный для npm-пакета (new SQLiteConnection(CapacitorSQLite)), работает без изменений; страница, написанная вручную, вызывает объект плагина напрямую, как показано ниже.

Возвращает: createConnection({ database, version, encrypted: false, mode: 'no-encryption', readonly: false }), open({ database }), close({ database }), closeConnection({ database }) → void; execute({ database, statements }) → { changes: { changes } }; run({ database, statement, values }) → { changes: { changes, lastId } }; query({ database, statement, values }) → { values: [rows] }; executeSet({ database, set: [{ statement, values }] }) → { changes }; isDBExists({ database }) / isDBOpen({ database }) → { result }; beginTransaction, commitTransaction, rollbackTransaction, isTransactionActive({ database }); deleteDatabase({ database }); exportToJson / importFromJson для резервных копий. Требуется: ничего включать не нужно, разрешение не требуется. База данных хранится в закрытом хранилище приложения и сохраняется при обновлениях. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

const DB = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.CapacitorSQLite;
const database = 'notes';

async function openNotes() {
  if (!DB) return false;
  await DB.createConnection({ database, version: 1, encrypted: false, mode: 'no-encryption', readonly: false });
  await DB.open({ database, readonly: false });
  await DB.execute({ database, statements:
    'CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT NOT NULL, at INTEGER);' });
  return true;
}

async function addNote(text) {
  const r = await DB.run({ database, statement: 'INSERT INTO notes (text, at) VALUES (?, ?);', values: [text, Date.now()] });
  return r.changes.lastId;
}

async function listNotes() {
  const r = await DB.query({ database, statement: 'SELECT id, text, at FROM notes ORDER BY at DESC;', values: [] });
  return r.values || [];
}

Несколько записей, которые должны выполниться все или ни одна, — одна транзакция через executeSet:

const DB = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.CapacitorSQLite;

async function moveMoney(database, fromId, toId, amount) {
  await DB.executeSet({ database, transaction: true, set: [
    { statement: 'UPDATE accounts SET balance = balance - ? WHERE id = ?;', values: [amount, fromId] },
    { statement: 'UPDATE accounts SET balance = balance + ? WHERE id = ?;', values: [amount, toId] },
    { statement: 'INSERT INTO ledger (from_id, to_id, amount, at) VALUES (?, ?, ?, ?);', values: [fromId, toId, amount, Date.now()] },
  ] });
}

Открывайте базу данных только один раз за запуск приложения, даже если два экрана просят об этом одновременно:

const DB = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.CapacitorSQLite;
let ready = null;

function db(database) {
  if (!ready) ready = (async () => {
    try {
      await DB.createConnection({ database, version: 1, encrypted: false, mode: 'no-encryption', readonly: false });
    } catch (e) {
      if (!/already exists/i.test(e.message)) throw e;   // a page reload keeps the native connection
    }
    if (!(await DB.isDBOpen({ database, readonly: false })).result) await DB.open({ database, readonly: false });
    return database;
  })();
  return ready;
}

Примечания: Приложение собрано без SQLCipher, поэтому базы данных не шифруются: encrypted: true, setEncryptionSecret, changeEncryptionSecret, isSecretStored и остальные вызовы шифрования отклоняются с сообщением «Encryption is not available in this app». isInConfigEncryption() отвечает false. Всё остальное (запросы, привязанные значения, транзакции, версионные обновления через addUpgradeStatement, импорт и экспорт JSON) — собственный код плагина. Отказы содержат текст ошибки SQLite.

Capacitor.Clipboard Capacitor#

Capacitor.Plugins.Clipboard — write(options), read()

Пример

Копирует текст (или ссылку) в системный буфер обмена и читает его обратно (@capacitor/clipboard 8.0.1).

Возвращает: write({ string?, url?, image?, label? }) → Promise void. read() → Promise { value, type } (type равен 'text/plain' для текста). Требуется: ничего включать не нужно — каждое приложение содержит плагин. window.Capacitor существует на страницах, которые приложение отдаёт само (HTML, ZIP и приложения, созданные ИИ), а в приложении режима Сайт — на вашем собственном сайте (его домене и поддоменах), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие.

async function copyCode(code) {
  var Clipboard = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Clipboard;
  if (!Clipboard) { alert('Copy is not available here'); return; }
  await Clipboard.write({ string: code, label: 'Invite code' });
}

async function pasteCode(input) {
  var Clipboard = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Clipboard;
  if (!Clipboard) return;
  try {
    var r = await Clipboard.read();
    input.value = r.value;
  } catch (e) {
    // An EMPTY clipboard is a rejection: "There is no data on the clipboard".
    if (/no data/i.test(e.message)) input.value = '';
    else alert('Could not read the clipboard: ' + e.message);
  }
}

В приложении, созданном с помощью ИИ, обёртка в стиле Capacitor превращает пустой буфер обмена в '':

import { Clipboard } from '@/lib/appmintNative';

await Clipboard.write({ string: 'ABC-123' });
const { value } = await Clipboard.read();   // '' when the clipboard is empty

Скопируйте ссылку и подтвердите это лёгкой вибрацией:

async function copyLink(url) {
  var P = window.Capacitor && window.Capacitor.Plugins;
  if (!P || !P.Clipboard) { alert('Copy is not available here'); return; }
  await P.Clipboard.write({ url: url });
  if (P.Haptics) P.Haptics.impact({ style: 'LIGHT' });
  copiedToast.hidden = false;
}

Примечания: write требует одно из string, url или image (data URL), иначе отклоняется с «No data provided». Android 12+ при чтении показывает собственное уведомление «вставлено из…» — это делает система, а не приложение. Метод write обёртки отправляет только string || url.

Capacitor.Contacts Capacitor#

Capacitor.Plugins.Contacts — getContacts, getContact, pickContact, createContact, deleteContact

Пример

Адресная книга телефона с API @capacitor-community/contacts. Включён в сборку @capacitor-community/contacts (template/plugins/contacts). Код, написанный для npm-пакета, работает без изменений.

Возвращает: checkPermissions() / requestPermissions() → { contacts, contactsWrite } (каждое 'granted' | 'denied' | 'prompt'; contacts — чтение, contactsWrite — добавление и удаление); getContacts({ projection }) → { contacts: [ContactPayload] }; getContact({ contactId, projection }) и pickContact({ projection }) → { contact }; createContact({ contact }) → { contactId }; deleteContact({ contactId }) → void. projection выбирает поля: { name, phones, emails, organization, birthday, note, urls, postalAddresses, image } (каждое true). Требуется: отметьте Контакты на шаге 4 (Доступ) для чтения и дополнительно Изменение контактов для добавления и удаления. Сборки из HTML, ZIP и AI Studio отмечают их сами, когда код вызывает Contacts.getContacts( / getContact( / pickContact( (чтение) или Contacts.createContact( / deleteContact( (оба). Плагин спрашивает пользователя при первом использовании; если флажок не отмечен, вызов отклоняется с сообщением «Permission is required to access contacts.», а в приложении, где не отмечено ни Контакты, ни SMS, ни Журнал звонков, плагина нет вообще (Capacitor отвечает «not implemented»). window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

const Contacts = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Contacts;

async function friendsWithPhones() {
  if (!Contacts) return [];
  const { contacts } = await Contacts.requestPermissions();
  if (contacts !== 'granted') return [];
  const res = await Contacts.getContacts({ projection: { name: true, phones: true } });
  return res.contacts
    .filter((c) => c.phones && c.phones.length)
    .map((c) => ({ id: c.contactId, name: (c.name && c.name.display) || '', phone: c.phones[0].number }));
}

Дайте пользователю выбрать одного человека в системном выборе:

const Contacts = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Contacts;

inviteBtn.onclick = async () => {
  if (!Contacts) return;
  try {
    const { contact } = await Contacts.pickContact({ projection: { name: true, phones: true, emails: true } });
    inviteField.value = (contact.emails && contact.emails[0] && contact.emails[0].address) || '';
  } catch (e) {
    // The user closed the picker, or refused access.
  }
};

Сохраните визитку из приложения в адресную книгу:

const Contacts = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Contacts;

async function saveCard(card) {
  const perm = await Contacts.requestPermissions();
  if (perm.contactsWrite !== 'granted') return null;
  const { contactId } = await Contacts.createContact({ contact: {
    name: { given: card.first, family: card.last },
    organization: { company: card.company, jobTitle: card.title },
    phones: [{ type: 'work', label: 'work', number: card.phone }],
    emails: [{ type: 'work', label: 'work', address: card.email }],
  } });
  return contactId;
}

Примечания: pickContact() отклоняется с кодом CANCELLED, когда пользователь выходит из выбора. Android заполняет contactId, name, phones, emails, organization, birthday, note, urls, postalAddresses и image только когда проекция их запрашивает — запрашивайте только то, что показываете. Чтение контактов — чувствительное разрешение в Google Play: опишите, зачем оно нужно, в форме безопасности данных страницы приложения. Приложения, созданные ИИ, используют listContacts(), pickContact() и addContact() из @/lib/appmintNative.

Capacitor.Device Capacitor#

Capacitor.Plugins.Device

Пример

Официальный плагин Capacitor Device, встроенный в приложение: модель телефона, версия ОС, батарея, постоянный ID и язык.

Возвращает: Promise. getInfo() → { model, platform, operatingSystem, osVersion, androidSDKVersion, manufacturer, isVirtual, webViewVersion, … }; getId() → { identifier }; getBatteryInfo() → { batteryLevel (0–1), isCharging }; getLanguageCode() → { value }. Требуется: ничего включать не нужно. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

Покажите телефон и батарею.

async function showDevice() {
  var cap = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Device;
  if (!cap) { document.getElementById('out').textContent = navigator.userAgent; return; }
  try {
    var info = await cap.getInfo();
    var battery = await cap.getBatteryInfo();
    document.getElementById('out').textContent =
      info.manufacturer + ' ' + info.model + ' - ' + info.operatingSystem + ' ' + info.osVersion +
      ' - battery ' + Math.round(battery.batteryLevel * 100) + '%' + (battery.isCharging ? ' (charging)' : '');
  } catch (err) {
    document.getElementById('out').textContent = 'Could not read device info';
  }
}

Постоянный ID и язык.

async function helloServer() {
  var cap = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Device;
  if (!cap) return;
  var id = (await cap.getId()).identifier;
  var lang = (await cap.getLanguageCode()).value;     // e.g. "en"
  await fetch('https://api.example.com/hello?lang=' + encodeURIComponent(lang), {
    method: 'POST', body: JSON.stringify({ device: id })
  });
}

В приложении, созданном с помощью ИИ, используйте обёртку, которая работает и в вебе:

import { Device } from '@/lib/appmintNative';
const info = await Device.getInfo();

Примечания: Для идентификатора сборки Android, патча безопасности, экрана, памяти и сети больше подробностей даёт WebToApk.getDeviceInfo() оболочки. Идентификаторы плагина — это не аппаратные ID; они могут измениться после сброса к заводским настройкам.

Capacitor.Dialog Capacitor#

Capacitor.Plugins.Dialog — alert, confirm, prompt

Пример

Показывает настоящий системный диалог Android: сообщение, вопрос «да/нет» или вопрос с однострочным текстовым ответом (@capacitor/dialog 8.0.1).

Возвращает: alert({ title?, message, buttonTitle? }) → Promise void. confirm({ title?, message, okButtonTitle?, cancelButtonTitle? }) → { value: boolean }. prompt({ title?, message, okButtonTitle?, cancelButtonTitle?, inputPlaceholder?, inputText? }) → { value: string, cancelled: boolean }. Требуется: ничего включать не нужно — плагин есть в каждом приложении. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var Dialog = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Dialog;

async function deleteNote(id) {
  if (!Dialog) return;
  var r = await Dialog.confirm({
    title: 'Delete note?',
    message: 'This cannot be undone.',
    okButtonTitle: 'Delete',
    cancelButtonTitle: 'Keep'
  });
  if (r.value) removeNote(id);
}

Alert и prompt:

async function renameFlow() {
  if (!Dialog) return;
  await Dialog.alert({ title: 'Saved', message: 'Your changes are stored.', buttonTitle: 'OK' });

  var p = await Dialog.prompt({ title: 'Rename', message: 'New name', inputText: 'Groceries', inputPlaceholder: 'List name' });
  if (!p.cancelled && p.value.trim()) renameList(p.value.trim());
}

В приложении, созданном с помощью ИИ, обёртка никогда не отклоняется: закрытие — это { value: false } / { cancelled: true }, а в вебе она использует собственные диалоги браузера:

import { Dialog } from '@/lib/appmintNative';

const { value } = await Dialog.confirm({ title: 'Delete note?', message: 'This cannot be undone.' });
if (value) removeNote(id);

Примечания: message обязателен — без него плагин отклоняется с «Please provide a message for the dialog». Диалоги модальные; используйте их только для решений, которые должны блокировать работу.

Capacitor.Filesystem Capacitor#

Capacitor.Plugins.Filesystem

Пример

Стандартный плагин Capacitor Filesystem: чтение и запись файлов в СОБСТВЕННОМ хранилище приложения (не в папке, выбранной пользователем, — для этого используйте WebToApkFS).

Возвращает: каждый метод возвращает Promise и отклоняется при ошибке (например, при чтении несуществующего файла). readFile → { data }, writeFile → { uri }, readdir → { files: [{ name, type, size, mtime, uri }] }, mkdir / deleteFile → ничего. Требуется: ничего включать не нужно; 'DATA' и 'CACHE' не требуют разрешений.

На странице нет импортов Directory / Encoding, поэтому передавайте обычные строки 'DATA' (закрытые файлы приложения, хранятся до удаления) или 'CACHE' (система может их очистить), а для текста — 'utf8':

async function saveAndReadBack() {
  var Fs = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Filesystem;
  if (!Fs) { console.log('Filesystem plugin not available (not inside the app)'); return; }

  try {
    await Fs.mkdir({ path: 'notes', directory: 'DATA', recursive: true });
    await Fs.writeFile({ path: 'notes/today.txt', data: 'Hello', directory: 'DATA', encoding: 'utf8', recursive: true });

    var r = await Fs.readFile({ path: 'notes/today.txt', directory: 'DATA', encoding: 'utf8' });
    console.log('text:', r.data);

    var dir = await Fs.readdir({ path: 'notes', directory: 'DATA' });
    dir.files.forEach(function (f) { console.log(f.type, f.name, f.size); });   // type: 'file' | 'directory'

    await Fs.deleteFile({ path: 'notes/today.txt', directory: 'DATA' });
  } catch (e) {
    console.log('File error:', e.message);   // e.g. the file does not exist
  }
}

Опустите encoding, чтобы записывать или читать двоичные данные в base64:

var Fs = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Filesystem;
var base64Data = btoa('raw bytes here');   // any base64 string, e.g. from a canvas or FileReader
if (Fs) {
  Fs.writeFile({ path: 'blobs/data.bin', data: base64Data, directory: 'CACHE', recursive: true })
    .then(function (w) { console.log('written to', w.uri); })
    .catch(function (e) { console.log('write failed:', e.message); });
}

В приложениях, созданных с помощью ИИ, обёртка @/lib/appmintNative, использующая этот плагин, называется fileTransfer (она размещает загрузки и выгрузки в 'CACHE'):

import { fileTransfer } from '@/lib/appmintNative';

const r = await fileTransfer.download('https://example.com/report.pdf', 'report.pdf', {
  onProgress: (p) => console.log(Math.round(p.fraction * 100) + '%'),
});
if (r.path) console.log('saved in the app at', r.path);   // on the web the browser saves it instead

Примечания: window.Capacitor существует только внутри приложения — всегда проверяйте наличие. Файлы в 'DATA' принадлежат только приложению и удаляются при удалении; чтобы положить файлы туда, где их увидит пользователь, используйте WebToApkFS (папку, к которой пользователь даёт доступ) или загрузку.

Capacitor.FileTransfer Capacitor#

Capacitor.Plugins.FileTransfer

Пример

Официальный плагин Capacitor File Transfer: нативно скачивает URL в файл на телефоне или загружает файл с телефона, с событиями прогресса.

Возвращает: downloadFile({url, path, headers?, progress?}) → Promise<{path}>; uploadFile({url, path, fileKey?, mimeType?, method?, headers?, progress?}) → Promise<{bytesSent, responseCode, response, headers}>. Прогресс приходит событиями слушателя 'progress' {type, url, bytes, contentLength, lengthComputable}. Ошибки отклоняются с {message, code}. Требуется: ничего включать не нужно. window.Capacitor существует на страницах, которые приложение отдаёт само, а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах. Всегда проверяйте наличие заранее.

Загрузка в кэш приложения с индикатором прогресса:

async function download(url, name, onPct) {
  const P = window.Capacitor && window.Capacitor.Plugins;
  if (!P || !P.FileTransfer || !P.Filesystem) return null;          // not in the app
  const target = await P.Filesystem.getUri({ path: name, directory: 'CACHE' });
  const listener = await P.FileTransfer.addListener('progress', function (p) {
    if (p.url === url && p.lengthComputable) onPct(Math.round(p.bytes / p.contentLength * 100));
  });
  try {
    const r = await P.FileTransfer.downloadFile({ url: url, path: target.uri, progress: true });
    return r.path;
  } catch (e) {
    alert('Download failed: ' + e.message);
    return null;
  } finally {
    listener.remove();
  }
}

Загрузите файл, который уже есть на телефоне:

const P = window.Capacitor && window.Capacitor.Plugins;
if (P && P.FileTransfer) {
  P.FileTransfer.uploadFile({
    url: 'https://example.com/api/upload', path: localFileUri,
    fileKey: 'file', mimeType: 'image/jpeg', headers: { Authorization: 'Bearer ' + token }
  }).then(function (r) { console.log('server said', r.responseCode, r.response); })
    .catch(function (e) { alert('Upload failed: ' + e.message); });
}

Сообщите о неудачной передаче по её коду:

async function safeDownload(url, target) {
  const P = window.Capacitor && window.Capacitor.Plugins;
  if (!P || !P.FileTransfer) { alert('Downloads need the app'); return null; }
  try {
    const r = await P.FileTransfer.downloadFile({ url: url, path: target });
    return r.path;
  } catch (e) {
    console.log('transfer failed', e.code, e.message);
    alert('Could not download the file.');
    return null;
  }
}

Примечания: Путь должен быть путём к файлу или URI file://, а не Blob — сначала запишите Blob в кэш через Filesystem.writeFile. На Android 10 и старше путь в общем хранилище запрашивает разрешение на хранилище. В приложениях, созданных с помощью ИИ, лучше использовать fileTransfer из @/lib/appmintNative, который делает всё это и работает также в вебе.

Capacitor.FileViewer Capacitor#

Capacitor.Plugins.FileViewer

Пример

Официальный плагин Capacitor File Viewer: открывает документ или медиафайл в приложении-просмотрщике телефона — по веб-адресу, локальному пути или из файла, упакованного в приложение.

Возвращает: каждый метод возвращает Promise<void>, который отклоняется с {message, code}, если открыть файл нечем. Требуется: ничего включать не нужно. window.Capacitor существует на страницах, которые приложение отдаёт само, а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах. Всегда проверяйте наличие заранее.

async function openPdf(url) {
  const FV = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.FileViewer;
  if (!FV) { window.open(url, '_blank'); return; }          // a normal browser: open a tab
  try {
    await FV.openDocumentFromUrl({ url: url });
  } catch (e) {
    alert('No app on this phone can open this file.');
  }
}

Файл на телефоне, например записанный с помощью Capacitor.Plugins.Filesystem:

async function openSaved(name) {
  const P = window.Capacitor && window.Capacitor.Plugins;
  if (!P || !P.FileViewer || !P.Filesystem) return;
  const f = await P.Filesystem.getUri({ path: name, directory: 'CACHE' });
  try {
    await P.FileViewer.openDocumentFromLocalPath({ path: f.uri.replace(/^file:\/\//, '') });
  } catch (e) {
    alert('Cannot open ' + name + ': ' + e.message);
  }
}

openDocumentFromResources({path}) открывает файл, упакованный в собственную папку assets внутри APK. Те же три метода есть и как previewMediaContentFromUrl, previewMediaContentFromLocalPath и previewMediaContentFromResources для видео и аудио.

Скачайте файл и откройте его, когда он будет готов:

async function downloadAndOpen(url, name) {
  const P = window.Capacitor && window.Capacitor.Plugins;
  if (!P || !P.FileTransfer || !P.Filesystem || !P.FileViewer) return;
  const f = await P.Filesystem.getUri({ path: name, directory: 'CACHE' });
  const r = await P.FileTransfer.downloadFile({ url: url, path: f.uri });
  await P.FileViewer.openDocumentFromLocalPath({ path: r.path.replace(/^file:\/\//, '') });
}

Примечания: В приложениях, созданных с помощью ИИ, лучше использовать fileViewer.open(target) из @/lib/appmintNative, который выбирает нужный метод и работает также в вебе. Локальный путь — это обычный путь (без file://).

Capacitor.Geolocation Capacitor#

Capacitor.Plugins.Geolocation

Пример

Официальный плагин Capacitor Geolocation, встроенный в приложение: положение телефона один раз или непрерывные обновления.

Возвращает: Promise. getCurrentPosition(opts) → { timestamp, coords: { latitude, longitude, accuracy, altitude, speed, heading, … } }; watchPosition(opts, callback) → id наблюдения (строка); clearWatch({ id }); checkPermissions() / requestPermissions() → { location, coarseLocation } ('granted' | 'denied' | 'prompt' | …). Требуется: GPS и/или Местоположение на шаге 4 (Доступ) — для приложений из HTML, ZIP и созданных с помощью ИИ сборка также включает их сама, если в ваших файлах есть вызов getCurrentPosition или watchPosition. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

Одно определение положения с предварительным запросом.

async function whereAmI() {
  var geo = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Geolocation;
  if (!geo) { showMessage('Open the app to use location.'); return; }
  try {
    var perm = await geo.checkPermissions();
    if (perm.location !== 'granted' && perm.coarseLocation !== 'granted') {
      perm = await geo.requestPermissions();
      if (perm.location !== 'granted' && perm.coarseLocation !== 'granted') {
        showMessage('Location is off for this app.');
        return;
      }
    }
    var pos = await geo.getCurrentPosition({ enableHighAccuracy: true, timeout: 15000 });
    showMessage(pos.coords.latitude.toFixed(5) + ', ' + pos.coords.longitude.toFixed(5));
  } catch (err) {
    showMessage('No location: ' + err.message);   // location off in settings, timeout, …
  }
}

Непрерывные обновления. Обработчик получает (position, error).

var watchId = null;

async function startTracking() {
  var geo = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Geolocation;
  if (!geo) return;
  watchId = await geo.watchPosition({ enableHighAccuracy: true }, function (pos, err) {
    if (err || !pos) return;
    addPointToMap(pos.coords.latitude, pos.coords.longitude);
  });
}

async function stopTracking() {
  var geo = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Geolocation;
  if (geo && watchId) { await geo.clearWatch({ id: watchId }); watchId = null; }
}

В приложении, созданном с помощью ИИ, используйте обёртку, которая работает и в вебе:

import { Geolocation } from '@/lib/appmintNative';
const pos = await Geolocation.getCurrentPosition({ enableHighAccuracy: true });

Примечания: Обычный navigator.geolocation в приложении тоже работает. Если переключатели геолокации были выключены на момент сборки, в разрешении отказывается без запроса. На Android 12+ пользователь может разрешить только приблизительное местоположение (coarseLocation выдано). Местоположение — персональные данные: укажите его в форме Play Data safety.

Capacitor.Haptics Capacitor#

Capacitor.Plugins.Haptics

Пример

Официальный плагин Capacitor Haptics (@capacitor/haptics), встроенный в приложение: лёгкие, средние и сильные касания, отклик успеха, предупреждения и ошибки, тики выбора и обычная вибрация.

Возвращает: каждый метод возвращает Promise, который выполняется без значения. Требуется: ничего включать не нужно — каждое приложение объявляет разрешение на вибрацию, независимо от того, отмечено ли Вибрация на шаге 4 (Доступ). window.Capacitor существует на страницах, которые приложение отдаёт само (HTML, ZIP и приложения, созданные ИИ), а в приложении режима Сайт — на вашем собственном сайте (его домене и поддоменах), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие.

function haptics() {
  return window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.Haptics : null;
}

async function tap(style) {            // 'LIGHT' | 'MEDIUM' | 'HEAVY'
  var h = haptics();
  if (!h) return;
  try { await h.impact({ style: style }); } catch (e) { console.log('haptics failed', e); }
}

async function result(type) {          // 'SUCCESS' | 'WARNING' | 'ERROR'
  var h = haptics();
  if (h) { try { await h.notification({ type: type }); } catch (e) {} }
}

document.getElementById('add').addEventListener('click', function () { tap('LIGHT'); });
document.getElementById('pay').addEventListener('click', function () { result('SUCCESS'); });

Тики выбора для списка или ползунка и обычная вибрация:

var h = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.Haptics : null;
var wheel = document.getElementById('wheel');

wheel.addEventListener('pointerdown', function () { if (h) h.selectionStart(); });
wheel.addEventListener('change', function () { if (h) h.selectionChanged(); });
wheel.addEventListener('pointerup', function () { if (h) h.selectionEnd(); });

document.getElementById('buzz').addEventListener('click', function () {
  if (h) h.vibrate({ duration: 300 });   // milliseconds
});

Дайте вибрацию при ошибке и сопроводите её видимым сообщением:

var h = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Haptics;

function rejectInput(field) {
  field.classList.add('invalid');
  if (h) h.notification({ type: 'ERROR' }).catch(function () {});
}

Примечания: Строки — в верхнем регистре ('HEAVY', а не 'heavy'). В приложениях, созданных с помощью ИИ, import { Haptics } from '@/lib/appmintNative' даёт те же названия. Для собственных ритмов с силой на каждый удар используйте Capacitor.Plugins.AppwrightHaptics.

Capacitor.InAppReview Capacitor#

Capacitor.Plugins.InAppReview — requestReview()

Пример

Просит Google Play показать собственное окно «Оцените приложение» прямо в приложении. Включён в сборку @capacitor-community/in-app-review (template/plugins/in-app-review).

Возвращает: Promise void — он завершается, когда заканчивается процесс Play, и Play никогда не сообщает, показывалось ли окно и что сделал пользователь. Отклоняется, если Play не смог запустить процесс. Требуется: ничего включать не нужно; чтобы окно появилось, приложение должно быть установлено из Google Play. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var Review = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.InAppReview;

async function afterThirdOrder() {
  if (!Review) return;
  try {
    await Review.requestReview();
  } catch (e) {
    console.log('review flow not available:', e.message);
  }
  // continue the app flow either way — you cannot know if the user rated
}

В приложении, созданном с помощью ИИ:

import { requestReview } from '@/lib/appmintNative';

// after a success moment, not on launch
const asked = await requestReview();   // false where nothing could be asked (web)

Спрашивайте один раз, после удачного момента, и запомните, что уже спрашивали:

var Review = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.InAppReview;

async function maybeAskForReview() {
  if (!Review || localStorage.getItem('review_asked')) return;
  localStorage.setItem('review_asked', '1');
  try { await Review.requestReview(); } catch (e) { console.log('review not shown:', e.message); }
}

Примечания: Play ограничивает частоту показа окна и показывает его не при каждом вызове — никогда не привязывайте награду к нему и не вызывайте его из кнопки «Оцените нас», рассчитывая, что оно откроется (для этого откройте страницу в магазине). Спрашивайте после удачного момента, а не при запуске. При установке вручную или отладочной сборке обычно ничего не показывается.

Capacitor.KeepAwake Capacitor#

Capacitor.Plugins.KeepAwake — keepAwake(), allowSleep(), isKeptAwake(), isSupported()

Пример

Не даёт экрану тускнеть и блокироваться, пока что-то должно оставаться на виду, — рецепт, маршрут, QR-код, таймер. Включён в сборку @capacitor-community/keep-awake (template/plugins/keep-awake).

Возвращает: keepAwake() / allowSleep() → Promise void. isKeptAwake() → { isKeptAwake: boolean }. isSupported() → { isSupported: true }. Требуется: ничего — плагин есть в каждом приложении; разрешение не нужно. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var KA = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.KeepAwake;

async function startCooking() {
  if (KA) await KA.keepAwake();
  showSteps();
}

async function stopCooking() {
  if (KA) await KA.allowSleep();      // always release when the view closes
}

if (KA) KA.isKeptAwake().then(function (r) { console.log('awake?', r.isKeptAwake); });

В приложении, созданном с помощью ИИ (Screen Wake Lock API в вебе):

import { keepAwake } from '@/lib/appmintNative';

useEffect(() => {
  void keepAwake(true);
  return () => { void keepAwake(false); };
}, []);

Держите экран включённым только пока идёт таймер:

var KA = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.KeepAwake;

async function runTimer(seconds) {
  if (KA) await KA.keepAwake();
  await new Promise(function (done) { setTimeout(done, seconds * 1000); });
  if (KA) await KA.allowSleep();
  alert('Time is up');
}

Примечания: Флаг принадлежит окну приложения: он работает только пока приложение на переднем плане и остаётся включённым до allowSleep() — снимайте его при каждом выходе с экрана. keepAwake() возвращает false, если не удалось удержать экран включённым.

Capacitor.Keyboard Capacitor#

Capacitor.Plugins.Keyboard — show(), hide(), addListener('keyboardDidShow' | ...)

Пример

Открывает или закрывает экранную клавиатуру и сообщает странице, когда она появляется и какой она высоты (@capacitor/keyboard 8.0.5).

Возвращает: show() / hide() → Promise void. addListener(event, fn) → Promise с объектом, у которого есть remove(), как в собственном API Capacitor, — используйте await или цепочку .then(); у возвращаемого значения тоже есть remove(); события показа передают { keyboardHeight } в CSS-пикселях. События: keyboardWillShow, keyboardDidShow, keyboardWillHide, keyboardDidHide. Требуется: ничего включать не нужно — плагин есть в каждом приложении. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var Keyboard = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Keyboard;

if (Keyboard) {
  Keyboard.addListener('keyboardDidShow', function (info) {
    console.log('keyboard height', info.keyboardHeight);
    document.body.classList.add('typing');       // e.g. hide the bottom tab bar
  });
  Keyboard.addListener('keyboardDidHide', function () {
    document.body.classList.remove('typing');
  });
}

function onSend() {
  if (Keyboard) Keyboard.hide().catch(function () {});   // rejects if no field is focused
}

В приложении, созданном с помощью ИИ, используйте onKeyboard() для вёрстки — он сообщает только ту часть клавиатуры, которую страница после изменения размера ещё не освободила, — и hideKeyboard(), чтобы закрыть её:

import { onKeyboard, hideKeyboard, Keyboard } from '@/lib/appmintNative';

const off = onKeyboard((px) => { composer.style.paddingBottom = `${px}px`; });  // call off() on unmount
hideKeyboard();

const h = Keyboard.addListener('keyboardWillShow', ({ keyboardHeight }) => console.log(keyboardHeight));
h.remove();

Покажите клавиатуру для поля после нажатия кнопки:

var Keyboard = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Keyboard;

searchBtn.addEventListener('click', function () {
  searchInput.focus();
  if (Keyboard) Keyboard.show().catch(function () {});
});

Примечания: Оболочка уже изменяет размер страницы под клавиатуру, поэтому НЕ добавляйте полный keyboardHeight как отступ — нижняя панель и так находится над клавишами. setAccessoryBarVisible, setStyle, setResizeMode и setScroll на Android не реализованы — не вызывайте их. hide() отклоняется с «Can't close keyboard, not currently focused», когда ни один элемент не в фокусе.

Capacitor.LocalNotifications Capacitor#

Capacitor.Plugins.LocalNotifications — schedule, cancel, getPending, requestPermissions, events

Пример

Уведомления, которые приложение публикует само — сразу или в заданное время — с официальным API @capacitor/local-notifications (com.capacitorjs:local-notifications). Код, написанный для npm-пакета, работает без изменений.

Возвращает: requestPermissions() / checkPermissions() → { display: 'granted' | 'denied' | 'prompt' }; schedule({ notifications: [{ id, title, body, schedule?: { at | on | every }, extra?, actionTypeId?, channelId? }] }) → { notifications: [{ id }] }; getPending() → { notifications }; cancel({ notifications: [{ id }] }); registerActionTypes({ types }); createChannel / listChannels / deleteChannel; checkExactNotificationSetting() → { exact_alarm }. События: localNotificationReceived, localNotificationActionPerformed (actionId равно 'tap' при нажатии на само уведомление). Требуется: отметьте Уведомления на шаге 4 (Доступ) — без этого плагина в приложении нет, и каждый вызов отклоняется с «not implemented». Для уведомлений по времени также отметьте ⏰ Напоминания (точное время и работа после перезагрузки). Сборки из HTML, ZIP и AI Studio отмечают оба пункта сами, когда код импортирует @capacitor/local-notifications и вызывает LocalNotifications.schedule(. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

const LN = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.LocalNotifications;

async function remindIn(minutes, text) {
  if (!LN) return false;
  const { display } = await LN.requestPermissions();
  if (display !== 'granted') return false;
  await LN.schedule({ notifications: [{
    id: Date.now() % 2147483647,
    title: 'Reminder',
    body: text,
    schedule: { at: new Date(Date.now() + minutes * 60000), allowWhileIdle: true },
    extra: { screen: 'tasks' },
  }] });
  return true;
}

Открывайте нужный экран, когда пользователь нажимает на уведомление, — в том числе когда нажатие запустило приложение, ведь событие ждёт слушателя:

const LN = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.LocalNotifications;

if (LN) {
  LN.addListener('localNotificationActionPerformed', ({ actionId, notification }) => {
    if (actionId === 'tap' && notification.extra && notification.extra.screen) {
      location.hash = '#/' + notification.extra.screen;
    }
  });
}

Ежедневное напоминание о привычке с кнопками «Готово» / «Отложить»:

const LN = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.LocalNotifications;

async function dailyWaterReminder() {
  await LN.registerActionTypes({ types: [{ id: 'water', actions: [
    { id: 'done', title: 'Done' },
    { id: 'snooze', title: 'In 30 min' },
  ] }] });
  await LN.schedule({ notifications: [{
    id: 42, title: 'Drink water', body: 'Time for a glass of water.',
    actionTypeId: 'water', schedule: { on: { hour: 10, minute: 0 }, repeats: true },
  }] });
  LN.addListener('localNotificationActionPerformed', ({ actionId }) => {
    if (actionId === 'snooze') {
      LN.schedule({ notifications: [{ id: 43, title: 'Drink water', body: 'Snoozed reminder.',
        schedule: { at: new Date(Date.now() + 30 * 60000) } }] });
    }
  });
}

Примечания: На Android 12+ для уведомления по времени нужно разрешение на точные будильники: первый schedule() может открыть системную страницу «Будильники и напоминания»; если пользователь оставит его выключенным, уведомление всё равно сработает, чуть позже (в результате будет warning). Уведомления плагина используют собственный канал «Default», отдельный от остальных уведомлений приложения. sound из веб-файлов не поддерживается (Android воспроизводит звук канала по умолчанию). Нажатия доходят до страницы через экран разблокировки, если у приложения есть PIN-код. Приложения, созданные с помощью ИИ, используют notify() / LocalNotifications.schedule из @/lib/appmintNative, которые публикуют через собственную систему уведомлений приложения.

Capacitor.Network Capacitor#

Capacitor.Plugins.Network — getStatus(), addListener('networkStatusChange')

Пример

Сообщает странице, подключён ли телефон к сети и каким способом, а также когда это меняется (@capacitor/network 8.0.1).

Возвращает: getStatus() → Promise { connected: boolean, connectionType: 'wifi' | 'cellular' | 'none' | 'unknown' }. Событие networkStatusChange передаёт ту же структуру. Требуется: ничего включать не нужно — плагин есть в каждом приложении. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var Network = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Network;

function showBanner(s) {
  document.getElementById('offline').hidden = s.connected;
}

if (Network) {
  Network.getStatus().then(showBanner);
  Network.addListener('networkStatusChange', function (s) {
    showBanner(s);
    if (s.connected) retryQueuedWrites();
    if (s.connectionType === 'cellular') console.log('on mobile data');
  });
}

В приложении, созданном с помощью ИИ:

import { Network, networkStatus, onNetworkChange } from '@/lib/appmintNative';

const { connected, connectionType } = await Network.getStatus();
const now = networkStatus();                        // sync: { connected, type }
const off = onNetworkChange((s) => setOnline(s.connected));   // call off() on unmount

Приостановите большую загрузку при мобильном интернете:

var Network = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Network;

async function canUploadVideo() {
  if (!Network) return navigator.onLine;
  var s = await Network.getStatus();
  if (!s.connected) return false;
  return s.connectionType === 'wifi' || confirm('Upload over mobile data?');
}

Примечания: connected означает, что у телефона есть сеть, а не что ваш сервер отвечает, — всё равно обрабатывайте неудачные запросы. Network.getStatus() и networkStatus() в обёртке читают navigator.onLine / navigator.connection, а не плагин, поэтому их тип может быть 'unknown' или классом скорости вроде '4g'; onNetworkChange() в приложении использует событие плагина.

Capacitor.Preferences Capacitor#

Capacitor.Plugins.Preferences — get / set / remove / keys / clear

Пример

Небольшое встроенное хранилище «ключ — значение» (Android SharedPreferences) для строк (@capacitor/preferences 8.0.1).

Возвращает: set({ key, value }), remove({ key }), clear() → Promise void; get({ key }) → { value } (null, если ключа нет); keys() → { keys: string[] }. Требуется: ничего включать не нужно — плагин есть в каждом приложении. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var Prefs = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Preferences;

async function saveTheme(name) {
  if (!Prefs) return false;          // not inside the app
  await Prefs.set({ key: 'theme', value: name });
  return true;
}

async function loadTheme() {
  if (!Prefs) return null;
  var r = await Prefs.get({ key: 'theme' });
  return r.value;            // null if never saved
}

Объекты сначала нужно превратить в строку:

async function saveUser() {
  if (!Prefs) return;
  await Prefs.set({ key: 'user', value: JSON.stringify({ id: 7, name: 'Asha' }) });
  var u = JSON.parse((await Prefs.get({ key: 'user' })).value || 'null');
  var all = (await Prefs.keys()).keys;
  await Prefs.remove({ key: 'user' });
}

Покажите и очистите сохранённые ключи на экране настроек:

async function resetSettings() {
  if (!Prefs) return;
  var k = (await Prefs.keys()).keys;
  console.log('clearing', k.length, 'saved values');
  await Prefs.clear();
}

Примечания: У @/lib/appmintNative НЕТ обёртки Preferences — намеренно: localStorage в приложении уже настоящее и постоянное, а второе хранилище разделило бы данные одного приложения между двумя местами. Приложения, созданные с помощью ИИ, должны и дальше использовать localStorage (или среду данных). Используйте Preferences на странице, написанной вручную, только если хотите хранить там все свои настройки, и выбирайте одно хранилище на каждое значение. configure({ group }) меняет имя хранилища (по умолчанию CapacitorStorage).

Capacitor.PrivacyScreen Capacitor#

Capacitor.Plugins.PrivacyScreen — enable(), disable(), isEnabled()

Пример

Официальный плагин @capacitor/privacy-screen: блокирует скриншоты и запись экрана и скрывает содержимое приложения в списке недавних приложений. Встроен, чтобы импортированные проекты Capacitor, которые его вызывают, работали.

Возвращает: enable({android}) → {success}; disable() → {success}; isEnabled() → {enabled}. Требуется: ничего включать не нужно.

const Privacy = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.PrivacyScreen;

async function openVault() {
  if (Privacy) await Privacy.enable({ android: { privacyModeOnActivityHidden: 'dim' } });
  showVault();
}
async function closeVault() {
  if (Privacy) await Privacy.disable();
}

Проверьте текущее состояние перед переключением:

async function toggleProtection() {
  if (!Privacy) return;
  var s = await Privacy.isEnabled();
  if (s.enabled) await Privacy.disable();
  else await Privacy.enable();
}

Защищайте экран только пока открыт конфиденциальный экран:

async function showCardNumber() {
  if (Privacy) await Privacy.enable({ android: { privacyModeOnActivityHidden: 'splash' } });
  cardBox.hidden = false;
}
async function hideCardNumber() {
  cardBox.hidden = true;
  if (Privacy) await Privacy.disable();
}

Примечания: privacyModeOnActivityHidden: 'none', 'dim' или 'splash'. Код, написанный для самого приложения, должен использовать WebToApk.setSecureScreen(true) / privacyScreen.enable() из @/lib/appmintNative, которые следуют за страницей (снимаются, когда приложение её покидает); при смешении побеждает последний вызов.

Capacitor.PushNotifications Capacitor#

Capacitor.Plugins.PushNotifications — register, requestPermissions, registration, pushNotificationReceived

Пример

Push-уведомления с ВАШЕГО сервера через ВАШ проект Firebase с официальным API @capacitor/push-notifications (com.capacitorjs:push-notifications, регистрируется PushNotificationsPlugin.kt оболочки). Код, написанный для npm-пакета, работает без изменений: register(), затем отправляйте на токен из события registration через Firebase Admin SDK или FCM HTTP v1 API.

Возвращает: requestPermissions() / checkPermissions() → { receive: 'granted' | 'denied' | 'prompt' }; register() / unregister() → void (токен приходит как событие registration { value }, ошибка — как registrationError { error }); getDeliveredNotifications() → { notifications }; removeDeliveredNotifications({ notifications }), removeAllDeliveredNotifications(); createChannel({ id, name, importance? }), listChannels(), deleteChannel({ id }). События: pushNotificationReceived (сообщение, пока приложение открыто: { id, title?, body?, data }) и pushNotificationActionPerformed ({ actionId: 'tap', notification: { id, data } }). Требуется: сборка с включённым AppMint Push и загруженным файлом google-services.json (шаг «Уведомления») — это проект Firebase, которому принадлежит токен. Без него register() отклоняется с кодом PUSH_NOT_CONFIGURED («Push is not set up in this app»). Сборки из HTML, ZIP и AI Studio отмечают Уведомления сами, когда код вызывает PushNotifications.register( / requestPermissions( / addListener(. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

const Push = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.PushNotifications;

async function enablePush(userId) {
  if (!Push) return;
  await Push.addListener('registration', ({ value }) => {
    // Your server stores the token and sends to it later.
    fetch('https://api.example.com/push-tokens', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ userId, token: value, platform: 'android' }),
    });
  });
  await Push.addListener('registrationError', ({ error }) => console.warn('push:', error));
  const { receive } = await Push.requestPermissions();
  if (receive !== 'granted') return;
  try { await Push.register(); }
  catch (e) { if (e.code === 'PUSH_NOT_CONFIGURED') alert('Push is not set up in this build.'); }
}

Реагируйте на сообщение, пока приложение открыто, и на нажатие на сообщение, пришедшее, когда оно было закрыто (событие нажатия ждёт вашего обработчика, поэтому при холодном запуске оно не теряется):

const Push = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.PushNotifications;

if (Push) {
  Push.addListener('pushNotificationReceived', (msg) => {
    alert((msg.title || 'New message') + (msg.body ? ': ' + msg.body : ''));
  });
  Push.addListener('pushNotificationActionPerformed', ({ notification }) => {
    const orderId = notification.data && notification.data.orderId;
    if (orderId) location.hash = '#/orders/' + orderId;
  });
}

Канал для важных оповещений (Android 8+), который используют сообщения, указывающие его в android.notification.channel_id:

const Push = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.PushNotifications;

async function setupChannels() {
  if (!Push) return;
  await Push.createChannel({ id: 'orders', name: 'Order updates', description: 'Your order status', importance: 4, vibration: true });
}

Примечания: Сообщение, отправленное с блоком notification, показывает сам Android, пока приложение в фоне; его нажатие открывает приложение (через экран блокировки, если у приложения есть PIN) и вызывает pushNotificationActionPerformed. Пока приложение открыто, сообщения приходят как pushNotificationReceived и не рисуются в шторке. Сообщения только с данными доходят до страницы, только пока она открыта. Сообщения AppMint Push продолжают работать параллельно — они никогда не попадают в этот плагин. В приложении, собранном с OneSignal вместо AppMint Push, сообщения доставляет OneSignal, и они никогда не попадают в этот плагин. Приложения, созданные ИИ, получают пуши автора через pushInbox() из @/lib/appmintNative.

Capacitor.ScreenBrightness Capacitor#

Capacitor.Plugins.ScreenBrightness — getBrightness(), setBrightness({ brightness })

Пример

Задаёт яркость экрана окна приложения: полная яркость для посадочного талона или QR-кода, затем возврат. Включён в сборку @capacitor-community/screen-brightness (template/plugins/screen-brightness).

Возвращает: getBrightness() → Promise { brightness } (0–1 или -1, если приложение не переопределило значение и экран следует системному). setBrightness({ brightness }) → Promise void. Требуется: ничего — плагин есть в каждом приложении; разрешение не нужно (меняется только окно этого приложения, а не системная настройка). window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var SB = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenBrightness;

async function showTicket() {
  if (SB) await SB.setBrightness({ brightness: 1 });     // full brightness
  document.getElementById('qr').hidden = false;
}

async function hideTicket() {
  if (SB) await SB.setBrightness({ brightness: -1 });    // -1 = follow the system again
  document.getElementById('qr').hidden = true;
}

В приложении, созданном с помощью ИИ:

import { brightness } from '@/lib/appmintNative';

await brightness.set(1);        // clamped to 0–1; false where not available (web)
// ... on leaving the screen
await brightness.restore();
const level = await brightness.get();   // -1 = following the system / not available

Сначала прочитайте текущий уровень и восстановите именно его:

var SB = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenBrightness;
var before = -1;

async function boost() {
  if (!SB) return;
  before = (await SB.getBrightness()).brightness;
  await SB.setBrightness({ brightness: 1 });
}
async function undo() {
  if (SB) await SB.setBrightness({ brightness: before });
}

Примечания: Всегда передавайте число — у setBrightness({}) нет значения, которое можно применить. Восстанавливайте через -1, когда экран закрывается; переопределение также заканчивается при закрытии приложения. Веб-страница не может управлять подсветкой, поэтому can('brightness') в браузере равен 'none'.

Capacitor.ScreenOrientation Capacitor#

Capacitor.Plugins.ScreenOrientation — orientation(), lock({ orientation }), unlock()

Пример

Считывает ориентацию экрана, фиксирует её для одного экрана (игра, диаграмма, поле для подписи) и снимает фиксацию (@capacitor/screen-orientation 8.0.1).

Возвращает: orientation() → Promise { type } (например, 'portrait-primary'). lock() / unlock() → Promise void. Событие screenOrientationChange передаёт { type }. Требуется: ничего включать не нужно — плагин есть в каждом приложении. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

orientation значения: 'portrait', 'landscape', 'portrait-primary', 'portrait-secondary', 'landscape-primary', 'landscape-secondary', 'natural', 'any'.

var SO = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenOrientation;

async function openChart() {
  if (SO) await SO.lock({ orientation: 'landscape' });
  showChart();
}

async function closeChart() {
  if (SO) await SO.unlock();          // always release when leaving the view
  hideChart();
}

if (SO) {
  SO.orientation().then(function (o) { console.log('now', o.type); });
  SO.addListener('screenOrientationChange', function (o) { console.log('rotated to', o.type); });
}

В приложении, созданном с помощью ИИ:

import { lockOrientation, ScreenOrientation } from '@/lib/appmintNative';

const ok = await lockOrientation('landscape');   // false when refused (always on desktop)
await lockOrientation('any');                    // release

await ScreenOrientation.lock({ orientation: 'portrait' });
await ScreenOrientation.unlock();

Обработайте отказ в блокировке и сообщите об этом пользователю:

var SO = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenOrientation;
var Toast = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Toast;

async function tryLandscape() {
  if (!SO) { document.getElementById('rotate-hint').hidden = false; return; }   // ask the user to rotate by hand
  try {
    await SO.lock({ orientation: 'landscape' });
  } catch (e) {
    if (Toast) Toast.show({ text: 'Could not rotate the screen', duration: 'short' });
  }
}

Примечания: Обёртки знают только portrait / landscape / any: ScreenOrientation.lock({ orientation: 'landscape-secondary' }) фиксирует обычный landscape. Дайте пользователю способ выйти из заблокированного экрана.

Capacitor.ScreenReader Capacitor#

Capacitor.Plugins.ScreenReader — isEnabled(), speak({ value, language }), addListener('stateChange')

Пример

Сообщает странице, включён ли TalkBack, и произносит объявление (@capacitor/screen-reader 8.0.1).

Возвращает: isEnabled() → Promise { value: boolean }. speak() → Promise void. Событие stateChange передаёт { value: boolean }, когда TalkBack включается или выключается. Требуется: ничего включать не нужно — плагин есть в каждом приложении. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var SR = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenReader;

function setCalmMode(on) {
  document.documentElement.classList.toggle('no-motion', on);   // shorten animations
}

if (SR) {
  SR.isEnabled().then(function (r) { setCalmMode(r.value); });
  SR.addListener('stateChange', function (s) { setCalmMode(s.value); });
}

async function announceSaved() {
  if (SR) await SR.speak({ value: 'Order placed', language: 'en' });
}

В приложении, созданном с помощью ИИ (speak в вебе использует синтез речи; isEnabled() там равно false):

import { screenReader } from '@/lib/appmintNative';

const on = await screenReader.isEnabled();
const off = screenReader.onChange((enabled) => setCalmMode(enabled));
await screenReader.speak('Order placed', 'en');

Проверьте наличие плагина и сохраните работоспособность страницы без него:

var SR = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenReader;

async function say(message) {
  if (SR) { await SR.speak({ value: message }); return; }
  document.getElementById('live-region').textContent = message;   // an aria-live="polite" element
}

Примечания: language по умолчанию — 'en'. Для большинства объявлений лучше использовать обычную доступную разметку (подписи, области aria-live); speak — для коротких важных изменений состояния. В вебе нет API для определения программы чтения с экрана, поэтому can('screenReader') в браузере равен 'none'.

Capacitor.Share Capacitor#

Capacitor.Plugins.Share — share(options), canShare()

Пример

Открывает системное меню «Поделиться» Android с заголовком, текстом и/или ссылкой (@capacitor/share 8.0.1).

Возвращает: share() → Promise { activityType } (имя компонента выбранного приложения, может быть ''); он ОТКЛОНЯЕТСЯ, когда пользователь закрывает меню. canShare() → Promise { value: boolean }. Требуется: ничего включать не нужно — плагин есть в каждом приложении. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

async function shareLink() {
  var Share = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Share;
  if (!Share) { alert('Sharing is not available here'); return; }
  // Only pass the fields you have. A present but empty `url` is rejected ("Unsupported url").
  var opts = { title: 'My list', text: 'Milk, eggs, bread' };
  var link = 'https://example.com/list/42';
  if (link) opts.url = link;
  try {
    var r = await Share.share(opts);
    console.log('shared to', r.activityType);
  } catch (e) {
    console.log('share cancelled or failed:', e.message);   // "Share canceled" on dismiss
  }
}

В приложении, созданном ИИ, обёртка имеет ту же форму и также отклоняется при отмене:

import { Share, can } from '@/lib/appmintNative';

if (can('share') !== 'none') {
  try {
    await Share.share({ title: 'My list', text: 'Milk, eggs, bread', url: 'https://example.com/list/42' });
  } catch {
    // the user closed the sheet — not an error to show
  }
}

Сначала проверьте canShare() и скройте кнопку, если отправка недоступна:

var Share = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Share;
var btn = document.getElementById('share');

btn.hidden = true;
if (Share) {
  Share.canShare().then(function (r) { btn.hidden = !r.value; });
}

Примечания: url должен быть http(s):// или file://. dialogTitle задаёт заголовок окна выбора. Для отправки файлов нужны пути file://, которыми владеет приложение; со страницы отправляйте файлы через navigator.share({ files }). Одновременно может быть открыта только одна отправка («Can't share while sharing is in progress»).

Capacitor.SpeechRecognition Capacitor#

Capacitor.Plugins.SpeechRecognition

Пример

Превращает речь пользователя в текст с помощью системного распознавателя речи Android (плагин сообщества @capacitor-community/speech-recognition, встроенный в приложение). В Android WebView нет собственного распознавания Web Speech, так что диктовка делается именно так.

Возвращает: Promise. available() → { available }. checkPermissions() / requestPermissions() → { speechRecognition: 'granted' | 'denied' | 'prompt' }. start({ partialResults: false }) → { status: 'success', matches: [...] } после того, как пользователь перестал говорить; отклоняется при ошибках, таких как отсутствие речи или совпадений. Требуется: отметьте Микрофон на шаге 4 (Доступ) при сборке (для приложений из HTML, ZIP и созданных с помощью ИИ сборка сама добавляет разрешение на микрофон, если в ваших файлах упоминается SpeechRecognition), и пользователь должен разрешить микрофон. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

Разовая диктовка (так её использует @/lib/appmintNative speech):

var SR = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.SpeechRecognition : null;

async function dictate() {
  var out = document.getElementById('text');
  if (!SR || !(await SR.available()).available) { out.textContent = 'Voice input is not available on this phone.'; return; }

  var p = (await SR.checkPermissions()).speechRecognition;
  if (p !== 'granted') p = (await SR.requestPermissions()).speechRecognition;
  if (p !== 'granted') { out.textContent = 'Microphone permission was not given.'; return; }

  try {
    var r = await SR.start({ language: 'en-US', maxResults: 3, partialResults: false, popup: false });
    out.textContent = r.matches && r.matches.length ? r.matches[0] : '';
  } catch (e) {
    out.textContent = 'Did not catch that. Try again.';
  }
}

document.getElementById('mic').addEventListener('click', dictate);

Текст в реальном времени, пока пользователь говорит (partialResults: true), и состояние прослушивания:

// Add the listeners ONCE, when the page starts.
if (SR) {
  SR.addListener('partialResults', function (d) {
    // Words as they come, and then the final text, all arrive here.
    if (d.matches && d.matches.length) document.getElementById('text').textContent = d.matches[0];
  });
  SR.addListener('listeningState', function (d) {
    // 'started' = the user began speaking, 'stopped' = the user stopped speaking
    document.body.classList.toggle('listening', d.status === 'started');
  });
}

document.getElementById('mic-live').addEventListener('click', async function () {
  if (!SR) return;
  try {
    await SR.start({ language: 'en-US', partialResults: true, popup: false });   // resolves at once, no matches
  } catch (e) {
    document.getElementById('text').textContent = 'Could not start: ' + e.message;
  }
});

document.getElementById('stop').addEventListener('click', function () { if (SR) SR.stop(); });

Показывайте кнопку микрофона, только когда диктовка может работать, и прекращайте слушать, когда страница скрыта:

var SR = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.SpeechRecognition : null;
var mic = document.getElementById('mic');

mic.hidden = true;
if (SR) {
  SR.available().then(function (r) { mic.hidden = !r.available; });
}

document.addEventListener('visibilitychange', function () {
  if (document.hidden && SR) SR.stop();
});

Примечания:

  • В режиме реального времени (partialResults: true) итоговый текст приходит после состояния 'stopped', поэтому не удаляйте слушатель partialResults на 'stopped'. Ошибка после старта (нет речи, нет совпадений) в этом режиме не сообщается.
  • language — это тег вида 'en-US' или 'ta-IN' (по умолчанию — язык телефона). popup: true показывает собственное диалоговое окно прослушивания Google. Также есть getSupportedLanguages() → { languages } и isListening() → { listening }.
  • start() отклоняется с 'Missing permission', если вы не запросили разрешение заранее. Прослушивание останавливается само после паузы в речи.

Capacitor.SplashScreen Capacitor#

Capacitor.Plugins.SplashScreen — show(), hide()

Пример

API @capacitor/splash-screen поверх собственной фирменной заставки приложения, чтобы импортированные проекты Capacitor / AI Studio, которые вызывают SplashScreen.hide(), когда первый экран готов, работали в Android-приложении.

Возвращает: hide() → Promise (заставка исчезает, когда пройдёт минимальное время заставки, заданное создателем); show({showDuration, autoHide}) → Promise (заставка возвращается поверх страницы и при autoHide — по умолчанию — исчезает через showDuration мс, по умолчанию 3000). Требуется: ничего включать не нужно.

const Splash = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.SplashScreen;

async function boot() {
  await loadFirstScreen();
  if (Splash) await Splash.hide();     // otherwise the shell hides it at first paint
}
boot();

Закройте медленное переключение аккаунта заставкой, которая остаётся, пока новые данные не появятся на экране:

const Splash = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.SplashScreen;

async function switchAccount(accountId) {
  if (Splash) await Splash.show({ autoHide: false });   // stays until hide()
  try {
    await loadAccount(accountId);
    renderHome();
  } finally {
    if (Splash) await Splash.hide();
  }
}

Покажите фирменную заставку на мгновение после выхода из аккаунта; она исчезает сама через showDuration:

const Splash = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.SplashScreen;

async function signOut() {
  await clearSession();
  showSignInScreen();
  if (Splash) {
    try {
      await Splash.show({ showDuration: 1200 });
    } catch (e) {
      console.warn('splash not shown:', e.code);
    }
  }
}

Примечания: без вызова hide() заставка всё равно исчезает при первой отрисовке, как у любого приложения. Приложения, созданные с помощью ИИ, используют splash.hold() / splash.ready() из @/lib/appmintNative.

Capacitor.StatusBar Capacitor#

Capacitor.Plugins.StatusBar — setStyle, setBackgroundColor, show, hide, getInfo

Пример

Меняет стиль значков, цвет и видимость строки состояния (@capacitor/status-bar 8.0.2).

Возвращает: каждый метод → Promise void, кроме getInfo() → { visible, style, color, overlays, height }. Требуется: ничего включать не нужно — плагин есть в каждом приложении. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

style называет ФОН, которому он подходит: 'DARK' = светлые значки (для тёмной панели), 'LIGHT' = тёмные значки, 'DEFAULT' = следовать системе.

var StatusBar = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.StatusBar;
if (StatusBar) {
  StatusBar.setStyle({ style: 'DARK' });          // light icons
  StatusBar.hide();                               // e.g. while a video plays
  // later
  StatusBar.show();
}

Красьте панель через мост оболочки, а не через плагин. На Android 15+ метод плагина setBackgroundColor ничего не делает (режим «от края до края» его игнорирует). Оболочка сама красит полосу и принимает стиль значков в том же вызове:

if (window.WebToApk && window.WebToApk.setStatusBarColor) {
  window.WebToApk.setStatusBarColor('#0B0F1A', 'light');   // dark bar, light icons
} else if (StatusBar) {
  StatusBar.setBackgroundColor({ color: '#0B0F1A' });      // hex only, e.g. #RRGGBB
}

В приложении, созданном с помощью ИИ, используйте setStatusBar(color, contentStyle); он отдаёт предпочтение мосту оболочки и задаёт цвет и значки одним вызовом:

import { setStatusBar, StatusBar } from '@/lib/appmintNative';

setStatusBar('#0B0F1A', 'light');                       // dark bar, light icons
await StatusBar.setBackgroundColor({ color: '#FFFFFF' }); // colour only; icons picked from luminance

Примечания: setBackgroundColor отклоняет цвет, не являющийся hex-строкой. В полноэкранных / киоск-сборках панели скрывает оболочка. В Android-приложении один лишь StatusBar.setStyle() обёртки ничего не меняет (мосту нужен цвет для заливки) — передайте стиль в setStatusBar() вместе с цветом.

Capacitor.TextToSpeech Capacitor#

Capacitor.Plugins.TextToSpeech — speak(), stop(), getSupportedLanguages(), getSupportedVoices()

Пример

Плагин сообщества @capacitor-community/text-to-speech поверх движка преобразования текста в речь телефона. Встроен, чтобы работали импортированные проекты Capacitor, которые его вызывают.

Возвращает: speak({text, lang, rate, pitch, volume, voice}) → Promise, который завершается, когда фраза произнесена; stop(); getSupportedLanguages() → {languages}; getSupportedVoices() → {voices}; isLanguageSupported({lang}) → {supported}. Требуется: ничего включать не нужно.

const Tts = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.TextToSpeech;

async function readAloud(text) {
  if (!Tts) { speechSynthesis.speak(new SpeechSynthesisUtterance(text)); return; }
  await Tts.speak({ text: text, lang: 'en-US', rate: 1.0, pitch: 1.0, volume: 1.0 });
}
document.getElementById('stop').onclick = () => Tts && Tts.stop();

Проверьте язык перед озвучиванием и покажите сообщение, если он не установлен:

const Tts = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.TextToSpeech;

async function speakIn(lang, text) {
  if (!Tts) return false;
  const { supported } = await Tts.isLanguageSupported({ lang: lang });
  if (!supported) { showMessage('This phone has no ' + lang + ' voice installed.'); return false; }
  await Tts.speak({ text: text, lang: lang });
  return true;
}

Прочитайте абзац вслух с кнопкой остановки и обработайте ошибку озвучивания:

const Tts = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.TextToSpeech;

async function readParagraph(el, btn) {
  if (!Tts) return;
  btn.textContent = 'Stop';
  btn.onclick = () => Tts.stop();
  try {
    await Tts.speak({ text: el.textContent, lang: 'en-US', rate: 0.9 });   // resolves when finished
  } catch (e) {
    console.warn('could not speak:', e.message);
  } finally {
    btn.textContent = 'Listen';
    btn.onclick = () => readParagraph(el, btn);
  }
}

Примечания: window.speechSynthesis работает и в приложении (собственный движок оболочки) и именно его используют приложения, созданные с помощью ИИ, через speak(); этот плагин — путь в стиле Capacitor.

Capacitor.TextZoom Capacitor#

Capacitor.Plugins.TextZoom — get(), getPreferred(), set({ value })

Пример

Читает и меняет размер текста страницы приложения для элемента управления «Размер текста» внутри приложения (@capacitor/text-zoom 8.0.1).

Возвращает: get() → Promise { value } (текущий масштаб, 1 = 100%). getPreferred() → { value } (размер, выбранный пользователем в настройках Android). set({ value }) → Promise void. Требуется: ничего включать не нужно — плагин есть в каждом приложении. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var TZ = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.TextZoom;

async function biggerText() {
  if (!TZ) return;
  var now = (await TZ.get()).value;
  await TZ.set({ value: Math.min(2, now + 0.1) });
}

async function resetText() {
  if (!TZ) return;
  var pref = (await TZ.getPreferred()).value;   // e.g. 1.15 when the phone uses large text
  await TZ.set({ value: pref });
}

В приложении, созданном с помощью ИИ (в вебе вместо этого масштабируется размер шрифта корневого элемента):

import { textZoom } from '@/lib/appmintNative';

const size = await textZoom.get();
await textZoom.set(size + 0.1);                     // clamped to 0.5–3
await textZoom.set(await textZoom.getPreferred());  // back to the system size

Ползунок размера текста, запоминающий выбор:

var TZ = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.TextZoom;
var slider = document.getElementById('text-size');   // <input type="range" min="0.8" max="1.6" step="0.1">

if (TZ) {
  var saved = parseFloat(localStorage.getItem('textZoom'));
  if (saved) TZ.set({ value: saved });
  slider.addEventListener('change', function () {
    TZ.set({ value: parseFloat(slider.value) });
    localStorage.setItem('textZoom', slider.value);
  });
} else {
  slider.hidden = true;
}

Примечания: Приложение уже следует системному размеру текста; это для дополнительного элемента управления внутри приложения. Проверьте вёрстку при крупных размерах — фиксированная высота у текстовых элементов приведёт к обрезанию.

Capacitor.Toast Capacitor#

Capacitor.Plugins.Toast — show({ text, duration, position })

Пример

Показывает короткое системное уведомление-тост Android, например «Сохранено» или «Скопировано» (@capacitor/toast 8.0.1).

Возвращает: Promise void. Требуется: ничего включать не нужно — плагин есть в каждом приложении. window.Capacitor существует на страницах, которые приложение отдаёт само (приложения из HTML, ZIP и созданные с помощью ИИ), а в приложении в режиме Сайт — на вашем собственном сайте (его домен и поддомены), но никогда на других сайтах, которые открывает приложение. Проверяйте наличие заранее.

var Toast = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Toast;

function saved() {
  if (Toast) Toast.show({ text: 'Saved', duration: 'short', position: 'bottom' });
}

duration — это 'short' (около 2 с) или 'long' (около 3,5 с). position — это 'top', 'center' или 'bottom'.

В приложении, созданном с помощью ИИ, используйте toast() или обёртку в стиле Capacitor Toast; в вебе они рисуют снекбар в цветах приложения:

import { Toast, toast } from '@/lib/appmintNative';

await Toast.show({ text: 'Added to list', duration: 'long' });
toast('Copied');   // short form, same result

Проверьте наличие плагина и покажите сообщение на странице, если его нет:

function notify(message) {
  var Toast = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Toast;
  if (Toast) { Toast.show({ text: message, duration: 'short' }); return; }
  var el = document.getElementById('snackbar');   // a plain in-page message
  el.textContent = message;
  el.hidden = false;
  setTimeout(function () { el.hidden = true; }, 2000);
}

Примечания: text обязателен («Must provide text»). Систему рисует toast в стиле производителя телефона, а новые версии Android игнорируют position для текстовых toast. Toast легко пропустить — никогда не используйте его для ошибки, на которую пользователь должен отреагировать; для этого используйте Dialog.alert.

Capacitor.Torch Capacitor#

Capacitor.Plugins.Torch — enable(), disable(), toggle(), isEnabled(), isAvailable()

Пример

Фонарик телефона с API @capawesome/capacitor-torch. Собственный плагин оболочки приложения (TorchPlugin.kt).

Возвращает: enable() / disable() / toggle() → Promise (отклоняется с CAMERA_IN_USE, пока открыт собственный поток камеры приложения, и с UNAVAILABLE, если нет вспышки); isEnabled() → {enabled}; isAvailable() → {available}. Требуется: ничего включать не нужно, разрешение не требуется.

const Torch = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Torch;

async function setupLightButton() {
  if (!Torch || !(await Torch.isAvailable()).available) { lightBtn.hidden = true; return; }
  lightBtn.onclick = async () => {
    try {
      await Torch.toggle();
      lightBtn.classList.toggle('on', (await Torch.isEnabled()).enabled);
    } catch (e) {
      alert(e.message);
    }
  };
}

Мигайте сигналом SOS по нажатию кнопки и выключайте свет, если камера забирает вспышку на середине:

const Torch = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Torch;
const wait = (ms) => new Promise((r) => setTimeout(r, ms));

sosBtn.onclick = async () => {
  if (!Torch || !(await Torch.isAvailable()).available) return;
  const flashes = [200, 200, 200, 600, 600, 600, 200, 200, 200];   // S O S, in ms
  try {
    for (const ms of flashes) {
      await Torch.enable();  await wait(ms);
      await Torch.disable(); await wait(200);
    }
  } catch (e) {
    await Torch.disable().catch(() => {});
    alert(e.code === 'CAMERA_IN_USE' ? 'Close the camera first.' : e.message);
  }
};

Синхронизируйте кнопку фонарика с системой, ведь пользователь может включить фонарик из плитки быстрых настроек, пока приложение в фоне:

const Torch = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Torch;

async function syncLightButton() {
  if (!Torch) return;
  const { enabled } = await Torch.isEnabled();
  lightBtn.classList.toggle('on', enabled);
  lightBtn.setAttribute('aria-pressed', String(enabled));
}

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible') syncLightButton();
});
syncLightButton();

Примечания: фонарик следует системному состоянию (выключение из плитки быстрых настроек читается как «выключен») и выключается при закрытии приложения. Пока открыт поток getUserMedia, включайте свет через дорожку: track.applyConstraints({ advanced: [{ torch: true }] }). Приложения, созданные с помощью ИИ, используют torch из @/lib/appmintNative.