Что нужно, чтобы собрать «По душе» для iPhone так, чтобы приложение совпадало с Android по функциям и вёрстке экранов: инвентарь кода, сравнение путей, проверенная сегодня сборка на макмини, требования Apple и России, план по этапам и вопросы владельцу.
ios/ лежит SwiftUI-клиент другого продукта, «По любви». Спека фронта DUSHA-A-17 обещала «затем iOS», папки ios-events нет. «iOS: 42 из 70 экранов» на созвоне 17.09 — это перевёрстка «По любви» (LYUBVI-39).android.graphics, нативный канвас в 8 экранах, карта MapLibre, камера селфи, выбор фото, разрешения, геолокация, окно макета. Плюс замена шести библиотек и перенос картинок, строк и шрифтов в ресурсы Compose.android-events/)| Что | Как есть |
|---|---|
| Код | 190 файлов Kotlin, 46 204 строки: экраны 25 004, кит и компоненты 10 991, данные 4 747 |
| Стек | Kotlin 2.0.21, Compose 1.7.6 (BOM 2024.12.01), AGP 8.7.3, Gradle 8.13, minSdk 31 |
| Навигация | navigation-compose 2.8.5, 54 маршрута, deep links podushe://event, podushe://user |
| Сеть | Retrofit + OkHttp, 46 ручек в двух API, 5 загрузок файлов, WebSocket чата, обновление токенов с мьютексом |
| Хранилище | DataStore Preferences 1.1.1 — уже кроссплатформенный |
| Графика | FigDraw.kt: тени и размытия Figma треугольным ядром на битмапе ALPHA_8, Paint, DST_OUT, кэш LruCache. Стекло — через GraphicsLayer + BlurEffect (общий API). Нативный канвас ещё в 8 экранах |
| Карта | MapLibre Android 11.5.2 + OpenFreeMap; маркеры и кластеры — растровые символы, нарисованные StaticLayout; снимки мини-карт MapSnapshotter; лист места на SurfaceView |
| Камера, фото, гео | CameraX (селфи), системный выбор фото, LocationManager без Google-сервисов, Photon + системный геокодер |
| Журнал | Sentry Android 8.57 + инструментация Log.* (GlitchTip, DSN пока пустой) |
| Прочее | ZXing (QR), Coil 2.7, java.time в 29 файлах, material-icons-extended подключена, но не используется ни разу |
| Ресурсы | 477 картинок (108 векторных XML из .fig, 333 PNG, 25 JPG; 104 МБ картинок макета), 6 шрифтов SF Pro, 1 542 строки |
| Тесты | 18 файлов; Paparazzi в геометрии 1170×2532 — то есть iPhone; 62 эталона; debug-экран съёмки кадров и 33 файла кадров |
| Паритет с Figma | 623 кадра, 549 зелёных по геометрии; 74 красных — растр карты (30), размытие листа регистрации (13), перевыборка фото и стекло (31) |
| Пуши | нет: ни Firebase, ни регистрации токена. Сервер умеет только FCM |
| Темп правок | 11 коммитов за последние 7 дней, параллельные сессии правят экраны ежедневно |
agent@100.85.254.75), проверено 24.09| Что | Как есть |
|---|---|
| Железо, ОС | Apple M4 Pro, 14 ядер, 64 ГБ, свободно 566 ГБ; macOS 26.5 |
| Xcode | 26.6, SDK iOS 26.5, лицензия принята; симуляторы iOS 26.3–26.5: iPhone 16e, 17e, 17, 17 Pro, Air, 16, 14 |
| Экран симулятора | iPhone 16e и 17e: 1170×2532, масштаб 3 — тот же кадр, что рендеры Figma и эталонный эмулятор Android |
| Java, Gradle | JDK 17, кэш Gradle 6,6 ГБ; Kotlin/Native до сегодняшнего дня не ставился, появился в пробной сборке (1,6 ГБ) |
| Сеть | прямой выход в интернет и DNS работают; DNS-шим больше не обязателен |
| Подпись | Apple Distribution ×2 «Doktor Svat, Ooo» (N63B7D662A), Apple Development (Вадим), 2 профиля, ключ App Store Connect API |
| Конвейер | скрипты архив → IPA → altool; «По любви» dushalove 1.0.0 (1) в TestFlight с 01.09 |
| Нет | Homebrew, CocoaPods, fastlane; xcodegen есть в ~/tools |
android-events/docs/FIG_PARITY_SPEC.md (v3.20) — закон и для iOS: те же правила К-1…К-16, тот же ритуал сравнения, тот же реестр кадров.Вёрстка Android выверена по нодам Figma и продолжает выверяться. Любой путь, где экраны iOS пишутся заново, — это второй проход всей программы паритета на другом движке.
| Путь | Совпадение вёрстки с Android | Повторное использование 46 тыс. строк | Кто пишет | Срок |
|---|---|---|---|---|
| A. Compose Multiplatform | Раскладка идентична (один код, один движок); текст ±1 px; эмодзи и отскок прокрутки платформенные | ≈85–90 % как есть, 22 файла в двух вариантах, 6 библиотек заменить | Kotlin + агенты, как сейчас | 8–12 недель |
| B. Skip | Нет: Skip переводит Swift → Kotlin или компилирует Swift под Android; UI на iOS — SwiftUI. Compose он не переносит | 0 % | Swift | месяцы, обе платформы пересверять |
| C. SwiftUI/UIKit с нуля | Нет по конструкции: Core Text, системная прокрутка, точная высота строки только с iOS 26 | 0 % | Swift, без iOS-разработчика | 8+ месяцев; опыт «По любви»: 42 из 70 экранов за недели, «оценки ненадёжны» |
| D. Flutter | Между iOS-Flutter и Android-Flutter — да; с нашим Android-Compose — нет | 0 % (Dart) | Dart | переписывание обеих платформ |
| E. React Native / KMP + SwiftUI | Нет: текст меряет платформа / UI на SwiftUI | RN 0 %; KMP — только данные | TS / Swift | как C |
| F. PWA / Capacitor | Нет (браузерный движок); риск отказа Apple по 4.2 | 0 % | Web | как C |
JetBrains объявила iOS стабильным в Compose Multiplatform 1.8 (май 2025); текущая стабильная — 1.12.1 (22.09.2026). В проде на нём целиком или почти целиком: Sony, Bitkey (95 % общего кода), Markaz (100+ экранов, порт на iOS за 28 дней), Instabee, Wrike, Feres, Respawn (96 %), Balary (карта MapLibre и видео — нативными вью).
Сейчас один Android-модуль. Станет: общий модуль с commonMain (весь UI, состояния, репозитории), androidMain (то, что и сейчас платформенное), iosMain (реализации для iOS) и папка iosApp — Xcode-проект из десятка строк Swift, генерируется xcodegen как у «По любви». Android-сборка остаётся прежней: те же флейворы, тот же APK для тестировщиков.
BasicText (тексты с режимами наложения и альфой), незагрузившийся шрифт молча заменяется системным, оптимизация текста при lineHeight + softWrap = false (ровно наш PdNodeText), пересчёт размера полей ввода.| Что | Сейчас | Станет | Объём |
|---|---|---|---|
| Картинки, векторы, шрифты | res/drawable*, res/font, painterResource(R.drawable.x) в 47 файлах | ресурсы Compose, Res.drawable.x; векторные XML Android поддерживаются как есть | 177 ссылок |
| Строки | strings.xml, stringResource в 68 файлах, числительные | Res.string.x, Res.plurals.x | 650 ссылок |
| Адреса стендов | BuildConfig по флейвору | BuildKonfig или константы по таргету; на iOS — конфигурации Xcode Dusha/Dushalove/Prod с разными bundle id | 5 флейворов |
| Даты | java.time в 29 файлах (русские месяцы, форматы) | kotlinx-datetime 0.8 со своими таблицами названий | ~60 мест |
| Локаль, ресурсы | Locale("ru") в 13 файлах | локаль и ресурсы Compose | 13 файлов |
| Навигация, ViewModel | androidx 2.8.x | те же API от JetBrains: navigation 2.9.2, lifecycle 2.11.0 | импорт |
| Узел | Android (есть) | iOS (написать) | Оценка |
|---|---|---|---|
Двигатель теней и размытий FigDraw.kt | Bitmap ALPHA_8, Paint с BlurMaskFilter и DST_OUT, LruCache | то же на Skia: org.jetbrains.skia.Bitmap, MaskFilter.makeBlur (принимает σ = R/2 напрямую), BlendMode.DST_OUT; ядро размытия — общий код | 3–5 дней |
| Нативный канвас в 8 экранах | android.graphics.Canvas, Paint с PLUS и градиентами | skiaCanvas с теми же 29 режимами наложения; LINEAR_BURN и там и там считается формулой — общий код; вынести в фасад | 2–3 дня |
| Стекло | GraphicsLayer + BlurEffect — общий API | как есть; в пробе на iOS работает (запись слоя — с явной плотностью) | проверка |
Карта EventMap.kt | MapLibre Android, символьные слои, маркеры-битмапы, MapSnapshotter | рекомендация: UIKitView + MLNMapView из MapLibre Native iOS 6.31 (11.09.2026), маркеры рисовать Skia, снимки MLNMapSnapshotter. Библиотека maplibre-compose 0.17 на iOS — beta поверх нестабильного FFI, снимки карт в ней появились лишь в сентябре | 5–8 дней, главный риск |
| Камера селфи | CameraX, фронталка | UIKitView + AVCaptureSession (пример в документации JetBrains); Kamera 1.2 или FileKit — если хватит системного видоискателя, но рамка макета требует своего | 2–3 дня |
| Выбор фото, снимок | PickVisualMedia, TakePicture, FileProvider, Bitmap | PHPicker, UIImagePickerController, JPEG из UIImage (FileKit 0.16 закрывает всё это) | 1–2 дня |
| Разрешения | checkSelfPermission, «в настройки» через Intent | AVCaptureDevice, CLLocationManager, openSettingsURL; свои expect/actual (moko-permissions собран на Kotlin 1.9) | 1 день |
| Геолокация, геокодер | LocationManager, Geocoder | CLLocationManager, CLGeocoder; Photon — общий код | 1–2 дня |
| Окно макета | Activity, WindowManager | UIScreen; инсеты: верх max(54, safe-area), низ 34 | 1 день |
| Deep links | intent-filter | URL scheme в Info.plist → навигация | 0,5 дня |
| Токены, флаги | DataStore + Context | DataStore 1.2.1 тот же; токены — Keychain (решение при переносе) | 0,5–1 день |
| Телеметрия | Sentry Android, OkHttp-интерцептор | sentry-kotlin-multiplatform 0.27 (0.x, «не тестируется полностью»), крошки запросов — плагин Ktor, dSYM через sentry-cli | 1–2 дня |
| Точка входа | Application, Activity | MainViewController + Swift-обёртка, AppDelegate | 0,5 дня |
| Сейчас | На обеих платформах | Версия на 24.09.2026 |
|---|---|---|
| Retrofit + OkHttp | Ktor client (Darwin на iOS, OkHttp на Android), WebSocket-плагин; 46 ручек переписать | 3.6.0 |
| Coil 2.7 + SVG | Coil 3 + coil-svg + coil-network-ktor3 | 3.6.3 |
| ZXing | qrose | 1.3.0 |
| Sentry Android | sentry-kotlin-multiplatform | 0.27.0 |
| java.time | kotlinx-datetime | 0.8.0 |
| DataStore 1.1.1 | DataStore (KMP с 1.1) | 1.2.1 |
| navigation, lifecycle | org.jetbrains.androidx.* | 2.9.2 / 2.11.0 |
| MapLibre Android 11.5.2 | на Android оставить; iOS — MapLibre Native iOS через UIKitView | 6.31.0 |
| material-icons-extended | убрать: не используется, в CMP заморожена на 1.7.3 | — |
| Paparazzi | оставить для Android; на iOS — съёмка симулятора или Roborazzi для iOS (экспериментальный) | 1.75.0 |
xcrun simctl io … screenshot. Скрипт съёмки получает второй бэкенд «ios» рядом с «adb».diff.py, attrib.py, inkcheck.py, textpos.py: на входе два PNG 1170×2532. Та же маска системного хрома. Прогон — на макмини.Проверялось: способен ли макмини собрать Compose Multiplatform под iOS и компилируются ли под iOS приёмы, на которых держится наша вёрстка. Проект вне рабочего дерева (~/cmp-smoke): Kotlin 2.4.20, Compose Multiplatform 1.12.1, navigation 2.9.2, lifecycle 2.11.0.
В пробе: Modifier.blur; GraphicsLayer + BlurEffect с тройной отрисовкой (как стекло PdGlassBackdrop); BlendMode.Plus; на Skia-канвасе — битмап ALPHA_8, размытие MaskFilter.makeBlur, режимы PLUS и DST_OUT (модель FigDraw); контур из pathData ноды; поле ввода с кириллицей и ₽; список; два экрана через NavHost.
CADisableMinimumFrameDurationOnPhone = true в Info.plist (нужен для 120 Гц) — первые два запуска упали на этом.drawWithCache в Compose 1.12 требует явной плотности — та же строка есть в GlassBackdrop.kt; nativeCanvas с 1.11 устарел, вместо него skiaCanvas; Gradle 8.13 для Kotlin 2.4.20 устарел.PrivacyInfo.xcprivacy, метки конфиденциальности в App Store Connect (телефон, геолокация, фото, идентификаторы, диагностика, селфи), экспорт-комплаенс через ITSAppUsesNonExemptEncryption = NO.com.podushe.app.dusha, .dushalove, com.podushe.app; capability Push включить заранее.В Android «По душе» пушей нет, сервер умеет только FCM, а очередь пушей на стенде мёртвая с 17.09. Первая iOS-версия выходит без пушей — это и есть паритет.
Когда пуши появятся (задача с сервером): на iOS слать напрямую в APNs — ключ .p8, HTTP/2, библиотека aioapns для Python; серверу нужно хранить токен с платформой. Firebase на iOS даёт только обёртку над тем же APNs и добавляет зависимость от googleapis, которые в РФ периодически режут; APNs пока не блокируют.
| Этап | Что делается | Зависит от | Оценка |
|---|---|---|---|
| Э0. Подготовка без переезда файлов | В текущем модуле: платформенное — за фасады, убрать неиспользуемое, java.time → kotlinx-datetime, Retrofit → Ktor, Coil 2 → 3, ZXing → qrose, ресурсы Compose. Каждая замена — отдельный коммит с пересъёмкой затронутых кадров. Соседние сессии продолжают работать в тех же файлах | решение владельца «идём CMP» | 2–3 недели |
| Э1. Двигатель | Kotlin 2.4.20, Compose 1.12, navigation 2.9, lifecycle 2.11; пересъёмка и сравнение всех 623 кадров на Android; разбор сдвигов | Э0 | 1 неделя |
| Э2. Переезд в KMP | Перенос файлов одним коммитом в согласованное окно (день-два без чужих правок), iosApp от xcodegen, флейворы Xcode, PrivacyInfo, иконка, Info.plist. Android-сборка прежняя | Э1, окно тишины | 3–5 дней |
| Э3. iOS-узлы | FigDraw на Skia, нативный канвас 8 экранов, карта, камера, фото, разрешения, гео, окно и инсеты, deep links, Sentry, хранилище | Э2 | 3–4 недели |
| Э4. Сверка на iOS | Съёмка по guid через simctl, diff по десяткам S01…S95, разбор красных, ручной проход в симуляторе и на айфоне | Э3 | 2–3 недели |
| Э5. TestFlight | App ID, запись в ASC, профили, архив, загрузка, внутренние тестеры | доступы (раздел 6), Э3 — можно раньше сверки | 2–3 дня |
| Э6. App Store | Удаление аккаунта и демо-номер (сервер), политика, анкеты, скриншоты, юрист по 572-ФЗ, внешние тестеры, ревью | владелец, сервер, юрист | не оцениваю |
Итого до сверенной внутренней сборки в TestFlight — 8–12 недель. Ускоряет: Э0 идёт в текущей ветке без остановки остальной работы; Э5 не ждёт Э4. Замедляет: карта и неожиданные сдвиги текста после Э1.
| Риск | Чем снимаем |
|---|---|
| Переезд файлов ломает параллельные ветки | Э0 без переноса файлов; перенос одним коммитом в объявленное окно; чужие ветки перебазируются одним rebase |
| Обновление Compose сдвигает текст на Android | Э1 отдельно, до переезда, с полной пересъёмкой |
| Карта на iOS: библиотека beta, FFI нестабилен | UIKitView + MapLibre Native iOS 6.31 (стабильный SDK); Android не трогаем; маркеры рисуем Skia — общий код |
| Текст на iOS ±1 px, эмодзи другие | Метрика «без текста» + inkcheck; класс остатка только после раскладки по слоям |
| Sentry KMP 0.x | Минимальный набор; при проблемах — Sentry Cocoa напрямую из Swift |
| 104 МБ картинок макета, размер бинарника | На iOS то же, что на Android; сжатие отдельной задачей; релизный размер проверить на Э5 |
| 572-ФЗ и селфи | Юрист до стора; технически селфи на iOS повторяет Android |
| App Store в РФ с перебоями; оплата членства | TestFlight по приглашению; внутренние тестеры; зарубежная карта, проверить дату продления |
| Xcode 27 в 2027 | Держать 26.x, следить за поддержкой в Kotlin/Native |
Код и инфраструктура: android-events/app/build.gradle.kts, спека паритета v3.20, docs/figma/REPORT.md, ios/README.md, docs/internal/10-mobile.md, база знаний (клиенты, доступы), трекер DUSHA-4, DUSHA-A-17, DUSHA-M-1, LYUBVI-39; макмини (проверки 24.09.2026); Maven Central и Google Maven; пробная сборка ~/cmp-smoke. Полный текст с таблицами — docs/41-ios-podushe-research-2026-09-24.md в репозитории.