UI-04: customizable, persisted HUD telemetry widget layout
Adds the full drag/resize/visibility infrastructure for a rider-owned HUD layout: HudMetric (the stable 8-metric set), HudWidgetLayout (fractional x/y/width/height + visible, with clamping to a legibility floor/ceiling and JSON round-trip that degrades to sane defaults rather than crashing), Config.hudLayout persistence, and a HudLayoutController that stays in-memory-authoritative during an edit session and only writes through on persist() -- never per drag frame. DraggableResizableHudWidget and HudEditOverlay assemble the interaction: edit mode is entered by a long-press on empty HUD space (not a specific widget) and exited via Done or a tap on empty space; only while editing does a widget attach any drag/resize gesture at all, so a normal tap can never move one mid-ride by construction, not by an internal flag. Fixes a real gesture-arena bug found during testing: outside edit mode, a long-press landing on a widget was free to bubble to the overlay's background long-press handler and wrongly enter edit mode. An inner no-op GestureDetector of the same gesture type now absorbs it. Adds a "Live HUD stats" Settings section, one switch per metric, that persists immediately (unlike drag frames). Placed at the end of the Settings list rather than in the middle -- inserting mid-list pushed every later section below several existing tests' viewport assumptions. Verified end-to-end on a real emulator including a full process restart: resized a widget, force-stopped the app, relaunched, and the resize held. No consuming screen exists yet (UI-05's job) -- verified via a throwaway preview entry point, deleted after use. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
This commit is contained in:
@@ -4,12 +4,15 @@
|
||||
/// recording must work with no server at all, and uploading is opt-in.
|
||||
library;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../domain/models.dart' show UnitSystem;
|
||||
import '../hud/hud_metric.dart';
|
||||
import '../hud/hud_widget_layout.dart';
|
||||
|
||||
const _keyEndpoint = 'upload_endpoint';
|
||||
const _keyDeviceId = 'device_id';
|
||||
@@ -17,6 +20,7 @@ const _keyMapEnabled = 'map_enabled';
|
||||
const _keyUnitSystem = 'unit_system';
|
||||
const _keyMountedMode = 'mounted_mode';
|
||||
const _keyCrashReportingEnabled = 'crash_reporting_enabled';
|
||||
const _keyHudLayout = 'hud_layout';
|
||||
|
||||
/// Countries that did not adopt metric for everyday distances. Not exhaustive — a
|
||||
/// best-effort default, not a claim of authority. Anyone can override it in Settings.
|
||||
@@ -88,6 +92,33 @@ class Config {
|
||||
Future<void> setCrashReportingEnabled(bool enabled) =>
|
||||
_prefs.setBool(_keyCrashReportingEnabled, enabled);
|
||||
|
||||
/// UI-04: every metric's position, size, and visibility on the customizable HUD.
|
||||
/// Missing entirely (fresh install) or missing a specific metric (an app update
|
||||
/// that added one) both fall back to [HudWidgetLayout.defaultFor] -- never a crash,
|
||||
/// same discipline [HudWidgetLayout.fromJson] itself follows for a malformed entry.
|
||||
Map<HudMetric, HudWidgetLayout> get hudLayout {
|
||||
final raw = _prefs.getString(_keyHudLayout);
|
||||
Map<String, dynamic> stored = const {};
|
||||
if (raw != null) {
|
||||
try {
|
||||
stored = jsonDecode(raw) as Map<String, dynamic>;
|
||||
} catch (_) {
|
||||
stored = const {};
|
||||
}
|
||||
}
|
||||
return {
|
||||
for (final metric in HudMetric.values)
|
||||
metric: stored[metric.name] is Map<String, dynamic>
|
||||
? HudWidgetLayout.fromJson(metric, stored[metric.name] as Map<String, dynamic>)
|
||||
: HudWidgetLayout.defaultFor(metric),
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> setHudLayout(Map<HudMetric, HudWidgetLayout> layout) => _prefs.setString(
|
||||
_keyHudLayout,
|
||||
jsonEncode({for (final entry in layout.entries) entry.key.name: entry.value.toJson()}),
|
||||
);
|
||||
|
||||
/// Stable per-install id so a server can distinguish riders in a group.
|
||||
String get deviceId {
|
||||
final existing = _prefs.getString(_keyDeviceId);
|
||||
|
||||
Reference in New Issue
Block a user