Иконка, сплэш, имя, версия

сборка≈ 15 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Прошлая статья закончилась двумя файлами для магазинов — .aab и .ipa. Прежде чем их отправлять, стоит посмотреть на то, что пользователь увидит раньше любого экрана: иконку, сплэш и имя. В конфиге это несколько строк, а prebuild нарезает из них два десятка картинок, и каждую потом режет кто-то ещё: лаунчер Android — своей маской, iOS — своим скруглением, строка состояния — до одного альфа-канала. От двух мелочей, за которые App Store Connect не примет сборку, Expo страхует сам. Остальные модерацию пройдут и испортят вид уже у пользователей. Их мы и найдём в приложении автора: растянутую иконку iOS, сплошной квадрат вместо значка уведомления, заглушку шаблона вместо сплэша, светлое приложение, которое после одной строки конфига начинает темнеть, и три объявления иконки на веб-странице. Всё, что сказано про файлы, снято с настоящего prebuild и веб-экспорта копии проекта автора (Expo SDK 57), а маски и значок уведомления проверим до телефона — миниатюрами в песочнице.

Разделы темы:·

Иконку в приложении автора задают три места конфига: общий icon, адаптивная иконка Android и значок уведомлений в параметрах плагина expo-notifications. Картинки лежат в public/ — те же, что обслуживают веб-версию, и это ещё аукнется в последней секции.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Мы выполнили npx expo prebuild --no-install на копии проекта автора и сняли размеры со всех картинок, которые легли в нативные папки. Это не вывод команды, а опись того, что появилось:

shлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Режут картинки три плагина. Иконки лаунчера — withAndroidIcons и withIosIcons из @expo/prebuild-config: это встроенные плагины, и, как мы видели в статье «Что Expo делает за вас», prebuild применяет их всегда, даже при пустом списке plugins. Значок уведомления — плагин expo-notifications. Правила у всех трёх короткие: базовый размер в dp, умноженный на плотность экрана. Соберём их миниатюрой, которая по конфигу выдаёт список файлов. Прогноз перед запуском: сколько файлов получится, какие исходники растянутся и из какого сделан круглый значок?

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Миниатюра сошлась с описью до пикселя. На Android каждая картинка — одна величина в dp, умноженная на пять плотностей: те самые dp из статьи «Размеры без пикселей: dp, проценты, PixelRatio». Лаунчер берёт файл из папки своей плотности, и на экране с плотностью 3 иконка займёт 144 пикселя. Круглый значок режется из общего icon, а не из адаптивного слоя: в исходнике legacyIcon = icon ?? foregroundImage и скругление в половину стороны, а без adaptiveIcon файла нет вовсе. Прямоугольный и круглый значки нужны только лаунчерам без адаптивных иконок, то есть Android 7.0 и 7.1 (круглый появился в 7.1). Ниже React Native 0.86 не опускается: в его libs.versions.toml стоит minSdk = "24", это и есть Android 7.0. Всё, что новее, берёт XML из mipmap-anydpi-v26 — о нём следующая секция.

На iOS файл один: начиная с Xcode 14 хватает картинки 1024 на 1024, остальные размеры Xcode нарезает сам. Исходник у автора — 512, и его растягивают вдвое. На домашнем экране это почти незаметно, иконка там в разы меньше, а в карточке App Store, где она крупная, края поплывут. Если icon не задан вовсе, плагин кладёт на это место белый квадрат, и комментарий в коде объясняет зачем: «to mitigate the chance of a submission failure to the app store». Без иконки App Store Connect сборку не примет, и белый квадрат спасает загрузку, но не вид в магазине.

И последнее: SVG сюда не годится. Все три плагина режут картинки функцией generateImageAsync из @expo/image-utils. Мы отдали ей icon.svg автора и получили Invalid mimeType for image with source: ./icon.svg. SVG остаётся вебу, нативной иконке нужен PNG.

Исходник иконки — PNG 1024×1024 в край, без своих скруглений. Меньше — растянут, SVG — не примут.

С Android 8 иконка приложения — не картинка, а два слоя по 108 на 108 dp: передний (foregroundImage) и фон (backgroundColor или backgroundImage). По 18 dp с каждой стороны документация Android отдаёт маске и эффектам вроде параллакса при касании. Остаётся середина 72 на 72 dp, и её лаунчер режет своей маской — кругом, скруглённым квадратом, «каплей», как решил производитель или пользователь. Логотипу документация велит уложиться в 66 на 66 dp. Самая тесная из обычных масок — круг диаметром 72, и углы квадрата 66 он срежет, поэтому на деле рисунок держат в круге диаметром 66. От Expo здесь только базовый размер слоя, ADAPTIVE_BASELINE_PIXEL_SIZE = 108, и файл, который связывает слои. Вот он после prebuild у автора:

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Фоновый цвет уходит в colors.xml под именем iconBackground. Не задали — плагин подставит белый: в коде стоит setBackgroundColor(backgroundColor ?? '#ffffff', …), и прозрачный передний слой ляжет на белое.

На передний слой так и просится та же картинка, что и в icon. Проверим, что из этого выйдет. Подложка рисует обе иконки автора тем же SVG, что лежит в public/icon.svg: обычную — как есть, маскируемую — с листом, уменьшенным до 200 из 512. Так он уменьшен и в настоящем icon-maskable-512.png: мы сняли с него рамку светлого листа, 200 на 250 пикселей. На экране примера каждая иконка стоит под тремя масками, а в консоль идёт расстояние от центра до самой дальней точки листа. Прогноз: какую иконку какая маска обрежет?

rn
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
экран примера

Обычная иконка переживает квадрат и скругление, но скруглённые углы листа лежат в 41,5 dp от центра, а круглая маска кончается на 36 — на экране примера круг срезает листу края вместе с углами. Маскируемая укладывается и в круг 66. Отсюда правило: передний слой — отдельная картинка, где рисунок уменьшен до безопасной зоны. У автора так и сделано. Настоящий icon-maskable-512.png мы проверили попиксельно: самая дальняя светлая точка листа — в 154,1 пикселя от центра, это 32,5 dp из 108, внутри 33. Миниатюра даёт 32,4, потому что не видит сглаживания краёв.

Маску на экране примера изображает View с overflow: "hidden" и borderRadius: он вырезает середину слоя так, как вырезала бы маска. Настоящую маску рисует лаунчер телефона, и какая она у вашего пользователя, песочница не знает — Platform.OS здесь "web". Но геометрия та же, и её хватает, чтобы увидеть срез до сборки.

Тот же файл автор отдаёт и веб-манифесту, с пометкой "purpose": "maskable" — что она значит, разбирала статья части II «Манифест и установка на экран». У PWA безопасная зона свободнее: спецификация манифеста задаёт круг радиусом 40% стороны, а у Android это 33 из 108, то есть 30,6%. Рисунок, уложенный в правило Android, проходит оба. Обратное неверно: рисунок до края зоны PWA доходит до 43,2 dp от центра, и круг Android его обрежет. Поэтому одну маскируемую иконку на две платформы рисуют по более строгому правилу.

Есть и третий слой, monochromeImage. Его берут тематические значки Android 13, которые лаунчер перекрашивает под обои. Плагин создаёт этот слой, только если поле задано. У автора его нет, в ic_launcher.xml выше нет строки <monochrome>, и перекрашивать лаунчеру нечего. Документация Android добавляет, что начиная с Android 16 QPR 2 система перекрашивает и значки без монохромного слоя — сама.

Логотип на переднем слое — в круге 66 dp из 108. Всё, что дальше, лаунчер вправе отрезать.

App Store Connect не принимает иконку 1024 с альфа-каналом: такую сборку заворачивают ещё при загрузке. Expo от этого страхует, и страхует без спроса:

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Мы прогнали generateImageAsync с этими параметрами на icon.svg автора, отрендеренном в PNG с прозрачными углами. На выходе PNG из трёх каналов, без альфы вовсе, и угловой пиксель — (255, 255, 255). Значит, логотип на прозрачном фоне получит на iOS белую подложку, а квадрат, скруглённый своими силами, — белые углы под системной маской. В icon-512.png автора они уже есть: файл непрозрачный, тёмный квадрат со скруглением 112 из 512 нарисован на белом. Останутся ли белые кромки после маски iOS, решает разница между этим скруглением и системной кривой. Гадать не придётся, если отдать квадрат в край без своих углов. Те же белые углы увидят и владельцы Android 7: там ic_launcher.webp ставится без маски, как есть.

Прозрачность сохраняется в одном случае — у тёмного варианта. С iOS 18 в ios.icon можно передать объект { light, dark, tinted }, и для dark плагин альфу не трогает: тёмному варианту она нужна, так сказано и в комментарии над этой строкой.

Значок уведомления на Android устроен наоборот. Система берёт у него только альфа-канал и красит его сама: белым в строке состояния и цветом из notification_icon_color в шторке. Автор отдал плагину icon-192.png — иконку приложения. В React Native есть ровно такое же правило — проп tintColor у Image: каждый непрозрачный пиксель становится одним цветом. Подложка рисует обоих кандидатов тем же SVG автора, а долю непрозрачных пикселей считает через холст страницы: у примера React Native холста нет. Прогноз: что напечатается и что окажется в строке состояния?

rn
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
экран примера

Иконка приложения стала сплошным квадратом: белым на тёмной строке, тёмным в светлой шторке. Настоящий icon-192.png мы проверили попиксельно: из 36 864 пикселей прозрачных ноль. Уведомление об окончании отдыха из статьи «Уведомления: планировщик, канал, нажатие» пришло бы с белым квадратом в строке состояния. Силуэт сохранил форму листа, а строки в нём прорезаны прозрачностью.

В песочнице tintColor делает SVG-фильтр react-native-web: feFlood заливает всё цветом, feComposite с operator="in" оставляет заливку только там, где у картинки есть альфа. Это правило Android, воспроизведённое браузером, а не сам Android, но правило то же. Лечится отдельным файлом для уведомлений: белый силуэт на прозрачном фоне, не меньше 96 на 96 — это размер для xxxhdpi, 24 dp на плотность 4, — в параметре icon плагина.

Иконка приложения — без прозрачности. Значок уведомления — только прозрачность и силуэт.

Сплэш рисует система, а не React Native: на iOS это storyboard из ключа UILaunchStoryboardName в Info.plist, на Android — тема окна запуска. Он появляется до первой строки вашего JS, поэтому описывается конфигом, а не компонентом. Пакет expo-splash-screen у автора установлен, но в plugins его нет. А плагин без параметров не делает ничего:

tsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Что тогда лежит в папках после prebuild? Остатки шаблона. На Android — те splashscreen_logo.png от 288 до 1152 пикселей из описи первой секции. Это заглушка шаблона, серая сетка с концентрическими кругами, и тема окна запуска ссылается прямо на неё. На iOS storyboard шаблона ждёт картинку, которой в Images.xcassets нет: там только AppIcon.appiconset. Вашего логотипа нет ни там, ни там.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Настраивается сплэш параметрами плагина. Логотип для него — отдельный прозрачный PNG: лист без тёмного квадрата.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

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

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Тема окна теперь наследует Theme.SplashScreen — системный сплэш Android 12, который библиотека AndroidX переносит и на версии постарше. Логотип лёг в холст 288 dp: в коде плагина canvasSize = 288 * multiplier, а сам логотип занимает в его середине imageWidth dp. Холст взят из требований Android: значок сплэша без своей подложки — 288 на 288 dp, и видно из него только круг диаметром 192 dp, всё снаружи система маскирует. Поэтому imageWidth больше 192 грозит той же обрезкой, что и адаптивной иконке. На iOS появились картинки 120, 240 и 360 пикселей и цвет со светлым и тёмным вариантом.

А в выводе prebuild появилась новая строка:

shлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.
вывод · терминал · настоящий запуск на копии приложения автора с плагином выше, Expo SDK 57; конец вывода
- Running prebuild
» ios: userInterfaceStyle: The existing `userInterfaceStyle` property is preventing splash screen from working properly. Remove it or disable dark mode splash screens.
- Running prebuild
» android: userInterfaceStyle: Install expo-system-ui in your project to enable this feature.
- Running prebuild
✔ Finished prebuild

Одним предупреждением дело не кончилось. В Info.plist этой копии вместо Light, которое стояло там без плагина, оказалось Automatic. Кто победил и почему, объясняет миниатюра модов из статьи «Что Expo делает за вас» — она в подложке. У автора userInterfaceStyle: "light", и ключ UIUserInterfaceStyle пишет встроенная копия плагина expo-system-ui: самого пакета в проекте нет, и prebuild применяет свою. Плагин сплэша пишет в тот же ключ. Встроенные плагины prebuild применяет после вашего списка. Прогноз: что окажется в ключе без тёмного варианта сплэша и с ним?

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Без тёмного варианта в ключе Light. С ним — предупреждение и Automatic. Встроенный плагин применён позже, значит, его мод стоит в цепочке раньше и пишет первым, а мод сплэша из вашего списка пишет последним. Настоящий Expo делает то же самое, и с EXPO_DEBUG=1 порядок виден по строкам ios.infoPlist:

shлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.
вывод · терминал · настоящий запуск на той же копии; только строки ios.infoPlist, пропуски отмечены …
ios.infoPlist: withIntrospectionBaseMods ➜ withIosBaseMods ➜ withGeneratedBaseMods ➜ withIosInfoPlistBaseMod
ios.infoPlist: withIosExpoPlugins ➜ withPlugins ➜ withStaticPlugin ➜ withVersion ➜ withInfoPlist
…
ios.infoPlist: withIosExpoPlugins ➜ withPlugins ➜ withStaticPlugin ➜ withDisplayName ➜ withInfoPlist
…
ios.infoPlist: withStaticPlugin ➜ withRunOnce ➜ withImagePicker ➜ withIosPermissions ➜ withInfoPlist
ios.infoPlist: withFallback ➜ withPlugins ➜ withStaticPlugin ➜ withIosUserInterfaceStyle ➜ withInfoPlist
ios.infoPlist: withFallback ➜ withPlugins ➜ withStaticPlugin ➜ withIosRootViewBackgroundColor ➜ withInfoPlist
…
ios.infoPlist: withStaticPlugin ➜ withRunOnce ➜ withSecureStore ➜ withIosPermissions ➜ withInfoPlist
ios.infoPlist: withSplashScreen ➜ withIosSplashScreen ➜ withPlugins ➜ withStaticPlugin ➜ withIosSplashInfoPlist ➜ withInfoPlist
ios.infoPlist: withConfigPlugins ➜ withPlugins ➜ withStaticPlugin ➜ withRouter ➜ withExpoHeadIos ➜ withInfoPlist

Сперва встроенные плагины Expo, потом пикер, который prebuild применил сам, потом withIosUserInterfaceStyle с пометкой withFallback — «своя копия вместо пакета», — и лишь в конце моды вашего списка, задом наперёд: secure-store, сплэш, роутер. В исходнике плагина сплэша рядом со строкой infoPlist.UIUserInterfaceStyle = 'Automatic' стоит честное признание: «this is fragile because the order of operations matter now». Для пользователя это значит вот что: приложение, свёрстанное только в светлой теме, получит на iOS тёмные системные элементы — клавиатуру, диалоги, выбор даты, — стоит ему включить тёмный режим. Выбор честный: либо без тёмного сплэша, либо с тёмной темой у всего приложения.

Убирает сплэш не система, а JS. Корень expo-router на старте просит сплэш подождать, но не сразу: вызов стоит в setTimeout с комментарием «This must be delayed so the user has a chance to call it first». Сам же его и прячет — в onReady навигации, на следующем кадре. При ошибке на старте сплэш тоже убирается, чтобы не закрыть собой экран ошибки. Если приложению нужно дольше — например, дочитать базу, как в статье «Первый кадр: пока хранилище читается», — сплэш придерживают сами; эффект здесь такой же, как в части IV:

rnлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

expo-splash-screen — пакет Expo, в песочнице он не соберётся, поэтому здесь только листинг. setOptions задаёт затухание: duration по умолчанию 400 мс, а fade, судя по типам пакета, действует только на iOS.

У приложения автора есть веб-версия на подпути /app, собирает её expo export --platform web — в package.json это скрипт export:web. Мы выполнили экспорт на копии проекта:

shлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.
вывод · терминал · настоящий запуск на копии приложения автора, Expo SDK 57; фрагмент, пропуски отмечены …
Using (experimental) base path: /app
Starting Metro Bundler
Static rendering is enabled. Learn more: https://docs.expo.dev/router/web/static-rendering/
…
› Static routes (13):
/ (index) (21KB)
/login (22KB)
…
Exported: dist-web
shлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.
вывод · терминал · тот же экспорт, заголовок главной страницы
<link rel="manifest" href="/app/manifest.webmanifest"/>
<link rel="apple-touch-icon" href="/app/apple-touch-icon.png"/>
<link rel="icon" type="image/png" sizes="32x32" href="/app/favicon-32.png"/>
<link rel="icon" type="image/svg+xml" href="/app/icon.svg"/>
<link rel="icon" href="/app/favicon.ico"/>

Ни icon, ни adaptiveIcon до веба не доезжают. Первые четыре ссылки автор написал сам, в своём шаблоне страницы src/app/html/html.tsx. Пятую дописал экспорт: из конфига он берёт одно поле, web.favicon — у автора это favicon-32.png. Из него получается favicon.ico на три кадра — 16, 32 и 48, мы разобрали заголовок файла, — и последний кадр растянут из 32 пикселей. Если в public/ лежит свой favicon.ico, экспорт его не трогает и ничего не генерирует. Всё прочее — манифест, apple-touch-icon.png, маскируемая иконка — скопировано из public/ байт в байт. Итог: три объявления rel="icon" на одной странице, и какое взять, браузер решает сам. Одно из двух лишнее: либо поле web.favicon, либо ручные ссылки.

Маскируемую иконку веб-версии удобно проверить глазами. В Chrome DevTools, на вкладке Application в разделе Manifest, у иконок с maskable есть флажок «Show only the minimum safe area for maskable icons». Но он показывает круг PWA, 40% стороны, а для Android, как мы выяснили, нужен более тесный — судить по нему о лаунчере нельзя.

Имя и версия — самые короткие строки конфига. Куда они уходят, разобрано в статьях «Что Expo делает за вас» и «EAS Build: сборка в облаке без Xcode»; здесь — что с ними может пойти не так.

ПолеЧто видит пользовательО чём помнить
nameПодпись под иконкой: CFBundleDisplayName, app_nameДлинное имя лаунчер обрежет многоточием. Имя в магазине задаётся отдельно, в консоли магазина
versionВерсия в карточке магазина: CFBundleShortVersionString, versionNameКогда задан EAS_PROJECT_ID, у автора включается runtimeVersion: { policy: "appVersion" }: новая версия — новый рантайм, и обновление по воздуху для 1.0.0 до сборки 1.0.1 не дойдёт. Разбор — в главе 11
Номер сборкиНикто, кроме магазинаКаждая загрузка — новый номер; у автора его ведёт EAS через autoIncrement

Сведём мелочи статьи в одну проверку перед отправкой. Из всего, что касается иконки и сплэша, отказ при загрузке грозит только за альфу в иконке iOS и за её отсутствие, и от обоих Expo страхует сам. Всё остальное пройдёт в магазин, и увидят это уже пользователи.

ЧтоЧем грозитГде проверить после prebuild
icon — PNG 1024 в край, без своих угловРастянутая или с белыми углами иконка на iOSios/…/AppIcon.appiconset/App-Icon-1024x1024@1x.png
Передний слой адаптивной иконки — рисунок в круге 66 dpСрезанные маской краяmipmap-*/ic_launcher_foreground.webp
Значок уведомления — белый силуэт на прозрачномСплошной квадрат в строке состоянияdrawable-*/notification_icon.png
expo-splash-screen с параметрамиЗаглушка шаблона вместо логотипаdrawable-*/splashscreen_logo.png, Images.xcassets
Тёмный сплэш и userInterfaceStyleПриложение на iOS молча начинает следовать тёмной темеПредупреждение в выводе prebuild, UIUserInterfaceStyle в Info.plist
web.favicon или свой favicon.icoТри объявления иконки на страницеdist-web/index.html

Иконку для карточки Google Play, 512 на 512, загружают отдельно, в консоли магазина. О ней и об остальных требованиях к карточке — статья «Магазины».

  • Из icon, адаптивной иконки и значка уведомления prebuild нарезает 21 файл: на Android — базовый размер в dp (48, 108, 24), умноженный на пять плотностей, на iOS — один PNG 1024 без альфы. Исходник должен быть PNG не меньше 1024: SVG не примут, а 512 растянут вдвое.
  • Адаптивная иконка — слои по 108 dp, лаунчер показывает середину 72 dp под своей маской, логотипу отведено 66. Передний слой — отдельная картинка с рисунком в этом круге; зона PWA свободнее, поэтому одну маскируемую иконку на обе платформы рисуют по правилу Android.
  • iOS снимает с иконки прозрачность и заливает её белым, поэтому иконку отдают квадратом в край без своих углов. Значку уведомления Android, наоборот, оставляет только альфу — это правило повторяет tintColor у Image, и непрозрачная иконка становится сплошным квадратом.
  • Сплэш — системный экран запуска, он описывается параметрами плагина expo-splash-screen; без них в приложении остаётся заглушка шаблона. Тёмный вариант сплэша переписывает UIUserInterfaceStyle на Automatic: его мод из вашего списка выполняется после встроенного. Прячет сплэш expo-router в onReady, придержать его можно preventAutoHideAsync и hideAsync.
  • Веб-экспорт берёт из конфига только web.favicon и делает favicon.ico на 16, 32 и 48, остальное копирует из public/. Имя — подпись под иконкой, версия у автора — ещё и замок для обновлений по воздуху.

1/4В конфиге заданы icon, android.adaptiveIcon и web.favicon, своего favicon.ico в public/ нет. Что из этого возьмёт expo export --platform web?

2/4Угол логотипа на переднем слое адаптивной иконки лежит в 41,5 dp от центра слоя 108 на 108. Что увидит пользователь?

3/4В icon лежит логотип на прозрачном фоне. Каким он станет на iOS?

4/4В приложении с userInterfaceStyle: "light" включили тёмный сплэш: dark: { backgroundColor: "#000000" }. Что сделает prebuild?

Уменьшить лист до безопасного круга

Из обычной иконки нужно сделать передний слой адаптивной: уменьшить лист вокруг центра так, чтобы его дальняя точка легла на край безопасного круга, 33 dp. Функция farthest из примера про маски лежит в подложке. Напишите fitScale, которая вернёт коэффициент уменьшения.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

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

tsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Печатается 0.795 · лист 203×254 · 33.0 dp. У автора в icon-maskable-512.png коэффициент 0,78125 и лист 200 на 250: до края остаётся около половины dp запаса на сглаживание и на лаунчеры, которые сдвигают слой при касании. Точно в край не рисуют.

Пять бед в одном конфиге

Коллега подготовил конфиг к первой отправке. Найдите всё, что пойдёт не так, и скажите, где каждая беда проявится: на prebuild, на загрузке в магазин или уже у пользователя.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

1. icon в SVG — упадёт prebuild: generateImageAsync отвечает «Invalid mimeType for image with source». Нужен PNG 1024×1024 в край, без своих скруглений.

2. foregroundImage — та же картинка, что иконка целиком. Рисунок не уменьшен до круга 66 dp, и у пользователя под круглой маской срежутся края. Заодно нет backgroundColor: фоновый слой станет #ffffff, и если в icon.png есть прозрачность, под ней будет белое.

3. Тёмный сплэш при userInterfaceStyle: "light" — prebuild предупредит и запишет UIUserInterfaceStyle = Automatic. Приложение на iOS начнёт следовать тёмной теме системы, хотя свёрстано только светлым. Либо убрать dark, либо поддержать тёмную тему.

4. У сплэша нет backgroundColor: светлый фон станет #ffffff — это значение по умолчанию и в getIosSplashConfig, и в getAndroidSplashConfig. Если splash.png — логотип на тёмном квадрате, у пользователя будет тёмный квадрат на белом экране.

5. Значок уведомлений — иконка приложения. Если в ней нет прозрачности, в строке состояния у пользователя будет сплошной квадрат: Android берёт у значка только альфу. Нужен белый силуэт на прозрачном фоне.

На загрузке в магазин из этого не упадёт ничего: альфу в иконке iOS Expo снимет сам. Первое ломает prebuild, остальное доедет до пользователей — поэтому такие вещи ищут в папках после prebuild, а не в отзывах.