import 'package:flutter/material.dart'; import 'package:reactive_mind_map/reactive_mind_map.dart' as rm; import '../models/mindmap_model.dart'; class MindMapViewerScreen extends StatefulWidget { final MindMapModel mindMap; const MindMapViewerScreen({super.key, required this.mindMap}); @override State createState() => _MindMapViewerScreenState(); } class _MindMapViewerScreenState extends State { rm.MindMapLayout _currentLayout = rm.MindMapLayout.radial; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.mindMap.title), actions: [ PopupMenuButton( icon: const Icon(Icons.account_tree), tooltip: 'Тип layout', onSelected: (layout) => setState(() => _currentLayout = layout), itemBuilder: (context) => [ _popupMenuItem( rm.MindMapLayout.radial, 'Радиальный', Icons.circle_outlined), _popupMenuItem( rm.MindMapLayout.right, 'Вправо', Icons.arrow_forward), _popupMenuItem(rm.MindMapLayout.left, 'Влево', Icons.arrow_back), _popupMenuItem(rm.MindMapLayout.horizontal, 'Горизонтальный', Icons.swap_horiz), _popupMenuItem( rm.MindMapLayout.vertical, 'Вертикальный', Icons.swap_vert), ], ), ], ), body: widget.mindMap.nodes.isEmpty ? _buildEmptyState() : rm.MindMapWidget( data: _buildMindMapData(), style: rm.MindMapStyle( layout: _currentLayout, nodeShape: rm.NodeShape.roundedRectangle, // Используем стандартные узлы (без кастомизации) connectionColor: Colors.blue.shade100, connectionWidth: 2.0, ), cameraFocus: rm.CameraFocus.fitAll, onNodeTap: (node) { // TODO: Редактирование узла }, ), ); } rm.MindMapData _buildMindMapData() { // Находим корневой узел (level 0) final rootNodes = widget.mindMap.nodes.where((n) => n['level'] == 0).toList(); if (rootNodes.isEmpty) { return rm.MindMapData( id: 'empty', title: widget.mindMap.title, children: [], ); } final root = rootNodes.first; return rm.MindMapData( id: root['id'] as String, title: root['title'] as String, color: _hexToColor(root['color'] as String? ?? '#2196F3'), children: _buildChildren(root['id'] as String), ); } List _buildChildren(String parentId) { final children = widget.mindMap.nodes.where((n) => n['parentId'] == parentId).toList(); return children.map((node) { return rm.MindMapData( id: node['id'] as String, title: node['title'] as String, color: _hexToColor(node['color'] as String? ?? '#4CAF50'), children: _buildChildren(node['id'] as String), ); }).toList(); } Widget _buildCustomNode( rm.MindMapData node, VoidCallback onTap, VoidCallback onLongPress, bool isSelected, ) { return GestureDetector( onTap: onTap, onLongPress: onLongPress, child: AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: node.color ?? const Color(0xFF2196F3), borderRadius: BorderRadius.circular(12), border: isSelected ? Border.all(color: Colors.white, width: 3) : null, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.15), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.circle, color: Colors.white70, size: 12), const SizedBox(width: 8), ConstrainedBox( constraints: const BoxConstraints(maxWidth: 200), child: Text( node.title, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14, ), overflow: TextOverflow.ellipsis, maxLines: 2, ), ), if (node.children.isNotEmpty) ...[ const SizedBox(width: 8), Icon(Icons.keyboard_arrow_down, color: Colors.white70, size: 16), ], ], ), ), ); } Widget _buildEmptyState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.account_tree, size: 100, color: Colors.grey[400]), const SizedBox(height: 24), Text( 'Mind map пуста', style: TextStyle(fontSize: 20, color: Colors.grey[600]), ), const SizedBox(height: 8), Text( 'Добавьте узлы для начала работы', style: TextStyle(color: Colors.grey[500]), ), ], ), ); } PopupMenuItem _popupMenuItem( rm.MindMapLayout layout, String title, IconData icon, ) { return PopupMenuItem( value: layout, child: Row( children: [ Icon(icon, size: 20), const SizedBox(width: 12), Text(title), ], ), ); } Color _hexToColor(String hex) { hex = hex.replaceAll('#', ''); if (hex.length == 6) { return Color(int.parse('FF$hex', radix: 16)); } return const Color(0xFF2196F3); } }