Живой документ. Здесь записаны калибровки, ритм и договорённости об интерфейсе; он обновляется всякий раз, когда что-то решено.
Иконки Material из androidx.compose.material.icons.outlined.* имеют один номинальный размер — сетку 24×24, — но нарисованная часть у каждой своя. Поставь всем простой Modifier.size(24.dp)— и одни будут выглядеть крупными и тяжёлыми (Bolt, Delete), а другие крошечными (Contacts, VpnKey). Их надо калибровать вручную.
new_size = 24 × 52 / measured_px, с ограничением до [18..28].52 px — эталонный нарисованный глиф, Fingerprint; всё меряется от него как от единицы.
| Icon | Размер | Why |
|---|---|---|
| Add, Apps, ArrowBack, Close | 28dp | тонкие линии, глиф 36–41 px |
| AutoMode, Bolt, DeleteOutline, Edit, List, Photo, QrCode, Tune, DriveFileRenameOutline | 26dp | средняя плотность, глиф около 47 px |
| LocationOn | 24dp | булавка на карте — не трогать, это эталон |
| LocationOff | 26dp | перечёркнутая булавка — не трогать |
| ContentCopy, MyLocation, PeopleAlt, Wifi, ExpandMore, ExpandLess | 22dp | широкие или высокие, зрительно тяжёлые |
| Contacts, VpnKey | 20dp | перегружены внутренними деталями |
Используется везде, где раньше стоял Bolt, — кнопка Автоматизация на карточке ключа, иконка раздела в настройках. Размер 26 dp, как у Bolt.
IconsDebugSheet.kt — карта iconSizeByName. Отладочный экран открывается из SettingsScreen кнопкой Иконки UI (дебаг): сверху FlowRow откалиброванных иконок, снизу список имён, размеров и контекстов.
Используем стандартные роли Material 3, сознательно ограничившись четырьмя:
| Role | Размер / межстрочный интервал | Где используется |
|---|---|---|
titleLarge SemiBold | 22sp/28sp | Заголовок нижнего листа (Гостю · <объект>) |
titleMedium SemiBold | 16sp/24sp | Заголовки правил (Геолокация, Wi-Fi, Интервал, Активация) и подписи FieldSection (Координаты, Радиус, Режим, Сохранённые сети) |
bodyMedium | 14sp/20sp | Подписи радиокнопок, текст внутри кнопок-пилюль, названия сегментов |
bodySmall muted | 12sp/16sp | Статусы и подсказки (36540 м до точки, 74 м · максимум, гость может уменьшить, тексты подсказок). Цвет: onSurfaceVariant |
Договорились работать кратно 2 / 4 / 8 / 10 / 14 / 16 / 20. Без то густо, то пусто: одинаковые элементы получают одинаковые отступы.
spacedBy(12.dp). Карточки на 12 dp друг от друга.padding(horizontal=20.dp).padding(top=16.dp).verticalArrangement = spacedBy(16.dp) между крупными блоками.spacedBy(2.dp). Всего 2 dp между жирной подписью и статусом или подсказкой под ней, чтобы они читались парой заголовок и пояснение.Spacer(10.dp). То же в Гео, подключении Wi-Fi и списке Wi-Fi.Ползунок Material 3 занимает в раскладке около 48 dp из-за минимальной области касания, из которых видимая дорожка — всего около 4 dp посередине, а сверху и снизу примерно по 22 dp пустой сенсорной зоны. Отсюда:
Modifier.offset(y=-16.dp) , чтобы подтянуть бегунок к тексту.Modifier.sliderTrim() (хелпер в GuestAutomationEditor.kt), который сообщает родителю высоту 22 dp вместо 48 dp. Так у карточки padding(16.dp) даёт от бегунка до нижнего края тот же зазор, что и у карточек, заканчивающихся кнопкой-пилюлей.Каждое правило — Геолокация, Wi-Fi, Время, Авторизация, Подтверждение — и блок Активация принимают один из трёх уровней. Кружок иконки на карточке окрашивается соответствующим цветом, когда выключатель включён.
| Key | Label | Colour | Description |
|---|---|---|---|
own |
Решает гость | #3BA55D (green) | Гость сам выбирает настройку |
suggest |
Preset | #E5B409 (yellow) | Значение задано, и гость может его изменить |
force |
Locked | #CC3322 (red) | Значение задано, и гость изменить его не может |
При выключенном переключателе кружок становится серым (#9AA0A6), какой бы уровень ни был выбран.
Единое посадочное место 38 dp по всему приложению:
CircleShape, фон — цвет уровня.contentAlignment = Alignment.Center.Оборачивать в Box(Modifier.size(38.dp), contentAlignment=Center) { RadioButton(...) } , чтобы центр кружка радиокнопки совпал с центром цветного кружка выше. Без обёртки радиокнопка уезжает по горизонтали, потому что RadioButton рисуется со своими отступами.
Между радиокнопкой и её текстом: Spacer(14.dp). Между строками радиокнопок в группе: Column(spacedBy(4.dp)).
Минимальная область касания отключена: CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) — иначе каждая строка радиокнопки раздувается до 48 dp и ломает ритм.
Стандартный переключатель Material 3, справа от заголовка карточки. Выключает правило целиком, и цвет уровня тогда не показывается — кружок серый.
Свой элемент для переключения режимов внутри правила — сейчас только в Wi-Fi: По конкретной сети / По сетям вокруг. Два элемента в Row, у каждого weight(1f):
colorScheme.surface, RoundedCornerShape(10.dp), отступ 4dp.primaryContainer, RoundedCornerShape(8.dp), текст SemiBold.Обводка, иконка и текст, полностью скруглённые. Для второстепенных действий — Использовать текущее положение, Снять сети вокруг. В выключенном состоянии обводка и текст уходят в alpha=0.4.
См. раздел 3 про offset(y=-16.dp) и sliderTrim(). Перед ползунком всегда идёт StatusLine со статусным текстом (bodySmall, приглушённый) — 74 м · максимум, гость может уменьшить.
Открывается нажатием на строку заголовка карточки — всю Row, кроме переключателя. Появляется прямо под шапкой, во всю ширину содержимого карточки. Для этого:
Box с onGloballyPositioned, и мы измеряем её ширину в dp.DropdownMenuItem с модификатором Modifier.width(anchorWidthDp).Пункты списка дышат, а не сплюснуты в строки: слева цветной маркер, название жирным, описание под ним мелким приглушённым шрифтом. По вертикали выравниваются по первой строке — по названию.
В отличие от обычных цветных кружков правил, кружок в шапке Активации пульсирует.
drawBehind + Stroke(width=6dp).LinearEasing. Не FastOutSlowInEasing — иначе на перезапуске цикла виден рывок.sin(π·p) × 0.6: при p=0 и p=1 прозрачность равна 0, пик — посередине. Перезапуск незаметен.Одно предложение в строке интерфейса точки не получает. Точки появляются только при двух и более предложениях, чтобы их разделить. Это касается всех описаний, подсказок, статусов, всплывающих пояснений, заголовков и отдельных фраз.
В статусах ползунка в жёлтом режиме (Задано) прямо указываем направление свободы:
В красном (Закреплено): 74 м · ровно / 5 из 7 · ровно.
Решает гость / Задано / Закреплено прижились, менять их не будем.
Если у правила есть выключенное состояние, выражать его переключателем, а не радиовариантом вроде Не требуется / Нет / Выкл. Иначе у пользователя два способа выключить одно и то же, и это путает.
Examples:
В блоке Координаты иконки копирования и удаления стоят справа вровень с обеими строками (подписью и статусом). Реализовано как Row { Column(weight=1f) { label; status }; CopyIcon; DeleteIcon }. Иконки центрируются по вертикали относительно пары целиком.
Тело раскрывается только когда:
Animation: expandVertically(tween(220)) + fadeIn(tween(220)) при открытии, shrinkVertically(tween(180)) + fadeOut(tween(120)) при закрытии.
При получении гостевого ключа — только если приложение на экране и ключ новый, а не повторный. Иначе сохраняем молча.
sin(π·p) × 0.6. Любое упрощение портит вид.Последнее обновление: 18 апреля 2026 · версия документа 2.07