Entrixy — Дизайн-гайд

Живой документ. Фиксирует калибровки, ритм и договорённости по UI. Обновляется при каждом уточнении.

Содержание
  1. Иконки — размеры и калибровка
  2. Типографика
  3. Ритм и отступы
  4. Цвета и уровни
  5. Элементы управления
  6. Анимации
  7. Правила текстов
  8. UX-паттерны
  9. Что не трогать

1. Иконки — размеры и калибровка

Проблема

У Material-иконок из библиотеки androidx.compose.material.icons.outlined.* одинаковый спек-размер (24×24 grid), но визуально «нарисованная» часть у всех разная. Если просто поставить всем Modifier.size(24.dp), одни иконки будут казаться большими и тяжёлыми (Bolt, Delete), другие — мелкими (Contacts, VpnKey). Нужна ручная калибровка.

Метод калибровки

  1. Скриншот экрана с иконками в 38dp-кружках (наш «посадочный слот»).
  2. Python + PIL + numpy: измеряем bounding box нарисованного глифа (макс ширина/высота в пикселях).
  3. Формула: new_size = 24 × 52 / measured_px, клип в [18..28].
  4. Также центрируем внутри 38dp-слота по вертикали и горизонтали.

52 px — референсный painted-глиф Fingerprint, на него ориентируемся как на «единичный».

Итоговая карта размеров

ИконкаРазмерПочему
Add, Apps, ArrowBack, Close28dpтонкие линии, глиф 36–41 px
AutoMode, Bolt, DeleteOutline, Edit, List, Photo, QrCode, Tune, DriveFileRenameOutline26dpсреднеплотные, глиф ~47 px
LocationOn24dpбалун — не трогать, референс
LocationOff26dpбалун с перечёркиванием — не трогать
ContentCopy, MyLocation, PeopleAlt, Wifi, ExpandMore, ExpandLess22dpширокие/высокие, визуально тяжёлые
Contacts, VpnKey20dpперегруженные внутренним содержимым
Не трогать: LocationOn (24), LocationOff (26), ExpandMore/ExpandLess (22). Это референсные иконки — юзер их пиксельно согласовал, и от них пляшет остальная калибровка.

AutoMode

Используется везде, где раньше был Bolt (кнопка «Автоматизация» на карточке ключа, иконка раздела в настройках). Размер 26dp — как у Bolt.

Где лежит таблица в коде

IconsDebugSheet.kt — карта iconSizeByName. Отладочный экран вызывается из SettingsScreen кнопкой «Иконки UI (дебаг)» — на нём FlowRow с калиброванными иконками сверху и список с именами/размерами/контекстом ниже.

2. Типографика

Используем стандартные роли Material3. Сознательно ограничились четырьмя:

РольРазмер/lineHeightГде применяется
titleLarge SemiBold22sp/28spЗаголовок bottom-sheet («Для гостя · <объект>»)
titleMedium SemiBold16sp/24spЗаголовки правил («Геолокация», «Wi-Fi», «Интервал», «Активация»); лейблы FieldSection («Координаты», «Радиус», «Режим», «Сохранённые сети»)
bodyMedium14sp/20spПодписи радио-кнопок, текст внутри pill-кнопок, текст сегментов
bodySmall muted12sp/16spСтатусы и подсказки («36540 м до точки», «74 м · максимум, гость может уменьшить», hint-тексты). Цвет: onSurfaceVariant
Правило: не смешивать веса внутри одной роли. Если что-то — titleMedium SemiBold, то везде SemiBold, не regular в одном месте и Semi в другом.

3. Ритм и отступы

Шкала

Договорились работать кратно 2 / 4 / 8 / 10 / 14 / 16 / 20. Не «то густо то пусто» — одинаковые элементы одинаково.

Между плашками (карточками) в общем скролле

Внутри плашки (RuleCard)

Внутри FieldSection (label + content)

Pill-кнопки и статусы

Слайдеры — особый случай

Material3 Slider занимает ~48dp по лейауту (из-за минимального touch-target), из которых видимая дорожка — только ~4dp по центру, а над/под ней по ~22dp пустой touch-зоны. Из-за этого:

Итог: расстояние от последнего визуального элемента (pill, радио, тамб слайдера) до нижнего края плашки ≈ одинаковое во всех карточках.

4. Цвета и уровни

Три уровня доверия гостю

У каждого правила (Геолокация, Wi-Fi, Время, Авторизация, Подтверждение) и у блока «Активация» — выбирается один из трёх уровней. Кружок-иконка карточки красится в соответствующий цвет, когда свитч включён.

КлючЯрлыкЦветОписание
own Гость решает #3BA55D (зелёный) Настройку выбирает сам гость
suggest Предустановка #E5B409 (жёлтый) Значение задано, гость может изменить
force Закреплено #CC3322 (красный) Значение задано, гость изменить не может

Выключенный свитч — кружок становится серым (#9AA0A6), независимо от выбранного уровня.

Семантика «максимум/минимум»: в жёлтом (Предустановка) для радиуса значение — максимум, гость может только уменьшить. Для числа Wi-Fi сетей (порог совпадения) — наоборот, минимум, гость может только увеличить. Это инверсия направления свободы — статус-тексты прямо так и написаны: «максимум, гость может уменьшить» / «минимум, гость может увеличить».

Правила для описаний уровней

5. Элементы управления

Цветной кружок с иконкой (38dp)

Единый «посадочный слот» 38dp по всему приложению:

Радио-кнопки

Оборачиваем в Box(Modifier.size(38.dp), contentAlignment=Center) { RadioButton(...) } — чтобы центр круга радио совпадал с центром цветного кружка выше. Без этой обёртки радио съезжает по горизонтали, потому что сам RadioButton рендерится с отступами.

Между радио и текстом — Spacer(14.dp). Между радио-строками в группе — Column(spacedBy(4.dp)).

Отключён минимальный touch-target: CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) — иначе радио-строки набирают по 48dp и ломают ритм.

Switch

Стандартный Material3 Switch. Справа от заголовка карточки. Выключает всё правило. Цвета уровня при этом не показываются (кружок серый).

Segmented row

Кастомный контрол для переключения режимов внутри правила (сейчас — только в Wi-Fi: «По конкретной сети» / «По сетям вокруг»). Два пункта в Row, каждый на weight(1f):

Pill-кнопки

Обводка + иконка + текст, закруглённая. Используется для действий второго порядка («Использовать текущее положение», «Сделать снимок сетей вокруг»). В отключённом состоянии — обводка и текст alpha=0.4.

Slider

См. раздел 3 про offset(y=-16.dp) и sliderTrim(). Перед слайдером обязательно идёт StatusLine со статус-текстом (бодиСмолл, muted) — «74 м · максимум, гость может уменьшить».

Dropdown выбора уровня

Вызывается по клику на строку заголовка карточки (весь Row кроме Switch). Открывается прямо под шапкой, по всей ширине контента карточки. Для этого:

Пункты dropdown — воздушные, не плоские строки: цветной маркер слева, ярлык жирно, описание под ним мелко-muted. Выровнены вертикально по первой строке (ярлыку).

6. Анимации

Пульсирующий кружок «Активация»

В отличие от обычных цветных кружков правил, у Активации в заголовке — пульсирующий.

Не трогать эту формулу альфы. Было несколько итераций, любое «проще» (линейное (1-p), ease-out) даёт заметное дёрганье.

7. Правила текстов

Пунктуация

Одно предложение в UI-строке — без точки в конце. Точки только когда предложений два и больше (для разделения). Касается всех описаний, подсказок, статусов, hint-текстов, заголовков, единичных фраз.

От кого пишем

Выбор между максимумом и точным значением

В статусах слайдеров в жёлтом (Предустановка) режиме — честно пишем про направление свободы:

В красном (Закреплено) — «74 м · точно» / «5 из 7 · точно».

Ярлыки уровней — закреплены

«Гость решает / Предустановка / Закреплено» — прижились, не меняем.

8. UX-паттерны

Свитч vs радио «не выбрано»

Если у правила есть состояние «выключено» — делаем его через Switch, не через радио-опцию «Не требуется / Нет / Выкл». Иначе у пользователя два способа выключить одно и то же — путает.

Примеры применения:

Координаты и действия справа

В блоке «Координаты» иконки копирования/удаления стоят справа на одном вертикальном уровне с обеими строками (label + статус). Реализовано через Row { Column(weight=1f) { label; status }; CopyIcon; DeleteIcon }. Иконки centered по вертикали всей пары.

Показ/скрытие тела карточки

Тело карточки раскрывается только если:

Анимация: expandVertically(tween(220)) + fadeIn(tween(220)) на открытии, shrinkVertically(tween(180)) + fadeOut(tween(120)) на закрытии.

Когда показываем диалог «Ключ добавлен»

При получении гостевого ключа — только если приложение в foreground и это новый ключ (не дубль). Иначе молча сохраняем. (Подробнее — см. memory feedback_key_dialog.md.)

9. Что не трогать

Связанные документы

Последнее обновление: 18.04.2026 · Версия приложения: 2.07