Кейс · Мобильные приложения, Веб и SaaS
IRK.Today обновлённое приложение городского издания
Приложение «Иркутск Сегодня» издания IRK.Today: новости Иркутска и Братска, афиша с покупкой билетов, погода и пробки. Мы перевели его на новый дизайн, починили вход и ссылки и выпустили новую версию в App Store, Google Play и RuStore.
Паспорт проекта
- Клиент
- ООО «Иркутск Сегодня»
- Отрасль
- Медиа
- Срок
- 17 дней
- Команда
- Команда E404 и ИИ-агенты
Стек

Задача
IRK.Today — городское издание Иркутска. Есть у него и издание для Братска (Иркутская область): его лента — на отдельном сайте. Приложение «Иркутск Сегодня» в App Store с июля 2018 года, одно на оба города: город выбирают при первом запуске. Собрано оно было на шаблоне новостного приложения NewsPro, и от шаблона в коде осталось много лишнего.
Когда мы начали, в приложении было что чинить:
- iOS-версия не собиралась: файлы входа через VK и Яндекс не были подключены к проекту Xcode;
- ссылки на статьи — из мессенджеров и с других сайтов — открывались в браузере, а не в приложении;
- из статей молча пропадали редакционные блоки «Справка», «Врезка» и «Прямая речь»;
- вход для комментариев через VK и Яндекс заканчивался ошибкой.



Параллельно владелец издания хотел новый дизайн. По нашему описанию всех функций и экранов приложения получился макет из 14 разделов — от дизайн-системы до планшета. Условие было одно: читатель не должен получить наполовину перекрашенное приложение. Поэтому новый дизайн до готовности был выключен флагом сборки и вышел одной версией 6.0, а до неё в App Store выходили только исправления старой версии.
Оценки и скачивания копились в магазинах приложений годами (в App Store — с 2018 года), это не итог редизайна. Данных аналитики «до и после» у нас нет, поэтому аудиторию не сравниваем.
Что сделали
Новый дизайн всех экранов
Внизу четыре вкладки с подписями: Главная, Афиша, Город и Моё. «Пульс» живёт внутри главной: хроника, свежие комментарии, ближайшие события и топ недели. Искать можно сразу по новостям и афише. Шрифты — Raleway для интерфейса и Lora для текста статей. Есть тёмная тема и отдельная раскладка для планшета: от 768 точек экран делится на колонки.



Цвет города задаётся в админке сайта, и приложение подхватывает его без нового выпуска. У Иркутска синий, у Братска — оранжевый.

Чтение
Размер текста меняется в одно касание — пять ступеней от 16 до 24 пунктов. Фоторепортажи листаются на весь экран, как на сайте. Аудио играет во встроенном плеере, видео открывается по обложке. «Сохранить» скачивает материал: он откроется и без интернета.







Афиша и покупка билета
Путь до билета:
- Афиша: подборки, вкладки рубрик, фильтры «Пушкинская карта», «Детям», «Бесплатно» и календарь.
- Событие: статус, место и кнопка «Билеты от …».
- Выбор сеанса — по дням и площадкам.
- Предупреждение, что откроется сайт кассы, — только при первой покупке. Событие сохраняется, и, если уведомления разрешены, за 2 часа до начала приходит напоминание.
- Касса — виджет Afisha.ru.



Город
Погода сейчас и на 10 дней, пробки по шкале «Яндекс Пробок» и погода в городах области.


Вход для комментариев
Отдельной регистрации в приложении нет: аккаунт появляется при первом входе. Чтобы написать комментарий, можно войти через VK ID, Яндекс ID, «Вход с Apple» на iPhone и iPad или по коду из письма, без пароля. Аккаунт удаляется прямо в настройках.



Выкладка в три магазина
Версию в App Store, Google Play и RuStore выкладывает один скрипт через API магазинов: проверить состояние, загрузить сборку, отправить на модерацию. Кнопку «Отправить на проверку» в App Store нажимает владелец.

Как устроено

Приложение написано на Flutter: один код для iPhone, iPad и Android, вход через VK и Яндекс — на Swift и Kotlin. Данные идут из REST API двух сайтов на WordPress, объединённых в сеть. Город, выбранный при первом запуске, определяет сайт; пуши приходят с тегом города.
В админку вынесено то, что должно меняться без нового выпуска: цвет города и флаги функций. Так, вход по почте и «Вход с Apple» включаются галочкой на сайте.
Отдельное приложение Братска «Твой Братск» больше не нужно: оба города живут в одном.
Инженерные решения
Блоки, которые пропадали из статей
Проблема. Редакция пишет статьи со своими блоками — «Справка», «Врезка», «Прямая речь». В приложении они исчезали без следа, вместе с подписью автора цитаты.
Что нашли. Обработчик HTML объявлял, что умеет рисовать любой div, а на деле узнавал только контейнер видео. Любой другой div получал пустой виджет — и пропадал со всем содержимым. Ошибка сидела в коде с самого начала и уже была в версии из магазинов.
Решение. Обработчик забирает только то, что умеет нарисовать, остальное отдаёт штатному рендеру. В новом дизайне «Прямая речь» стала цитатой с автором, а «Справка» и «Врезка» — врезками, а тест держит их на месте.
Падение при входе через Яндекс
Проблема. Вход через VK заработал, а Яндекс ронял приложение ровно в момент ответа — и на успехе, и на ошибке.
Что нашли. SDK Яндекса рассылает уведомления внутри синхронного блока на своей очереди, а наш наблюдатель снимал себя прямо из уведомления — вложенный sync на ту же последовательную очередь. Это гарантированная взаимная блокировка.
Решение. Снимать наблюдателя и отвечать во Flutter на следующем витке главного цикла: к этому моменту очередь уже свободна.
let observer = LoginObserver { outcome in
// Снимать наблюдателя прямо здесь нельзя: SDK рассылает уведомления
// внутри sync на своей очереди, а remove(observer:) делает sync на
// ней же — взаимная блокировка.
DispatchQueue.main.async {
detach()
switch outcome {
case .success(let login):
result(login.token)
case .failure(let error):
result(FlutterError(
code: isCancellation(error) ? "USER_CANCELLED" : "SDK_ERROR",
message: describe(error),
details: nil
))
}
}
}
Прокрутка статьи подтормаживала
Проблема. Владелец заметил, что длинная статья листается рывками. Замер на симуляторе: сборка кадра в среднем 15,4 мс, треть кадров дольше 16 мс — это заметные рывки.
Что нашли. Полоска прогресса чтения обновляла всю страницу почти на каждом кадре. Вместе с шапкой заново собиралась статья: HTML разбирался дважды, абзацы и фото строились с нуля.
Решение. Прогресс и шапку слушают только те, кому они нужны. Разобранный HTML запоминается.
// Прогресс чтения и «шапка поверх обложки» меняются на каждом кадре.
// Слушают их только шапка, кнопка «назад» и цвет строки состояния.
final _progress = ValueNotifier<double>(0);
final _compact = ValueNotifier<bool>(false);
void _onScroll() {
final p = _scroll.position;
_progress.value = p.maxScrollExtent <= 0 ? 0.0 : (p.pixels / p.maxScrollExtent).clamp(0.0, 1.0);
final top = MediaQuery.paddingOf(context).top;
_compact.value = !_hasCover || p.pixels > IrkArticlePage.coverHeight + top - (top + IrkSize.appBar);
}
| Симулятор, отладочная сборка, 6 быстрых прокруток фоторепортажа | До правки | После правки | Перемер |
|---|---|---|---|
| Сборка кадра в среднем | 15,4 мс | 2,8 мс | 2,7 мс |
| Кадров дольше 16 мс | 137 из 415 | 0 из 457 | 1 из 457 |
Шаг «прокрутка» в автоматическом обходе экранов теперь не проходит, если медленных кадров больше 5 %.
Результат
За 12 дней в App Store вышло 8 версий: сначала исправления старого приложения, затем новый дизайн. Через восемь дней после начала работ версия 6.0 была в App Store и Google Play, а 6.0.3 вышла во всех трёх магазинах; в RuStore новый дизайн пришёл сразу с ней.
| Что | До | После |
|---|---|---|
| Вкладок внизу | 5, подпись только у выбранной | 4, подписи у всех |
| Автотестов приложения | 211 | 643 |
Два отказа Apple. Проверка App Store дважды вернула новую версию: сначала Apple потребовала вход не хуже, чем через VK и Яндекс, а затем не признала таким вход по почте и попросила рабочий демо-доступ. По журналам сайта мы увидели, что проверяющий ввёл верный код, но на шаге с именем остановился и запросил код заново. Мы добавили «Вход с Apple», а демо-аккаунт завели заранее — код из письма пускает в него сразу, без шага с именем. Следующая проверка прошла.
Поделиться записью



Комментарии
Писать можно, войдя через VK ID, через Яндекс ID или по коду из письма. У комментария — имя и фото из профиля VK или Яндекса либо имя, которое вы укажете при входе по почте. Как мы обращаемся с данными — в политике конфиденциальности.