@@ -8,68 +8,102 @@ import com.mg4.control.accessibility.JoystickFocus
88 * Ici le joystick ne déplace aucun focus, contrairement aux popups de profils : chaque direction
99 * agit directement, comme dans la fenêtre clim d'origine de la voiture.
1010 *
11- * | | Action |
12- * |----------|------------------|
13- * | Haut | température + 1 |
14- * | Bas | température − 1 |
15- * | Droite | ventilation + 1 |
16- * | Gauche | ventilation − 1 |
17- * | Clic | ventilation AUTO |
11+ * | | Action |
12+ * |----------|------------------------------------------ |
13+ * | Haut | température + 1 |
14+ * | Bas | température − 1 |
15+ * | Droite | ventilation + 1 |
16+ * | Gauche | ventilation − 1 |
17+ * | Clic | mode AUTO, ou retour au réglage manuel |
1818 *
1919 * La fenêtre se ferme au doigt, par le raccourci qui l'a ouverte, ou seule après son délai.
20+ *
21+ * ## Le mode AUTO
22+ *
23+ * En AUTO la voiture règle elle-même la ventilation (et le sens de l'air), et n'annonce plus un
24+ * cran mais **15** : c'est à ce 15 que les écrans d'origine reconnaissent AUTO et l'affichent
25+ * (`HvacControlDialog` et `AcCardView` de SWI133, `HvacView` de SWI165). Ces mêmes écrans ne
26+ * « coupent » jamais AUTO : ils en SORTENT en écrivant un niveau de ventilation — le dernier
27+ * réglé à la main, plus ou moins un cran. C'est la règle reprise ici, et partagée avec la page
28+ * Clim et les raccourcis « ventilation ± ».
2029 */
2130object HvacPopup {
2231
2332 sealed class Action {
2433 data class Temperature (val cible : Int ) : Action()
34+
35+ /* * Un niveau de ventilation écrit à la main — c'est aussi ce qui fait sortir d'AUTO. */
2536 data class Ventilation (val cible : Int ) : Action()
26- /* * Ventilation automatique : la case centrale de la croix. */
27- data class Auto (val actif : Boolean ) : Action()
37+
38+ /* * Passer en mode AUTO : la case centrale de la croix. */
39+ object Auto : Action()
2840 }
2941
3042 /* * Ce qui est lu sur la voiture ; les bornes en viennent aussi. null = illisible. */
3143 data class Reglages (
3244 val temp : Int? , val tempMin : Int , val tempMax : Int ,
3345 val fan : Int? , val fanMin : Int , val fanMax : Int ,
34- /* * Ventilation AUTO ; null = illisible. */
46+ /* * Mode AUTO annoncé par la voiture ; null = illisible. */
3547 val auto : Boolean? = null ,
48+ /* * Dernier niveau de ventilation réglé à la main, retenu avant AUTO ; null = jamais vu. */
49+ val dernierNiveau : Int? = null ,
3650 ) {
3751 /* *
38- * Niveau de ventilation à montrer et à régler, ou null s'il n'y en a pas : en AUTO la
39- * voiture annonce 15, qui n'est pas un cran (constaté sur SWI133 le 2026-10-09). Tout ce
40- * qui dépasse le maximum est traité de même — ce 15 peut traîner un instant après AUTO.
52+ * AUTO actif : la voiture le dit, ou son niveau de ventilation le trahit — au-delà du
53+ * maximum ce n'est plus un cran (15), et c'est à cela que les écrans d'origine s'y fient.
4154 */
42- val ventilationReelle: Int? get() = fan?.takeIf { auto != true && it <= fanMax }
55+ val enAuto: Boolean get() = auto == true || (fan != null && fan > fanMax)
56+
57+ /* * Niveau de ventilation à montrer et à régler ; null en AUTO ou s'il est illisible. */
58+ val ventilationReelle: Int? get() = fan?.takeIf { ! enAuto }
4359 }
4460
4561 /* * Action portée par [commande], ou null s'il n'y a rien à écrire (butée, valeur illisible). */
4662 fun action (commande : JoystickFocus .Commande , reglages : Reglages ): Action ? = when (commande) {
4763 JoystickFocus .Commande .HAUT -> pas(reglages.temp, reglages.tempMin, reglages.tempMax, + 1 )?.let { Action .Temperature (it) }
4864 JoystickFocus .Commande .BAS -> pas(reglages.temp, reglages.tempMin, reglages.tempMax, - 1 )?.let { Action .Temperature (it) }
49- JoystickFocus .Commande .DROITE -> ventilation(reglages, + 1 )
50- JoystickFocus .Commande .GAUCHE -> ventilation(reglages, - 1 )
51- // Sans état lisible on ne bascule pas : c'est l'inverse de l'intention qui pourrait partir.
52- JoystickFocus .Commande .VALIDER -> reglages.auto?.let { Action .Auto (! it) }
65+ JoystickFocus .Commande .DROITE -> pasVentilation(reglages, + 1 )?.let { Action .Ventilation (it) }
66+ JoystickFocus .Commande .GAUCHE -> pasVentilation(reglages, - 1 )?.let { Action .Ventilation (it) }
67+ JoystickFocus .Commande .VALIDER -> when {
68+ // Rendre la main : le dernier niveau, tel quel.
69+ reglages.enAuto -> Action .Ventilation (niveauDeSortie(reglages))
70+ reglages.auto == false -> Action .Auto
71+ // Sans état lisible on n'appuie pas : sur l'ancien SDK la commande est un APPUI, et
72+ // rien ne dit qu'un second ne couperait pas le mode qu'on voulait activer.
73+ else -> null
74+ }
5375 }
5476
5577 /* *
56- * En AUTO, un geste sur la ventilation en SORT sans écrire de niveau : il n'y a pas de cran
57- * d'où partir, et partir du 15 annoncé écrirait le maximum même pour un « moins ». La voiture
58- * annonce ensuite son vrai niveau, et c'est le geste suivant qui le règle.
78+ * Niveau de ventilation à écrire pour un cran de plus ([sens] = +1) ou de moins (−1) ; null
79+ * s'il n'y a rien à écrire (butée, niveau illisible).
80+ *
81+ * En AUTO on repart du dernier niveau réglé à la main — surtout pas du 15 annoncé, d'où un
82+ * « moins » écrirait le MAXIMUM — et on écrit MÊME en butée : c'est cette écriture qui rend
83+ * la main, et l'utilisateur vient de demander à régler lui-même.
84+ */
85+ fun pasVentilation (reglages : Reglages , sens : Int ): Int? =
86+ if (reglages.enAuto) (niveauDeSortie(reglages) + sens).coerceIn(reglages.fanMin, reglages.fanMax)
87+ else pas(reglages.fan, reglages.fanMin, reglages.fanMax, sens)
88+
89+ /* *
90+ * Niveau à écrire pour quitter AUTO sans rien changer d'autre : le dernier réglé à la main.
91+ * Faute de l'avoir jamais vu (application neuve, voiture en AUTO depuis toujours), le milieu
92+ * de l'échelle — ni le minimum ni le maximum ne feraient un point de départ raisonnable.
5993 */
60- private fun ventilation (reglages : Reglages , sens : Int ): Action ? =
61- if ( reglages.auto == true ) Action . Auto ( false )
62- else pas (reglages.ventilationReelle, reglages. fanMin, reglages.fanMax, sens)?. let { Action . Ventilation (it) }
94+ fun niveauDeSortie (reglages : Reglages ): Int =
95+ (reglages.dernierNiveau ? : (( reglages.fanMin + reglages.fanMax) / 2 ) )
96+ .coerceIn (reglages.fanMin, reglages.fanMax)
6397
6498 // ── Taille de la fenêtre ─────────────────────────────────────────────────
65- private const val LARGEUR_DP = 780f // la carte de overlay_hvac_popup.xml
99+ private const val LARGEUR_DP = 900f // la carte de overlay_hvac_popup.xml
66100 private const val HAUTEUR_DP = 390f // sa hauteur en texte « Standard »
67- private const val PART_LARGEUR = 0.56f
101+ private const val PART_LARGEUR = 0.63f
68102 private const val PART_HAUTEUR = 0.72f
69103
70104 /* *
71105 * Agrandissement à appliquer à la fenêtre, dessinée pour [LARGEUR_DP] × [HAUTEUR_DP] dp, pour
72- * qu'elle prenne la même part de l'ÉCRAN quelle que soit sa densité : au plus 56 % de la
106+ * qu'elle prenne la même part de l'ÉCRAN quelle que soit sa densité : au plus 63 % de la
73107 * largeur et 72 % de la hauteur. Le reste de la hauteur laisse passer la barre d'état, le
74108 * texte « Très grand » et la ligne d'avertissement sans déborder.
75109 *
0 commit comments