Skip to content

Commit c2825d4

Browse files
atulmguptaCopilot
andcommitted
fix(apps): ambient unit preference so every page honours the account units
Most data-backed pages were registered without an explicit UnitPref, so their ViewModels fell back to "units ?? UnitPref.Metric" and rendered km / Wh-per-km / per-km while the account (and the web) is imperial -- e.g. the Statistics page showed 78 km and Wh/km. Only the list pages (which self-subscribe to settings) and the two pages I wired by hand (Live Map, Climate) were correct. Systemic fix (web useUnits() parity) instead of threading units through ~40 page ctors + shell registrations: - New App.Core UnitPrefAmbient.Current (defaults to UnitPref.Metric). - ShellWindow publishes it from the account preference at startup and on every settings change, before the page rebuild -- so a recreated page renders in the new units. - Swap the 166 production "?? UnitPref.Metric" fallbacks (144 files: pages, projections, sections/panels/ cards, dashboard widget VMs, FormattedValues, TsVehicleHeroCard) to "?? UnitPrefAmbient.Current". Explicit units still win where passed; only the omitted-units path changes. Test fallbacks are untouched, and no test mutates the ambient, so the 31537 tests keep their deterministic metric default. Gates: full sln build 0 err, dotnet format clean, 31537 tests pass. Verified via live screenshot: Statistics (never edited individually) now renders 48 mi and Wh/mi matching http://localhost:3000. Known follow-up: a few hardcoded per-km labels (e.g. "Cost per km") still need the unit label -- the values already convert. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 243b8da commit c2825d4

145 files changed

Lines changed: 193 additions & 165 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace TeslaSync.App.Core.Units;
2+
3+
/// <summary>
4+
/// Process-wide ambient display-unit preference. The web resolves units per render via
5+
/// <c>useUnits()</c>; the native shell mirrors that by publishing the account's resolved preference here
6+
/// so that any ViewModel, projection or component constructed WITHOUT an explicit <see cref="UnitPref"/>
7+
/// falls back to the user's choice instead of hard-coded metric. Unit-aware fallbacks therefore read
8+
/// <c>units ?? UnitPrefAmbient.Current</c> rather than <c>units ?? UnitPrefAmbient.Current</c>.
9+
///
10+
/// <para>Defaults to <see cref="UnitPref.Metric"/> (SI) until the shell publishes the resolved preference.
11+
/// Only the shell mutates it (at startup and whenever the settings change), so unit tests — which never
12+
/// touch the shell — keep the metric default and stay deterministic. A <see cref="UnitPref"/> is an
13+
/// immutable record reference, so reads/writes of <see cref="Current"/> are atomic and need no locking.</para>
14+
/// </summary>
15+
public static class UnitPrefAmbient
16+
{
17+
/// <summary>The current ambient display-unit preference (never null; defaults to metric).</summary>
18+
public static UnitPref Current { get; set; } = UnitPref.Metric;
19+
}

apps/windows/TeslaSync.App/Components/DataDisplay/FormattedValues.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public UnitPref? Pref
6161
}
6262

6363
/// <summary>The effective preference, falling back to metric defaults.</summary>
64-
protected UnitPref EffectivePref => Pref ?? UnitPref.Metric;
64+
protected UnitPref EffectivePref => Pref ?? UnitPrefAmbient.Current;
6565

6666
/// <summary>The effective per-call precision, or null when the default applies.</summary>
6767
protected int? EffectivePrecision => Precision >= 0 ? Precision : null;

apps/windows/TeslaSync.App/Components/Vehicles/TsVehicleHeroCard.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public void SetState(VehicleHeroState? state)
106106
private static void OnPrefChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
107107
((TsVehicleHeroCard)d).Rebuild();
108108

109-
private UnitPref EffectivePref => Pref ?? UnitPref.Metric;
109+
private UnitPref EffectivePref => Pref ?? UnitPrefAmbient.Current;
110110

111111
private void BuildHeader()
112112
{

apps/windows/TeslaSync.App/Shell/ShellWindow.xaml.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using TeslaSync.App.Core.Navigation;
1515
using TeslaSync.App.Core.Settings;
1616
using TeslaSync.App.Core.Theme;
17+
using TeslaSync.App.Core.Units;
1718
using TeslaSync.App.Notifications;
1819
using TeslaSync.App.Platform.Lifecycle;
1920
using TeslaSync.App.Push;
@@ -1397,6 +1398,10 @@ private void ConfigureWindow()
13971398
_appliedMode = startup.ColorModeId;
13981399
_appliedUnits = startup.Units;
13991400

1401+
// Publish the ambient display-unit preference before any page is built so unit-aware ViewModels
1402+
// constructed without explicit units fall back to the account preference (web useUnits parity).
1403+
UnitPrefAmbient.Current = startup.ToUnitPref();
1404+
14001405
// The startup palette above is the local default; the deferred local-settings load and the
14011406
// backend /settings theme seed (web ThemeProvider parity) arrive as later Changed events. Clear the
14021407
// first-apply guard now so those events rebuild the already-constructed page when the theme actually
@@ -1921,6 +1926,10 @@ private void ApplySettings(AppSettings settings)
19211926
_appliedUnits = settings.Units;
19221927
_firstThemeApply = false;
19231928

1929+
// Republish the ambient unit preference before the page rebuild below so the recreated page (and any
1930+
// unit-aware ViewModel that defaults to the ambient) renders in the new units.
1931+
UnitPrefAmbient.Current = settings.ToUnitPref();
1932+
19241933
ApplyDensity(settings.Density);
19251934
MaybeApplyStartupRoute(settings);
19261935

apps/windows/TeslaSync.App/dashboard-widgets/AnalyticsSummaryWidget/AnalyticsSummaryWidget.ViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public AnalyticsSummaryViewModel(
143143
_source = source;
144144
_localizer = localizer;
145145
_size = size;
146-
_units = units ?? UnitPref.Metric;
146+
_units = units ?? UnitPrefAmbient.Current;
147147
_currencySymbol = string.IsNullOrWhiteSpace(currencySymbol) ? "$" : currencySymbol;
148148
_clock = clock ?? (() => DateTimeOffset.Now);
149149
_display = AnalyticsSummaryProjection.Project(AnalyticsSummary.Empty, _size, _units, _currencySymbol, _localizer);

apps/windows/TeslaSync.App/dashboard-widgets/ChargeCostTrackerWidget/ChargeCostTrackerWidget.ViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public ChargeCostTrackerViewModel(
145145
_source = source;
146146
_localizer = localizer;
147147
_size = size;
148-
_units = units ?? UnitPref.Metric;
148+
_units = units ?? UnitPrefAmbient.Current;
149149
_settings = settings ?? ChargeCostTrackerSettings.Default;
150150
_clock = clock ?? (() => DateTimeOffset.Now);
151151
_display = BuildDisplay(Array.Empty<ChargeCostTrackerSession>());

apps/windows/TeslaSync.App/dashboard-widgets/ChargeStatusLiveWidget/ChargeStatusLiveWidget.ViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public ChargeStatusLiveViewModel(
142142
_source = source;
143143
_localizer = localizer;
144144
_size = size;
145-
_units = units ?? UnitPref.Metric;
145+
_units = units ?? UnitPrefAmbient.Current;
146146
}
147147

148148
/// <inheritdoc />

apps/windows/TeslaSync.App/dashboard-widgets/ChargeStatusWidget/ChargeStatusWidget.ViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ public ChargeStatusViewModel(
141141
_source = source;
142142
_localizer = localizer;
143143
_size = size;
144-
_units = units ?? UnitPref.Metric;
144+
_units = units ?? UnitPrefAmbient.Current;
145145
}
146146

147147
/// <inheritdoc />

apps/windows/TeslaSync.App/dashboard-widgets/ClimateControlPanelWidget/ClimateControlPanelWidget.ViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public ClimateControlPanelViewModel(
143143
_source = source;
144144
_localizer = localizer;
145145
_size = size;
146-
_units = units ?? UnitPref.Metric;
146+
_units = units ?? UnitPrefAmbient.Current;
147147
}
148148

149149
/// <inheritdoc />

apps/windows/TeslaSync.App/dashboard-widgets/ClimateHistoryWidget/ClimateHistoryWidget.ViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ public ClimateHistoryViewModel(
128128
_source = source;
129129
_localizer = localizer;
130130
_size = size;
131-
_units = units ?? UnitPref.Metric;
131+
_units = units ?? UnitPrefAmbient.Current;
132132
_display = ClimateHistoryProjection.Project(Array.Empty<ClimateHistorySample>(), _size, _units, _localizer);
133133
}
134134

0 commit comments

Comments
 (0)