Skip to content

Commit 5975d35

Browse files
committed
Add pop-up HVAC V0.1
1 parent 57cfea0 commit 5975d35

6 files changed

Lines changed: 110 additions & 8 deletions

File tree

‎app/src/main/java/com/mg4/control/model/HvacPopup.kt‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,29 @@ object HvacPopup {
3939
JoystickFocus.Commande.VALIDER -> Action.Fermer
4040
}
4141

42+
// ── Taille de la fenêtre ─────────────────────────────────────────────────
43+
private const val LARGEUR_DP = 600f // la carte de overlay_hvac_popup.xml
44+
private const val HAUTEUR_DP = 375f // sa hauteur en texte « Standard »
45+
private const val PART_LARGEUR = 0.46f
46+
private const val PART_HAUTEUR = 0.72f
47+
48+
/**
49+
* Agrandissement à appliquer à la fenêtre, dessinée pour [LARGEUR_DP] × [HAUTEUR_DP] dp, pour
50+
* qu'elle prenne la même part de l'ÉCRAN quelle que soit sa densité : au plus 46 % de la
51+
* largeur et 72 % de la hauteur. Le reste de la hauteur laisse passer la barre d'état, le
52+
* texte « Très grand » et la ligne d'avertissement sans déborder.
53+
*
54+
* On raisonne en part d'écran, pas en dp fixes : la voiture affiche 1920 × 720 px à 1 px par
55+
* dp (SWI133, mesuré sur une photo le 2026-10-09 — la fenêtre de 600 dp n'y couvrait que
56+
* 31 % de la largeur), mais la densité des firmwares A9 n'est pas connue.
57+
*/
58+
fun echelle(largeurPx: Int, hauteurPx: Int, densite: Float): Float {
59+
if (largeurPx <= 0 || hauteurPx <= 0 || !(densite > 0f)) return 1f
60+
val parLargeur = PART_LARGEUR * largeurPx / (LARGEUR_DP * densite)
61+
val parHauteur = PART_HAUTEUR * hauteurPx / (HAUTEUR_DP * densite)
62+
return minOf(parLargeur, parHauteur).coerceIn(0.75f, 2f)
63+
}
64+
4265
/**
4366
* On CLAMPE, on ne boucle pas : arriver à 32 °C et repartir à 16 en poussant sur le volant
4467
* serait une très mauvaise surprise. Une cible égale à la valeur lue ne s'écrit pas.

‎app/src/main/java/com/mg4/control/service/HvacPopupOverlay.kt‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.mg4.control.service
22

33
import android.content.Context
44
import android.content.res.ColorStateList
5+
import android.content.res.Configuration
56
import android.graphics.PixelFormat
67
import android.os.Handler
78
import android.os.Looper
@@ -24,7 +25,9 @@ import com.mg4.control.hardware.MG4Hardware
2425
import com.mg4.control.model.AirFlow
2526
import com.mg4.control.model.HvacPopup
2627
import com.mg4.control.util.LocaleHelper
28+
import java.util.Locale
2729
import java.util.concurrent.Executors
30+
import kotlin.math.roundToInt
2831

2932
/**
3033
* Pop-up HVAC (issues #120 et #125) : réglage rapide de la clim par-dessus n'importe quel écran,
@@ -123,7 +126,17 @@ object HvacPopupOverlay {
123126
fermer()
124127

125128
val localized = LocaleHelper.applyLocale(context)
126-
val themed = ContextThemeWrapper(localized, R.style.Theme_MG4Control)
129+
// La mise en page est écrite pour une carte de 600 × 375 dp, trop petite sur l'écran de la
130+
// voiture (constaté sur SWI133). Plutôt que de figer d'autres dp, qui ne conviendraient
131+
// qu'à UNE densité, on la gonfle avec une densité corrigée : dp et sp suivent d'un bloc,
132+
// pictos et texte restent nets, et la fenêtre prend la même part de l'écran partout.
133+
val mesures = localized.resources.displayMetrics
134+
val echelle = HvacPopup.echelle(mesures.widthPixels, mesures.heightPixels, mesures.density)
135+
val agrandi = localized.createConfigurationContext(
136+
Configuration(localized.resources.configuration).apply {
137+
densityDpi = (mesures.densityDpi * echelle).roundToInt()
138+
})
139+
val themed = ContextThemeWrapper(agrandi, R.style.Theme_MG4Control)
127140
val view = LayoutInflater.from(themed).inflate(R.layout.overlay_hvac_popup, null)
128141
val v = Vues(view, themed, localized)
129142

@@ -150,7 +163,9 @@ object HvacPopupOverlay {
150163
etat = lu
151164
overlayView = view
152165
AppLogger.i(TAG, "Pop-up HVAC affiché : ${lu.tempC} °C (${lu.tempMin}..${lu.tempMax}), " +
153-
"ventilation ${lu.fanLevel} (${lu.fanMin}..${lu.fanMax}), sens de l'air ${lu.airFlow}")
166+
"ventilation ${lu.fanLevel} (${lu.fanMin}..${lu.fanMax}), sens de l'air ${lu.airFlow} ; " +
167+
"échelle ×${String.format(Locale.US, "%.2f", echelle)} (écran ${mesures.widthPixels}×" +
168+
"${mesures.heightPixels} px, ${mesures.densityDpi} dpi)")
154169
afficher()
155170

156171
relancerDelai()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- D'après Google Material Icons « close » (24 px) — Apache License 2.0.
3+
Teinte d'origine retirée : c'est le bouton qui colore l'icône (iconTint). -->
4+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
5+
android:width="24dp"
6+
android:height="24dp"
7+
android:viewportWidth="24"
8+
android:viewportHeight="24">
9+
<path
10+
android:fillColor="@android:color/white"
11+
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
12+
</vector>

‎app/src/main/res/layout/overlay_hvac_popup.xml‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<!-- Pop-up HVAC (issues #120 et #125). La croix de gauche montre ce que fait le joystick droit
3-
du volant ET se touche du doigt : sans service d'accessibilité, c'est le seul moyen d'agir. -->
3+
du volant ET se touche du doigt : sans service d'accessibilité, c'est le seul moyen d'agir.
4+
5+
Les tailles ci-dessous sont des tailles DE BASE (carte de 600 × 375 dp) : HvacPopupOverlay
6+
gonfle cette mise en page avec une densité corrigée pour qu'elle prenne la même part de
7+
l'écran sur tous les firmwares (HvacPopup.echelle). Changer la largeur ou la hauteur de la
8+
carte demande d'y reporter les constantes. -->
49
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
510
xmlns:app="http://schemas.android.com/apk/res-auto"
611
android:id="@+id/hvac_backdrop"
@@ -61,10 +66,14 @@
6166
app:strokeColor="@color/dash_border"
6267
app:cornerRadius="10dp" />
6368

69+
<!-- baselineAligned="false" : sinon la rangée aligne les TEXTES des trois cases, et
70+
une case dont le texte n'est pas à la même hauteur que ses voisines se décale
71+
(constaté sur véhicule avec « Fermer », alors sans picto). -->
6472
<LinearLayout
6573
android:layout_width="wrap_content"
6674
android:layout_height="wrap_content"
67-
android:orientation="horizontal">
75+
android:orientation="horizontal"
76+
android:baselineAligned="false">
6877

6978
<com.google.android.material.button.MaterialButton
7079
android:id="@+id/hvac_btn_fan_down"
@@ -112,6 +121,11 @@
112121
android:textColor="@color/text_secondary"
113122
android:textSize="12sp"
114123
android:maxLines="1"
124+
app:icon="@drawable/ic_hvac_close"
125+
app:iconGravity="top"
126+
app:iconSize="26dp"
127+
app:iconPadding="0dp"
128+
app:iconTint="@color/text_secondary"
115129
app:backgroundTint="@color/dash_btn"
116130
app:strokeWidth="1dp"
117131
app:strokeColor="@color/dash_border"

‎app/src/test/java/com/mg4/control/model/HvacPopupTest.kt‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,41 @@ class HvacPopupTest {
7575
val rien = milieu.copy(temp = null, fan = null)
7676
assertEquals(Action.Fermer, HvacPopup.action(Commande.VALIDER, rien))
7777
}
78+
79+
// ── Taille de la fenêtre ─────────────────────────────────────────────────
80+
// Dessinée pour 600 × 375 dp, elle paraissait petite sur la voiture : l'écran y fait
81+
// 1920 × 720 pixels à 1 pixel par dp (mesuré sur une photo, SWI133, 2026-10-09).
82+
83+
@Test
84+
fun `sur l ecran de la voiture la fenetre grandit`() {
85+
// Limitée par la hauteur : 72 % de 720 px pour 375 dp → × 1,38.
86+
assertEquals(1.38f, HvacPopup.echelle(1920, 720, 1f), 0.01f)
87+
}
88+
89+
@Test
90+
fun `la place prise a l ecran ne depend pas de la densite`() {
91+
// La densité des firmwares A9 n'est pas connue. Si l'un d'eux annonce 1280 × 480 dp
92+
// (1,5 pixel par dp), la fenêtre doit couvrir les mêmes pixels, pas déborder.
93+
val pixelsParDpDeBase = HvacPopup.echelle(1920, 720, 1f) * 1f
94+
assertEquals(pixelsParDpDeBase, HvacPopup.echelle(1920, 720, 1.5f) * 1.5f, 0.001f)
95+
}
96+
97+
@Test
98+
fun `un ecran moins large limite par la largeur`() {
99+
// 46 % de 1400 px pour 600 dp → × 1,07, sous la limite de hauteur (× 1,38).
100+
assertEquals(1.07f, HvacPopup.echelle(1400, 720, 1f), 0.01f)
101+
}
102+
103+
@Test
104+
fun `l echelle reste bornee`() {
105+
assertEquals(2f, HvacPopup.echelle(8000, 4000, 1f), 0f)
106+
assertEquals(0.75f, HvacPopup.echelle(400, 300, 1f), 0f)
107+
}
108+
109+
@Test
110+
fun `des mesures absurdes laissent la fenetre telle quelle`() {
111+
assertEquals(1f, HvacPopup.echelle(0, 0, 1f), 0f)
112+
assertEquals(1f, HvacPopup.echelle(1920, 720, 0f), 0f)
113+
assertEquals(1f, HvacPopup.echelle(1920, 720, Float.NaN), 0f)
114+
}
78115
}

‎docs/assets/js/sim.js‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,7 +1886,7 @@
18861886
const cell = (dir, cls, ico, key) => '<button class="b hv-cell hv-' + cls + (o.flash === dir ? ' hit' : '') + '" data-a="hvKey" data-v="' + dir + '">' + ico + '<span>' + esc(S(key)) + '</span></button>';
18871887
const ab = (f, on, key, icon) => '<button class="b air sm' + (on ? ' on' : '') + '" data-a="hvAir" data-v="' + f + '">' + AIR_ICON[icon] + '<span>' + esc(S(key)) + '</span></button>';
18881888
return '<div class="a-scrim" data-a="hvBg"><div class="hvacpop" data-hl="hvac-popup"><div class="hv-top"><div class="hv-cross">' +
1889-
cell('up', 'up', HV_ICON.temp, 'hvac_popup_temp_up') + cell('left', 'left', HV_ICON.fan, 'hvac_popup_fan_down') + cell('ok', 'ok', '', 'nav_close') +
1889+
cell('up', 'up', HV_ICON.temp, 'hvac_popup_temp_up') + cell('left', 'left', HV_ICON.fan, 'hvac_popup_fan_down') + cell('ok', 'ok', HV_ICON.close, 'nav_close') +
18901890
cell('right', 'right', HV_ICON.fan, 'hvac_popup_fan_up') + cell('down', 'down', HV_ICON.temp, 'hvac_popup_temp_down') + '</div>' +
18911891
'<div class="hv-vals"><div class="lbl">' + esc(S('clim_temperature')) + '</div><div class="hv-temp">' + cl.temp + ' °C</div>' +
18921892
'<div class="lbl">' + esc(S('clim_fan')) + '<b>' + cl.fan + '</b></div><div class="hv-fan"><i style="width:' + Math.round(100 * cl.fan / cl.fMax) + '%"></i></div></div></div>' +
@@ -2559,12 +2559,13 @@
25592559
};
25602560
const airSvg = (k) => '<svg class="air-ico" viewBox="0 0 960 960" fill="currentColor" aria-hidden="true"><path d="' + AIR_PATH[k] + '"/></svg>';
25612561
const AIR_ICON = { face: airSvg('face'), feet: airSvg('feet'), ws: airSvg('ws'), rear: airSvg('rear') };
2562-
// Croix du pop-up HVAC (res/drawable/ic_hvac_temp.xml et ic_hvac_fan.xml) : d'après Google
2563-
// Material Icons « thermostat » et « toys » (Apache License 2.0).
2562+
// Croix du pop-up HVAC (res/drawable/ic_hvac_temp.xml, ic_hvac_fan.xml et ic_hvac_close.xml) :
2563+
// d'après Google Material Icons « thermostat », « toys » et « close » (Apache License 2.0).
25642564
const hvSvg = (d) => '<svg class="hv-ico" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="' + d + '"/></svg>';
25652565
const HV_ICON = {
25662566
temp: hvSvg('M15,13V5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v8c-1.21,0.91 -2,2.37 -2,4 0,2.76 2.24,5 5,5s5,-2.24 5,-5c0,-1.63 -0.79,-3.09 -2,-4zM11,5c0,-0.55 0.45,-1 1,-1s1,0.45 1,1h-1v1h1v2h-1v1h1v2h-2L11,5z'),
2567-
fan: hvSvg('M12,12c0,-3 2.5,-5.5 5.5,-5.5S23,9 23,12H12zM12,12c0,3 -2.5,5.5 -5.5,5.5S1,15 1,12h11zM12,12c-3,0 -5.5,-2.5 -5.5,-5.5S9,1 12,1v11zM12,12c3,0 5.5,2.5 5.5,5.5S15,23 12,23V12z')
2567+
fan: hvSvg('M12,12c0,-3 2.5,-5.5 5.5,-5.5S23,9 23,12H12zM12,12c0,3 -2.5,5.5 -5.5,5.5S1,15 1,12h11zM12,12c-3,0 -5.5,-2.5 -5.5,-5.5S9,1 12,1v11zM12,12c3,0 5.5,2.5 5.5,5.5S15,23 12,23V12z'),
2568+
close: hvSvg('M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z')
25682569
};
25692570
// Tout fermer / Tout ouvrir (res/drawable/ic_window_all_*.xml) : Material Symbols keyboard_double_arrow_up/down.
25702571
const WIN_PATH = {

0 commit comments

Comments
 (0)