|
| 1 | +package com.mg4.control.automation |
| 2 | + |
| 3 | +/** |
| 4 | + * Carte « A/C selon la température » : ce que ses curseurs et ses boutons enregistrent — la partie |
| 5 | + * sans vue, donc testable. |
| 6 | + * |
| 7 | + * La carte est passée des champs de saisie aux curseurs sans toucher à [ClimateAutomationSettings] : |
| 8 | + * mêmes clés, mêmes valeurs, aucune reprise à faire chez ceux qui l'avaient déjà réglée. |
| 9 | + */ |
| 10 | +object ClimateRuleEditor { |
| 11 | + |
| 12 | + // ── Recirculation ──────────────────────────────────────────────────────── |
| 13 | + // Un seul choix à quatre positions remplace la case « Forcer la recirculation » et ses trois |
| 14 | + // boutons : 0 = inchangée (la case décochée), puis 1 = intérieur, 2 = extérieur, 3 = auto — |
| 15 | + // soit le mode de la voiture (0, 1, 2) décalé d'un rang. |
| 16 | + |
| 17 | + /** Position à allumer pour les réglages enregistrés. */ |
| 18 | + fun recircChoice(force: Boolean, mode: Int): Int = if (force) mode.coerceIn(0, 2) + 1 else 0 |
| 19 | + |
| 20 | + /** Vrai si le choix demande à piloter la recirculation. */ |
| 21 | + fun recircForce(choice: Int): Boolean = choice != 0 |
| 22 | + |
| 23 | + /** |
| 24 | + * Mode à enregistrer, ou `null` pour « inchangée » : le mode déjà enregistré reste alors en |
| 25 | + * place, comme quand on décochait la case. |
| 26 | + */ |
| 27 | + fun recircMode(choice: Int): Int? = if (choice in 1..3) choice - 1 else null |
| 28 | + |
| 29 | + // ── Seuils ─────────────────────────────────────────────────────────────── |
| 30 | + // Le seuil froid reste sous le seuil chaud. Croisés, ils déclenchent les deux règles à la |
| 31 | + // fois et [ClimateAutomationDecision] retient « chaud » — ce que rien à l'écran ne disait. |
| 32 | + |
| 33 | + /** Seuil chaud à retenir quand l'utilisateur vise [wanted], le seuil froid étant à [cold]. */ |
| 34 | + fun hotThreshold(wanted: Int, cold: Int): Int = |
| 35 | + maxOf(wanted, cold + 1).coerceIn(ClimateAutomationSettings.MIN_TEMP, ClimateAutomationSettings.MAX_TEMP) |
| 36 | + |
| 37 | + /** Seuil froid à retenir quand l'utilisateur vise [wanted], le seuil chaud étant à [hot]. */ |
| 38 | + fun coldThreshold(wanted: Int, hot: Int): Int = |
| 39 | + minOf(wanted, hot - 1).coerceIn(ClimateAutomationSettings.MIN_TEMP, ClimateAutomationSettings.MAX_TEMP) |
| 40 | + |
| 41 | + // ── Curseurs ───────────────────────────────────────────────────────────── |
| 42 | + |
| 43 | + /** |
| 44 | + * Valeur à poser sur un curseur borné à [min]..[max]. Les bornes sont celles de la voiture |
| 45 | + * (16–32 °C et 7 crans sur l'ancien SDK, 17–33 °C et 11 crans sur A9) : une valeur saisie |
| 46 | + * autrefois peut en sortir, et un Slider posé hors de ses bornes fait planter l'écran. |
| 47 | + */ |
| 48 | + fun sliderValue(stored: Int, min: Int, max: Int): Int = stored.coerceIn(min, maxOf(min, max)) |
| 49 | + |
| 50 | + /** Seuil tel qu'il se lit à droite de son curseur : « ≥ 28 °C » ou « ≤ 5 °C ». */ |
| 51 | + fun thresholdLabel(hot: Boolean, value: Int): String = (if (hot) "\u2265 " else "\u2264 ") + value + " °C" |
| 52 | + |
| 53 | + // ── Boutons de recirculation ───────────────────────────────────────────── |
| 54 | + |
| 55 | + /** Largeur en dessous de laquelle un bouton ne tient plus son libellé dans les sept langues. */ |
| 56 | + const val MIN_BUTTON_DP = 130f |
| 57 | + |
| 58 | + /** |
| 59 | + * Nombre de boutons par ligne pour une rangée de [rowWidthDp] : les quatre côte à côte quand |
| 60 | + * la place le permet, sinon trois — « Auto » passe alors dessous, sous « Inchangée ». |
| 61 | + */ |
| 62 | + fun recircColumns(rowWidthDp: Float): Int = if (rowWidthDp >= 4 * MIN_BUTTON_DP) 4 else 3 |
| 63 | +} |
0 commit comments