@@ -9,6 +9,7 @@ import android.widget.*
99import androidx.fragment.app.Fragment
1010import androidx.navigation.fragment.findNavController
1111import com.google.android.material.button.MaterialButton
12+ import com.google.android.material.slider.Slider
1213import com.mg4.control.R
1314import com.mg4.control.bluetooth.BluetoothProfileManager
1415import com.mg4.control.hardware.MG4Hardware
@@ -37,6 +38,14 @@ import kotlinx.coroutines.withContext
3738class ProfileEditFragment : Fragment () {
3839
3940 companion object {
41+ /* * Bornes des curseurs de climatisation — les VRAIES limites sont lues sur le véhicule
42+ * au moment d'appliquer, et la consigne y est clampée. Celles-ci ne servent qu'à la
43+ * saisie, et doivent couvrir tous les firmwares (A9 accepte 17–33 °C et 1–11). */
44+ const val HVAC_TEMP_MIN = 15
45+ const val HVAC_TEMP_MAX = 33
46+ const val HVAC_FAN_MIN = 1
47+ const val HVAC_FAN_MAX = 11
48+
4049 /* *
4150 * Passage de données depuis [ProfileFragment]. Un profil complet ne tient pas
4251 * confortablement dans un Bundle (enums + une vingtaine de champs) et l'écran n'est ouvert
@@ -105,6 +114,19 @@ class ProfileEditFragment : Fragment() {
105114 var seatHeatEnabled = data.appliesSeatHeat
106115 var seatLeft = data.seatHeatLeft
107116 var seatRight = data.seatHeatRight
117+ var hvacEnabledSel = data.hvacEnabled
118+ var hvacPowerSel = data.hvacPower
119+ var hvacAcSel = data.hvacAc
120+ var hvacAutoSel = data.hvacAuto
121+ // ⚠️ CLAMPÉS À LA LECTURE, et ce n'est pas de la prudence gratuite : Gson n'appelle pas
122+ // le constructeur Kotlin, donc un profil enregistré avant cette fonctionnalité rend 0
123+ // pour ces deux champs. Le curseur Material LÈVE une exception si on lui pose une valeur
124+ // hors de [valueFrom, valueTo] — l'éditeur planterait à l'ouverture d'un ancien profil.
125+ var hvacTempSel = data.hvacTemp.coerceIn(HVAC_TEMP_MIN , HVAC_TEMP_MAX )
126+ var hvacFanSel = data.hvacFan.coerceIn(HVAC_FAN_MIN , HVAC_FAN_MAX )
127+ var hvacDfSel = data.hvacDefrostFront
128+ var hvacDrSel = data.hvacDefrostRear
129+ var hvacLoopSel = data.hvacLoopMode
108130 var adasMode = data.adasMode
109131 var swi68Mode = data.swi68AdasMode
110132 var swi132SasMode = data.swi132SasMode // 0=Off, 2=Manuel, 3=Intelligent
@@ -220,6 +242,97 @@ class ProfileEditFragment : Fragment() {
220242 setBtnsEnabled(seatLeftBtns + seatRightBtns, checked)
221243 }
222244
245+ // ── Climatisation — bloc facultatif du profil ────────────────────────
246+ val swHvac = view.findViewById<Switch >(R .id.sw_hvac_enabled)
247+ val btnPower = view.findViewById<MaterialButton >(R .id.btn_hvac_power)
248+ val btnAc = view.findViewById<MaterialButton >(R .id.btn_hvac_ac)
249+ val btnAuto = view.findViewById<MaterialButton >(R .id.btn_hvac_auto)
250+ val sldTemp = view.findViewById<Slider >(R .id.sld_hvac_temp)
251+ val tvTemp = view.findViewById<TextView >(R .id.tv_hvac_temp)
252+ val sldFan = view.findViewById<Slider >(R .id.sld_hvac_fan)
253+ val tvFan = view.findViewById<TextView >(R .id.tv_hvac_fan)
254+ val loopBtns = listOf (
255+ view.findViewById<MaterialButton >(R .id.btn_hvac_loop_in),
256+ view.findViewById<MaterialButton >(R .id.btn_hvac_loop_out),
257+ view.findViewById<MaterialButton >(R .id.btn_hvac_loop_auto),
258+ view.findViewById<MaterialButton >(R .id.btn_hvac_loop_none)
259+ )
260+ val dfBtns = listOf (
261+ view.findViewById<MaterialButton >(R .id.btn_hvac_df_off),
262+ view.findViewById<MaterialButton >(R .id.btn_hvac_df_on),
263+ view.findViewById<MaterialButton >(R .id.btn_hvac_df_none)
264+ )
265+ val drBtns = listOf (
266+ view.findViewById<MaterialButton >(R .id.btn_hvac_dr_off),
267+ view.findViewById<MaterialButton >(R .id.btn_hvac_dr_on),
268+ view.findViewById<MaterialButton >(R .id.btn_hvac_dr_none)
269+ )
270+
271+ /* *
272+ * Trois grisages en cascade, qui reproduisent des règles du véhicule et non des choix
273+ * d'interface :
274+ * • interrupteur sur OFF → le profil ne touche pas à la clim, rien n'est modifiable ;
275+ * • clim éteinte → consigne, ventilation et le reste n'ont plus de sens ;
276+ * • mode AUTO → la ventilation manuelle est exclue, régler une vitesse ferait sortir
277+ * du mode auto (voir MG4Hardware.applyProfileClimate).
278+ */
279+ fun majHvac () {
280+ val actif = hvacEnabledSel
281+ val allume = actif && hvacPowerSel
282+ setBtnsEnabled(listOf (btnPower), actif)
283+ setBtnsEnabled(listOf (btnAc, btnAuto) + loopBtns + dfBtns + drBtns, allume)
284+ listOf (sldTemp, sldFan).forEach { it.isEnabled = allume }
285+ sldFan.isEnabled = allume && ! hvacAutoSel
286+ sldTemp.alpha = if (allume) 1f else 0.35f
287+ sldFan.alpha = if (allume && ! hvacAutoSel) 1f else 0.35f
288+ tvTemp.alpha = sldTemp.alpha
289+ tvFan.alpha = sldFan.alpha
290+ activateBtn(btnPower, hvacPowerSel)
291+ activateBtn(btnAc, hvacAcSel)
292+ activateBtn(btnAuto, hvacAutoSel)
293+ }
294+
295+ bindGroup(listOf (
296+ loopBtns[0 ] to 0 , loopBtns[1 ] to 1 , loopBtns[2 ] to 2 , loopBtns[3 ] to null
297+ ), hvacLoopSel) { hvacLoopSel = it }
298+ bindGroup(listOf (
299+ dfBtns[0 ] to false , dfBtns[1 ] to true , dfBtns[2 ] to null
300+ ), hvacDfSel) { hvacDfSel = it }
301+ bindGroup(listOf (
302+ drBtns[0 ] to false , drBtns[1 ] to true , drBtns[2 ] to null
303+ ), hvacDrSel) { hvacDrSel = it }
304+
305+ // Bascules et non groupes : ce sont les mêmes commandes que sur le Dashboard, où un
306+ // bouton unique s'allume quand le réglage est actif.
307+ btnPower.setOnClickListener { hvacPowerSel = ! hvacPowerSel; majHvac() }
308+ btnAc.setOnClickListener { hvacAcSel = ! hvacAcSel; majHvac() }
309+ btnAuto.setOnClickListener { hvacAutoSel = ! hvacAutoSel; majHvac() }
310+
311+ sldTemp.value = hvacTempSel.toFloat()
312+ tvTemp.text = getString(R .string.profile_hvac_temp_value, hvacTempSel)
313+ sldTemp.addOnChangeListener { _, v, _ ->
314+ hvacTempSel = v.toInt()
315+ tvTemp.text = getString(R .string.profile_hvac_temp_value, hvacTempSel)
316+ }
317+ sldFan.value = hvacFanSel.toFloat()
318+ tvFan.text = hvacFanSel.toString()
319+ sldFan.addOnChangeListener { _, v, _ ->
320+ hvacFanSel = v.toInt()
321+ tvFan.text = hvacFanSel.toString()
322+ }
323+
324+ swHvac.isChecked = hvacEnabledSel
325+ swHvac.setOnCheckedChangeListener { _, checked ->
326+ hvacEnabledSel = checked
327+ majHvac()
328+ }
329+ majHvac()
330+
331+ // Firmware sans clim pilotable : la section entière disparaît, comme la page Clim du
332+ // Dashboard. Proposer des réglages qui n'écriraient rien serait pire que rien.
333+ view.findViewById<View >(R .id.section_hvac)?.visibility =
334+ if (MG4Hardware .hasClimateControl()) View .VISIBLE else View .GONE
335+
223336 // ── Sections Climat — masquées si pas de chauffage (SWI69/SWI131) ────
224337 val hasHeat = FirmwareInfo .hasHeatFeatures()
225338 val heatVis = if (hasHeat) View .VISIBLE else View .GONE
@@ -537,6 +650,15 @@ class ProfileEditFragment : Fragment() {
537650 lasVibrationReminder = lasVibrationReminderSel,
538651 energySaving = energySavingSel,
539652 tsrEnabled = tsrEnabledSel,
653+ hvacEnabled = hvacEnabledSel,
654+ hvacPower = hvacPowerSel,
655+ hvacAc = hvacAcSel,
656+ hvacAuto = hvacAutoSel,
657+ hvacTemp = hvacTempSel,
658+ hvacFan = hvacFanSel,
659+ hvacDefrostFront = hvacDfSel,
660+ hvacDefrostRear = hvacDrSel,
661+ hvacLoopMode = hvacLoopSel,
540662 btDeviceMac = selectedBtMac // [BT-PROFILES]
541663 )
542664 manager.save(profile)
0 commit comments