mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-11 18:34:04 +01:00
main.dart 538 -> 130, policy.dart 272 -> 85, enforcement_status_card.dart 268 -> 145, plus both test files. Every lib split uses part/part of rather than import. The first attempt used import+export and the analyzer gave 30 errors like "The name '_Card' isn't a class": Dart privates are visible to the whole LIBRARY, and a separate file is a separate library unless it is a part. The test splits separated groups from the testWidgets() calls that had been initialising the binding implicitly, so four tests failed with "Binding has not yet been initialized" - both halves now call ensureInitialized() explicitly. 94 tests passing before and after; flutter analyze reports no issues. focus_owner/analysis_options.yaml has an uncommitted change from an earlier session and is deliberately left unstaged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
86 lines
2.8 KiB
Dart
86 lines
2.8 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter/services.dart' show AssetBundle, rootBundle;
|
|
|
|
part 'focus_policy.dart';
|
|
|
|
/// Schema version this build understands.
|
|
///
|
|
/// The asset is generated by `python3 -m python_pkg.focus_policy`, which is the
|
|
/// single source of truth. Refusing an unknown version is deliberate: silently
|
|
/// accepting a newer schema would mean enforcing a policy this code has
|
|
/// misread, and a misread allowlist is an allowlist that blocks the dialer.
|
|
const int kSupportedSchemaVersion = 1;
|
|
|
|
/// Raised when the bundled policy cannot be trusted.
|
|
class PolicyFormatException implements Exception {
|
|
const PolicyFormatException(this.message);
|
|
|
|
final String message;
|
|
|
|
@override
|
|
String toString() => 'PolicyFormatException: $message';
|
|
}
|
|
|
|
/// A nightly window that may wrap midnight (23:00 -> 05:00).
|
|
class CurfewWindow {
|
|
const CurfewWindow({required this.startMinutes, required this.endMinutes});
|
|
|
|
/// Parses an `{"start": "23:00", "end": "05:00"}` object.
|
|
factory CurfewWindow.fromJson(Map<String, Object?> json) {
|
|
return CurfewWindow(
|
|
startMinutes: _parseHhMm(json['start'], 'curfew.start'),
|
|
endMinutes: _parseHhMm(json['end'], 'curfew.end'),
|
|
);
|
|
}
|
|
|
|
/// Minutes since midnight.
|
|
final int startMinutes;
|
|
final int endMinutes;
|
|
|
|
/// Whether [minutesSinceMidnight] falls inside the window.
|
|
///
|
|
/// Wrapping windows are the normal case here, so "inside" means at or after
|
|
/// the start *or* before the end, not both.
|
|
bool contains(int minutesSinceMidnight) {
|
|
if (startMinutes <= endMinutes) {
|
|
return minutesSinceMidnight >= startMinutes &&
|
|
minutesSinceMidnight < endMinutes;
|
|
}
|
|
return minutesSinceMidnight >= startMinutes ||
|
|
minutesSinceMidnight < endMinutes;
|
|
}
|
|
}
|
|
|
|
/// The home anchor. Coordinates are null in a redacted policy.
|
|
class HomeLocation {
|
|
const HomeLocation({
|
|
required this.latitude,
|
|
required this.longitude,
|
|
required this.radiusM,
|
|
required this.hysteresisM,
|
|
});
|
|
|
|
/// Parses the `home` object.
|
|
factory HomeLocation.fromJson(Map<String, Object?> json) {
|
|
return HomeLocation(
|
|
latitude: (json['latitude'] as num?)?.toDouble(),
|
|
longitude: (json['longitude'] as num?)?.toDouble(),
|
|
radiusM: _requireNum(json['radius_m'], 'home.radius_m'),
|
|
hysteresisM: _requireNum(json['hysteresis_m'], 'home.hysteresis_m'),
|
|
);
|
|
}
|
|
|
|
final double? latitude;
|
|
final double? longitude;
|
|
final double radiusM;
|
|
final double hysteresisM;
|
|
|
|
/// Whether this policy carries real coordinates.
|
|
///
|
|
/// The committed asset is redacted, so the location gate cannot run against
|
|
/// it. Enforcement must check this rather than treating a missing coordinate
|
|
/// as (0, 0), which would put "home" in the Atlantic.
|
|
bool get hasCoordinates => latitude != null && longitude != null;
|
|
}
|