Доступность
SDK имеет базовую поддержку доступности через статические contentDescription в XML-layouts. Динамические сообщения, кастомные ChatComponents и контрастность цветов остаются на стороне интегратора.
Перечень того, что SDK поддерживает «из коробки» (TalkBack для встроенных элементов, sp-юниты для Dynamic Type), и того, что необходимо обеспечить на стороне приложения (контраст пар цветов в ChatColors, contentDescription для кастомных иконок). Чек-лист перед релизом — в конце страницы.
Матрица поддержки a11y
В таблице ниже — что покрывает SDK «из коробки», что покрыто частично и за что отвечает интегратор.
| Функция | Поддержка | Пруф / комментарий |
|---|---|---|
| TalkBack (screen reader) | Базовая | contentDescription задан только на части декоративных элементов (play/info, аватар оператора, лоадер, иконки ошибок). Иконки тулбара, поля ввода и нижней панели чата не покрыты — TalkBack озвучит их как «Кнопка без названия». Новые входящие сообщения не объявляются через LiveRegion |
| Dynamic Type / Accessibility Scaling | Системно | Размеры текста SDK заданы в sp (R.dimen.ecc_*) — увеличиваются вместе с системными настройками шрифта |
| High Contrast Text | Частично | Системный High Contrast Text (Android 13+, API 33) применяется только к семантическим цветам Material. У значений ChatColors, заданных как hex-литералы, контраст системой автоматически не подкручивается |
| Reduce Animations | Не реализовано | SDK не проверяет ни Settings.Global.ANIMATOR_DURATION_SCALE (API 21+), ни AccessibilityManager.isAnimationsEnabled() (API 31+). Анимации (типинг, прокрутка, лоадеры) воспроизводятся независимо от системных флагов |
| RTL (Right-To-Left) | Минимальная | В layouts SDK не используется явный layoutDirection, но часть отступов задана через физические paddingLeft/Right / marginLeft/Right вместо логических Start/End — зеркалирование чипов, рейтингов и плашек дат при RTL-локали не гарантировано. Для Arabic/Hebrew установите <application android:supportsRtl="true"> на стороне приложения и проверьте экран чата визуально |
| Контрастность цветов | Ответственность интегратора | SDK не валидирует контраст между цветами ChatColors. Целевой уровень — WCAG 2.1 AA: ≥ 4.5:1 для обычного текста (< 18sp regular / < 14sp bold), ≥ 3:1 для крупного текста (≥ 18sp regular / ≥ 14sp bold) и UI-элементов / графических объектов (критерий 1.4.11) |
Известные ограничения
SDK не вызывает View.announceForAccessibility(...) и не использует accessibilityLiveRegion для RecyclerView с историей сообщений. TalkBack-пользователь увидит новое сообщение только при прокрутке к нему вручную. До исправления в SDK реализуйте объявления самостоятельно — подпишитесь на ChatUpdateProcessor.newMessageFlow (Делегаты) и вызовите announceForAccessibility на root-view вашего экрана.
Не устанавливайте accessibilityLiveRegion="polite" или "assertive" на сам RecyclerView истории сообщений. Для длинных списков это известный антипаттерн: TalkBack будет повторно зачитывать содержимое при каждом notifyItemInserted, notifyDataSetChanged и прокрутке.
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import edna.chatcenter.core.chatUpdates.ChatUpdateProcessor
import edna.chatcenter.core.models.ConsultPhrase
import edna.chatcenter.core.serviceLocator.core.inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
// Получение шины событий — через публичный DI SDK (см. ../methods/delegates.md):
val processor: ChatUpdateProcessor by inject()
// Снипет написан для Fragment (использует viewLifecycleOwner). В Activity
// замените `viewLifecycleOwner.lifecycleScope` на `lifecycleScope`.
// newMessageFlow эмитит ChatItem; входящие сообщения от оператора — это ConsultPhrase
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
processor.newMessageFlow
.filterIsInstance<ConsultPhrase>()
.collect { phrase ->
// newMessageFlow эмитится из Dispatchers.Unconfined —
// явно переключаемся на главный поток для UI-вызова.
withContext(Dispatchers.Main) {
rootView.announceForAccessibility(
getString(R.string.a11y_new_incoming_message, phrase.phraseText.orEmpty())
)
}
}
}
}
flowOn(Dispatchers.Main) для этой цели не подойдёт — он меняет контекст upstream-эмиттера, а не коллектора. Подписка в lifecycleScope уже идёт на Main; для надёжности оборачивайте UI-вызов в withContext(Dispatchers.Main). Подробности про unconfined-контекст ChatUpdateProcessor — в Известных ограничениях.
ChatComponents без contentDescriptionЕсли вы переопределяете компоненты через ChatComponents или ChatFlows и подменяете иконки/изображения, обязательно проверьте, что у каждого ImageView/ImageButton задан contentDescription. Иначе TalkBack пропустит элемент.
contentDescription — debug-строкаИсходящее файловое сообщение в SDK помечено contentDescription="chatText" (сырой идентификатор, не локализованная строка) — TalkBack буквально зачитает «chatText». До исправления в SDK переопределите компонент через ChatComponents.imageComponent / ChatComponents.bubbleComponent либо задайте contentDescription программно во вью-холдере.
Критичные пары ChatColors для проверки контраста
У ChatColors 116 полей; для контраста ключевы следующие пары «текст / фон». Целевые уровни — WCAG 2.1 AA (≥ 4.5:1 для обычного текста, ≥ 3:1 для крупного текста и UI-элементов). Проверяйте через WebAIM Contrast Checker.
| Пара (foreground / background) | Назначение | Дефолтные значения |
|---|---|---|
incomingText / incomingBubble | Текст входящего сообщения на бабле оператора | ecc_black / ecc_white |
outgoingText / outgoingBubble | Текст исходящего сообщения на бабле пользователя | ecc_white / main (ecc_green_83b144) |
inputMessage / inputFieldBackground | Поле ввода сообщения | ecc_black / ecc_white |
mainTextWhenErrorBubble / errorBackground | Текст внутри бабла-ошибки | ecc_white / ecc_error_red_df0000 |
unreadMessagesCountText / unreadMsgSticker | Счётчик непрочитанных сообщений | ecc_chat_unread_msg_count_text / ecc_chat_unread_msg_sticker_background |
searchText / toolbar | Поле поиска в тулбаре | ecc_white / ecc_chat_toolbar |
errorText совпадает с errorBackgroundПоля errorText и errorBackground в ChatColors оба указывают на R.color.ecc_error_red_df0000 — контраст «1:1». Если ваш кастом сценарий рисует errorText поверх errorBackground, переопределите одно из двух полей вручную. Для текста внутри самого бабла-ошибки используется отдельное поле mainTextWhenErrorBubble = ecc_white, оно даёт корректный контраст.
Автотесты доступности
Для CI добавьте Espresso-Accessibility-проверки во все UI-тесты, затрагивающие экран чата.
import androidx.test.espresso.accessibility.AccessibilityChecks
import org.junit.Before
@Before
fun enableA11yChecks() {
AccessibilityChecks.enable()
.setRunChecksFromRootView(true)
}
Это запускает accessibility-test-framework на каждом UI-взаимодействии и проваливает тест, если хотя бы один элемент в иерархии нарушает базовые правила (нет contentDescription, недостаточный контраст, тач-зона < 48dp).
Если внутри SDK есть элементы, проваливающие a11y-проверку (тач-зона/контраст), используйте setSuppressingResultMatcher(...) с whitelist именно этих view; глобально проверку не отключайте.
Чек-лист перед релизом
- Прошлись по чату с TalkBack: тулбар, поле ввода, кнопки прикрепления / голосового / отправки, ответ, скачивание файла озвучиваются осмысленно (не «Кнопка без названия», не
chatText). Кнопки безcontentDescriptionпереопределите черезChatComponents. - Проверили контраст пар
ChatColors(≥ 4.5:1обычный текст,≥ 3:1крупный текст и UI) через WebAIM Contrast Checker. Список пар — выше. - Включили максимальный шрифт (
adb shell settings put system font_scale 1.5) — текст не обрезается. - Если приложение поддерживает RTL — проверили чат с
Force RTL layout direction. В SDK не все отступы заданы логически (Start/End), часть UI может не зеркалироваться. - Включили «Убрать анимации» — SDK сам этот флаг не учитывает, может потребоваться workaround на стороне приложения.
- Запустили автотесты с Espresso-Accessibility — см. Автотесты доступности.
- Если добавляли свои строки
ecc_content_descr_*— они переведены на все поддерживаемые локали.
adb shell settings put system font_scale 1.5 # максимальный шрифт
adb shell settings put global force_rtl_layout_direction 1 # RTL
adb shell settings put global animator_duration_scale 0 # выключить анимации
Связанные разделы
- Дизайн-система: обзор — уровни кастомизации.
- Темы — три конструктора
ChatTheme, готовые примеры тем. - Цвета — таблица
ChatColors, в т. ч. поля из секции «Критичные пары» выше. - Типографика — sp-юниты и Dynamic Type.
- Изображения — drawable, в т. ч. иконки статусов сообщений.
- Компоненты — переопределение элементов с сохранением a11y.
- Потоки (Flows) — экран-специфичная кастомизация.
- Известные ограничения — общий список багов SDK 5.x (включая unconfined-контекст
ChatUpdateProcessor).