Skip to content

Commit e00ce16

Browse files
malysclaude
andcommitted
feat(eco): warm the Energy page as driving hardens
The page background between the cards blends gradually towards ev_warn as the harsh-acceleration share rises (none up to 6 %, full at 24 %), eased per frame and gated on the eco advice switch. Cards keep their own surface, so no figure loses contrast. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 36d782b commit e00ce16

4 files changed

Lines changed: 64 additions & 0 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ All notable changes follow [Keep a Changelog](https://keepachangelog.com/en/1.1.
66

77
### Added
88

9+
- **Energy page warms when driving is not smooth.** With eco advice on, the page background
10+
between the cards blends gradually towards the warning colour as the harsh-acceleration share
11+
rises: none up to 6 %, full at 24 % (the advice mark is 12 %). Eased over a few seconds,
12+
never flashed; the cards keep their own surface, so no figure loses contrast, and the gauge's
13+
word still carries the meaning.
914
- **"Validation results complete" notice (unstable, CP-055).** Once every validation question
1015
has recorded at least one answer, a notification asks the driver to export the diagnostic to
1116
USB; tapping it opens Diagnostics. Posted once per process, only with the car read as stopped.

‎app/src/main/java/com/evsuite/chargepilot/EcoCoach.kt‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,18 @@ class EcoCoach(context: Context) {
171171
private const val PREF_ADVICE = "advice_enabled"
172172
private const val PREF_VOICE = "voice_enabled"
173173

174+
/**
175+
* How far the Energy page leans towards the warning colour: nothing up to half the
176+
* advice mark, all the way at twice it. A ramp rather than a switch, so the page warms
177+
* as the driving hardens instead of flipping on one sample; null (not a minute of
178+
* movement yet) is no tint, never a guess.
179+
*/
180+
fun harshTint(share: Double?): Float {
181+
val mark = EcoDrivingMonitor.MIN_HARSH_SHARE_PERCENT
182+
share ?: return 0f
183+
return ((share - mark / 2) / (mark * 1.5)).coerceIn(0.0, 1.0).toFloat()
184+
}
185+
174186
fun adviceEnabled(context: Context): Boolean =
175187
prefs(context).getBoolean(PREF_ADVICE, EcoCoachDefaults.ADVICE_ENABLED)
176188

‎app/src/main/java/com/evsuite/chargepilot/MainActivity.kt‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import android.widget.TextView
1717
import android.widget.Toast
1818
import androidx.activity.result.contract.ActivityResultContracts
1919
import androidx.core.content.ContextCompat
20+
import androidx.core.graphics.ColorUtils
2021
import com.evsuite.chargepilot.databinding.ActivityMainBinding
2122
import com.evsuite.chargepilot.update.UpdateHook
2223
import com.evsuite.hardware.AppLogger
@@ -67,6 +68,8 @@ class MainActivity : PrimaryNavigationActivity() {
6768
/** The recorder owns the sampler; this screen is one of its readers. */
6869
private var recorder: TripRecordingService? = null
6970
private var loadedHistoryRevision = -1
71+
/** The page tint as drawn, 0..1, eased towards [EcoCoach.harshTint] each frame. */
72+
private var ecoTint = 0f
7073
/** Exact normalized service frame; the parked-only actions re-read it rather than the UI. */
7174
@Volatile private var latestSnapshot: EnergySnapshot? = null
7275

@@ -265,6 +268,24 @@ class MainActivity : PrimaryNavigationActivity() {
265268
val gauge = line == null && EcoCoach.adviceEnabled(this)
266269
binding.ecoGauge.visibility = if (gauge) View.VISIBLE else View.GONE
267270
if (gauge) renderEcoGauge(eco.harshShare())
271+
renderEcoTint(if (EcoCoach.adviceEnabled(this)) eco.harshShare() else null)
272+
}
273+
274+
/**
275+
* The page itself warms towards the warning colour as the driving hardens, so a glance
276+
* catches it without reading the gauge. Only the background between the cards is tinted:
277+
* every figure stays on its own surface and keeps its contrast. Eased per frame, never
278+
* flashed, and the gauge's word stays the carrier of the meaning.
279+
*/
280+
private fun renderEcoTint(share: Double?) {
281+
ecoTint += (EcoCoach.harshTint(share) - ecoTint) * ECO_TINT_EASE
282+
binding.root.setBackgroundColor(
283+
ColorUtils.blendARGB(
284+
ContextCompat.getColor(this, R.color.ev_background),
285+
ContextCompat.getColor(this, R.color.ev_warn),
286+
ecoTint * ECO_TINT_MAX,
287+
),
288+
)
268289
}
269290

270291
/** CP-084. Empty and grey before a minute of movement; amber once past the advice mark. */
@@ -518,5 +539,9 @@ class MainActivity : PrimaryNavigationActivity() {
518539
const val ARRIVAL_REFRESH_MS = 5_000L
519540
/** The full gauge: two and a half times the advice mark, so the mark sits left of centre. */
520541
const val ECO_GAUGE_FULL_PERCENT = 30.0
542+
/** Share of the way to the target tint covered each frame: a few seconds to settle. */
543+
const val ECO_TINT_EASE = 0.25f
544+
/** The strongest blend of the warning colour into the page background. */
545+
const val ECO_TINT_MAX = 0.6f
521546
}
522547
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.evsuite.chargepilot
2+
3+
import org.junit.Assert.assertEquals
4+
import org.junit.Test
5+
6+
class EcoTintTest {
7+
8+
@Test
9+
fun `no tint before a minute of movement or for a smooth drive`() {
10+
assertEquals(0f, EcoCoach.harshTint(null))
11+
assertEquals(0f, EcoCoach.harshTint(0.0))
12+
assertEquals(0f, EcoCoach.harshTint(6.0))
13+
}
14+
15+
@Test
16+
fun `the tint grows with the harsh share and stops at full`() {
17+
assertEquals(1f / 3, EcoCoach.harshTint(12.0), 1e-6f)
18+
assertEquals(2f / 3, EcoCoach.harshTint(18.0), 1e-6f)
19+
assertEquals(1f, EcoCoach.harshTint(24.0))
20+
assertEquals(1f, EcoCoach.harshTint(80.0))
21+
}
22+
}

0 commit comments

Comments
 (0)