Жизненный цикл приложения, буфер обмена и openURL
Собственный жизненный цикл приложения (передний план/фон), URL, с которым его открыли (диплинки),
системный буфер обмена и открытие внешних URL. app и clipboard — обычные глобальные объекты,
ничего создавать не нужно.
Обзор
app.addEventListener('pause', () => saveGame()) // пользователь ушёл — сохраняйся сейчас
app.addEventListener('resume', () => refreshFeed())
if (app.launchUrl) openFromLink(app.launchUrl) // диплинк холодного старта
app.addEventListener('url', url => openFromLink(url)) // …и ссылки, приходящие во время работы
clipboard.write('LECODES-42')
openURL('https://le.codes/docs')Жизненный цикл: app.state, pause / resume
app.state // "active" | "background" (только чтение)
app.addEventListener('pause', () => { }) // приложение ушло с переднего плана
app.addEventListener('resume', () => { }) // приложение вернулось
app.removeEventListener('pause', callback) // та же ссылка на функцию"pause" срабатывает, когда приложение перестаёт быть приложением переднего плана — кнопка «домой»,
другое приложение сверху, скрытая вкладка браузера. Оно доставляется до того, как хост остановит
цикл кадров, так что это последний надёжный момент сохранить состояние, поставить музыку на паузу
или остановить работу, которой нельзя идти вслепую (watch у Geolocation, таймеры опроса).
"resume" срабатывает, когда кадры снова идут.
События семантические и одинаковые на каждой платформе — словарь Activity/Scene ты не видишь
никогда. app.state — та же информация в виде pull, читается в любой момент.
не рассчитывай, что код работает, пока приложение в фоне — на мобильных цикл кадров
останавливается и таймеры замирают до resume. pause — для сохранения, а не для планирования
фоновой работы.
Диплинки: app.launchUrl и событие url
app.launchUrl // string | null (только чтение)
app.addEventListener('url', (url: string) => { })launchUrl — URL, с которым приложение было открыто (в последний раз) — null при обычном запуске.
Когда ссылка приходит, пока приложение уже работает (тёплая ссылка), launchUrl обновляется на
новое значение и с ним срабатывает событие "url". Обрабатывай оба случая, именно в этом порядке:
const openFromLink = (url: string) => {
const screen = new URL(url).searchParams.get('screen')
if (screen === 'stats') router.push(StatsScreen())
}
if (app.launchUrl) openFromLink(app.launchUrl)
app.addEventListener('url', openFromLink)гейтится хостом — launchUrl читается как null, а "url" никогда не срабатывает на хостах без
понятия ссылки (headless).
Управление миром: app.restart / app.quit
app.restart(url?) // перезапустить бандл приложения в свежем мире — location.reload()
app.quit() // уйти в лаунчер LeCodes (домой) — в остальных случаях no-oprestart() перезапускает приложение с нуля: свежий мир, тот же код. Передай url, чтобы
перезапуститься с другими аргументами запуска (новый запуск читает их через app.launchUrl),
передай null, чтобы перезапуститься с очищенным, или опусти его, чтобы повторить текущий —
аргумент зеркалит роль launchUrl как «командной строки» приложения. Несохранённое состояние
пропадает, ровно как при перезагрузке страницы.
quit() возвращает пользователя в лаунчер LeCodes. В standalone-сборке лаунчера нет, поэтому он
ничего не делает (платформы запрещают программный выход) — безопасно вешать на кнопку
«назад в LeCodes» безусловно.
UIButton(UIText('Start over')).onClick(() => app.restart(null))
UIButton(UIText('Exit')).onClick(() => app.quit())Клавиатура: app.keyboardHeight и событие keyboard {#keyboard}
app.keyboardHeight // number (только чтение, логические px; 0, когда скрыта)
app.addEventListener('keyboard', (height: number, duration: number) => { })keyboardHeight — сырое перекрытие экранной клавиатуры с вьюпортом приложения, независимо от
политики keyboardShrink
сфокусированного инпута. С политикой сжатия по умолчанию layout и так избегает клавиатуры, так что
оно нужно редко; главный потребитель — режим оверлея (keyboardShrink: false), где приложение
само управляет своим инсетом. Событие срабатывает на каждое изменение фрейма клавиатуры;
duration — длительность анимации клавиатуры на платформе в мс (0, где её нет) — передай её в
animateTo, чтобы двигать UI синхронно:
// композер чата: клавиатура перекрывает UI, композер сам сдвигается вверх
input.style({ keyboardShrink: false })
app.addEventListener('keyboard', (height, duration) => {
composer.animateTo({ transform: `translateY(${-height}px)`, duration })
})гейтится хостом — читается как 0 и никогда не срабатывает там, где экранной клавиатуры нет
(macOS, десктоп, аппаратные клавиатуры).
clipboard
clipboard.write(text: string): void // выстрелил и забыл
clipboard.read(): Promise<string> // отклоняется: нет доступа / отказано / нечего читатьwrite кладёт текст в системный буфер обмена (тихий no-op там, где буфера обмена нет). read
асинхронный, потому что веб-хост гейтит его за запросом разрешения; ожидай отклонение и обрабатывай
его:
clipboard.write(inviteCode)
toast('Copied!')
try { input.value = await clipboard.read() }
catch { toast('Nothing to paste') }openURL
openURL(url: string): voidОткрой url в системном браузере / внешнем обработчике — выстрелил и забыл, как
share. Используй для «Условий использования», «Оцените нас»,
ссылок mailto:. Тихий no-op на хостах, где такого нет (headless).
UIButton('Privacy policy').onClick(() => openURL('https://le.codes/privacy'))Смотрите также
- Устройство —
device.online+ событияonline/offline(связность). - Geolocation — останавливай watch'и в
pause. - Хранилище — что сохранять, когда срабатывает
pause.