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

JavaScript bridge API

Окно

Полный экран, «картинка в картинке», цвета системных панелей, блокировка скриншотов, включена ли в сборке защита от копирования (AppMint.copyProtection), заставка и закрытие окна.

AppMint.copyProtection помощник#

AppMint.copyProtection

Пример

true когда приложение собрано с включённой защитой от копирования; иначе отсутствует (undefined). Только для чтения — страница не может включить или выключить защиту, а только спросить, включена ли она.

Возвращает: true или undefined, когда защита выключена (и в браузере). Требуется: включённая Защита от копирования на шаге 3 (Интеграция), в разделе Параметры отображения, при сборке. По умолчанию выключено.

При включённой защите текст приложения нельзя выделить, скопировать или вырезать; копирование / вырезание, событие contextmenu и Ctrl/Cmd + C, X и A отменяются вне редактируемых полей; меню долгого нажатия не появляется. Поля ввода, textarea, select и области contenteditable остаются полностью редактируемыми, так что формы продолжают работать.

Скройте собственные кнопки «Копировать», когда защита включена, — они ничего не скопируют и будут выглядеть сломанными:

const protectedApp = !!(window.AppMint && window.AppMint.copyProtection === true);

document.querySelectorAll('[data-copy-button]').forEach((btn) => {
  btn.hidden = protectedApp;
});

Или выберите формулировку подсказки один раз, при запуске:

const hint = document.getElementById('share-hint');
hint.textContent = (window.AppMint && window.AppMint.copyProtection === true)
  ? 'Tap Share to send this lesson to a friend.'
  : 'Select any text to copy it, or tap Share.';

Совместите это с блокировкой скриншотов для страницы, которая не должна покидать приложение:

if (window.AppMint && AppMint.copyProtection === true && window.WebToApk && WebToApk.setSecureScreen) {
  WebToApk.setSecureScreen(true);                    // also blocks screenshots and recents thumbnails
  document.body.classList.add('protected');
}

Примечания: меню долгого нажатия на ссылках и изображениях (открыть, скопировать, поделиться или скачать) — отдельный переключатель сборки, Меню долгого нажатия на ссылках и изображениях, в том же месте. По умолчанию он выключен, поэтому долгое нажатие на ссылку ничего не делает, пока вы его не включите; защита от копирования всегда держит его выключенным. Страница всё равно может отказаться от меню на одном элементе, отменив contextmenu (el.addEventListener('contextmenu', (e) => e.preventDefault())). Защита останавливает копирование внутри приложения; она не останавливает скриншот — о нём см. WebToApk.setSecureScreen(true).

navigator.wakeLock веб-стандарт#

navigator.wakeLock.request('screen')

Пример

Стандартный API Screen Wake Lock: не даёт экрану гаснуть, пока на нём рецепт, карта, посадочный талон или таймер тренировки.

Возвращает: Promise с WakeLockSentinel {type:'screen', released, release(), onrelease}. Завершается с NotAllowedError, если страница не видна. Требуется: ничего включать не нужно, разрешение не требуется.

let wakeLock = null;

async function startCooking() {
  try {
    wakeLock = await navigator.wakeLock.request('screen');
    wakeLock.addEventListener('release', () => { wakeLock = null; });
  } catch (e) {
    console.log('Screen may dim:', e.name);
  }
}

async function stopCooking() {
  if (wakeLock) await wakeLock.release();
}

// The lock is dropped when the app goes to the background — take it again on return.
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible' && cookingModeOn && !wakeLock) startCooking();
});

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

let lock = null;

async function startTimer(seconds) {
  if ('wakeLock' in navigator) {
    try { lock = await navigator.wakeLock.request('screen'); } catch (e) { lock = null; }
  }
  runCountdown(seconds, async () => {        // your timer; the callback runs at 0
    if (lock && !lock.released) await lock.release();
    lock = null;
    showDone();
  });
}

Переключатель «Не выключать экран», который сам снимается, когда блокировка потеряна (например, приложение ушло в фон):

const box = document.getElementById('keep-on');
let sentinel = null;

box.hidden = !('wakeLock' in navigator);
box.addEventListener('change', async () => {
  if (box.checked) {
    try {
      sentinel = await navigator.wakeLock.request('screen');
      sentinel.onrelease = () => { box.checked = false; sentinel = null; };
    } catch (e) {
      box.checked = false;                     // NotAllowedError: page not visible
    }
  } else if (sentinel) {
    await sentinel.release();
  }
});

Примечания: как сказано в спецификации, все блокировки снимаются, когда страница скрывается (приложение уходит в фон или переходит на другую страницу); у каждого sentinel срабатывает событие release. Можно одновременно держать несколько sentinel — экран остаётся включённым, пока не освобождён последний. Приложения, созданные с помощью ИИ, используют keepAwake(true/false) из @/lib/appmintNative. Событие за этим: appmint:wakelock-lost.

screen.orientation веб-стандарт#

screen.orientation.lock(type) / unlock()

Пример

Стандартная блокировка Screen Orientation, которую обычный WebView отвергает: поворачивает приложение в альбомную ориентацию для видео или игры и обратно.

Возвращает: lock(type) → Promise, который завершается, когда ориентация запрошена; для неизвестного типа отклоняется с TypeError, на Android TV — с NotSupportedError. unlock() возвращает ориентацию, с которой собрано приложение. Требуется: ничего.

const player = document.getElementById('player');

document.getElementById('fullscreen').addEventListener('click', async () => {
  try {
    await screen.orientation.lock('landscape');       // either landscape, follows the sensor
  } catch (e) {
    console.log('Could not rotate:', e.name);
  }
  player.classList.add('wide');
});

document.getElementById('done').addEventListener('click', () => {
  screen.orientation.unlock();
  player.classList.remove('wide');
});

screen.orientation.addEventListener('change', () => console.log(screen.orientation.type));

Поверните игру в альбомную ориентацию при запуске; на Android TV экран уже альбомный, так что продолжайте:

async function startGame() {
  try {
    await screen.orientation.lock('landscape');
  } catch (e) {
    if (e.name !== 'NotSupportedError') console.log('Could not rotate:', e.name);
  }
  runGame();
}

function quitGame() {
  screen.orientation.unlock();   // back to the orientation the app was built with
  showMenu();
}

Подстраивайтесь под форму воспроизводимого видео и возвращайтесь, когда оно закончится:

const video = document.getElementById('clip');

video.addEventListener('play', () => {
  const wide = video.videoWidth >= video.videoHeight;
  screen.orientation.lock(wide ? 'landscape' : 'portrait').catch((e) => console.log(e.name));
});

video.addEventListener('ended', () => screen.orientation.unlock());

Примечания: типы: any, natural, portrait, portrait-primary, portrait-secondary, landscape, landscape-primary, landscape-secondary. В отличие от Chrome, страница не обязана сначала быть в полноэкранном режиме. Приложения, созданные с помощью ИИ, используют lockOrientation() из @/lib/appmintNative.

window.close веб-стандарт#

window.close()

Пример

Закрывает главный экран приложения. Оболочка подменяет window.close(), чтобы кнопка «Закрыть» или «Выход» на вашей странице работала внутри приложения.

Возвращает: ничего. Требуется: ничего.

document.getElementById('exit-btn').onclick = function () {
  saveDraft();          // anything unsaved — the screen closes at once
  window.close();       // in the app: WebToApk.closeWindow(); in a browser: normal behaviour
};

Скройте кнопку в браузере, где window.close() обычно ничего не делает с вкладкой, открытой пользователем:

if (!window.WebToApk) document.getElementById('exit-btn').hidden = true;

Сначала спросите, потому что приложение закрывается в момент выполнения window.close():

document.getElementById('quit').addEventListener('click', function () {
  if (hasUnsavedChanges() && !confirm('You have unsaved changes. Close the app anyway?')) {
    return;
  }
  window.close();
});

Примечания: замена устанавливается после загрузки каждой страницы, поэтому вызов во время самого первого запуска скрипта страницы использует собственный window.close() WebView (который ничего не делает). На этом пути нет подтверждения выхода. Всплывающее окно входа, которое вызывает window.close(), обрабатывается отдельно: оно закрывает всплывающее окно, а не приложение.

__lockOrientation мост#

window.WebToApk.__lockOrientation(type: String): String

screen.orientation.lock(type): «ok», «type» для строки, не являющейся OrientationLockType, или «unsupported» (Android TV всегда альбомный).

Пример

Внутренний транспорт для screen.orientation.lock(type). Страницы используют стандартный API; никогда не вызывайте это напрямую — методы __ могут измениться без предупреждения.

Возвращает: 'ok', 'type' для строки, не являющейся типом блокировки ориентации, или 'unsupported' (Android TV). Требуется: ничего включать не нужно.

Используйте публичный API:

document.getElementById('play').addEventListener('click', async () => {
  try { await screen.orientation.lock('landscape'); } catch (e) { /* NotSupportedError on TV */ }
});
document.getElementById('exit').addEventListener('click', () => screen.orientation.unlock());

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

async function lockTo(type) {
  if (!screen.orientation || typeof screen.orientation.lock !== 'function') return 'unsupported';
  try {
    await screen.orientation.lock(type);
    return 'ok';
  } catch (e) {
    if (e instanceof TypeError) return 'bad-type';   // not an OrientationLockType string
    return 'unsupported';                            // NotSupportedError (Android TV)
  }
}

lockTo('portrait-primary').then((r) => console.log('orientation lock:', r));

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

async function startSigning() {
  const current = screen.orientation.type;   // e.g. 'landscape-primary'
  try { await screen.orientation.lock(current); } catch (e) { /* keep going unlocked */ }
  showSignaturePad();
}

function finishSigning() {
  hideSignaturePad();
  screen.orientation.unlock();
}

Примечания: Реализовано в оболочке приложения (WebApiPolyfills.kt). Тот же код страницы без изменений работает в Chrome.

__unlockOrientation мост#

window.WebToApk.__unlockOrientation()

screen.orientation.unlock(): возврат к ориентации, с которой собрал приложение автор.

Пример

Внутренний транспорт для screen.orientation.unlock(). Страницы используют стандартный API; никогда не вызывайте это напрямую — методы __ могут измениться без предупреждения.

Возвращает: ничего; приложение возвращается к ориентации, выбранной при сборке. Требуется: ничего включать не нужно.

Используйте публичный API:

document.getElementById('play').addEventListener('click', async () => {
  try { await screen.orientation.lock('landscape'); } catch (e) { /* NotSupportedError on TV */ }
});
document.getElementById('exit').addEventListener('click', () => screen.orientation.unlock());

Только маршрут игры альбомный; все остальные маршруты возвращаются к собственной ориентации приложения:

function onRouteChange(path) {
  if (path.startsWith('/play')) {
    screen.orientation.lock('landscape').catch(() => {});
  } else if (screen.orientation && typeof screen.orientation.unlock === 'function') {
    screen.orientation.unlock();       // menus, settings and the store
  }
}

window.addEventListener('popstate', () => onRouteChange(location.pathname));
onRouteChange(location.pathname);

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

async function showOnboarding() {
  try { await screen.orientation.lock('portrait'); } catch (e) { /* TV: no lock */ }
  await runOnboardingSlides();         // your own flow
  screen.orientation.unlock();         // back to the orientation the app was built with
}

Примечания: Реализовано в оболочке приложения (WebApiPolyfills.kt). Тот же код страницы без изменений работает в Chrome.

__wakeLock мост#

window.WebToApk.__wakeLock(docId: String, held: Boolean): Boolean

Screen Wake Lock: [held] true удерживает экран включённым, пока документ [docId] держит sentinel. Возвращает false, если блокировку получить не удалось.

Пример

Внутренний транспорт для navigator.wakeLock.request('screen') и WakeLockSentinel.release(). Страницы используют стандартный API; никогда не вызывайте его напрямую — методы __ могут измениться без предупреждения.

Возвращает: true, когда флаг «экран включён» применён для этого документа. Требуется: ничего включать не нужно.

Используйте публичный API:

let lock = null;
async function keepScreenOn() {
  if (!('wakeLock' in navigator)) return;
  lock = await navigator.wakeLock.request('screen');
  lock.addEventListener('release', () => { lock = null; });
}

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

let lock = null;
let wantLock = false;

async function acquire() {
  if (!('wakeLock' in navigator)) return;
  try {
    lock = await navigator.wakeLock.request('screen');
    lock.addEventListener('release', () => { lock = null; });
  } catch (e) {
    lock = null;                       // NotAllowedError: the page was not visible
  }
}

function startRecipeMode() { wantLock = true; acquire(); }
function stopRecipeMode()  { wantLock = false; if (lock) lock.release(); }

document.addEventListener('visibilitychange', () => {
  if (wantLock && !lock && document.visibilityState === 'visible') acquire();
});

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

async function runTimer(seconds) {
  const lock = ('wakeLock' in navigator)
    ? await navigator.wakeLock.request('screen').catch(() => null)
    : null;
  try {
    for (let left = seconds; left > 0; left--) {
      showRemaining(left);
      await new Promise((r) => setTimeout(r, 1000));
    }
  } finally {
    if (lock && !lock.released) await lock.release();
  }
}

Примечания: приложение снимает все блокировки, когда уходит в фон, и вызывает appmint:wakelock-lost, что освобождает sentinel (срабатывает их событие release).

closeWindow мост#

window.WebToApk.closeWindow()

Используйте обёртку на промисах: const r = await AppMint.requestReview(); // {result, openedStore} const r = await AppMint.requestReview({ openStore: true }); // страница в магазине, если окна нет r.result: 'shown' | 'not_from_play' | 'quota' | 'error'. См. RatePrompt.

Описано в своей группе, Окно оценки Google Play внутри приложения, а не отдельно.

Пример

Закрывает главный экран приложения (то же, что нажатие «Назад» на последней странице). Страницы обычно вызывают стандартный window.close(), который оболочка направляет сюда.

Возвращает: ничего. Требуется: ничего.

document.getElementById('quit').onclick = function () {
  if (confirm('Close the app?')) window.close();   // in the app: the screen closes
};

Низкоуровневый вызов делает то же самое:

if (window.WebToApk && typeof window.WebToApk.closeWindow === 'function') {
  window.WebToApk.closeWindow();
}

Кнопка «Выход», которая сначала сохраняет данные и скрыта в браузере, где нет экрана приложения, который можно закрыть:

var exitBtn = document.getElementById('exit-btn');

if (!(window.WebToApk && typeof window.WebToApk.closeWindow === 'function')) {
  exitBtn.hidden = true;
} else {
  exitBtn.onclick = async function () {
    exitBtn.disabled = true;
    await saveDraft();                 // your own save; closing does not wait for anything
    window.WebToApk.closeWindow();
  };
}

Примечания: она сразу закрывает экран приложения — без подтверждения выхода, даже если оно есть в сборке. Сначала сохраните всё несохранённое. В браузере window.close() закрывает только вкладку, которую открыл сам скрипт.

enterPip мост#

window.WebToApk.enterPip(): Boolean

Сразу сворачивает всё приложение в плавающее окно «картинка в картинке» — видео или звонок, который пользователь хочет продолжать смотреть, уходя из приложения. Android 8+ и только если в сборке включён PiP (нужный флаг в манифесте); возвращает, открылось ли окно. При включённом PiP выход из приложения делает это сам; это явная кнопка «свернуть».

Пример

Сразу сворачивает всё приложение в небольшое плавающее окно «картинка в картинке» — кнопка «свернуть» для видео или звонка.

Возвращает: true, если плавающее окно открылось, иначе false (синхронно). Требуется: включённый «Картинка в картинке» (PiP) на шаге 3 (Интеграция), в разделе Параметры отображения, при сборке. Android 8+.

var pipBtn = document.getElementById('pip-btn');
pipBtn.onclick = function () {
  if (!(window.WebToApk && typeof window.WebToApk.enterPip === 'function')) return;
  var ok = window.WebToApk.enterPip();
  if (!ok) pipBtn.hidden = true;   // PiP not enabled in this build, or Android 7 and older
};

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

window.addEventListener('resize', function () {
  var small = window.innerWidth < 300;
  document.querySelector('nav').style.display = small ? 'none' : '';
});

Отслеживайте открытие и закрытие плавающего окна по событию appmint:pip (detail.inPip), например чтобы скрыть элементы управления звонком:

window.addEventListener('appmint:pip', function (e) {
  var inPip = !!(e.detail && e.detail.inPip);
  document.body.classList.toggle('pip', inPip);   // CSS: .pip .call-controls { display: none; }
  if (!inPip) {
    document.getElementById('chat-panel').hidden = false;   // back to the full-size screen
  }
});

document.getElementById('minimise').onclick = function () {
  if (window.WebToApk && typeof window.WebToApk.enterPip === 'function') {
    document.getElementById('chat-panel').hidden = true;
    window.WebToApk.enterPip();
  }
};

Примечания: при включённом PiP нажатие «Домой» само переводит приложение в плавающее окно; этот метод нужен только для явной кнопки. В сборке без переключателя PiP возвращает false, потому что нужный флаг в манифесте отсутствует. Размер окна PiP выбирает Android. Приложения, созданные с помощью ИИ, могут использовать pip из @/lib/appmintNative.

isFullscreen мост#

window.WebToApk.isFullscreen(): Boolean

Позволяет странице самой скрыть строку состояния (часы/батарея) и панель навигации — например, когда игра переходит на игровой экран: WebToApk.setFullscreen(true) // скрыть системные панели WebToApk.setFullscreen(false) // вернуть их WebToApk.toggleFullscreen() WebToApk.isFullscreen() Выбор запоминается между запусками. В приложениях, которые автор уже заблокировал в полноэкранном режиме/режиме киоска, ничего не происходит — восстанавливать там нечего. Пункт бокового меню «Полный экран» — аналог для конечных пользователей, он зависит от опции автора enableFullscreenToggle; этот мост всегда доступен собственной странице приложения.

Описано в своей группе, Мост полноэкранного режима, а не отдельно.

Пример

Сообщает, скрыты ли сейчас строка состояния и панель навигации Android.

Возвращает: true / false синхронно. Требуется: ничего.

function barsHidden() {
  if (window.WebToApk && typeof window.WebToApk.isFullscreen === 'function') {
    return window.WebToApk.isFullscreen();
  }
  return false;   // in a browser
}

if (barsHidden()) {
  document.getElementById('fs-btn').textContent = 'Exit fullscreen';
}

Разметьте страницу под запомненный выбор при запуске, так как он переживает перезапуски:

document.addEventListener('DOMContentLoaded', function () {
  var hidden = !!(window.WebToApk && typeof window.WebToApk.isFullscreen === 'function' &&
                  window.WebToApk.isFullscreen());
  document.documentElement.classList.toggle('bars-hidden', hidden);
  // CSS: .bars-hidden header { padding-top: env(safe-area-inset-top); }
});

Добавьте кнопку «Показать панели», если приложение собрано в полноэкранном режиме и панели не могут вернуться:

function showBars() {
  var W = window.WebToApk;
  if (!(W && typeof W.isFullscreen === 'function')) return;
  W.setFullscreen(false);
  setTimeout(function () {             // the change is applied on the UI thread
    if (W.isFullscreen()) {
      document.getElementById('bars-btn').hidden = true;
    }
  }, 100);
}

Примечания: возвращает true, когда автор собрал приложение с Полным экраном или Иммерсивным режимом киоска, ИЛИ когда страница вызвала setFullscreen(true) / toggleFullscreen(). В первом случае setFullscreen(false) не может вернуть панели.

isSecureScreen мост#

window.WebToApk.isSecureScreen(): Boolean

WebToApk.setSecureScreen(true) // запретить скриншоты на ЭТОЙ странице WebToApk.setSecureScreen(false) // снять защиту раньше срока WebToApk.isSecureScreen() // -> boolean Защита относится к странице, которая её запросила. Она снимается в момент перехода приложения куда-либо ещё — новый документ, маршрут pushState, маршрут #hash — поэтому защищённый экран не может заблокировать остальное приложение, а странице, которой нужна защита после перезагрузки, достаточно запросить её снова. Что она делает: пока включена, прекращаются скриншоты, запись экрана, трансляция и миниатюра в списке недавних. Чего она не может: помешать камере, направленной на экран. Этого не может ничто на Android, и этот мост не будет делать вид, что может.

Описано в своей группе, Защита от захвата экрана, а не отдельно.

Пример

Сообщает, включена ли сейчас защита от скриншотов (setSecureScreen).

Возвращает: boolean сразу. Требуется: ничего.

function updateLockIcon() {
  var on = !!window.WebToApk &&
    typeof window.WebToApk.isSecureScreen === 'function' &&
    window.WebToApk.isSecureScreen();
  document.getElementById('shield').hidden = !on;
}

Проверьте перед показом номера карты и сначала включите защиту, если эта страница ещё её не запрашивала:

function revealCardNumber(full) {
  var W = window.WebToApk;
  if (W && typeof W.isSecureScreen === 'function' && !W.isSecureScreen()) {
    W.setSecureScreen(true);   // the new state is committed when this returns
  }
  document.getElementById('card-number').textContent = full;
}

Переключатель «Скрыть от скриншотов», который берёт реальное состояние и переключает его:

var toggle = document.getElementById('privacy-toggle');
var W = window.WebToApk;

if (!W || typeof W.isSecureScreen !== 'function') {
  toggle.hidden = true;
} else {
  toggle.checked = W.isSecureScreen();
  toggle.addEventListener('change', function () {
    toggle.checked = W.setSecureScreen(!W.isSecureScreen());
  });
}

Примечания: она сама возвращается к false, когда приложение переходит на другую страницу или маршрут, потому что защита относится к странице, которая её запросила.

setFullscreen мост#

window.WebToApk.setFullscreen(enabled: Boolean)

Позволяет странице самой скрыть строку состояния (часы/батарея) и панель навигации — например, когда игра переходит на игровой экран: WebToApk.setFullscreen(true) // скрыть системные панели WebToApk.setFullscreen(false) // вернуть их WebToApk.toggleFullscreen() WebToApk.isFullscreen() Выбор запоминается между запусками. В приложениях, которые автор уже заблокировал в полноэкранном режиме/режиме киоска, ничего не происходит — восстанавливать там нечего. Пункт бокового меню «Полный экран» — аналог для конечных пользователей, он зависит от опции автора enableFullscreenToggle; этот мост всегда доступен собственной странице приложения.

Описано в своей группе, Мост полноэкранного режима, а не отдельно.

Пример

Скрывает (true) или показывает (false) строку состояния и панель навигации Android, например когда игра переходит на игровой экран.

Возвращает: ничего. Требуется: ничего — он всегда доступен вашей странице. Выбор запоминается между запусками.

function enterPlayScreen() {
  if (window.WebToApk && typeof window.WebToApk.setFullscreen === 'function') {
    window.WebToApk.setFullscreen(true);    // hide clock/battery and the nav bar
  }
}
function leavePlayScreen() {
  if (window.WebToApk && window.WebToApk.setFullscreen) {
    window.WebToApk.setFullscreen(false);   // bring the bars back
  }
}

Не размещайте содержимое под вырезом камеры, когда панели скрыты:

document.body.style.paddingTop = 'env(safe-area-inset-top)';
document.body.style.paddingBottom = 'env(safe-area-inset-bottom)';

Скрывайте панели, пока играет видео, и возвращайте их при паузе или окончании:

var video = document.getElementById('movie');

function hideBars(hidden) {
  if (window.WebToApk && typeof window.WebToApk.setFullscreen === 'function') {
    window.WebToApk.setFullscreen(hidden);
  }
}

video.addEventListener('play', function () { hideBars(true); });
video.addEventListener('pause', function () { hideBars(false); });
video.addEventListener('ended', function () { hideBars(false); });

Примечания: ничего не делает в приложении, собранном с включённым Полным экраном (или Иммерсивным режимом киоска) на шаге 3 (Интеграция) — автор зафиксировал этот выбор. Скрывает только панели Android; собственная верхняя панель приложения (с кнопкой бокового меню) — это переключатель Показывать верхнюю панель. При включении оболочка добавляет viewport-fit=cover в ваш viewport meta, чтобы страница разворачивалась на весь экран. См. toggleFullscreen и isFullscreen. Приложения, созданные с помощью ИИ, могут использовать fullscreen из @/lib/appmintNative.

setSecureScreen мост#

window.WebToApk.setSecureScreen(enabled: Boolean): Boolean

Устанавливает защиту для ТЕКУЩЕЙ страницы и возвращает действующее состояние. Сам флаг окна применяется в UI-потоке, но возвращаемое состояние уже зафиксировано, поэтому страница может вызвать это и сразу показать своё конфиденциальное содержимое, не дожидаясь обратного вызова.

Пример

Блокирует скриншоты, запись экрана, трансляцию и предпросмотр в списке недавних, пока ТЕКУЩАЯ страница показывает что-то личное.

Возвращает: boolean сразу — действующее состояние (переданное вами значение). Личное содержимое можно показывать сразу после вызова. Требуется: ничего.

function showStatement() {
  if (window.WebToApk && typeof window.WebToApk.setSecureScreen === 'function') {
    window.WebToApk.setSecureScreen(true);
  }
  render(statementView);
}

Снимайте защиту сами, только когда скрываете личное содержимое БЕЗ перехода на другую страницу (закрывается модальное окно, переключается вкладка внутри одной страницы):

function closeCardDetails() {
  hide(cardDetailsModal);
  if (window.WebToApk && typeof window.WebToApk.setSecureScreen === 'function') {
    window.WebToApk.setSecureScreen(false);
  }
}

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

function revealRecoveryCode(code) {
  var W = window.WebToApk;
  if (!(W && typeof W.setSecureScreen === 'function')) {
    alert('Open the app to see your recovery code.');
    return;
  }
  if (W.setSecureScreen(true)) {
    document.getElementById('recovery-code').textContent = code;
  }
}

Примечания: защита относится к странице, которая её запросила. Она снимается, как только приложение переходит куда-либо ещё — новая страница, маршрут pushState или #hash, — так что при выходе ничего чистить не нужно, а после перезагрузки страница должна запросить её снова. Не применяйте её повторно по таймеру: флаг должен быть включён уже в тот момент, когда пользователь нажимает кнопки скриншота. Ничто не может помешать камере, направленной на экран.

setStatusBarColor мост#

window.WebToApk.setStatusBarColor(color: String, style: String): Boolean

Красит панели состояния и навигации в [color] и выбирает оттенок значков: [style] 'light' — светлые значки на тёмной панели, 'dark' — наоборот, '' — решает яркость. [color] — это `#RGB`, `#RGBA`, `#RRGGBB`, `#AARRGGBB` (альфа ПЕРВОЙ — порядок CSS `#RRGGBBAA` тоже читается как альфа-первый) или базовое название цвета ('black', 'navy'…); `rgb()`/`hsl()` не читаются. Возвращает false, если цвет не разобран.

Пример

Красит строку состояния Android в цвет и выбирает светлые или тёмные значки на ней.

Возвращает: true или false, если цвет не удалось разобрать (синхронно). Требуется: ничего.

style это 'light' (светлые значки, для тёмной панели), 'dark' (тёмные значки, для светлой панели) или '' (оболочка выбирает по яркости цвета):

function paintBar(color, style) {
  if (window.WebToApk && typeof window.WebToApk.setStatusBarColor === 'function') {
    return window.WebToApk.setStatusBarColor(color, style);
  }
  return false;   // in a browser
}

paintBar('#0B0F1A', 'light');   // dark bar, light icons
paintBar('#FFFFFF', 'dark');    // white bar, dark icons
paintBar('#0E7C86', '');        // icons chosen automatically

Подбирайте цвет панели под каждый экран:

function showScreen(name) {
  var colors = { home: '#0B0F1A', settings: '#FFFFFF' };
  paintBar(colors[name] || '#0B0F1A', name === 'settings' ? 'dark' : 'light');
}

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

var darkTheme = window.matchMedia('(prefers-color-scheme: dark)');

function matchTheme() {
  if (!(window.WebToApk && typeof window.WebToApk.setStatusBarColor === 'function')) return;
  if (darkTheme.matches) {
    window.WebToApk.setStatusBarColor('#121212', 'light');
  } else {
    window.WebToApk.setStatusBarColor('#F5F5F5', 'dark');
  }
}

matchTheme();
darkTheme.addEventListener('change', matchTheme);

Примечания: цвета — это строки цветов Android: #RRGGBB или #AARRGGBB (и названия вроде red). CSS-формы вроде rgb(...) или #RGB возвращают false. Работает и на Android 15+, где плагин StatusBar от Capacitor не может красить панель. Пока панели скрыты (полный экран), цвет сохраняется до их возвращения. Приложения, созданные с помощью ИИ, могут использовать setStatusBar из @/lib/appmintNative.

splashHold мост#

window.WebToApk.splashHold()

Удерживает фирменную стартовую заставку, пока не будет вызван [splashReady]. По умолчанию заставка скрывается, как только у тела страницы появляются дочерние элементы с разметкой, что для приложения, рисующего скелет и затем загружающего первый экран, означает «заставка, затем скелет». Приложение, которое хочет показать готовый первый экран, вызывает это при импорте, а [splashReady] — когда экран готов. Ограничено той же страховкой в 6 с, что и проверка отрисовки, поэтому страница, которая так и не сообщила о готовности, всё равно откроется.

Пример

Удерживает стартовую заставку приложения на экране, пока вы не вызовете splashReady(), — чтобы пользователь видел готовый первый экран, а не «заставку, затем скелет загрузки».

Возвращает: ничего. Требуется: ничего.

Вызывайте как можно раньше (в начале первого скрипта), затем вызовите splashReady(), когда первый экран нарисован:

if (window.WebToApk && typeof window.WebToApk.splashHold === 'function') {
  window.WebToApk.splashHold();
}

loadFirstScreen()                      // your own async start-up (fetch data, render)
  .catch(function (err) { showError(err); })
  .then(function () {
    if (window.WebToApk && window.WebToApk.splashReady) window.WebToApk.splashReady();
  });

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

if (window.WebToApk && typeof window.WebToApk.splashHold === 'function') {
  window.WebToApk.splashHold();
}

function releaseSplash() {
  if (window.WebToApk && window.WebToApk.splashReady) window.WebToApk.splashReady();
}

var hero = document.getElementById('hero');
if (hero.complete) {
  releaseSplash();
} else {
  hero.addEventListener('load', releaseSplash);
  hero.addEventListener('error', releaseSplash);   // a broken image must not keep the splash up
}

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

var signedIn = !!localStorage.getItem('session');

if (signedIn && window.WebToApk && typeof window.WebToApk.splashHold === 'function') {
  window.WebToApk.splashHold();        // the dashboard needs its data before it looks right
}

if (signedIn) {
  loadDashboard().finally(function () {
    if (window.WebToApk && window.WebToApk.splashReady) window.WebToApk.splashReady();
  });
} else {
  showLoginScreen();                   // paints at once; the splash hides by itself
}

Примечания: без splashHold() заставка скрывается, как только у тела страницы появляются видимые дочерние элементы. Удерживаемая заставка всё равно скрывается сама через несколько секунд (та же страховка, что и у обычной проверки отрисовки), поэтому страница, которая так и не вызвала splashReady(), всё равно откроется. Всегда вызывайте splashReady() и в ветке ошибки. Минимальное время заставки, заданное автором, по-прежнему действует. Приложения, созданные с помощью ИИ, могут использовать splash из @/lib/appmintNative.

splashReady мост#

window.WebToApk.splashReady()

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

Пример

Сообщает оболочке, что ваш первый экран готов, и стартовая заставка скрывается сейчас.

Возвращает: ничего. Требуется: ничего. Можно вызывать несколько раз, безвредно, если ничего не удерживалось.

Работает в паре с splashHold():

if (window.WebToApk && window.WebToApk.splashHold) window.WebToApk.splashHold();

async function start() {
  try {
    var res = await fetch('/api/today');
    renderToday(await res.json());
  } catch (e) {
    renderOfflineMessage();
  } finally {
    if (window.WebToApk && typeof window.WebToApk.splashReady === 'function') {
      window.WebToApk.splashReady();   // hide the splash in every case
    }
  }
}
start();

Покажите экран только после того, как браузер отрисовал только что созданное (два кадра анимации):

renderApp(document.getElementById('root'));

requestAnimationFrame(function () {
  requestAnimationFrame(function () {
    if (window.WebToApk && typeof window.WebToApk.splashReady === 'function') {
      window.WebToApk.splashReady();
    }
  });
});

Дождитесь нескольких стартовых задач и скройте заставку, даже если одна из них завершилась ошибкой:

if (window.WebToApk && window.WebToApk.splashHold) window.WebToApk.splashHold();

Promise.allSettled([
  loadProfile(),          // your own start-up requests
  loadFeed(),
  document.fonts.ready    // no flash of the wrong font
]).then(function (results) {
  results.forEach(function (r) { if (r.status === 'rejected') console.warn(r.reason); });
  if (window.WebToApk && typeof window.WebToApk.splashReady === 'function') {
    window.WebToApk.splashReady();
  }
});

Примечания: минимальная длительность заставки, заданная автором, по-прежнему соблюдается. Если вы так и не вызвали splashHold(), заставка скрывается сама, когда страница отрисована, а splashReady() ничего дополнительно не делает.

toggleFullscreen мост#

window.WebToApk.toggleFullscreen()

Позволяет странице самой скрыть строку состояния (часы/батарея) и панель навигации — например, когда игра переходит на игровой экран: WebToApk.setFullscreen(true) // скрыть системные панели WebToApk.setFullscreen(false) // вернуть их WebToApk.toggleFullscreen() WebToApk.isFullscreen() Выбор запоминается между запусками. В приложениях, которые автор уже заблокировал в полноэкранном режиме/режиме киоска, ничего не происходит — восстанавливать там нечего. Пункт бокового меню «Полный экран» — аналог для конечных пользователей, он зависит от опции автора enableFullscreenToggle; этот мост всегда доступен собственной странице приложения.

Описано в своей группе, Мост полноэкранного режима, а не отдельно.

Пример

Переключает панели состояния и навигации Android между скрытым и показанным состоянием.

Возвращает: ничего. Требуется: ничего. Новое состояние запоминается между запусками.

Кнопка «на весь экран», которая заодно обновляет собственную подпись:

var btn = document.getElementById('fs-btn');
if (window.WebToApk && typeof window.WebToApk.toggleFullscreen === 'function') {
  btn.onclick = function () {
    window.WebToApk.toggleFullscreen();
    // The change is applied on the UI thread; read the state a moment later.
    setTimeout(function () {
      btn.textContent = window.WebToApk.isFullscreen() ? 'Exit fullscreen' : 'Fullscreen';
    }, 100);
  };
} else {
  btn.hidden = true;   // in a browser
}

Двойное касание по фото или режиму чтения скрывает или показывает панели:

var viewer = document.getElementById('photo-viewer');

viewer.addEventListener('dblclick', function () {
  if (window.WebToApk && typeof window.WebToApk.toggleFullscreen === 'function') {
    window.WebToApk.toggleFullscreen();
  }
});

Переключайте клавишей F на аппаратной клавиатуре, но не пока пользователь вводит текст в поле:

document.addEventListener('keydown', function (e) {
  if (e.key !== 'f' || e.target.closest('input, textarea, [contenteditable]')) return;
  if (window.WebToApk && typeof window.WebToApk.toggleFullscreen === 'function') {
    e.preventDefault();
    window.WebToApk.toggleFullscreen();
  }
});

Примечания: ничего не делает в приложении, собранном с включённым Полным экраном или Иммерсивным режимом киоска (панели остаются скрытыми). Пункт бокового меню «Полный экран» для конечных пользователей — отдельный переключатель сборки (Кнопка полного экрана на шаге 3, в разделе Параметры отображения); этот метод моста работает и без него.