import 'package:flutter/material.dart'; import 'package:uto_manager/domain/services/unit_tree_service.dart'; import 'package:uto_manager/presentation/widgets/unit_node_card.dart'; /// An interactive, scrollable/zoomable canvas displaying the unit org chart. /// /// Uses [InteractiveViewer] for pan and zoom. Unit nodes are rendered as /// [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, }); /// Root-level tree nodes to render. final List roots; /// Currently selected unit ID (or null). final String? selectedUnitId; /// Called when a unit node is tapped. final void Function(String unitId) onUnitSelected; /// Called when a unit is dragged onto a new parent. final Future Function(String unitId, String newParentId) onReparent; @override State createState() => _UtoCanvasWidgetState(); } class _UtoCanvasWidgetState extends State { final TransformationController _transformController = TransformationController(); @override void dispose() { _transformController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { if (widget.roots.isEmpty) { return _buildEmptyState(context); } return InteractiveViewer( transformationController: _transformController, minScale: 0.2, maxScale: 3.0, 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(), ), ), ); } Widget _buildSubtree(UnitTreeNode node, int depth) { final card = _buildDraggableNode(node); if (node.children.isEmpty) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: card, ); } return Column( children: [ card, // Connector line Container(width: 2, height: 20, color: Colors.blueGrey.withValues(alpha: 0.4)), // Children row IntrinsicHeight( child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: node.children.map((child) { return Column( children: [ Container(width: 2, height: 20, color: Colors.blueGrey.withValues(alpha: 0.4)), _buildSubtree(child, depth + 1), ], ); }).toList(), ), ), ], ); } Widget _buildDraggableNode(UnitTreeNode node) { final unitId = node.unit.unitId; final isSelected = widget.selectedUnitId == unitId; return DragTarget( onWillAcceptWithDetails: (details) => details.data != unitId, onAcceptWithDetails: (details) async { await widget.onReparent(details.data, unitId); }, builder: (context, candidateData, rejectedData) { final isDragOver = candidateData.isNotEmpty; return Draggable( data: unitId, feedback: Material( elevation: 8, borderRadius: BorderRadius.circular(6), child: Opacity( opacity: 0.85, child: UnitNodeCard( treeNode: node, isSelected: false, onTap: () {}, ), ), ), childWhenDragging: Opacity( opacity: 0.3, child: UnitNodeCard( treeNode: node, isSelected: false, onTap: () {}, ), ), child: AnimatedContainer( duration: const Duration(milliseconds: 120), decoration: isDragOver ? BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all( color: Theme.of(context).colorScheme.primary, width: 2, ), ) : null, child: UnitNodeCard( treeNode: node, isSelected: isSelected, onTap: () => widget.onUnitSelected(unitId), ), ), ); }, ); } Widget _buildEmptyState(BuildContext context) { final theme = Theme.of(context); return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.account_tree_outlined, size: 64, color: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.4), ), const SizedBox(height: 16), Text( 'No units loaded', style: theme.textTheme.titleMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 8), Text( 'Add units using the controls above,\nor load a saved template.', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.7), ), textAlign: TextAlign.center, ), ], ), ); } }