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

JavaScript bridge API

Виджет главного экрана

Небольшая карточка Android на главном экране, которую ваша страница заполняет заголовком, значением и несколькими строками — AppMint.widget.

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

AppMint.widget.set(card) / update(partial) / clear() / placedCount() / requestPin()

Пример

Заполняет виджет главного экрана вашего приложения: нативную карточку с заголовком, одним крупным значением, подписью, до двенадцати строк (у каждой при желании свой цвет, маркер и маршрут по нажатию), вашими цветами и маршрутом, который открывается по нажатию. Когда пользователь увеличивает виджет, он показывает больше строк, а с textSize: 'auto' текст растёт, заполняя его, — пока каждая строка помещается, при этом длинная строка занимает две строки, а не обрезается. Виджет не может показывать HTML — вы отправляете ему текст.

Возвращает: каждый метод возвращает Promise. set(card) → {ok: true, placed} (placed = сколько копий на главном экране) или {ok: false, error}. clear() → undefined. placedCount() → число. requestPin() → true, если окно лаунчера «Добавить» открылось. Требуется: ничего — виджет есть в каждой сборке.

Задайте карточку — один раз при запуске и снова каждый раз, когда меняются показываемые в ней данные:

async function updateWidget(events) {
  if (!(window.AppMint && AppMint.widget)) return;          // in a browser
  var r = await AppMint.widget.set({
    title: 'Today',
    value: events.length + ' events',
    lines: events.slice(0, 6).map(function (ev) { return ev.time + ' ' + ev.name; }),
    route: '/calendar',      // a tap opens the app at this route
    bg: '#0E7C86',           // background
    ink: '#FFFFFF',          // title and lines
    accent: '#FFE08A'        // the big value
  });
  if (!r.ok) console.log('Widget not updated: ' + r.error);
}

Повестка, заполняющая виджет — прозрачный фон, текст, растущий при изменении размера виджета, цветные строки с маркером, просроченная строка красным и нажатие на строку, открывающее этот день:

AppMint.widget.set({
  title: 'Agenda',
  value: 'Sun 18 Oct',
  caption: '5 events, 1 overdue',          // small text under the value
  bg: 'rgba(20, 38, 74, 0.6)',             // 60 % opaque — the wallpaper shows through
  ink: '#FFFFFF', accent: '#FFE08A',
  textSize: 'auto',                        // 'auto' | 'small' | 'normal' | 'large' | 'xl' | a number 0.6–3
  align: 'start',                          // 'start' (left) | 'center' | 'end'
  lines: [
    { text: '09:00 Stand-up', marker: '#29B6F6', route: '/#/day/2026-10-18' },
    { text: '10:30 Dentist, Dr Rao', marker: '#43A047', route: '/#/day/2026-10-18' },
    { text: 'Overdue: renew the car insurance', color: '#FF5252', marker: '#FF5252', bold: true, route: '/#/tasks/overdue' },
    { text: 'Tue 20: Team lunch', route: '/#/day/2026-10-20' },
    'Wed 21: Gym'                           // a plain string is still a line
  ],
  route: '/#/today'                         // a tap anywhere else on the card
});

Строка — это текст или { text, color?, marker?, bold?, wrap?, route? }; marker ставит перед текстом цветной ●, wrap: true в одной строке (или на карточке) позволяет длинной строке занимать две строки при любом размере текста; при textSize: 'auto' строки уже так делают, а wrap: false удерживает строку в одном ряду. До шести строк показываются всегда; строки с седьмой по двенадцатую показываются, когда виджет достаточно высокий.

Более насыщенные виджеты — список, крупное число с индикатором выполнения, фоновая картинка и кнопки. Карточка рисуется так только при наличии одного из этих ключей; карточка без них выглядит как раньше:

AppMint.widget.set({
  layout: 'list',                 // 'list' | 'progress' | 'card'
  title: 'Today', subtitle: 'Tue 30 Sep',
  items: [                         // up to 5 rows; a row tap opens its own route
    { text: '09:00 Dentist', secondary: 'Dr Rao, 2nd floor', image: 'img/tooth.png', route: '/event/41' },
    { text: '13:30 Lunch with Sam', image: 'https://example.com/sam.jpg', route: '/event/42' }
  ],
  buttons: [{ label: 'Add', route: '/new' }, { label: 'Week', route: '/week' }],   // up to 2
  bgImage: 'img/widget-bg.jpg', bg: '#101820', ink: '#FFFFFF', accent: '#FFB000'
});

AppMint.widget.set({
  layout: 'progress', title: 'Steps', value: '8 214',
  progress: { value: 8214, max: 10000, label: '82% of today's goal' }
});

AppMint.widget.update({ value: '8 950', progress: { value: 8950, max: 10000 } });   // change only these keys

Собственный шрифт — положите файл шрифта в ваше приложение (.ttf или .otf, например из «Download family» Google Fonts) и укажите путь к нему, как для <img>; весь текст на карточке будет нарисован этим шрифтом:

AppMint.widget.set({
  title: 'Score', value: '1 250',
  font: 'fonts/RocknRollOne-Regular.ttf',   // a file in your app, an https address, or a data: URL
  lines: ['Level 7', { text: 'New record!', bold: true }]
}).then(function (r) { if (!r.ok) console.log(r.error); });   // e.g. "font file not found in the app: …"

Имя шрифта, например 'RocknRoll One', работать не может: виджеты рисует главный экран, а у него есть только собственные шрифты телефона — set() отвечает {ok: false, error} с таким пояснением. Этим встроенным семействам файл не нужен: 'serif', 'monospace', 'sans-serif-condensed', 'sans-serif-light', 'sans-serif-medium', 'sans-serif-black', 'casual', 'cursive', 'sans-serif-smallcaps'. Файлы WOFF / WOFF2 отклоняются (Android не умеет их читать) — используйте .ttf. С файлом шрифта карточка рисуется как картинка размером с виджет и перерисовывается при изменении его размера; нажатия на строки, ряды и кнопки и программы чтения с экрана работают как на любой карточке.

Обновление при закрытом приложении — укажите адрес https, возвращающий тот же JSON; Android запрашивает его каждые refreshMinutes (30 или больше, по умолчанию 60), пока виджет находится на главном экране, даже после перезапуска:

AppMint.widget.set({
  title: 'Orders', value: '…',
  refreshUrl: 'https://api.example.com/widget.json?user=42',
  refreshMinutes: 30
}).then(function (r) { console.log('refreshing every ' + r.refreshMinutes + ' min'); });
// widget.json answers e.g. {"title":"Orders","value":"12 new","layout":"list","items":[{"text":"#1042 paid"}]}

Изображения: путь внутри вашего приложения (разрешается так же, как <img src> на странице), адрес https или URL data:image/…; они уменьшаются и хранятся на телефоне. В JSON обновления относительный путь означает файл приложения. refreshUrl: '' останавливает обновление; clear() тоже его останавливает. Момент каждого обновления определяет Android (он объединяет фоновую работу, чтобы экономить заряд).

Кнопка «Добавить на главный экран»:

async function addWidget() {
  if (!(window.AppMint && AppMint.widget)) return;
  if ((await AppMint.widget.placedCount()) > 0) { alert('The widget is already on your home screen'); return; }
  var opened = await AppMint.widget.requestPin();
  if (!opened) alert('Touch and hold your home screen, tap Widgets, and pick this app.');
}

Очистите его (например, после выхода пользователя из аккаунта):

if (window.AppMint && AppMint.widget) AppMint.widget.clear();   // shows "Nothing to show yet"

Нажатие на виджет открывает приложение со ссылкой запуска appmint-shortcut://<package><route> — читайте её через WebToApk.getLaunchUrl() при запуске и событие appmint:deep-link во время работы:

window.addEventListener('appmint:deep-link', function (e) {
  var m = /^appmint-shortcut:\/\/[^\/?#]*([^#]*)/i.exec(e.detail.url);
  if (m) goTo(m[1] || '/');
});

Цвета: #RRGGBB, #RGB, rgb(20, 38, 74), rgba(20, 38, 74, 0.6) (также rgb(20 38 74 / 60%)), transparent и простые названия, например red. Восьмизначный hex задаётся в порядке Android, альфа-канал ПЕРВЫМ: #AARRGGBB. #9914264A — это 60 % непрозрачности; CSS-запись #14264A99 здесь читается как альфа 14 (8 %), почти невидимая карточка. Для прозрачных фонов rgba() избавляет от путаницы. Цвета линий (color, marker) принимают те же формы; неверный цвет линии отклоняется с {ok: false, error: 'lines[2].color is not a colour'}, а неверный цвет карточки заменяется цветом по умолчанию.

Примечания: Все поля карточки необязательны: пустой title показывает название приложения; пустые value, caption или строки скрываются; строки после двенадцатой отбрасываются. textSize и align применяются ко всему тексту на карточке; неизвестное значение отклоняется с {ok: false, error}. Карточка без новых ключей (caption, textSize, align, wrap, оформленные строки) выглядит как раньше. Виджет хранит последнюю карточку, даже после перезапуска, и меняется только тогда, когда ваша страница снова вызывает set(). requestPin() требует Android 8+ и лаунчер с поддержкой этой функции. Приложения, созданные с помощью ИИ, могут использовать widget из @/lib/appmintNative. Оформленные строки, caption, textSize, align, wrap и rgba() требуют приложения, собранного в AppMint 1.11.38 или новее — пересоберите приложение, прежде чем отправлять объекты строк, иначе старая сборка покажет их как сырой текст. font требует приложения, собранного в AppMint 1.11.41 или новее (старая сборка его игнорирует). Приложения в режиме сайта: нажатие открывает маршрут на вашем сайте — /#/today загружает https://<your site>/#/today — а getLaunchUrl() по-прежнему возвращает ссылку appmint-shortcut://.

widgetClear мост#

window.WebToApk.widgetClear()

Удаляет карточку; размещённые виджеты возвращаются в пустое состояние.

Пример

Внутренний механизм для AppMint.widget.clear() — удаляет карточку виджета; все размещённые виджеты возвращаются к состоянию «Пока нечего показывать».

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

Сначала публичный API:

async function onSignOut() {
  if (window.AppMint && AppMint.widget) await AppMint.widget.clear();
}

Прямой вызов:

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

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

document.getElementById('widget-toggle').onchange = async function (e) {
  if (!(window.AppMint && AppMint.widget)) return;
  if (e.target.checked) {
    await AppMint.widget.set(buildWidgetCard()); // your own card builder
  } else {
    await AppMint.widget.clear();
  }
};

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

function afterAccountDeleted() {
  var W = window.WebToApk;
  if (!W || typeof W.widgetClear !== 'function') return;
  W.widgetClear();
  if (W.widgetPlacedCount() > 0) {
    showMessage('Your widget is now empty. You can remove it from your home screen.');
  }
}

Примечания: Виджет остаётся на главном экране; удаляется только его содержимое. Вызовите AppMint.widget.set(), чтобы заполнить его снова.

widgetPlacedCount мост#

window.WebToApk.widgetPlacedCount(): Int

Сколько копий виджета пользователь разместил на главном экране.

Пример

Внутренний механизм для AppMint.widget.placedCount() — сколько копий вашего виджета пользователь разместил на главном экране.

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

Сначала публичный API — показывайте кнопку «Добавить виджет» только тогда, когда виджет ещё не размещён:

async function showAddWidgetButton() {
  var btn = document.getElementById('add-widget');
  if (!(window.AppMint && AppMint.widget)) { btn.hidden = true; return; }
  btn.hidden = (await AppMint.widget.placedCount()) > 0;
}

Прямой вызов:

if (window.WebToApk && typeof window.WebToApk.widgetPlacedCount === 'function') {
  var n = window.WebToApk.widgetPlacedCount();
  console.log('Widgets on the home screen: ' + n);
}

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

document.addEventListener('visibilitychange', async function () {
  if (document.visibilityState !== 'visible') return;
  if (!(window.AppMint && AppMint.widget)) return;
  var n = await AppMint.widget.placedCount();
  document.getElementById('widget-promo').hidden = n > 0;
  if (n > 0) document.getElementById('widget-thanks').hidden = false;
});

Покажите состояние виджета на странице настроек синхронным низкоуровневым вызовом:

var status = document.getElementById('widget-status');
if (window.WebToApk && typeof window.WebToApk.widgetPlacedCount === 'function') {
  var n = window.WebToApk.widgetPlacedCount();
  status.textContent = n === 0 ? 'Not on your home screen'
    : n === 1 ? 'On your home screen' : 'On your home screen ' + n + ' times';
} else {
  status.textContent = 'Available in the installed app';
}

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

widgetRequestPin мост#

window.WebToApk.widgetRequestPin(): Boolean

Открывает окно лаунчера «Добавить на главный экран» для виджета (Android 8+). Возвращает false там, где лаунчер этого не умеет.

Пример

Внутренний механизм для AppMint.widget.requestPin() — открывает окно лаунчера «Добавить на главный экран» для вашего виджета.

Возвращает: true, если окно открылось, false, если лаунчер этого не умеет (синхронно). Требуется: Android 8+ и лаунчер с поддержкой закрепления виджетов.

Сначала публичный API, по нажатию кнопки:

document.getElementById('add-widget').onclick = async function () {
  if (!(window.AppMint && AppMint.widget)) return;
  var opened = await AppMint.widget.requestPin();
  if (!opened) {
    alert('Touch and hold your home screen, tap Widgets, then pick this app.');
  }
};

Прямой вызов:

if (window.WebToApk && typeof window.WebToApk.widgetRequestPin === 'function') {
  if (!window.WebToApk.widgetRequestPin()) showManualSteps();
}

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

document.getElementById('add-widget').onclick = async function () {
  if (!(window.AppMint && AppMint.widget)) return;
  await AppMint.widget.set({ title: 'Streak', value: '12 days', lines: ['Best 30'], route: '/streak' });
  if (!(await AppMint.widget.requestPin())) {
    document.getElementById('widget-help').hidden = false; // manual steps
  }
};

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

document.getElementById('add-widget').onclick = function () {
  var W = window.WebToApk;
  if (!W || typeof W.widgetRequestPin !== 'function') return;
  var before = W.widgetPlacedCount();
  if (!W.widgetRequestPin()) { showManualSteps(); return; }
  document.addEventListener('visibilitychange', function check() {
    if (document.visibilityState !== 'visible') return;
    document.removeEventListener('visibilitychange', check);
    if (W.widgetPlacedCount() > before) showToast('Widget added');
  });
};

Примечания: true означает лишь, что окно открылось — пользователь всё ещё может его отменить. Позже используйте AppMint.widget.placedCount(), чтобы узнать, был ли виджет добавлен. Сначала заполните карточку через AppMint.widget.set(), чтобы новый виджет не был пустым.

widgetSet мост#

window.WebToApk.widgetSet(cardJson: String): String

Сохраняет карточку виджета [cardJson] `{title?, value?, lines?, route?, bg?, ink?, accent?}` и перерисовывает все размещённые виджеты. Возвращает `{ok, placed}`.

Пример

Внутренний механизм для AppMint.widget.set(card) — страницы используют этот помощник, чтобы заполнять виджет главного экрана.

Возвращает: JSON-строку {"ok":true,"placed":<number>} или {"ok":false,"error":"…"} (синхронно). Требуется: ничего — виджет есть в каждой сборке.

Сначала публичный API:

if (window.AppMint && AppMint.widget) {
  AppMint.widget.set({ title: 'Steps', value: '8 214', lines: ['Goal 10 000'], route: '/' })
    .then(function (r) { if (!r.ok) console.log(r.error); });
}

Низкоуровневый вызов принимает карточку как JSON-строку и возвращает текст JSON:

if (window.WebToApk && typeof window.WebToApk.widgetSet === 'function') {
  var card = { title: 'Steps', value: '8 214', lines: ['Goal 10 000'], route: '/',
               bg: '#15161A', ink: '#FFFFFF', accent: '#22D3EE' };
  var r = JSON.parse(window.WebToApk.widgetSet(JSON.stringify(card)));
  console.log(r.ok ? 'on ' + r.placed + ' home screen(s)' : r.error);
}

Держите виджет в соответствии со списком дел — число невыполненных как значение, первые шесть задач как строки, а нажатие открывает экран задач:

async function syncTaskWidget(tasks) {
  if (!(window.AppMint && AppMint.widget)) return;
  var open = tasks.filter(function (t) { return !t.done; });
  var r = await AppMint.widget.set({
    title: 'To do',
    value: open.length ? open.length + ' left' : 'All done',
    lines: open.slice(0, 6).map(function (t) { return '• ' + t.title; }),
    route: '/tasks'
  });
  if (!r.ok) console.warn('Widget not updated: ' + r.error);
}

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

document.addEventListener('visibilitychange', function () {
  if (document.visibilityState !== 'hidden') return;
  if (!window.WebToApk || typeof window.WebToApk.widgetSet !== 'function') return;
  var s = getTodayStats(); // your app's own numbers
  window.WebToApk.widgetSet(JSON.stringify({
    title: 'Today', value: s.spent + ' spent',
    lines: ['Budget ' + s.budget, 'Left ' + (s.budget - s.spent)], route: '/budget'
  }));
});

Примечания: Ключи карточки: title, value, caption, lines (до двенадцати; строка — это текст или {text, color, marker, bold, wrap, route}), route (если нет, добавляется /), bg, ink, accent, textSize, align, wrap, font (файл .ttf/.otf в вашем приложении или системное семейство, например serif), а также ключи расширенного макета — см. AppMint.widget. Цвета: #RRGGBB, #AARRGGBB (альфа первой), rgba(). Все размещённые виджеты перерисовываются сразу. placed равно 0, если пользователь ещё не добавил виджет — карточка всё равно сохраняется и появится, когда он это сделает.

widgetUpdate мост#

window.WebToApk.widgetUpdate(partialJson: String): String

Объединяет [partialJson] с сохранённой карточкой виджета (названные ключи заменяются, остальные остаются) и перерисовывает. Возвращает `{ok, placed}`.

Пример

Изменяет часть виджета главного экрана: переданные вами ключи заменяют сохранённые, всё остальное остаётся. Обычно страницы вызывают AppMint.widget.update(partial).

Возвращает: JSON-строку {"ok":true,"placed":<n>} или {"ok":false,"error":"…"}. Требуется: ничего — виджет есть в каждой сборке.

// Only the number and the progress bar change; title, rows and buttons stay as they were.
if (window.AppMint && AppMint.widget) {
  AppMint.widget.update({ value: '7 420', progress: { value: 7420, max: 10000, label: '74% of goal' } });
}

// Raw form:
if (window.WebToApk && typeof window.WebToApk.widgetUpdate === 'function') {
  var r = JSON.parse(window.WebToApk.widgetUpdate(JSON.stringify({ subtitle: 'Updated ' + new Date().toLocaleTimeString() })));
  if (!r.ok) console.warn(r.error);
}

Очистите карточку при выходе из аккаунта и сообщите об ошибке из update:

async function onSignOut() {
  if (!(window.AppMint && AppMint.widget)) return;
  await AppMint.widget.clear();
}

async function bumpCount(n) {
  if (!(window.AppMint && AppMint.widget)) return;
  const r = await AppMint.widget.update({ value: String(n) });
  if (!r.ok) console.warn('Widget not updated: ' + r.error);
}

Совместите это с проверкой размещения: обновляйте карточку только пока копия находится на главном экране.

async function refreshWidget(stats) {
  if (!(window.AppMint && AppMint.widget)) return;
  if (await AppMint.widget.placedCount() === 0) return;
  AppMint.widget.update({ value: stats.total + ' today', subtitle: 'Updated ' + new Date().toLocaleTimeString() });
}

Примечания: Если сохранённой карточки нет, update() ведёт себя как set(). Все ключи карточки см. в AppMint.widget.