refactor(legacy): split legacy_shared into cohesive packages

legacy_shared was the junk drawer of the legacy app mode — 37 files
mixing device entities, command infrastructure, UI primitives, generic
formatters, and a duplicate of sf_infrastructure's dio_error_mapper.
Any module needing one piece pulled the whole bag into its graph.

Split it by responsibility, following Mandamiento 4 of Real-World Flutter:

- legacy_ui (new): 6 widget/layout primitives (PageLayout, MenuButton,
  SectionButton, PulsingLocationMarker, RefreshableErrorState,
  WeekDayChips) plus mapStyleProvider — shared UI state that was the
  only reason two modules needed a common package.
- legacy_device_state (expanded 9 → 30): absorbed device entities,
  commands infrastructure (datasource + repo + provider + guard),
  device settings update flow, and the CSV exporter. Now one package
  owns the device domain end-to-end.
- packages/utils: absorbed battery_utils and date_format_utils as pure
  formatters that never belonged in a legacy-scoped package.
- legacy_shared: deleted entirely.

The duplicate dio_error_mapper in legacy_shared is gone; callers now use
the sf_infrastructure version (which was always the superset — it adds
ApiException and the dart:io socket handling).

DeviceEntity note: legacy_device_state keeps its own DeviceEntity (with
int timestamps and typed paymentOptions) separate from sf_shared's
DeviceEntity (String timestamps, untyped paymentOptions). The legacy
one is intentionally not exported from the barrel to avoid the
ambiguous_import collision that legacy_shared quietly hid by never
exporting it in the first place. Unifying the two is a domain-model
refactor out of scope here.

0 cross-module imports remain among legacy feature modules.
This commit is contained in:
2026-04-19 05:42:31 +02:00
parent e59ce36033
commit 7e1ead9cae
142 changed files with 225 additions and 302 deletions

View File

@@ -15,9 +15,21 @@ late final SfTrackingRepository sfTracking;
void initSfTracking() {
sfTracking = SfTrackingRepository(
clients: <TrackingClient>[
// TODO: read consent from a persisted preference once the GDPR
// consent screen exists, and call `sfTracking.setConsentStatus(...)`
// when the user toggles it.
// TODO(gdpr): consentAnalytics is hardcoded to `true`. This ships
// Firebase Analytics events for every EU user WITHOUT explicit opt-in,
// which violates GDPR Art. 6/7. Sensitive context: this app tracks
// minors' location, so regulatory scrutiny is higher than baseline.
//
// To close this out:
// 1. Build an onboarding consent screen with separate toggles for
// analytics and crashlytics (granularity is required).
// 2. Persist the decision in SharedPreferences (key per client).
// 3. Read the stored preference here before constructing the client
// — default to `false` until the user has explicitly accepted.
// 4. Expose a "Privacy" entry in Settings that lets the user change
// their mind; on toggle, call
// `sfTracking.setConsentStatus(client: ..., granted: ...)`.
// 5. Add the privacy notice + data-processing summary in-app.
FirebaseTrackingClient(consentAnalytics: true),
if (kDebugMode) const DebugTrackingClient(),
],

View File

@@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
IconData toBatteryIcon(int battery) {
if (battery < 15) return Icons.battery_0_bar;
if (battery < 30) return Icons.battery_1_bar;
if (battery < 45) return Icons.battery_2_bar;
if (battery < 60) return Icons.battery_3_bar;
if (battery < 75) return Icons.battery_4_bar;
if (battery < 90) return Icons.battery_5_bar;
return Icons.battery_6_bar;
}

View File

@@ -0,0 +1,9 @@
String formatPositionDate(int millisSinceEpoch) {
final d = DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch);
final mm = d.month.toString().padLeft(2, '0');
final dd = d.day.toString().padLeft(2, '0');
final hh = d.hour.toString().padLeft(2, '0');
final min = d.minute.toString().padLeft(2, '0');
final ss = d.second.toString().padLeft(2, '0');
return '$mm-$dd $hh:$min:$ss';
}

View File

@@ -3,3 +3,5 @@ export 'src/duration_format.dart';
export 'src/query_params_builder.dart';
export 'src/size_utils.dart';
export 'src/test.dart';
export 'src/battery_utils.dart';
export 'src/date_format_utils.dart';