Um documento vivo. Regista as calibrações, o ritmo e os acordos sobre a interface, e é atualizado sempre que algo fica decidido.
Os ícones Material de androidx.compose.material.icons.outlined.* partilham um tamanho nominal — uma grelha de 24×24 — mas a parte desenhada difere de um para outro. Dê a todos um simples Modifier.size(24.dp)e uns parecerão grandes e pesados (Bolt, Delete) enquanto outros parecerão minúsculos (Contacts, VpnKey). É preciso calibrá-los à mão.
new_size = 24 × 52 / measured_px, limitado a [18..28].52 px é o glifo de referência, Fingerprint; tudo se mede em relação a ele como unidade.
| Icon | Tamanho | Why |
|---|---|---|
| Add, Apps, ArrowBack, Close | 28dp | linhas finas, glifo de 36–41 px |
| AutoMode, Bolt, DeleteOutline, Edit, List, Photo, QrCode, Tune, DriveFileRenameOutline | 26dp | densidade média, glifo de cerca de 47 px |
| LocationOn | 24dp | o alfinete do mapa — não mexer, é referência |
| LocationOff | 26dp | o alfinete riscado — não mexer |
| ContentCopy, MyLocation, PeopleAlt, Wifi, ExpandMore, ExpandLess | 22dp | largos ou altos, e visualmente pesados |
| Contacts, VpnKey | 20dp | sobrecarregados de detalhe interno |
Usa-se onde antes estava o Bolt — o botão Automatização no cartão da chave, o ícone da secção nas definições. Tamanho 26 dp, igual ao Bolt.
IconsDebugSheet.kt — o mapa iconSizeByName. O ecrã de depuração abre-se a partir de SettingsScreen com o botão Ícones da interface (depuração): em cima uma FlowRow de ícones calibrados, em baixo uma lista de nomes, tamanhos e contextos.
Usamos os papéis padrão do Material 3, limitados de propósito a quatro:
| Role | Tamanho / entrelinha | Onde se usa |
|---|---|---|
titleLarge SemiBold | 22sp/28sp | O título da folha inferior (Para o convidado · <objeto>) |
titleMedium SemiBold | 16sp/24sp | Cabeçalhos de regra (Geolocalização, Wi-Fi, Intervalo, Ativação) e rótulos de FieldSection (Coordenadas, Raio, Modo, Redes guardadas) |
bodyMedium | 14sp/20sp | Rótulos dos botões de opção, o texto dentro dos botões cápsula, nomes dos segmentos |
bodySmall muted | 12sp/16sp | Estados e dicas (36540 m até ao ponto, 74 m · máximo, o convidado pode reduzir, textos de ajuda). Cor: onSurfaceVariant |
Combinámos trabalhar em múltiplos de 2 / 4 / 8 / 10 / 14 / 16 / 20. Nada de festim ou fome: elementos iguais recebem espaços iguais.
spacedBy(12.dp). Os cartões ficam a 12 dp uns dos outros.padding(horizontal=20.dp).padding(top=16.dp).verticalArrangement = spacedBy(16.dp) entre os blocos grandes.spacedBy(2.dp). Apenas 2 dp entre o rótulo a negrito e o estado ou dica por baixo, para se lerem como um par título-legenda.Spacer(10.dp). O mesmo em Geo, na ligação Wi-Fi e na lista de Wi-Fi.Um cursor do Material 3 ocupa cerca de 48 dp de disposição por causa da área tátil mínima, dos quais a pista visível são apenas uns 4 dp ao centro, com cerca de 22 dp de zona tátil vazia acima e abaixo. Daí que:
Modifier.offset(y=-16.dp) para puxar o cursor até ao texto.Modifier.sliderTrim() (um auxiliar em GuestAutomationEditor.kt), que reporta ao pai uma altura de 22 dp em vez de 48 dp. Assim o padding(16.dp) dá o mesmo intervalo do cursor à margem inferior que nos cartões que acabam num botão cápsula.Cada regra — Geolocalização, Wi-Fi, Tempo, Autorização, Confirmação — e o bloco de Ativação assumem um de três níveis. O círculo do ícone do cartão fica com a cor correspondente quando o interruptor está ligado.
| Key | Label | Colour | Description |
|---|---|---|---|
own |
Decide o convidado | #3BA55D (green) | O convidado escolhe ele próprio a definição |
suggest |
Preset | #E5B409 (yellow) | O valor está definido e o convidado pode alterá-lo |
force |
Locked | #CC3322 (red) | O valor está definido e o convidado não pode alterá-lo |
Com o interruptor desligado o círculo fica cinzento (#9AA0A6), seja qual for o nível escolhido.
Um único lugar de assentamento de 38 dp em toda a aplicação:
CircleShape, com o fundo na cor do nível.contentAlignment = Alignment.Center.Envolvê-los em Box(Modifier.size(38.dp), contentAlignment=Center) { RadioButton(...) } para que o centro do círculo do botão coincida com o centro do círculo colorido acima. Sem o invólucro o botão desloca-se na horizontal, porque RadioButton se desenha com preenchimentos próprios.
Entre o botão de opção e o seu texto: Spacer(14.dp). Entre linhas de botões de opção de um grupo: Column(spacedBy(4.dp)).
A área tátil mínima está desativada: CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) — caso contrário cada linha de opção incha até 48 dp e quebra o ritmo.
Um interruptor padrão do Material 3, à direita do título do cartão. Desliga a regra inteira e a cor do nível deixa de ser mostrada — o círculo fica cinzento.
Um controlo próprio para trocar de modo dentro de uma regra — hoje só no Wi-Fi: Por uma rede específica / Pelas redes em redor. Dois elementos numa Row, cada um com weight(1f):
colorScheme.surface, RoundedCornerShape(10.dp), preenchimento 4dp.primaryContainer, RoundedCornerShape(8.dp), com texto SemiBold.Um contorno, um ícone e texto, totalmente arredondados. Para ações secundárias — Usar a posição atual, Capturar as redes em redor. Quando desativados, o contorno e o texto descem para alpha=0.4.
Ver a secção 3 sobre offset(y=-16.dp) e sliderTrim(). Antes de um cursor vai sempre um StatusLine com texto de estado (bodySmall, atenuado) — 74 m · máximo, o convidado pode reduzir.
Abre-se tocando na linha de título do cartão — toda a Row exceto o interruptor. Aparece logo abaixo do cabeçalho, a toda a largura do conteúdo do cartão. Para isso:
Box com onGloballyPositioned, e medimos a sua largura em dp.DropdownMenuItem com o modificador Modifier.width(anchorWidthDp).Os itens do menu respiram em vez de serem linhas achatadas: um marcador de cor à esquerda, o rótulo a negrito e por baixo a descrição em letra pequena e atenuada. Alinham-se na vertical pela primeira linha, o rótulo.
Ao contrário dos círculos coloridos normais das regras, o círculo do cabeçalho da Ativação pulsa.
drawBehind + Stroke(width=6dp).LinearEasing. Não FastOutSlowInEasing — caso contrário o reinício do ciclo mostra um solavanco.sin(π·p) × 0.6: em p=0 e p=1 a opacidade é 0, com o pico a meio. O reinício é invisível.Uma única frase numa cadeia da interface não leva ponto. Os pontos só aparecem quando há duas frases ou mais, para as separar. Vale para toda a descrição, dica, estado, balão de ajuda, cabeçalho e frase solta.
Nos estados do cursor em modo amarelo (Predefinido) indicamos sem rodeios a direção da liberdade:
A vermelho (Bloqueado): 74 m · exatamente / 5 de 7 · exatamente.
Decide o convidado / Predefinido / Bloqueado pegaram; não os mudamos.
Se uma regra tem estado desligado, exprima-o com um interruptor e não com uma opção de rádio do tipo Não é preciso / Não / Desligado. Caso contrário o utilizador tem duas maneiras de desligar a mesma coisa, e isso confunde.
Examples:
No bloco Coordenadas os ícones de copiar e apagar ficam à direita ao nível das duas linhas (o rótulo e o estado). Implementado como Row { Column(weight=1f) { label; status }; CopyIcon; DeleteIcon }. Os ícones centram-se na vertical em relação ao par inteiro.
O corpo abre-se apenas quando:
Animation: expandVertically(tween(220)) + fadeIn(tween(220)) ao abrir, shrinkVertically(tween(180)) + fadeOut(tween(120)) ao fechar.
Ao receber uma chave de convidado — só se a aplicação estiver no ecrã e a chave for nova e não repetida. Caso contrário guardamos em silêncio.
sin(π·p) × 0.6. Qualquer simplificação estraga o aspeto.Última atualização: 18 de abril de 2026 · versão do documento 2.07