Skip to content

Commit 4127f65

Browse files
committed
Window Screen Redesign + Preparation 2.6.7
1 parent 2bded56 commit 4127f65

15 files changed

Lines changed: 516 additions & 580 deletions

File tree

‎app/src/main/java/com/mg4/control/hardware/PowerWindows.kt‎

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -129,25 +129,68 @@ object PowerWindows {
129129

130130
private fun calKey(window: PowerWindow, part: String) = "win_cal_${window.name.lowercase(Locale.ROOT)}_$part"
131131

132-
fun calibration(context: Context, window: PowerWindow): WindowCalibration? {
132+
private const val KEY_COURSE_MS = "win_course_ms"
133+
private const val KEY_CAL_ADVANCED = "win_cal_advanced"
134+
135+
/** Durée de course retenue pour les vitres sans calibrage propre, lue une fois par le worker. */
136+
@Volatile
137+
private var courseMs = WindowCommand.DEFAULT_COURSE_MS
138+
139+
fun courseMs(context: Context): Long = WindowCommand.clampCourseMs(
140+
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
141+
.getLong(KEY_COURSE_MS, WindowCommand.DEFAULT_COURSE_MS))
142+
143+
fun setCourseMs(context: Context, ms: Long) {
144+
val valeur = WindowCommand.clampCourseMs(ms)
145+
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
146+
putLong(KEY_COURSE_MS, valeur)
147+
}
148+
courseMs = valeur
149+
AppLogger.i(TAG, "durée de course : $valeur ms")
150+
}
151+
152+
/**
153+
* Calibrage par vitre, option avancée.
154+
*
155+
* Éteint, chaque vitre sans capteur suit la durée de course générale, **mais les mesures déjà
156+
* faites restent enregistrées** : rallumer l'option les remet en service sans refaire
157+
* l'assistant. À la première lecture, l'option s'allume d'elle-même si des mesures existent
158+
* déjà — personne ne doit perdre sa précision à la mise à jour.
159+
*/
160+
fun advancedCalibration(context: Context): Boolean {
161+
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
162+
if (!prefs.contains(KEY_CAL_ADVANCED)) {
163+
val dejaCalibre = PowerWindow.entries.any { storedCalibration(context, it) != null }
164+
prefs.edit { putBoolean(KEY_CAL_ADVANCED, dejaCalibre) }
165+
if (dejaCalibre) AppLogger.i(TAG, "calibrage existant : option avancée activée d'office")
166+
return dejaCalibre
167+
}
168+
return prefs.getBoolean(KEY_CAL_ADVANCED, false)
169+
}
170+
171+
fun setAdvancedCalibration(context: Context, on: Boolean) {
172+
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
173+
putBoolean(KEY_CAL_ADVANCED, on)
174+
}
175+
AppLogger.i(TAG, "calibrage par vitre ${if (on) "activé" else "désactivé"}")
176+
// Les estimateurs portent la calibration : ils doivent repartir de la nouvelle règle.
177+
reloadEstimators()
178+
}
179+
180+
/** Mesures enregistrées pour cette vitre, sans tenir compte de l'option avancée. */
181+
private fun storedCalibration(context: Context, window: PowerWindow): WindowCalibration? {
133182
if (window.hasPositionSensor) return null
134183
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
135184
val down = prefs.getLong(calKey(window, "down_ms"), 0L)
136185
val up = prefs.getLong(calKey(window, "up_ms"), 0L)
137186
return WindowCalibration(down, up).takeIf { WindowCalibration.isValidMeasure(down) && WindowCalibration.isValidMeasure(up) }
138187
}
139188

140-
/** Enregistre la calibration ; l'assistant finit sur une montée complète, la vitre est donc fermée. */
141-
/**
142-
* Vitres sans capteur dont la calibration manque encore.
143-
*
144-
* Sert de garde-fou à la fermeture automatique : elle ferme au temps mesuré, une vitre non
145-
* calibrée partirait sur la course par défaut sans que personne puisse le vérifier depuis
146-
* l'extérieur de la voiture.
147-
*/
148-
fun uncalibrated(context: Context): List<PowerWindow> =
149-
PowerWindow.entries.filter { !it.hasPositionSensor && calibration(context, it) == null }
189+
/** Calibrage réellement appliqué : null quand l'option avancée est éteinte. */
190+
fun calibration(context: Context, window: PowerWindow): WindowCalibration? =
191+
if (advancedCalibration(context)) storedCalibration(context, window) else null
150192

193+
/** Enregistre la calibration ; l'assistant finit sur une montée complète, la vitre est donc fermée. */
151194
fun saveCalibration(context: Context, window: PowerWindow, cal: WindowCalibration) {
152195
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
153196
putLong(calKey(window, "down_ms"), cal.downMs)
@@ -161,9 +204,19 @@ object PowerWindows {
161204
* Sur [worker] : charge une fois les calibrations enregistrées. Chaque vitre calibrée part fermée
162205
* (0 %) : cas le plus courant, sans exiger de course complète au démarrage de l'app.
163206
*/
207+
/** Relit calibrations et durée de course après un changement de réglage. */
208+
private fun reloadEstimators() {
209+
worker.post {
210+
estimators.clear()
211+
estimatorsLoaded = false
212+
loadEstimators()
213+
}
214+
}
215+
164216
private fun loadEstimators() {
165217
if (estimatorsLoaded) return
166218
val context = MG4Hardware.appContext() ?: return
219+
courseMs = courseMs(context)
167220
PowerWindow.entries.forEach { w ->
168221
val cal = calibration(context, w) ?: return@forEach
169222
estimators[w] = WindowEstimator(cal).apply { setClosed() }
@@ -231,7 +284,7 @@ object PowerWindows {
231284
*/
232285
fun closeForCalibration(window: PowerWindow) {
233286
lastAutoMs.remove(window)
234-
worker.post { startAuto(window, Direction.UP, "calibration", minEmulatedMs = WindowCommand.EMULATED_COURSE_MS) }
287+
worker.post { startAuto(window, Direction.UP, "calibration", minEmulatedMs = courseMs) }
235288
}
236289

237290
/** Sur [worker]. Course automatique : native pour le conducteur, émulée pour les autres vitres. */
@@ -251,7 +304,7 @@ object PowerWindows {
251304
*/
252305
private fun startEmulatedCourse(window: PowerWindow, direction: Direction, origin: String, minMs: Long = 0L) {
253306
loadEstimators()
254-
val ms = maxOf(minMs, WindowCommand.emulatedCourseMs(direction, estimators[window]?.calibration))
307+
val ms = maxOf(minMs, WindowCommand.emulatedCourseMs(direction, estimators[window]?.calibration, courseMs))
255308
val what = if (direction == Direction.UP) "fermeture" else "ouverture"
256309
startRepeat(window, WindowCommand.manual(direction), ms, finger = false, label = "$origin, $what auto émulée")
257310
}
@@ -354,7 +407,7 @@ object PowerWindows {
354407
// Ajout APRÈS le démarrage : remplacer une course d'une séquence précédente ne
355408
// doit pas compter comme l'interruption de celle-ci.
356409
startRepeat(w, WindowCommand.MANUAL_UP,
357-
WindowCommand.emulatedCourseMs(Direction.UP, estimators[w]?.calibration),
410+
WindowCommand.emulatedCourseMs(Direction.UP, estimators[w]?.calibration, courseMs),
358411
finger = false, label = origin, autoClose = true)
359412
autoClosePending += w
360413
}

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,22 @@ object WindowCommand {
4949

5050
/** Au-delà, un appui court n'est plus pris pour un « stop » : la course auto native est finie. */
5151
const val AUTO_TRAVEL_MS = 6_000L
52-
/** Durée d'une course auto émulée tant que la vitre n'est pas calibrée. */
53-
const val EMULATED_COURSE_MS = AUTO_TRAVEL_MS
52+
53+
/**
54+
* Durée d'une course auto émulée pour une vitre sans calibrage propre — le cas normal.
55+
*
56+
* Cinq secondes : la valeur qu'emploie winclose sur MG4 sans jamais rien mesurer. Réglable par
57+
* l'utilisateur, parce qu'une vitre arrière fatiguée est plus lente ; trop courte, elle reste
58+
* entrouverte, et rien ne peut le signaler puisque l'application croit avoir fini.
59+
*/
60+
const val DEFAULT_COURSE_MS = 5_000L
61+
const val MIN_COURSE_MS = 2_000L
62+
const val MAX_COURSE_MS = 10_000L
63+
const val COURSE_STEP_MS = 500L
64+
65+
/** Ramène une durée saisie dans les bornes, sur un pas entier. */
66+
fun clampCourseMs(ms: Long): Long =
67+
(ms / COURSE_STEP_MS * COURSE_STEP_MS).coerceIn(MIN_COURSE_MS, MAX_COURSE_MS)
5468
/**
5569
* Plus grande position valide. Le service véhicule SWI68 rejette toute lecture au-delà
5670
* (`max_vehicle_window_get` = 100) ; en voiture, 127.5 et 255 restent figés sur les vitres
@@ -82,11 +96,15 @@ object WindowCommand {
8296
/** Position utilisable, ou null (illisible, ou valeur de remplacement d'une vitre sans capteur). */
8397
fun position(raw: Float?): Float? = raw?.takeIf { it in 0f..POSITION_MAX }
8498

85-
/** Durée d'une course émulée : la course calibrée dans ce sens si elle existe, sinon la valeur par défaut. */
86-
fun emulatedCourseMs(direction: Direction, calibration: WindowCalibration?): Long = when (direction) {
99+
/** Durée d'une course émulée : la course calibrée dans ce sens si elle existe, sinon [defaultMs]. */
100+
fun emulatedCourseMs(
101+
direction: Direction,
102+
calibration: WindowCalibration?,
103+
defaultMs: Long = DEFAULT_COURSE_MS,
104+
): Long = when (direction) {
87105
Direction.DOWN -> calibration?.emulatedOpenMs
88106
Direction.UP -> calibration?.emulatedCloseMs
89-
} ?: EMULATED_COURSE_MS
107+
} ?: defaultMs
90108

91109
/**
92110
* Une fermeture émulée n'a pas l'anti-pincement de la course native : elle ne continue que

‎app/src/main/java/com/mg4/control/ui/WindowAutoClosePanel.kt‎

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ class WindowAutoClosePanel {
2222

2323
private var root: View? = null
2424
private var enableSwitch: Switch? = null
25-
private var calNotice: TextView? = null
2625
private var paramsGroup: View? = null
2726
private var speedSwitch: Switch? = null
2827
private var timeSwitch: Switch? = null
@@ -59,18 +58,14 @@ class WindowAutoClosePanel {
5958
anyButton = view.findViewById(R.id.btn_win_auto_any)
6059
allButton = view.findViewById(R.id.btn_win_auto_all)
6160

62-
calNotice = view.findViewById(R.id.win_auto_cal_notice)
6361
paramsGroup = view.findViewById(R.id.row_win_auto_config)
6462
enableSwitch = view.findViewById<Switch>(R.id.switch_win_autoclose).apply {
6563
isChecked = WindowAutoClose.isEnabled(context)
6664
setOnCheckedChangeListener { sw, on ->
6765
WindowAutoClose.setEnabled(sw.context, on)
68-
// Option relâchée sans calibration complète : le verrou reprend la main.
69-
refreshCalibrationGate()
7066
render()
7167
}
7268
}
73-
refreshCalibrationGate()
7469

7570
val s = WindowAutoClose.settings(view.context)
7671
requireBoth = s.requireBoth
@@ -132,25 +127,6 @@ class WindowAutoClosePanel {
132127
beepVolume = beepSlider?.value?.toInt() ?: WindowAutoClose.BEEP_VOLUME_DEFAULT,
133128
)
134129

135-
/**
136-
* Verrouille l'activation tant que les vitres sans capteur ne sont pas calibrées : la
137-
* fermeture en quittant ne connaît la durée de course que par la calibration.
138-
*
139-
* Le verrou ne porte que sur l'ACTIVATION. Une option déjà active reste débrayable — sinon
140-
* un utilisateur l'ayant activée avant ce garde-fou se retrouverait sans moyen de l'arrêter.
141-
*/
142-
fun refreshCalibrationGate() {
143-
val ctx = root?.context ?: return
144-
val manquantes = PowerWindows.uncalibrated(ctx)
145-
calNotice?.visibility = if (manquantes.isEmpty()) View.GONE else View.VISIBLE
146-
if (manquantes.isNotEmpty()) calNotice?.text = ctx.getString(
147-
R.string.win_auto_need_cal,
148-
manquantes.joinToString(", ") { ctx.getString(WindowsPanel.nameRes(it)) })
149-
val sw = enableSwitch ?: return
150-
sw.isEnabled = manquantes.isEmpty() || sw.isChecked
151-
sw.alpha = if (sw.isEnabled) 1f else 0.4f
152-
}
153-
154130
/** Un Slider refuse une valeur qui ne tombe pas sur un pas : on l'y ramène. */
155131
private fun beepStep(volume: Int): Int =
156132
(volume / WindowAutoClose.BEEP_VOLUME_STEP * WindowAutoClose.BEEP_VOLUME_STEP)

‎app/src/main/java/com/mg4/control/ui/WindowCalibrationPanel.kt‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ class WindowCalibrationPanel {
4646
private var save: MaterialButton? = null
4747

4848
/** Prévenu après un enregistrement : la fermeture automatique en dépend pour se déverrouiller. */
49-
var onCalibrationSaved: (() -> Unit)? = null
5049

5150
private var window: PowerWindow? = null
5251
private var step = Step.CLOSE
@@ -110,7 +109,7 @@ class WindowCalibrationPanel {
110109
}
111110
val cal = PowerWindows.calibration(ctx, row.window)
112111
row.status.text = cal?.let { ctx.getString(R.string.win_cal_done, seconds(it.downMs), seconds(it.upMs)) }
113-
?: ctx.getString(R.string.win_cal_none)
112+
?: ctx.getString(R.string.win_cal_general)
114113
row.start.setText(if (cal != null) R.string.win_cal_redo_row else R.string.win_cal_start)
115114
}
116115
}
@@ -218,7 +217,6 @@ class WindowCalibrationPanel {
218217
Toast.makeText(ctx, R.string.win_cal_saved, Toast.LENGTH_SHORT).show()
219218
closeWizard("enregistrée")
220219
refreshRows()
221-
onCalibrationSaved?.invoke()
222220
}
223221

224222
private fun closeWizard(reason: String) {

0 commit comments

Comments
 (0)