From 6afd2658a0d16c6e0d806b2e9d0a9bdf06ecbdc8 Mon Sep 17 00:00:00 2001 From: UTO Manager Dev Date: Sun, 9 Aug 2026 10:17:43 -0400 Subject: [PATCH] fix: resolve all analyzer info-level issues (cascade, deprecations, constructor ordering) --- development_status.md | 4 +- lib/application/blocs/template_cubit.dart | 2 +- lib/application/blocs/unit_tree_cubit.dart | 2 +- lib/domain/services/command_event_store.dart | 12 +- lib/domain/services/doctrinal_templates.dart | 170 +++++++++--------- .../services/mtoe_validation_engine.dart | 91 +++++----- lib/domain/services/unit_tree_service.dart | 10 +- lib/domain/services/validation_isolate.dart | 6 +- .../codecs/pdf_chart_exporter.dart | 156 ++++++++-------- lib/infrastructure/codecs/protobuf_codec.dart | 19 +- lib/presentation/screens/home_screen.dart | 62 ++++--- .../widgets/personnel_list_tile.dart | 4 +- .../widgets/position_slot_card.dart | 6 +- lib/presentation/widgets/unit_node_card.dart | 5 +- .../widgets/uto_canvas_widget.dart | 9 +- .../services/assignment_service_test.dart | 18 +- .../services/command_event_store_test.dart | 51 +++--- .../services/mtoe_validation_engine_test.dart | 12 +- .../services/template_instantiator_test.dart | 24 +-- .../services/validation_isolate_test.dart | 29 ++- .../codecs/pdf_chart_exporter_test.dart | 60 ++++--- .../codecs/protobuf_codec_test.dart | 33 ++-- .../codecs/unit_tree_codec_test.dart | 9 +- test/widget_test.dart | 2 +- 24 files changed, 410 insertions(+), 386 deletions(-) diff --git a/development_status.md b/development_status.md index 7f45726..5df2023 100644 --- a/development_status.md +++ b/development_status.md @@ -1,11 +1,11 @@ # UTO Manager — Development Status -_Last updated: 2026-08-09 (fix/phase-4-analyzer-errors)_ +_Last updated: 2026-08-09 (fix/phase-4-analyzer-round2)_ --- ## Current Branch -`main` (fix/phase-4-analyzer-errors merged) +`main` (fix/phase-4-analyzer-round2 merged) ## Overall Progress diff --git a/lib/application/blocs/template_cubit.dart b/lib/application/blocs/template_cubit.dart index 8ff9786..441d6af 100644 --- a/lib/application/blocs/template_cubit.dart +++ b/lib/application/blocs/template_cubit.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:uuid/uuid.dart'; import 'package:uto_manager/domain/entities/position_slot.dart' as domain; import 'package:uto_manager/domain/entities/unit_node.dart'; import 'package:uto_manager/domain/repositories/unit_repository.dart'; @@ -12,6 +11,7 @@ import 'package:uto_manager/infrastructure/codecs/unit_tree_codec.dart'; import 'package:uto_manager/infrastructure/database/app_database.dart'; import 'package:uto_manager/infrastructure/database/daos/template_dao.dart'; import 'package:uto_manager/infrastructure/repositories/drift_position_slot_repository.dart'; +import 'package:uuid/uuid.dart'; // --------------------------------------------------------------------------- // State diff --git a/lib/application/blocs/unit_tree_cubit.dart b/lib/application/blocs/unit_tree_cubit.dart index 78b0fa6..395018f 100644 --- a/lib/application/blocs/unit_tree_cubit.dart +++ b/lib/application/blocs/unit_tree_cubit.dart @@ -143,7 +143,7 @@ class UnitTreeCubit extends Cubit { final currentSelection = state is UnitTreeLoaded ? (state as UnitTreeLoaded).selectedUnitId : null; emit(UnitTreeLoaded( - units: _units, slots: _slots, roots: roots, selectedUnitId: currentSelection)); + units: _units, slots: _slots, roots: roots, selectedUnitId: currentSelection,),); } // --------------------------------------------------------------------------- diff --git a/lib/domain/services/command_event_store.dart b/lib/domain/services/command_event_store.dart index 114014c..2bbdc65 100644 --- a/lib/domain/services/command_event_store.dart +++ b/lib/domain/services/command_event_store.dart @@ -232,14 +232,8 @@ class DeltaSynchronizer { final CommandEventStore store; /// Whether the synchronizer believes it has network connectivity. - bool _isOnline = true; - - /// Sets the connectivity state. When set to true, [syncIfOnline] will - /// attempt to transmit pending events. - set isOnline(bool value) => _isOnline = value; - - /// Returns true if the synchronizer believes it has network connectivity. - bool get isOnline => _isOnline; + /// Set via [isOnline] setter; read via [isOnline] getter. + bool isOnline = true; /// Transmits all pending events if online. /// @@ -249,7 +243,7 @@ class DeltaSynchronizer { /// In this implementation the transmission is simulated (no real network /// call). Replace [_transmit] with a real HTTP/gRPC call in production. Future syncIfOnline() async { - if (!_isOnline) return 0; + if (!isOnline) return 0; final pending = store.pendingEvents; if (pending.isEmpty) return 0; diff --git a/lib/domain/services/doctrinal_templates.dart b/lib/domain/services/doctrinal_templates.dart index 66f3d85..4bedf6d 100644 --- a/lib/domain/services/doctrinal_templates.dart +++ b/lib/domain/services/doctrinal_templates.dart @@ -1,3 +1,5 @@ +import 'package:uto_manager/domain/entities/position_slot.dart'; +import 'package:uto_manager/domain/entities/unit_node.dart'; import 'package:uto_manager/domain/services/template_instantiator.dart'; /// Library of 2026 U.S. Army doctrinal force structure templates. @@ -169,11 +171,11 @@ abstract final class DoctrinalTemplates { _riflePlatoon('1st Platoon'), _riflePlatoon('2nd Platoon'), _riflePlatoon('3rd Platoon'), - UnitDefinition( + const UnitDefinition( name: 'Weapons Platoon', echelon: 'platoon', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Weapons Platoon Leader', dutyMos: '11A', @@ -297,11 +299,11 @@ abstract final class DoctrinalTemplates { _infantryBattalion('2-1 Mobile Infantry Battalion'), _infantryBattalion('3-1 Mobile Infantry Battalion'), // Cavalry Squadron - UnitDefinition( + const UnitDefinition( name: 'Cavalry Squadron', echelon: 'battalion', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Squadron Commander', dutyMos: '11A', @@ -320,7 +322,7 @@ abstract final class DoctrinalTemplates { name: 'HHT', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'HHT Commander', dutyMos: '11A', @@ -339,7 +341,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha Troop', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Troop Commander', dutyMos: '11A', @@ -358,7 +360,7 @@ abstract final class DoctrinalTemplates { name: 'Bravo Troop', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Troop Commander', dutyMos: '11A', @@ -377,7 +379,7 @@ abstract final class DoctrinalTemplates { name: 'Anti-Tank Troop', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Troop Commander', dutyMos: '11A', @@ -395,11 +397,11 @@ abstract final class DoctrinalTemplates { ], ), // Field Artillery Battalion - UnitDefinition( + const UnitDefinition( name: 'Field Artillery Battalion', echelon: 'battalion', branch: 'fires', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '13A', @@ -423,7 +425,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha Battery (105mm)', echelon: 'company', branch: 'fires', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battery Commander', dutyMos: '13A', @@ -447,7 +449,7 @@ abstract final class DoctrinalTemplates { name: 'Bravo Battery (105mm)', echelon: 'company', branch: 'fires', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battery Commander', dutyMos: '13A', @@ -466,7 +468,7 @@ abstract final class DoctrinalTemplates { name: 'Charlie Battery (155mm Towed)', echelon: 'company', branch: 'fires', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battery Commander', dutyMos: '13A', @@ -484,11 +486,11 @@ abstract final class DoctrinalTemplates { ], ), // Multi-Domain Effects Platoon - UnitDefinition( + const UnitDefinition( name: 'Multi-Domain Effects Platoon (MDEP)', echelon: 'platoon', branch: 'cyberEw', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'MDEP Officer', dutyMos: '17A', @@ -513,11 +515,11 @@ abstract final class DoctrinalTemplates { ], ), // Hunter-Killer Platoon - UnitDefinition( + const UnitDefinition( name: 'Hunter-Killer Platoon (HKP)', echelon: 'platoon', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'HKP Leader', dutyMos: '11A', @@ -542,11 +544,11 @@ abstract final class DoctrinalTemplates { ], ), // Brigade Engineer Battalion - UnitDefinition( + const UnitDefinition( name: 'Brigade Engineer Battalion', echelon: 'battalion', branch: 'engineer', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '12A', @@ -565,7 +567,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha Engineer Company', echelon: 'company', branch: 'engineer', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '12A', @@ -588,11 +590,11 @@ abstract final class DoctrinalTemplates { ], ), // Brigade Support Battalion - UnitDefinition( + const UnitDefinition( name: 'Brigade Support Battalion', echelon: 'battalion', branch: 'logistics', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '92A', @@ -669,11 +671,11 @@ abstract final class DoctrinalTemplates { _infantryBattalion('2-2 Stryker Infantry Battalion'), _infantryBattalion('3-2 Stryker Infantry Battalion'), // Cavalry Reconnaissance Squadron - UnitDefinition( + const UnitDefinition( name: 'Cavalry Reconnaissance Squadron', echelon: 'battalion', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Squadron Commander', dutyMos: '11A', @@ -692,7 +694,7 @@ abstract final class DoctrinalTemplates { name: 'HHT', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'HHT Commander', dutyMos: '11A', @@ -711,7 +713,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha Cav Troop (Stryker)', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Troop Commander', dutyMos: '11A', @@ -730,7 +732,7 @@ abstract final class DoctrinalTemplates { name: 'Bravo Cav Troop (Stryker)', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Troop Commander', dutyMos: '11A', @@ -749,7 +751,7 @@ abstract final class DoctrinalTemplates { name: 'Charlie Cav Troop (Stryker)', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Troop Commander', dutyMos: '11A', @@ -768,7 +770,7 @@ abstract final class DoctrinalTemplates { name: 'Anti-Tank Troop (9× Stryker ATGM)', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Troop Commander', dutyMos: '11A', @@ -786,11 +788,11 @@ abstract final class DoctrinalTemplates { ], ), // Field Artillery Battalion (M777A2) - UnitDefinition( + const UnitDefinition( name: 'Field Artillery Battalion (M777A2)', echelon: 'battalion', branch: 'fires', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '13A', @@ -809,7 +811,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha Battery (M777A2)', echelon: 'company', branch: 'fires', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battery Commander', dutyMos: '13A', @@ -828,7 +830,7 @@ abstract final class DoctrinalTemplates { name: 'Bravo Battery (M777A2)', echelon: 'company', branch: 'fires', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battery Commander', dutyMos: '13A', @@ -847,7 +849,7 @@ abstract final class DoctrinalTemplates { name: 'Charlie Battery (M777A2)', echelon: 'company', branch: 'fires', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battery Commander', dutyMos: '13A', @@ -865,11 +867,11 @@ abstract final class DoctrinalTemplates { ], ), // Brigade Engineer Battalion - UnitDefinition( + const UnitDefinition( name: 'Brigade Engineer Battalion', echelon: 'battalion', branch: 'engineer', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '12A', @@ -893,7 +895,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha Engineer Company', echelon: 'company', branch: 'engineer', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '12A', @@ -916,11 +918,11 @@ abstract final class DoctrinalTemplates { ], ), // Brigade Support Battalion - UnitDefinition( + const UnitDefinition( name: 'Brigade Support Battalion', echelon: 'battalion', branch: 'logistics', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '92A', @@ -984,11 +986,11 @@ abstract final class DoctrinalTemplates { ], children: [ // Armor-Heavy CAB 1 - UnitDefinition( + const UnitDefinition( name: '1st Combined Arms Battalion (Armor-Heavy)', echelon: 'battalion', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '11A', @@ -1017,7 +1019,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha Tank Company (14× M1A2 SEPv3)', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '11A', @@ -1036,7 +1038,7 @@ abstract final class DoctrinalTemplates { name: 'Bravo Tank Company (14× M1A2 SEPv3)', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '11A', @@ -1055,7 +1057,7 @@ abstract final class DoctrinalTemplates { name: 'Charlie Mech Infantry Company (14× M2A3 Bradley)', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '11A', @@ -1073,11 +1075,11 @@ abstract final class DoctrinalTemplates { ], ), // Armor-Heavy CAB 2 - UnitDefinition( + const UnitDefinition( name: '2nd Combined Arms Battalion (Armor-Heavy)', echelon: 'battalion', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '11A', @@ -1101,7 +1103,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha Tank Company (14× M1A2 SEPv3)', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '11A', @@ -1120,7 +1122,7 @@ abstract final class DoctrinalTemplates { name: 'Bravo Tank Company (14× M1A2 SEPv3)', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '11A', @@ -1139,7 +1141,7 @@ abstract final class DoctrinalTemplates { name: 'Charlie Mech Infantry Company (14× M2A3 Bradley)', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '11A', @@ -1159,11 +1161,11 @@ abstract final class DoctrinalTemplates { // Infantry-Heavy CAB _infantryBattalion('3rd Combined Arms Battalion (Infantry-Heavy)'), // Armored Cavalry Squadron - UnitDefinition( + const UnitDefinition( name: 'Armored Cavalry Squadron', echelon: 'battalion', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Squadron Commander', dutyMos: '11A', @@ -1182,7 +1184,7 @@ abstract final class DoctrinalTemplates { name: 'HHT', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'HHT Commander', dutyMos: '11A', @@ -1201,7 +1203,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha Armored Cav Troop', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Troop Commander', dutyMos: '11A', @@ -1220,7 +1222,7 @@ abstract final class DoctrinalTemplates { name: 'Bravo Armored Cav Troop', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Troop Commander', dutyMos: '11A', @@ -1239,7 +1241,7 @@ abstract final class DoctrinalTemplates { name: 'Charlie Armored Cav Troop', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Troop Commander', dutyMos: '11A', @@ -1258,7 +1260,7 @@ abstract final class DoctrinalTemplates { name: 'Tank Troop (14× M1A2)', echelon: 'company', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Troop Commander', dutyMos: '11A', @@ -1276,11 +1278,11 @@ abstract final class DoctrinalTemplates { ], ), // Field Artillery Battalion (M109A7 Paladin) - UnitDefinition( + const UnitDefinition( name: 'Field Artillery Battalion (M109A7 Paladin)', echelon: 'battalion', branch: 'fires', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '13A', @@ -1299,7 +1301,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha Battery (M109A7)', echelon: 'company', branch: 'fires', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battery Commander', dutyMos: '13A', @@ -1318,7 +1320,7 @@ abstract final class DoctrinalTemplates { name: 'Bravo Battery (M109A7)', echelon: 'company', branch: 'fires', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battery Commander', dutyMos: '13A', @@ -1337,7 +1339,7 @@ abstract final class DoctrinalTemplates { name: 'Charlie Battery (M109A7)', echelon: 'company', branch: 'fires', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battery Commander', dutyMos: '13A', @@ -1355,11 +1357,11 @@ abstract final class DoctrinalTemplates { ], ), // Brigade Engineer Battalion - UnitDefinition( + const UnitDefinition( name: 'Brigade Engineer Battalion', echelon: 'battalion', branch: 'engineer', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '12A', @@ -1378,7 +1380,7 @@ abstract final class DoctrinalTemplates { name: 'Sapper Company', echelon: 'company', branch: 'engineer', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '12A', @@ -1408,11 +1410,11 @@ abstract final class DoctrinalTemplates { // --------------------------------------------------------------------------- /// Combat Aviation Brigade — Heavy (2026 doctrine). - static final UnitDefinition cabHeavy = UnitDefinition( + static const UnitDefinition cabHeavy = UnitDefinition( name: 'Combat Aviation Brigade (Heavy)', echelon: 'brigade', branch: 'aviation', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Brigade Commander', dutyMos: '153A', @@ -1438,7 +1440,7 @@ abstract final class DoctrinalTemplates { name: 'Attack Aviation Battalion (24× AH-64E Apache)', echelon: 'battalion', branch: 'aviation', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '153A', @@ -1467,7 +1469,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha Attack Company', echelon: 'company', branch: 'aviation', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '153A', @@ -1486,7 +1488,7 @@ abstract final class DoctrinalTemplates { name: 'Bravo Attack Company', echelon: 'company', branch: 'aviation', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '153A', @@ -1508,7 +1510,7 @@ abstract final class DoctrinalTemplates { name: 'Assault Helicopter Battalion (UH-60M / CH-47F)', echelon: 'battalion', branch: 'aviation', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '153A', @@ -1542,7 +1544,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha Assault Company (UH-60M)', echelon: 'company', branch: 'aviation', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '153A', @@ -1561,7 +1563,7 @@ abstract final class DoctrinalTemplates { name: 'Bravo Heavy Lift Company (CH-47F)', echelon: 'company', branch: 'aviation', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '153A', @@ -1583,7 +1585,7 @@ abstract final class DoctrinalTemplates { name: 'Air Ambulance Company (15× HH-60M)', echelon: 'company', branch: 'medical', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '153A', @@ -1618,7 +1620,7 @@ abstract final class DoctrinalTemplates { name: 'Aviation Support Battalion', echelon: 'battalion', branch: 'logistics', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '92A', @@ -1656,11 +1658,11 @@ abstract final class DoctrinalTemplates { // --------------------------------------------------------------------------- /// Combat Aviation Brigade — Light (2026 doctrine). - static final UnitDefinition cabLight = UnitDefinition( + static const UnitDefinition cabLight = UnitDefinition( name: 'Combat Aviation Brigade (Light)', echelon: 'brigade', branch: 'aviation', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Brigade Commander', dutyMos: '153A', @@ -1686,7 +1688,7 @@ abstract final class DoctrinalTemplates { name: 'Attack Reconnaissance Battalion (AH-64E + MQ-9A)', echelon: 'battalion', branch: 'aviation', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '153A', @@ -1715,7 +1717,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha Attack Company', echelon: 'company', branch: 'aviation', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '153A', @@ -1734,7 +1736,7 @@ abstract final class DoctrinalTemplates { name: 'Troop F (MQ-9A / sUAS)', echelon: 'company', branch: 'aviation', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Troop Commander', dutyMos: '153A', @@ -1761,7 +1763,7 @@ abstract final class DoctrinalTemplates { name: 'Assault Helicopter Battalion (UH-60M / CH-47F)', echelon: 'battalion', branch: 'aviation', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '153A', @@ -1790,7 +1792,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha Assault Company', echelon: 'company', branch: 'aviation', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '153A', @@ -1812,7 +1814,7 @@ abstract final class DoctrinalTemplates { name: 'Assault Helicopter Battalion (Medical)', echelon: 'battalion', branch: 'medical', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '153A', @@ -1841,7 +1843,7 @@ abstract final class DoctrinalTemplates { name: 'Alpha MEDEVAC Company', echelon: 'company', branch: 'medical', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Company Commander', dutyMos: '153A', @@ -1868,7 +1870,7 @@ abstract final class DoctrinalTemplates { name: 'Aviation Support Battalion', echelon: 'battalion', branch: 'logistics', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Battalion Commander', dutyMos: '92A', diff --git a/lib/domain/services/mtoe_validation_engine.dart b/lib/domain/services/mtoe_validation_engine.dart index b292859..3b84a10 100644 --- a/lib/domain/services/mtoe_validation_engine.dart +++ b/lib/domain/services/mtoe_validation_engine.dart @@ -90,12 +90,13 @@ class MtoeValidationEngine { for (final unit in units) { final unitSlots = slotsByUnit[unit.unitId] ?? []; - findings.addAll(_checkMosMismatches(unit, unitSlots, personnelById)); - findings.addAll(_checkRankMismatches(unit, unitSlots, personnelById)); - findings.addAll(_checkVacantKeyLeaders(unit, unitSlots)); - findings.addAll(_checkMissingCombatMedic(unit, unitSlots)); - findings.addAll(_checkMissingFireSupport(unit, unitSlots)); - findings.addAll(_checkManning(unit, unitSlots)); + findings + ..addAll(_checkMosMismatches(unit, unitSlots, personnelById)) + ..addAll(_checkRankMismatches(unit, unitSlots, personnelById)) + ..addAll(_checkVacantKeyLeaders(unit, unitSlots)) + ..addAll(_checkMissingCombatMedic(unit, unitSlots)) + ..addAll(_checkMissingFireSupport(unit, unitSlots)) + ..addAll(_checkManning(unit, unitSlots)); } findings.addAll(_checkStructuralHierarchy(units)); @@ -118,15 +119,17 @@ class MtoeValidationEngine { final soldier = personnelById[slot.assignedPersonnelId]; if (soldier == null) continue; if (soldier.primaryMos != slot.dutyMos) { - findings.add(ValidationFinding( - severity: FindingSeverity.warning, - findingCode: 'V001', - description: '${unit.name} — "${slot.positionTitle}": assigned soldier ' - '${soldier.name} has MOS ${soldier.primaryMos.code} but slot ' - 'requires ${slot.dutyMos.code}.', - unitId: unit.unitId, - slotId: slot.slotId, - )); + findings.add( + ValidationFinding( + severity: FindingSeverity.warning, + findingCode: 'V001', + description: '${unit.name} — "${slot.positionTitle}": assigned soldier ' + '${soldier.name} has MOS ${soldier.primaryMos.code} but slot ' + 'requires ${slot.dutyMos.code}.', + unitId: unit.unitId, + slotId: slot.slotId, + ), + ); } } return findings; @@ -147,15 +150,17 @@ class MtoeValidationEngine { final soldier = personnelById[slot.assignedPersonnelId]; if (soldier == null) continue; if (!soldier.payGrade.meetsMinimum(slot.authorizedRank)) { - findings.add(ValidationFinding( - severity: FindingSeverity.warning, - findingCode: 'V002', - description: '${unit.name} — "${slot.positionTitle}": assigned soldier ' - '${soldier.name} is ${soldier.payGrade.payGrade} but slot ' - 'requires minimum ${slot.authorizedRank.payGrade}.', - unitId: unit.unitId, - slotId: slot.slotId, - )); + findings.add( + ValidationFinding( + severity: FindingSeverity.warning, + findingCode: 'V002', + description: '${unit.name} — "${slot.positionTitle}": assigned soldier ' + '${soldier.name} is ${soldier.payGrade.payGrade} but slot ' + 'requires minimum ${slot.authorizedRank.payGrade}.', + unitId: unit.unitId, + slotId: slot.slotId, + ), + ); } } return findings; @@ -172,14 +177,16 @@ class MtoeValidationEngine { final findings = []; for (final slot in slots) { if (slot.isKeyLeader && slot.isVacant) { - findings.add(ValidationFinding( - severity: FindingSeverity.critical, - findingCode: 'V003', - description: '${unit.name} — Key leader position "${slot.positionTitle}" ' - '(${slot.dutyMos.code}/${slot.authorizedRank.payGrade}) is vacant.', - unitId: unit.unitId, - slotId: slot.slotId, - )); + findings.add( + ValidationFinding( + severity: FindingSeverity.critical, + findingCode: 'V003', + description: '${unit.name} — Key leader position "${slot.positionTitle}" ' + '(${slot.dutyMos.code}/${slot.authorizedRank.payGrade}) is vacant.', + unitId: unit.unitId, + slotId: slot.slotId, + ), + ); } } return findings; @@ -249,15 +256,17 @@ class MtoeValidationEngine { if (parent == null) continue; if (!unit.echelon.isValidParent(parent.echelon)) { - findings.add(ValidationFinding( - severity: FindingSeverity.critical, - findingCode: 'V006', - description: '"${unit.name}" (${unit.echelon.displayName}) is assigned under ' - '"${parent.name}" (${parent.echelon.displayName}). ' - 'A ${unit.echelon.displayName} must be subordinate to a ' - '${_expectedParentEchelon(unit.echelon)?.displayName ?? "root"}.', - unitId: unit.unitId, - )); + findings.add( + ValidationFinding( + severity: FindingSeverity.critical, + findingCode: 'V006', + description: '"${unit.name}" (${unit.echelon.displayName}) is assigned under ' + '"${parent.name}" (${parent.echelon.displayName}). ' + 'A ${unit.echelon.displayName} must be subordinate to a ' + '${_expectedParentEchelon(unit.echelon)?.displayName ?? "root"}.', + unitId: unit.unitId, + ), + ); } } return findings; diff --git a/lib/domain/services/unit_tree_service.dart b/lib/domain/services/unit_tree_service.dart index 52285fb..870b4b3 100644 --- a/lib/domain/services/unit_tree_service.dart +++ b/lib/domain/services/unit_tree_service.dart @@ -13,12 +13,12 @@ class ValidationResult { /// Human-readable reason if [isValid] is false. final String? reason; + // ignore: sort_constructors_first + /// Creates a failing validation result with a [reason]. + ValidationResult.invalid(this.reason) : isValid = false; + /// A passing validation result. static const valid = ValidationResult(isValid: true); - - /// Creates a failing validation result with a [reason]. - static ValidationResult invalid(String reason) => - ValidationResult(isValid: false, reason: reason); } /// Represents a node in the unit tree with its children and slots. @@ -213,7 +213,7 @@ class UnitTreeService { /// Calculates the fill rate for a list of [slots] (0.0–1.0). double calculateFillRate(List slots) { - if (slots.isEmpty) return 0.0; + if (slots.isEmpty) return 0; final filled = slots.where((s) => s.isFilled).length; return filled / slots.length; } diff --git a/lib/domain/services/validation_isolate.dart b/lib/domain/services/validation_isolate.dart index e986138..da7ed55 100644 --- a/lib/domain/services/validation_isolate.dart +++ b/lib/domain/services/validation_isolate.dart @@ -43,9 +43,9 @@ class ValidationResponse { /// Entry point for the background validation isolate. /// -/// Receives [ValidationRequest] messages on [receivePort], runs the -/// [MtoeValidationEngine], and sends a [ValidationResponse] back via the -/// request's [ValidationRequest.replyPort]. +/// Receives `ValidationRequest` messages on a `ReceivePort`, runs the +/// [MtoeValidationEngine], and sends a `ValidationResponse` back via the +/// request's replyPort. void validationIsolateEntryPoint(SendPort mainSendPort) { final receivePort = ReceivePort(); // Send our receive port to the main isolate so it can send us requests. diff --git a/lib/infrastructure/codecs/pdf_chart_exporter.dart b/lib/infrastructure/codecs/pdf_chart_exporter.dart index 30492e9..5edd5ee 100644 --- a/lib/infrastructure/codecs/pdf_chart_exporter.dart +++ b/lib/infrastructure/codecs/pdf_chart_exporter.dart @@ -77,11 +77,11 @@ class PdfChartExporter { const PdfChartExporter(); static const String _classificationBanner = 'UNCLASSIFIED'; - static const double _margin = 36.0; // 0.5 inch in points - static const double _nodeW = 110.0; - static const double _nodeH = 36.0; - static const double _levelGap = 60.0; // vertical gap between echelon levels - static const double _siblingGap = 8.0; // horizontal gap between siblings + static const double _margin = 36; // 0.5 inch in points + static const double _nodeW = 110; + static const double _nodeH = 36; + static const double _levelGap = 60; // vertical gap between echelon levels + static const double _siblingGap = 8; // horizontal gap between siblings // --------------------------------------------------------------------------- // Public API @@ -98,30 +98,30 @@ class PdfChartExporter { DateTime? exportDate, }) { final date = exportDate ?? DateTime.now(); - final treeService = const UnitTreeService(); + const treeService = UnitTreeService(); final roots = treeService.buildTree(units, slots); // Compute layout positions for all nodes. final positions = {}; - double xCursor = _margin; + var xCursor = _margin; for (final root in roots) { xCursor = _layoutSubtree(root, xCursor, 0, positions); xCursor += _siblingGap * 4; } // Build the PDF document. - final builder = _PdfBuilder(pageSize: pageSize); - builder.addCoverPage( - unitDesignation: unitDesignation, - exportDate: date, - classification: _classificationBanner, - ); - builder.addChartPage( - positions: positions, - roots: roots, - units: units, - classification: _classificationBanner, - ); + final builder = _PdfBuilder(pageSize: pageSize) + ..addCoverPage( + unitDesignation: unitDesignation, + exportDate: date, + classification: _classificationBanner, + ) + ..addChartPage( + positions: positions, + roots: roots, + units: units, + classification: _classificationBanner, + ); return builder.build(); } @@ -148,7 +148,7 @@ class PdfChartExporter { } // Layout children first to determine total width. - double childX = startX; + var childX = startX; for (final child in node.children) { childX = _layoutSubtree(child, childX, depth + 1, positions); } @@ -207,29 +207,29 @@ class _PdfBuilder { final h = pageSize.heightPt; final cx = w / 2; - final stream = StringBuffer(); - stream.writeln('BT'); - // Classification banner — top - stream.writeln('/F1 10 Tf'); - stream.writeln('${cx - 40} ${h - 20} Td'); - stream.writeln('($classification) Tj'); - // Title - stream.writeln('/F1 24 Tf'); - stream.writeln('${cx - 80} ${h / 2 + 40} Td'); - stream.writeln('(UTO Manager — Organizational Chart) Tj'); - // Unit designation - stream.writeln('/F1 18 Tf'); - stream.writeln('${cx - 60} ${h / 2} Td'); - stream.writeln('($unitDesignation) Tj'); - // Export date - stream.writeln('/F1 12 Tf'); - stream.writeln('${cx - 40} ${h / 2 - 30} Td'); - stream.writeln('(Exported: $dateStr) Tj'); - // Classification banner — bottom - stream.writeln('/F1 10 Tf'); - stream.writeln('${cx - 40} 10 Td'); - stream.writeln('($classification) Tj'); - stream.writeln('ET'); + final stream = StringBuffer() + ..writeln('BT') + // Classification banner — top + ..writeln('/F1 10 Tf') + ..writeln('${cx - 40} ${h - 20} Td') + ..writeln('($classification) Tj') + // Title + ..writeln('/F1 24 Tf') + ..writeln('${cx - 80} ${h / 2 + 40} Td') + ..writeln('(UTO Manager — Organizational Chart) Tj') + // Unit designation + ..writeln('/F1 18 Tf') + ..writeln('${cx - 60} ${h / 2} Td') + ..writeln('($unitDesignation) Tj') + // Export date + ..writeln('/F1 12 Tf') + ..writeln('${cx - 40} ${h / 2 - 30} Td') + ..writeln('(Exported: $dateStr) Tj') + // Classification banner — bottom + ..writeln('/F1 10 Tf') + ..writeln('${cx - 40} 10 Td') + ..writeln('($classification) Tj') + ..writeln('ET'); _addPage(stream.toString()); } @@ -248,11 +248,10 @@ class _PdfBuilder { final h = pageSize.heightPt; final cx = w / 2; - final stream = StringBuffer(); - - // Classification banners - stream.writeln('BT /F1 10 Tf ${cx - 40} ${h - 20} Td ($classification) Tj ET'); - stream.writeln('BT /F1 10 Tf ${cx - 40} 10 Td ($classification) Tj ET'); + final stream = StringBuffer() + // Classification banners + ..writeln('BT /F1 10 Tf ${cx - 40} ${h - 20} Td ($classification) Tj ET') + ..writeln('BT /F1 10 Tf ${cx - 40} 10 Td ($classification) Tj ET'); // Draw connector lines first (behind boxes). for (final unit in units) { @@ -266,9 +265,10 @@ class _PdfBuilder { final parentCx = parentPos.x + PdfChartExporter._nodeW / 2; final parentBottom = h - parentPos.y - PdfChartExporter._nodeH; - stream.writeln('0.4 0.4 0.4 RG'); // gray stroke - stream.writeln('0.5 w'); // line width - stream.writeln('$parentCx $parentBottom m $childCx $childTop l S'); + stream + ..writeln('0.4 0.4 0.4 RG') // gray stroke + ..writeln('0.5 w') // line width + ..writeln('$parentCx $parentBottom m $childCx $childTop l S'); } // Draw node boxes and labels. @@ -281,23 +281,23 @@ class _PdfBuilder { // Box fill color based on echelon. final rgb = _echelonColor(unit.echelon); - stream.writeln('${rgb[0]} ${rgb[1]} ${rgb[2]} rg'); // fill - stream.writeln('0.2 0.2 0.4 RG'); // stroke - stream.writeln('0.8 w'); - stream.writeln( - '$x $y ${PdfChartExporter._nodeW} ${PdfChartExporter._nodeH} re B', - ); - // Unit name label (truncated to fit box). final label = _truncate(unit.name, 18); final echelonLabel = unit.echelon.displayName; - stream.writeln('1 1 1 rg'); // white text - stream.writeln( - 'BT /F1 7 Tf ${x + 4} ${y + PdfChartExporter._nodeH - 12} Td ($label) Tj ET', - ); - stream.writeln( - 'BT /F1 6 Tf ${x + 4} ${y + 4} Td ($echelonLabel) Tj ET', - ); + stream + ..writeln('${rgb[0]} ${rgb[1]} ${rgb[2]} rg') // fill + ..writeln('0.2 0.2 0.4 RG') // stroke + ..writeln('0.8 w') + ..writeln( + '$x $y ${PdfChartExporter._nodeW} ${PdfChartExporter._nodeH} re B', + ) + ..writeln('1 1 1 rg') // white text + ..writeln( + 'BT /F1 7 Tf ${x + 4} ${y + PdfChartExporter._nodeH - 12} Td ($label) Tj ET', + ) + ..writeln( + 'BT /F1 6 Tf ${x + 4} ${y + 4} Td ($echelonLabel) Tj ET', + ); } _addPage(stream.toString()); @@ -385,9 +385,9 @@ class _PdfBuilder { // Ensure font object exists. _fontObjNum(); - final buf = StringBuffer(); - buf.writeln('%PDF-1.4'); - buf.writeln('%âãÏÓ'); // binary comment to mark as binary file + final buf = StringBuffer() + ..writeln('%PDF-1.4') + ..writeln('%âãÏÓ'); // binary comment to mark as binary file // Write all objects and record their byte offsets for the xref table. final offsets = []; @@ -421,19 +421,21 @@ class _PdfBuilder { // Cross-reference table. final xrefOffset = byteOffset; final totalObjects = offsets.length; - buf.writeln('xref'); - buf.writeln('0 ${totalObjects + 1}'); - buf.writeln('0000000000 65535 f '); + buf + ..writeln('xref') + ..writeln('0 ${totalObjects + 1}') + ..writeln('0000000000 65535 f '); for (final offset in offsets) { - buf.writeln(offset.toString().padLeft(10, '0') + ' 00000 n '); + buf.writeln('${offset.toString().padLeft(10, '0')} 00000 n '); } // Trailer. - buf.writeln('trailer'); - buf.writeln('<< /Size ${totalObjects + 1} /Root $catalogObjNum 0 R >>'); - buf.writeln('startxref'); - buf.writeln(xrefOffset); - buf.writeln('%%EOF'); + buf + ..writeln('trailer') + ..writeln('<< /Size ${totalObjects + 1} /Root $catalogObjNum 0 R >>') + ..writeln('startxref') + ..writeln(xrefOffset) + ..writeln('%%EOF'); return Uint8List.fromList(buf.toString().codeUnits); } diff --git a/lib/infrastructure/codecs/protobuf_codec.dart b/lib/infrastructure/codecs/protobuf_codec.dart index 64c5fe6..d665b1a 100644 --- a/lib/infrastructure/codecs/protobuf_codec.dart +++ b/lib/infrastructure/codecs/protobuf_codec.dart @@ -17,7 +17,7 @@ import 'package:uto_manager/infrastructure/codecs/unit_tree_codec.dart'; /// significantly smaller than the equivalent JSON export. The encoding /// uses a length-prefixed binary format: /// -/// ``` +/// ```text /// [4 bytes: magic] [4 bytes: version] [4 bytes: payload length] /// [N bytes: gzip-compressed JSON payload] /// ``` @@ -69,11 +69,10 @@ class ProtobufCodec { final payloadLength = compressed.length; final totalLength = 12 + payloadLength; final result = Uint8List(totalLength); - final header = ByteData.sublistView(result, 0, 12); - - header.setUint32(0, _magic, Endian.big); - header.setUint32(4, _version, Endian.big); - header.setUint32(8, payloadLength, Endian.big); + ByteData.sublistView(result, 0, 12) + ..setUint32(0, _magic) + ..setUint32(4, _version) + ..setUint32(8, payloadLength); result.setRange(12, totalLength, compressed); return result; @@ -95,9 +94,9 @@ class ProtobufCodec { } final header = ByteData.sublistView(bytes, 0, 12); - final magic = header.getUint32(0, Endian.big); - final version = header.getUint32(4, Endian.big); - final payloadLength = header.getUint32(8, Endian.big); + final magic = header.getUint32(0); + final version = header.getUint32(4); + final payloadLength = header.getUint32(8); if (magic != _magic) { throw FormatException( @@ -137,7 +136,7 @@ class ProtobufCodec { /// A ratio < 1.0 means the binary encoding is smaller than the JSON. /// Typical BCT trees achieve 0.25–0.40 (60–75% size reduction). static double compressionRatio(int binaryBytes, int jsonBytes) { - if (jsonBytes == 0) return 1.0; + if (jsonBytes == 0) return 1; return binaryBytes / jsonBytes; } } diff --git a/lib/presentation/screens/home_screen.dart b/lib/presentation/screens/home_screen.dart index 592f513..a6c4c7e 100644 --- a/lib/presentation/screens/home_screen.dart +++ b/lib/presentation/screens/home_screen.dart @@ -428,7 +428,11 @@ class HomeScreen extends StatelessWidget { final a = valState.advisories.length; if (valState.isClean) { return _statusChip( - theme, Icons.check_circle_outline, 'MTOE Valid', Colors.green.shade400); + theme, + Icons.check_circle_outline, + 'MTOE Valid', + Colors.green.shade400, + ); } return Row( children: [ @@ -437,8 +441,12 @@ class HomeScreen extends StatelessWidget { const SizedBox(width: 8), ], if (w > 0) ...[ - _statusChip(theme, Icons.warning_amber_outlined, '$w warnings', - Colors.orange.shade400), + _statusChip( + theme, + Icons.warning_amber_outlined, + '$w warnings', + Colors.orange.shade400, + ), const SizedBox(width: 8), ], if (a > 0) @@ -572,14 +580,20 @@ class _ExportDialogState extends State<_ExportDialog> { children: [ const Text('Format:', style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 8), - ..._ExportFormat.values.map( - (f) => RadioListTile<_ExportFormat>( - title: Text(f.label), - subtitle: Text(f.description, style: const TextStyle(fontSize: 11)), - value: f, - groupValue: _format, - dense: true, - onChanged: (v) => setState(() => _format = v!), + RadioGroup<_ExportFormat>( + groupValue: _format, + onChanged: (v) => setState(() => _format = v!), + child: Column( + children: _ExportFormat.values + .map( + (f) => RadioListTile<_ExportFormat>( + title: Text(f.label), + subtitle: Text(f.description, style: const TextStyle(fontSize: 11)), + value: f, + dense: true, + ), + ) + .toList(), ), ), if (_format == _ExportFormat.pdf) ...[ @@ -653,31 +667,37 @@ class _ExportDialogState extends State<_ExportDialog> { switch (_format) { case _ExportFormat.json: - final codec = const UnitTreeCodec(); + const codec = UnitTreeCodec(); final json = codec.encode(units: units, slots: slots, personnel: const []); - setState(() => _result = 'JSON export ready — ${json.length} characters ' - '(${(json.length / 1024).toStringAsFixed(1)} KB)'); + setState( + () => _result = 'JSON export ready — ${json.length} characters ' + '(${(json.length / 1024).toStringAsFixed(1)} KB)', + ); case _ExportFormat.binary: - final codec = const ProtobufCodec(); + const codec = ProtobufCodec(); final bytes = codec.encode(units: units, slots: slots, personnel: const []); final jsonLen = const UnitTreeCodec().encode(units: units, slots: slots, personnel: const []).length; final ratio = ProtobufCodec.compressionRatio(bytes.length, jsonLen); - setState(() => _result = 'Binary export ready — ${bytes.length} bytes ' - '(${(ratio * 100).round()}% of JSON size)'); + setState( + () => _result = 'Binary export ready — ${bytes.length} bytes ' + '(${(ratio * 100).round()}% of JSON size)', + ); case _ExportFormat.pdf: - final exporter = const PdfChartExporter(); + const exporter = PdfChartExporter(); final bytes = exporter.export( units: units, slots: slots, unitDesignation: units.isNotEmpty ? units.first.name : 'Unit Tree', pageSize: _pageSize, ); - setState(() => _result = 'PDF export ready — ${bytes.length} bytes ' - '(${(bytes.length / 1024).toStringAsFixed(1)} KB), ' - '${_pageSize.displayName}'); + setState( + () => _result = 'PDF export ready — ${bytes.length} bytes ' + '(${(bytes.length / 1024).toStringAsFixed(1)} KB), ' + '${_pageSize.displayName}', + ); } } catch (e) { setState(() => _error = 'Export failed: $e'); diff --git a/lib/presentation/widgets/personnel_list_tile.dart b/lib/presentation/widgets/personnel_list_tile.dart index afe3803..c9bebfb 100644 --- a/lib/presentation/widgets/personnel_list_tile.dart +++ b/lib/presentation/widgets/personnel_list_tile.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:uto_manager/domain/entities/personnel_record.dart'; +import 'package:uto_manager/presentation/widgets/position_slot_card.dart' show PositionSlotCard; /// A draggable list tile representing a soldier in the personnel panel. /// /// Can be dragged onto a [PositionSlotCard] to assign the soldier. class PersonnelListTile extends StatelessWidget { const PersonnelListTile({ - super.key, - required this.record, + required this.record, super.key, this.onTap, }); diff --git a/lib/presentation/widgets/position_slot_card.dart b/lib/presentation/widgets/position_slot_card.dart index 363eade..963f821 100644 --- a/lib/presentation/widgets/position_slot_card.dart +++ b/lib/presentation/widgets/position_slot_card.dart @@ -8,8 +8,8 @@ import 'package:uto_manager/domain/entities/position_slot.dart'; /// Supports drag-and-drop target for soldier assignment. class PositionSlotCard extends StatelessWidget { const PositionSlotCard({ - super.key, required this.slot, + super.key, this.assignedSoldier, this.onUnassign, this.onAcceptDrop, @@ -121,7 +121,9 @@ class PositionSlotCard extends StatelessWidget { return const Color(0xFFFF9800); case ReadinessState.notMissionCapable: return const Color(0xFFF44336); - default: + case ReadinessState.unknown: + return Colors.grey; + case null: return Colors.grey; } } diff --git a/lib/presentation/widgets/unit_node_card.dart b/lib/presentation/widgets/unit_node_card.dart index d635f23..e508269 100644 --- a/lib/presentation/widgets/unit_node_card.dart +++ b/lib/presentation/widgets/unit_node_card.dart @@ -7,10 +7,7 @@ import 'package:uto_manager/domain/services/unit_tree_service.dart'; /// Shows the unit's echelon, branch, name, and fill rate indicator. class UnitNodeCard extends StatelessWidget { const UnitNodeCard({ - super.key, - required this.treeNode, - required this.isSelected, - required this.onTap, + required this.treeNode, required this.isSelected, required this.onTap, super.key, }); final UnitTreeNode treeNode; diff --git a/lib/presentation/widgets/uto_canvas_widget.dart b/lib/presentation/widgets/uto_canvas_widget.dart index e7719c9..1c85aab 100644 --- a/lib/presentation/widgets/uto_canvas_widget.dart +++ b/lib/presentation/widgets/uto_canvas_widget.dart @@ -8,11 +8,7 @@ import 'package:uto_manager/presentation/widgets/unit_node_card.dart'; /// [UnitNodeCard] widgets arranged in a hierarchical tree layout. class UtoCanvasWidget extends StatefulWidget { const UtoCanvasWidget({ - super.key, - required this.roots, - required this.selectedUnitId, - required this.onUnitSelected, - required this.onReparent, + required this.roots, required this.selectedUnitId, required this.onUnitSelected, required this.onReparent, super.key, }); /// Root-level tree nodes to render. @@ -49,12 +45,11 @@ class _UtoCanvasWidgetState extends State { return InteractiveViewer( transformationController: _transformController, minScale: 0.2, - maxScale: 3.0, + maxScale: 3, boundaryMargin: const EdgeInsets.all(500), child: Padding( padding: const EdgeInsets.all(40), child: Column( - crossAxisAlignment: CrossAxisAlignment.center, children: widget.roots.map((root) => _buildSubtree(root, 0)).toList(), ), ), diff --git a/test/domain/services/assignment_service_test.dart b/test/domain/services/assignment_service_test.dart index 3a0c75b..7b58f76 100644 --- a/test/domain/services/assignment_service_test.dart +++ b/test/domain/services/assignment_service_test.dart @@ -81,7 +81,7 @@ void main() { test('MOS mismatch is flagged but does not block assignment', () { final slot = makeSlot(mosCode: '68W'); - final soldier = makeSoldier(mosCode: '11B'); + final soldier = makeSoldier(); final outcome = service.assign(slot, soldier); expect(outcome.isSuccess, isTrue); expect(outcome.hasMosMismatch, isTrue); @@ -96,7 +96,7 @@ void main() { }); test('no rank mismatch when soldier meets minimum rank', () { - final slot = makeSlot(rank: AuthorizedRank.e5); + final slot = makeSlot(); final soldier = makeSoldier(rank: AuthorizedRank.e6); final outcome = service.assign(slot, soldier); expect(outcome.isSuccess, isTrue); @@ -151,15 +151,15 @@ void main() { test('all FMC returns green', () { final soldiers = [ - makeSoldier(id: 'p1', readiness: ReadinessState.fullyMissionCapable), - makeSoldier(id: 'p2', readiness: ReadinessState.fullyMissionCapable), + makeSoldier(), + makeSoldier(id: 'p2'), ]; expect(service.aggregateReadiness(soldiers), ReadinessColor.green); }); test('any NMC returns red', () { final soldiers = [ - makeSoldier(id: 'p1', readiness: ReadinessState.fullyMissionCapable), + makeSoldier(), makeSoldier(id: 'p2', readiness: ReadinessState.notMissionCapable), ]; expect(service.aggregateReadiness(soldiers), ReadinessColor.red); @@ -167,14 +167,14 @@ void main() { test('mixed FMC and PMC returns amber', () { final soldiers = [ - makeSoldier(id: 'p1', readiness: ReadinessState.fullyMissionCapable), + makeSoldier(), makeSoldier(id: 'p2', readiness: ReadinessState.partiallyMissionCapable), ]; expect(service.aggregateReadiness(soldiers), ReadinessColor.amber); }); test('single PMC soldier returns amber', () { - final soldiers = [makeSoldier(id: 'p1', readiness: ReadinessState.partiallyMissionCapable)]; + final soldiers = [makeSoldier(readiness: ReadinessState.partiallyMissionCapable)]; expect(service.aggregateReadiness(soldiers), ReadinessColor.amber); }); }); @@ -185,14 +185,14 @@ void main() { group('UnitTreeService integration via AssignmentService', () { test('validateReparent is accessible through treeService', () { - final bde = UnitNode( + const bde = UnitNode( unitId: 'bde', echelon: Echelon.brigade, branch: DoctrinalBranch.infantry, name: 'Brigade', isKeyLeader: false, ); - final bn = UnitNode( + const bn = UnitNode( unitId: 'bn', echelon: Echelon.battalion, branch: DoctrinalBranch.infantry, diff --git a/test/domain/services/command_event_store_test.dart b/test/domain/services/command_event_store_test.dart index 70c7bd9..135ad8f 100644 --- a/test/domain/services/command_event_store_test.dart +++ b/test/domain/services/command_event_store_test.dart @@ -18,8 +18,7 @@ void main() { newParentId: 'unit-2', previousParentId: 'unit-0', ), - occurredAt: DateTime.utc(2026, 8, 9, 10, 0, 0), - isSynced: false, + occurredAt: DateTime.utc(2026, 8, 9, 10), ); final json = event.toJson(); @@ -144,16 +143,19 @@ void main() { test('allEvents is unmodifiable', () { store.append(_makeEvent('e1', CommandEventType.attachNode)); - expect(() => store.allEvents.add(_makeEvent('e2', CommandEventType.detachNode)), - throwsUnsupportedError); + expect( + () => store.allEvents.add(_makeEvent('e2', CommandEventType.detachNode)), + throwsUnsupportedError, + ); }); test('pendingEvents excludes synced events', () { final e1 = _makeEvent('e1', CommandEventType.attachNode); final e2 = _makeEvent('e2', CommandEventType.detachNode); - store.append(e1); - store.append(e2); - store.markSynced('e1'); + store + ..append(e1) + ..append(e2) + ..markSynced('e1'); expect(store.pendingCount, 1); expect(store.pendingEvents.first.eventId, 'e2'); @@ -170,17 +172,19 @@ void main() { }); test('markAllSynced marks every event', () { - store.append(_makeEvent('e1', CommandEventType.attachNode)); - store.append(_makeEvent('e2', CommandEventType.assignPersonnel)); - store.append(_makeEvent('e3', CommandEventType.modifySlot)); - store.markAllSynced(); + store + ..append(_makeEvent('e1', CommandEventType.attachNode)) + ..append(_makeEvent('e2', CommandEventType.assignPersonnel)) + ..append(_makeEvent('e3', CommandEventType.modifySlot)) + ..markAllSynced(); expect(store.pendingCount, 0); }); test('exportPendingToJson produces valid JSON array', () { - store.append(_makeEvent('e1', CommandEventType.attachNode)); - store.append(_makeEvent('e2', CommandEventType.detachNode)); - store.markSynced('e1'); // e1 is synced, only e2 should appear + store + ..append(_makeEvent('e1', CommandEventType.attachNode)) + ..append(_makeEvent('e2', CommandEventType.detachNode)) + ..markSynced('e1'); // e1 is synced, only e2 should appear final json = store.exportPendingToJson(); final decoded = jsonDecode(json) as List; @@ -189,9 +193,10 @@ void main() { }); test('clear removes all events', () { - store.append(_makeEvent('e1', CommandEventType.attachNode)); - store.append(_makeEvent('e2', CommandEventType.detachNode)); - store.clear(); + store + ..append(_makeEvent('e1', CommandEventType.attachNode)) + ..append(_makeEvent('e2', CommandEventType.detachNode)) + ..clear(); expect(store.eventCount, 0); }); @@ -230,8 +235,9 @@ void main() { }); test('syncIfOnline transmits pending events when online', () async { - store.append(_makeEvent('e1', CommandEventType.attachNode)); - store.append(_makeEvent('e2', CommandEventType.assignPersonnel)); + store + ..append(_makeEvent('e1', CommandEventType.attachNode)) + ..append(_makeEvent('e2', CommandEventType.assignPersonnel)); sync.isOnline = true; final count = await sync.syncIfOnline(); @@ -249,9 +255,10 @@ void main() { test('already-synced events are not re-transmitted', () async { final e1 = _makeEvent('e1', CommandEventType.attachNode); final e2 = _makeEvent('e2', CommandEventType.detachNode); - store.append(e1); - store.append(e2); - store.markSynced('e1'); + store + ..append(e1) + ..append(e2) + ..markSynced('e1'); final count = await sync.syncIfOnline(); expect(count, 1); // only e2 was pending diff --git a/test/domain/services/mtoe_validation_engine_test.dart b/test/domain/services/mtoe_validation_engine_test.dart index c7234d6..308ae6b 100644 --- a/test/domain/services/mtoe_validation_engine_test.dart +++ b/test/domain/services/mtoe_validation_engine_test.dart @@ -89,7 +89,7 @@ void main() { mos: '68W', assignedPersonnelId: 'p1', ); - final soldier = makeSoldier(id: 'p1', mos: '11B', assignedSlotId: 's1'); + final soldier = makeSoldier(id: 'p1', assignedSlotId: 's1'); final findings = engine.validate( units: [unit], @@ -109,10 +109,9 @@ void main() { final slot = makeSlot( slotId: 's1', unitId: 'u1', - mos: '11B', assignedPersonnelId: 'p1', ); - final soldier = makeSoldier(id: 'p1', mos: '11B', assignedSlotId: 's1'); + final soldier = makeSoldier(id: 'p1', assignedSlotId: 's1'); final findings = engine.validate( units: [unit], @@ -174,7 +173,6 @@ void main() { final slot = makeSlot( slotId: 's1', unitId: 'u1', - rank: AuthorizedRank.e5, assignedPersonnelId: 'p1', ); final soldier = makeSoldier( @@ -240,7 +238,7 @@ void main() { test('no V003 for non-key-leader vacant slots', () { final unit = makeUnit(id: 'u1', echelon: Echelon.platoon); - final slot = makeSlot(slotId: 's1', unitId: 'u1', isKeyLeader: false); + final slot = makeSlot(slotId: 's1', unitId: 'u1'); final findings = engine.validate( units: [unit], @@ -259,7 +257,7 @@ void main() { group('V004 — missing combat medic', () { test('flags platoon with no 68W slot as advisory', () { final unit = makeUnit(id: 'u1', echelon: Echelon.platoon); - final slot = makeSlot(slotId: 's1', unitId: 'u1', mos: '11B'); + final slot = makeSlot(slotId: 's1', unitId: 'u1'); final findings = engine.validate( units: [unit], @@ -289,7 +287,7 @@ void main() { test('no V004 for non-platoon echelons', () { final unit = makeUnit(id: 'u1', echelon: Echelon.squad); - final slot = makeSlot(slotId: 's1', unitId: 'u1', mos: '11B'); + final slot = makeSlot(slotId: 's1', unitId: 'u1'); final findings = engine.validate( units: [unit], diff --git a/test/domain/services/template_instantiator_test.dart b/test/domain/services/template_instantiator_test.dart index f02b4a5..e587ea6 100644 --- a/test/domain/services/template_instantiator_test.dart +++ b/test/domain/services/template_instantiator_test.dart @@ -4,13 +4,13 @@ import 'package:uto_manager/domain/services/template_instantiator.dart'; void main() { // Use a deterministic counter-based ID generator for tests. - int _counter = 0; - String nextId() => 'id-${_counter++}'; + var counter0 = 0; + String nextId() => 'id-${counter0++}'; late TemplateInstantiator instantiator; setUp(() { - _counter = 0; + counter0 = 0; instantiator = TemplateInstantiator(idGenerator: nextId); }); @@ -109,12 +109,12 @@ void main() { group('parent-child relationships', () { test('child unit has correct parentUnitId', () { - final def = UnitDefinition( + const def = UnitDefinition( name: 'Battalion', echelon: 'battalion', branch: 'infantry', children: [ - const UnitDefinition( + UnitDefinition( name: 'Alpha Company', echelon: 'company', branch: 'infantry', @@ -131,7 +131,7 @@ void main() { }); test('deeply nested tree has correct parent chain', () { - final def = UnitDefinition( + const def = UnitDefinition( name: 'Brigade', echelon: 'brigade', branch: 'infantry', @@ -141,7 +141,7 @@ void main() { echelon: 'battalion', branch: 'infantry', children: [ - const UnitDefinition( + UnitDefinition( name: 'Company', echelon: 'company', branch: 'infantry', @@ -164,14 +164,14 @@ void main() { test('all generated IDs are unique', () { // Use UUID-style generator to verify uniqueness. - int counter = 0; + var counter = 0; final inst = TemplateInstantiator(idGenerator: () => 'uid-${counter++}'); - final def = UnitDefinition( + const def = UnitDefinition( name: 'Brigade', echelon: 'brigade', branch: 'infantry', - slots: const [ + slots: [ SlotDefinition( positionTitle: 'Commander', dutyMos: '11A', @@ -179,7 +179,7 @@ void main() { ), ], children: [ - const UnitDefinition( + UnitDefinition( name: 'Battalion', echelon: 'battalion', branch: 'infantry', @@ -293,7 +293,7 @@ void main() { 'abct', 'cab_heavy', 'cab_light', - ])); + ]),); }); test('DoctrinalTemplates.displayName returns non-empty strings', () { diff --git a/test/domain/services/validation_isolate_test.dart b/test/domain/services/validation_isolate_test.dart index 67b01a9..52a1f6d 100644 --- a/test/domain/services/validation_isolate_test.dart +++ b/test/domain/services/validation_isolate_test.dart @@ -14,7 +14,7 @@ void main() { // Helpers // --------------------------------------------------------------------------- - UnitNode _makeUnit(String id, Echelon echelon, {String? parentId}) => UnitNode( + UnitNode makeUnit(String id, Echelon echelon, {String? parentId}) => UnitNode( unitId: id, echelon: echelon, branch: DoctrinalBranch.infantry, @@ -23,7 +23,7 @@ void main() { isKeyLeader: false, ); - PositionSlot _makeSlot( + PositionSlot makeSlot( String id, String unitId, { bool isKeyLeader = false, @@ -89,8 +89,8 @@ void main() { test('validate() detects vacant key leader (V003)', () async { await runner.start(); - final unit = _makeUnit('u1', Echelon.squad); - final slot = _makeSlot('sl1', 'u1', isKeyLeader: true); // vacant + final unit = makeUnit('u1', Echelon.squad); + final slot = makeSlot('sl1', 'u1', isKeyLeader: true); // vacant final response = await runner.validate( units: [unit], @@ -104,8 +104,8 @@ void main() { test('validate() detects missing medic in platoon (V004)', () async { await runner.start(); - final unit = _makeUnit('u1', Echelon.platoon); - final slot = _makeSlot('sl1', 'u1'); // no 68W slot + final unit = makeUnit('u1', Echelon.platoon); + final slot = makeSlot('sl1', 'u1'); // no 68W slot final response = await runner.validate( units: [unit], @@ -120,7 +120,7 @@ void main() { test('validate() returns clean result for fully-staffed valid unit', () async { await runner.start(); // A squad with a filled key leader slot and no hierarchy violations. - final unit = _makeUnit('u1', Echelon.squad); + final unit = makeUnit('u1', Echelon.squad); final keyLeaderSlot = PositionSlot( slotId: 'sl1', unitId: 'u1', @@ -135,7 +135,6 @@ void main() { name: 'SSG Smith', primaryMos: DutyMos.unvalidated('11B'), payGrade: AuthorizedRank.e6, - qualifications: const [], readinessState: ReadinessState.fullyMissionCapable, ); @@ -153,8 +152,8 @@ void main() { test('validate() detects structural hierarchy violation (V006)', () async { await runner.start(); // Squad directly under Brigade — invalid. - final brigade = _makeUnit('bde', Echelon.brigade); - final squad = _makeUnit('sq1', Echelon.squad, parentId: 'bde'); + final brigade = makeUnit('bde', Echelon.brigade); + final squad = makeUnit('sq1', Echelon.squad, parentId: 'bde'); final response = await runner.validate( units: [brigade, squad], @@ -173,13 +172,13 @@ void main() { test('validate() results match synchronous engine output', () async { await runner.start(); final units = [ - _makeUnit('bde', Echelon.brigade), - _makeUnit('bn', Echelon.battalion, parentId: 'bde'), - _makeUnit('co', Echelon.company, parentId: 'bn'), - _makeUnit('plt', Echelon.platoon, parentId: 'co'), + makeUnit('bde', Echelon.brigade), + makeUnit('bn', Echelon.battalion, parentId: 'bde'), + makeUnit('co', Echelon.company, parentId: 'bn'), + makeUnit('plt', Echelon.platoon, parentId: 'co'), ]; final slots = [ - _makeSlot('sl1', 'plt', isKeyLeader: true), // vacant key leader + makeSlot('sl1', 'plt', isKeyLeader: true), // vacant key leader ]; final isolateResponse = await runner.validate( diff --git a/test/infrastructure/codecs/pdf_chart_exporter_test.dart b/test/infrastructure/codecs/pdf_chart_exporter_test.dart index 7d747d0..8e5812d 100644 --- a/test/infrastructure/codecs/pdf_chart_exporter_test.dart +++ b/test/infrastructure/codecs/pdf_chart_exporter_test.dart @@ -1,10 +1,10 @@ import 'package:flutter_test/flutter_test.dart'; +import 'package:uto_manager/domain/entities/authorized_rank.dart'; import 'package:uto_manager/domain/entities/doctrinal_branch.dart'; import 'package:uto_manager/domain/entities/echelon.dart'; import 'package:uto_manager/domain/entities/position_slot.dart'; import 'package:uto_manager/domain/entities/unit_node.dart'; import 'package:uto_manager/domain/value_objects/duty_mos.dart'; -import 'package:uto_manager/domain/entities/authorized_rank.dart'; import 'package:uto_manager/infrastructure/codecs/pdf_chart_exporter.dart'; void main() { @@ -14,7 +14,7 @@ void main() { // Helpers // --------------------------------------------------------------------------- - UnitNode _makeUnit( + UnitNode makeUnit( String id, Echelon echelon, { String? parentId, @@ -29,7 +29,7 @@ void main() { isKeyLeader: false, ); - PositionSlot _makeSlot(String id, String unitId) => PositionSlot( + PositionSlot makeSlot(String id, String unitId) => PositionSlot( slotId: id, unitId: unitId, positionTitle: 'Rifleman', @@ -111,7 +111,7 @@ void main() { group('PdfChartExporter — tree rendering', () { test('single root unit renders without error', () { - final units = [_makeUnit('u1', Echelon.platoon, name: '1st Platoon')]; + final units = [makeUnit('u1', Echelon.platoon, name: '1st Platoon')]; final bytes = exporter.export( units: units, slots: const [], @@ -123,9 +123,9 @@ void main() { test('parent-child tree renders connector lines', () { final units = [ - _makeUnit('u1', Echelon.platoon, name: '1st Platoon'), - _makeUnit('u2', Echelon.squad, parentId: 'u1', name: '1st Squad'), - _makeUnit('u3', Echelon.squad, parentId: 'u1', name: '2nd Squad'), + makeUnit('u1', Echelon.platoon, name: '1st Platoon'), + makeUnit('u2', Echelon.squad, parentId: 'u1', name: '1st Squad'), + makeUnit('u3', Echelon.squad, parentId: 'u1', name: '2nd Squad'), ]; final bytes = exporter.export( units: units, @@ -139,11 +139,11 @@ void main() { }); test('all echelon levels render without error', () { - final echelons = Echelon.values; + const echelons = Echelon.values; final units = echelons .asMap() .entries - .map((e) => _makeUnit('u${e.key}', e.value, name: e.value.displayName)) + .map((e) => makeUnit('u${e.key}', e.value, name: e.value.displayName)) .toList(); expect( @@ -158,8 +158,8 @@ void main() { test('unit names appear in PDF content stream', () { final units = [ - _makeUnit('u1', Echelon.company, name: 'Alpha Company'), - _makeUnit('u2', Echelon.platoon, parentId: 'u1', name: '1st Platoon'), + makeUnit('u1', Echelon.company, name: 'Alpha Company'), + makeUnit('u2', Echelon.platoon, parentId: 'u1', name: '1st Platoon'), ]; final bytes = exporter.export( units: units, @@ -173,8 +173,8 @@ void main() { }); test('slots are accepted without error', () { - final units = [_makeUnit('u1', Echelon.squad)]; - final slots = List.generate(9, (i) => _makeSlot('sl$i', 'u1')); + final units = [makeUnit('u1', Echelon.squad)]; + final slots = List.generate(9, (i) => makeSlot('sl$i', 'u1')); expect( () => exporter.export( units: units, @@ -222,7 +222,6 @@ void main() { units: const [], slots: const [], unitDesignation: 'Test', - pageSize: PdfPageSize.tabloid, ); final content = String.fromCharCodes(bytes); expect(content, contains('792.0')); @@ -265,24 +264,27 @@ void main() { group('PdfChartExporter — large tree', () { test('50-node tree exports without error', () { - final units = []; - units.add(_makeUnit('root', Echelon.company, name: 'Alpha Co')); + final units = [makeUnit('root', Echelon.company, name: 'Alpha Co')]; for (var i = 0; i < 3; i++) { - units.add(_makeUnit('plt$i', Echelon.platoon, parentId: 'root', name: '${i + 1}st Plt')); + units.add(makeUnit('plt$i', Echelon.platoon, parentId: 'root', name: '${i + 1}st Plt')); for (var j = 0; j < 3; j++) { - units.add(_makeUnit( - 'sq${i}_$j', - Echelon.squad, - parentId: 'plt$i', - name: '${j + 1}st Sq', - )); + units.add( + makeUnit( + 'sq${i}_$j', + Echelon.squad, + parentId: 'plt$i', + name: '${j + 1}st Sq', + ), + ); for (var k = 0; k < 2; k++) { - units.add(_makeUnit( - 'tm${i}_${j}_$k', - Echelon.team, - parentId: 'sq${i}_$j', - name: '${k + 1} Team', - )); + units.add( + makeUnit( + 'tm${i}_${j}_$k', + Echelon.team, + parentId: 'sq${i}_$j', + name: '${k + 1} Team', + ), + ); } } } diff --git a/test/infrastructure/codecs/protobuf_codec_test.dart b/test/infrastructure/codecs/protobuf_codec_test.dart index cea0d6c..95b20f6 100644 --- a/test/infrastructure/codecs/protobuf_codec_test.dart +++ b/test/infrastructure/codecs/protobuf_codec_test.dart @@ -18,7 +18,7 @@ void main() { // Helpers // --------------------------------------------------------------------------- - UnitNode _makeUnit(String id, {String? parentId}) => UnitNode( + UnitNode makeUnit(String id, {String? parentId}) => UnitNode( unitId: id, echelon: Echelon.platoon, branch: DoctrinalBranch.infantry, @@ -27,7 +27,7 @@ void main() { isKeyLeader: false, ); - PositionSlot _makeSlot(String id, String unitId) => PositionSlot( + PositionSlot makeSlot(String id, String unitId) => PositionSlot( slotId: id, unitId: unitId, positionTitle: 'Rifleman', @@ -36,12 +36,11 @@ void main() { isKeyLeader: false, ); - PersonnelRecord _makeSoldier(String id) => PersonnelRecord( + PersonnelRecord makeSoldier(String id) => PersonnelRecord( soldierId: id, name: 'SGT Test', primaryMos: DutyMos.unvalidated('11B'), payGrade: AuthorizedRank.e5, - qualifications: const [], readinessState: ReadinessState.fullyMissionCapable, ); @@ -51,14 +50,14 @@ void main() { group('ProtobufCodec — encode/decode', () { test('encodes to non-empty Uint8List', () { - final units = [_makeUnit('u1')]; + final units = [makeUnit('u1')]; final bytes = codec.encode(units: units, slots: const [], personnel: const []); expect(bytes, isA()); expect(bytes.length, greaterThan(12)); }); test('header contains correct magic bytes "UTOB"', () { - final bytes = codec.encode(units: [_makeUnit('u1')], slots: const [], personnel: const []); + final bytes = codec.encode(units: [makeUnit('u1')], slots: const [], personnel: const []); // Magic: 0x55544F42 expect(bytes[0], 0x55); expect(bytes[1], 0x54); @@ -67,7 +66,7 @@ void main() { }); test('header contains version 1', () { - final bytes = codec.encode(units: [_makeUnit('u1')], slots: const [], personnel: const []); + final bytes = codec.encode(units: [makeUnit('u1')], slots: const [], personnel: const []); // Version at bytes 4–7 expect(bytes[4], 0x00); expect(bytes[5], 0x00); @@ -76,7 +75,7 @@ void main() { }); test('round-trip preserves units', () { - final units = [_makeUnit('u1'), _makeUnit('u2', parentId: 'u1')]; + final units = [makeUnit('u1'), makeUnit('u2', parentId: 'u1')]; final bytes = codec.encode(units: units, slots: const [], personnel: const []); final snapshot = codec.decode(bytes); @@ -86,8 +85,8 @@ void main() { }); test('round-trip preserves slots', () { - final units = [_makeUnit('u1')]; - final slots = [_makeSlot('sl1', 'u1'), _makeSlot('sl2', 'u1')]; + final units = [makeUnit('u1')]; + final slots = [makeSlot('sl1', 'u1'), makeSlot('sl2', 'u1')]; final bytes = codec.encode(units: units, slots: slots, personnel: const []); final snapshot = codec.decode(bytes); @@ -96,8 +95,8 @@ void main() { }); test('round-trip preserves personnel', () { - final units = [_makeUnit('u1')]; - final personnel = [_makeSoldier('s1'), _makeSoldier('s2')]; + final units = [makeUnit('u1')]; + final personnel = [makeSoldier('s1'), makeSoldier('s2')]; final bytes = codec.encode(units: units, slots: const [], personnel: personnel); final snapshot = codec.decode(bytes); @@ -116,9 +115,9 @@ void main() { test('binary output is smaller than JSON for non-trivial tree', () { final units = List.generate( 20, - (i) => _makeUnit('u$i', parentId: i > 0 ? 'u${i - 1}' : null), + (i) => makeUnit('u$i', parentId: i > 0 ? 'u${i - 1}' : null), ); - final slots = List.generate(40, (i) => _makeSlot('sl$i', 'u${i % 20}')); + final slots = List.generate(40, (i) => makeSlot('sl$i', 'u${i % 20}')); final binaryBytes = codec.encode(units: units, slots: slots, personnel: const []); final jsonLen = @@ -151,14 +150,14 @@ void main() { test('decode throws FormatException for wrong version', () { // Build a valid header but with version = 99. - final bytes = codec.encode(units: [_makeUnit('u1')], slots: const [], personnel: const []); + final bytes = codec.encode(units: [makeUnit('u1')], slots: const [], personnel: const []); final tampered = Uint8List.fromList(bytes); tampered[7] = 99; // version byte (big-endian, LSB) expect(() => codec.decode(tampered), throwsA(isA())); }); test('decode throws FormatException for truncated payload', () { - final bytes = codec.encode(units: [_makeUnit('u1')], slots: const [], personnel: const []); + final bytes = codec.encode(units: [makeUnit('u1')], slots: const [], personnel: const []); // Truncate to just the header. final truncated = bytes.sublist(0, 12); expect(() => codec.decode(truncated), throwsA(isA())); @@ -179,7 +178,7 @@ void main() { }); test('ratio < 1.0 for compressed data', () { - final units = List.generate(10, (i) => _makeUnit('u$i')); + final units = List.generate(10, (i) => makeUnit('u$i')); final bytes = codec.encode(units: units, slots: const [], personnel: const []); final jsonLen = const UnitTreeCodec().encode(units: units, slots: const [], personnel: const []).length; diff --git a/test/infrastructure/codecs/unit_tree_codec_test.dart b/test/infrastructure/codecs/unit_tree_codec_test.dart index 227600d..f12039c 100644 --- a/test/infrastructure/codecs/unit_tree_codec_test.dart +++ b/test/infrastructure/codecs/unit_tree_codec_test.dart @@ -15,7 +15,7 @@ void main() { // Fixtures // --------------------------------------------------------------------------- - final brigade = UnitNode( + const brigade = UnitNode( unitId: 'bde-1', echelon: Echelon.brigade, branch: DoctrinalBranch.infantry, @@ -23,7 +23,7 @@ void main() { isKeyLeader: false, ); - final battalion = UnitNode( + const battalion = UnitNode( unitId: 'bn-1', echelon: Echelon.battalion, branch: DoctrinalBranch.infantry, @@ -170,8 +170,7 @@ void main() { }); test('throws FormatException for missing version', () { - final map = codec.encodeToMap(units: [], slots: [], personnel: []); - map.remove('version'); + final map = codec.encodeToMap(units: [], slots: [], personnel: [])..remove('version'); expect( () => codec.decodeFromMap(map), @@ -224,7 +223,7 @@ void main() { primaryMos: DutyMos.unvalidated('11B'), payGrade: AuthorizedRank.e5, readinessState: ReadinessState.fullyMissionCapable, - qualifications: ['Airborne', 'Ranger', 'SERE'], + qualifications: const ['Airborne', 'Ranger', 'SERE'], ); final json = codec.encode( units: [], diff --git a/test/widget_test.dart b/test/widget_test.dart index 77eb3a4..2d28679 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -28,7 +28,7 @@ void main() { final unitRepo = DriftUnitRepository(dao: db.unitDao, db: db); final personnelRepo = DriftPersonnelRepository(dao: db.personnelDao); final slotRepo = DriftPositionSlotRepository(dao: db.positionSlotDao); - final assignmentService = AssignmentService(treeService: treeService); + const assignmentService = AssignmentService(treeService: treeService); // Create cubits but do NOT call startWatching() — avoids live streams. unitTreeCubit = UnitTreeCubit(