Un document vivant. Il consigne les calibrages, le rythme et les accords sur l’interface, et se met à jour dès qu’une décision est prise.
Les icônes Material de androidx.compose.material.icons.outlined.* partagent une taille nominale — une grille 24×24 — mais la partie dessinée diffère de l’une à l’autre. Donnez-leur à toutes un simple Modifier.size(24.dp)et certaines paraîtront grandes et lourdes (Bolt, Delete) tandis que d’autres sembleront minuscules (Contacts, VpnKey). Il faut les calibrer à la main.
new_size = 24 × 52 / measured_px, borné à [18..28].52 px, c’est le glyphe de référence, Fingerprint ; tout se mesure par rapport à lui comme unité.
| Icon | Taille | Why |
|---|---|---|
| Add, Apps, ArrowBack, Close | 28dp | traits fins, glyphe de 36–41 px |
| AutoMode, Bolt, DeleteOutline, Edit, List, Photo, QrCode, Tune, DriveFileRenameOutline | 26dp | densité moyenne, glyphe d’environ 47 px |
| LocationOn | 24dp | la punaise de carte — ne pas toucher, c’est une référence |
| LocationOff | 26dp | la punaise barrée — ne pas toucher |
| ContentCopy, MyLocation, PeopleAlt, Wifi, ExpandMore, ExpandLess | 22dp | larges ou hautes, et visuellement lourdes |
| Contacts, VpnKey | 20dp | surchargées de détails internes |
S’utilise partout où se trouvait Bolt — le bouton Automatisation sur la carte de clé, l’icône de section dans les réglages. Taille 26 dp, comme Bolt.
IconsDebugSheet.kt — la table iconSizeByName. L’écran de débogage s’ouvre depuis SettingsScreen par le bouton Icônes UI (debug) : en haut une FlowRow d’icônes calibrées, en bas une liste de noms, tailles et contextes.
Nous utilisons les rôles standard de Material 3, délibérément limités à quatre :
| Role | Taille / interligne | Où c’est utilisé |
|---|---|---|
titleLarge SemiBold | 22sp/28sp | Le titre de la feuille inférieure ( Pour l’invité · <objet> ) |
titleMedium SemiBold | 16sp/24sp | Titres de règles ( Géolocalisation , Wi-Fi , Intervalle , Activation ) et libellés de FieldSection ( Coordonnées , Rayon , Mode , Réseaux enregistrés ) |
bodyMedium | 14sp/20sp | Libellés des boutons radio, texte dans les boutons pilule, noms des segments |
bodySmall muted | 12sp/16sp | Statuts et indications ( 36540 m jusqu’au point , 74 m · maximum, l’invité peut réduire , textes d’aide). Couleur : onSurfaceVariant |
Nous avons convenu de travailler en multiples de 2 / 4 / 8 / 10 / 14 / 16 / 20. Ni festin ni disette : des éléments identiques reçoivent des espacements identiques.
spacedBy(12.dp). Les cartes sont espacées de 12 dp.padding(horizontal=20.dp).padding(top=16.dp).verticalArrangement = spacedBy(16.dp) entre les grands blocs.spacedBy(2.dp). Seulement 2 dp entre le libellé en gras et le statut ou l’indication en dessous, pour qu’ils se lisent comme un couple titre-légende.Spacer(10.dp). Idem dans Géo, la connexion Wi-Fi et la liste Wi-Fi.Un curseur Material 3 occupe environ 48 dp dans la mise en page à cause de la zone tactile minimale, dont la piste visible ne fait qu’environ 4 dp au milieu, avec près de 22 dp de zone tactile vide au-dessus et en dessous. D’où :
Modifier.offset(y=-16.dp) pour rapprocher la poignée du texte.Modifier.sliderTrim() (un utilitaire dans GuestAutomationEditor.kt), qui annonce au parent une hauteur de 22 dp au lieu de 48 dp. Ainsi le padding(16.dp) donne le même écart entre la poignée et le bord inférieur que sur les cartes se terminant par un bouton pilule.Chaque règle — Géolocalisation, Wi-Fi, Temps, Autorisation, Confirmation — et le bloc Activation prennent l’un des trois niveaux. Le cercle d’icône de la carte prend la couleur correspondante quand l’interrupteur est allumé.
| Key | Label | Colour | Description |
|---|---|---|---|
own |
L’invité décide | #3BA55D (green) | L’invité choisit lui-même le réglage |
suggest |
Preset | #E5B409 (yellow) | La valeur est fixée, et l’invité peut la modifier |
force |
Locked | #CC3322 (red) | La valeur est fixée et l’invité ne peut pas la modifier |
Quand l’interrupteur est éteint, le cercle devient gris (#9AA0A6), quel que soit le niveau choisi.
Un unique emplacement d’accueil de 38 dp dans toute l’application :
CircleShape, le fond prenant la couleur du niveau.contentAlignment = Alignment.Center.Les envelopper dans Box(Modifier.size(38.dp), contentAlignment=Center) { RadioButton(...) } pour que le centre du cercle radio s’aligne sur celui du cercle coloré au-dessus. Sans l’enveloppe, le radio dérive horizontalement, car RadioButton se dessine avec ses propres marges.
Entre un bouton radio et son texte : Spacer(14.dp). Entre les lignes radio d’un groupe : Column(spacedBy(4.dp)).
La zone tactile minimale est désactivée : CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) — sinon chaque ligne radio gonfle à 48 dp et casse le rythme.
Un interrupteur standard Material 3, à droite du titre de la carte. Il désactive toute la règle, et la couleur du niveau n’est alors pas affichée — le cercle devient gris.
Un contrôle maison pour changer de mode dans une règle — aujourd’hui seulement en Wi-Fi : Par un réseau précis / Par les réseaux alentour . Deux éléments dans une Row, chacun avec weight(1f):
colorScheme.surface, RoundedCornerShape(10.dp), marge 4dp.primaryContainer, RoundedCornerShape(8.dp), texte en SemiBold.Un contour, une icône et du texte, entièrement arrondis. Pour les actions secondaires — Utiliser la position actuelle , Relever les réseaux alentour . À l’état désactivé, le contour et le texte tombent à alpha=0.4.
Voir la section 3 sur offset(y=-16.dp) et sliderTrim(). Un curseur est toujours précédé d’un StatusLine portant un texte de statut (bodySmall, atténué) — 74 m · maximum, l’invité peut réduire .
S’ouvre en touchant la ligne de titre de la carte — toute la Row sauf l’interrupteur. Il apparaît juste sous l’en-tête, sur toute la largeur du contenu de la carte. Pour cela :
Box avec onGloballyPositioned, et l’on mesure sa largeur en dp.DropdownMenuItem avec le modificateur Modifier.width(anchorWidthDp).Les éléments du menu respirent au lieu d’être des lignes plates : un marqueur coloré à gauche, le libellé en gras, la description en dessous en petits caractères atténués. Ils s’alignent verticalement sur la première ligne, le libellé.
Contrairement aux cercles colorés ordinaires des règles, le cercle d’en-tête d’Activation pulse.
drawBehind + Stroke(width=6dp).LinearEasing. Pas FastOutSlowInEasing — sinon le redémarrage du cycle laisse voir un à-coup.sin(π·p) × 0.6 : à p=0 et p=1 l’alpha vaut 0, avec un pic au milieu. Le redémarrage est invisible.Une phrase unique dans une chaîne d’interface ne prend pas de point. Les points n’apparaissent qu’à partir de deux phrases, pour les séparer. Cela vaut pour toute description, indication, statut, infobulle, titre et phrase isolée.
Dans les statuts de curseur en mode jaune (Prédéfini), nous indiquons franchement le sens de la liberté :
En rouge (Verrouillé) : 74 m · exactement / 5 sur 7 · exactement .
L’invité décide / Prédéfini / Verrouillé se sont imposés ; nous ne les changeons pas.
Si une règle possède un état désactivé, exprimez-le par un interrupteur plutôt que par une option radio du genre Non requis / Non / Désactivé . Sinon l’utilisateur dispose de deux façons de désactiver la même chose, ce qui déroute.
Examples:
Dans le bloc Coordonnées, les icônes de copie et de suppression sont à droite au niveau des deux lignes (le libellé et le statut). Implémenté sous la forme Row { Column(weight=1f) { label; status }; CopyIcon; DeleteIcon }. Les icônes sont centrées verticalement par rapport au couple entier.
Le corps ne se déplie que si :
Animation: expandVertically(tween(220)) + fadeIn(tween(220)) à l’ouverture, shrinkVertically(tween(180)) + fadeOut(tween(120)) à la fermeture.
À la réception d’une clé invité — seulement si l’application est à l’écran et que la clé est nouvelle et non répétée. Sinon, on l’enregistre en silence.
sin(π·p) × 0.6. Toute simplification gâche le rendu.Dernière mise à jour : 18 avril 2026 · version du document 2.07