testsAndMisc/focus_owner/lib/policy.dart
Krzysztof kuhy Rudnicki d9982e53dd Split the focus_owner Dart files under the cap
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
2026-08-16 20:39:13 +02:00

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;
}