import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:reactive_mind_map/reactive_mind_map.dart' as rm; import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; class MindMapSettingsScreen extends ConsumerStatefulWidget { final String projectId; const MindMapSettingsScreen({super.key, required this.projectId}); @override ConsumerState createState() => _MindMapSettingsScreenState(); } class _MindMapSettingsScreenState extends ConsumerState { rm.MindMapLayout _defaultLayout = rm.MindMapLayout.radial; rm.NodeShape _defaultShape = rm.NodeShape.roundedRectangle; rm.CameraFocus _defaultCamera = rm.CameraFocus.fitAll; bool _autoExpandNodes = true; bool _showConnections = true; bool _enableShadows = true; double _connectionWidth = 2.0; Color _connectionColor = const Color(0xFFB0BEC5); List _nodeColors = const [ Color(0xFF2196F3), Color(0xFF4CAF50), Color(0xFFFF9800), Color(0xFFE91E63), Color(0xFF9C27B0), ]; bool _isLoading = true; @override void initState() { super.initState(); _loadSettings(); } Future _loadSettings() async { try { final prefs = await SharedPreferences.getInstance(); setState(() { _defaultLayout = rm.MindMapLayout.values[ prefs.getInt('${widget.projectId}_layout') ?? 0]; _defaultShape = rm.NodeShape.values[ prefs.getInt('${widget.projectId}_shape') ?? 0]; _defaultCamera = rm.CameraFocus.values[ prefs.getInt('${widget.projectId}_camera') ?? 0]; _autoExpandNodes = prefs.getBool('${widget.projectId}_auto_expand') ?? true; _showConnections = prefs.getBool('${widget.projectId}_show_connections') ?? true; _enableShadows = prefs.getBool('${widget.projectId}_shadows') ?? true; _connectionWidth = prefs.getDouble('${widget.projectId}_conn_width') ?? 2.0; _isLoading = false; }); } catch (e) { setState(() { _isLoading = false; }); } } Future _saveSettings() async { try { final prefs = await SharedPreferences.getInstance(); await prefs.setInt( '${widget.projectId}_layout', _defaultLayout.index); await prefs.setInt('${widget.projectId}_shape', _defaultShape.index); await prefs.setInt( '${widget.projectId}_camera', _defaultCamera.index); await prefs.setBool( '${widget.projectId}_auto_expand', _autoExpandNodes); await prefs.setBool( '${widget.projectId}_show_connections', _showConnections); await prefs.setBool('${widget.projectId}_shadows', _enableShadows); await prefs.setDouble( '${widget.projectId}_conn_width', _connectionWidth); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('✅ Настройки сохранены'), backgroundColor: Colors.green, ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('❌ Ошибка: $e'), backgroundColor: Colors.red, ), ); } } } Future _exportMindMap() async { // TODO: Экспорт mind map в JSON файл if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('📤 Экспорт в разработке'), backgroundColor: Colors.orange, ), ); } } Future _importMindMap() async { // TODO: Импорт mind map из JSON файла if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('📥 Импорт в разработке'), backgroundColor: Colors.orange, ), ); } } @override Widget build(BuildContext context) { if (_isLoading) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } return Scaffold( appBar: AppBar( title: const Text('Настройки Mind Map'), actions: [ IconButton( icon: const Icon(Icons.save), onPressed: _saveSettings, tooltip: 'Сохранить', ), ], ), body: ListView( padding: const EdgeInsets.all(16), children: [ _buildSection( 'Layout', [ _buildDropdown( 'Layout по умолчанию', _defaultLayout, rm.MindMapLayout.values, (value) => setState(() => _defaultLayout = value!), labels: const { rm.MindMapLayout.radial: 'Радиальный', rm.MindMapLayout.right: 'Вправо', rm.MindMapLayout.left: 'Влево', rm.MindMapLayout.horizontal: 'Горизонтальный', rm.MindMapLayout.vertical: 'Вертикальный', }, ), _buildDropdown( 'Форма узлов', _defaultShape, rm.NodeShape.values, (value) => setState(() => _defaultShape = value!), labels: const { rm.NodeShape.roundedRectangle: 'Скругленный прямоугольник', rm.NodeShape.circle: 'Круг', rm.NodeShape.diamond: 'Ромб', rm.NodeShape.hexagon: 'Шестиугольник', rm.NodeShape.ellipse: 'Эллипс', rm.NodeShape.rectangle: 'Прямоугольник', }, ), _buildDropdown( 'Фокус камеры', _defaultCamera, rm.CameraFocus.values, (value) => setState(() => _defaultCamera = value!), labels: const { rm.CameraFocus.fitAll: 'Вместить всё', rm.CameraFocus.rootNode: 'Корневой узел', rm.CameraFocus.center: 'Центр', rm.CameraFocus.firstLeaf: 'Первый лист', }, ), ], ), const SizedBox(height: 24), _buildSection( 'Поведение', [ SwitchListTile( title: const Text('Авто-раскрытие узлов'), subtitle: const Text('Раскрывать все узлы при загрузке'), value: _autoExpandNodes, onChanged: (value) => setState(() => _autoExpandNodes = value), ), SwitchListTile( title: const Text('Показывать соединения'), value: _showConnections, onChanged: (value) => setState(() => _showConnections = value), ), SwitchListTile( title: const Text('Тени узлов'), value: _enableShadows, onChanged: (value) => setState(() => _enableShadows = value), ), ], ), const SizedBox(height: 24), _buildSection( 'Соединительные линии', [ ListTile( title: const Text('Толщина линий'), subtitle: Text('${_connectionWidth.toStringAsFixed(1)}px'), trailing: SizedBox( width: 150, child: Slider( value: _connectionWidth, min: 1.0, max: 5.0, divisions: 8, label: _connectionWidth.toStringAsFixed(1), onChanged: (value) => setState(() => _connectionWidth = value), ), ), ), ListTile( title: const Text('Цвет линий'), trailing: GestureDetector( onTap: () => _showColorPicker('connection'), child: Container( width: 40, height: 40, decoration: BoxDecoration( color: _connectionColor, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey), ), ), ), ), ], ), const SizedBox(height: 24), _buildSection( 'Цвета узлов', [ Wrap( spacing: 8, runSpacing: 8, children: List.generate(_nodeColors.length, (index) { return GestureDetector( onTap: () => _showColorPicker('node_$index'), child: Container( width: 50, height: 50, decoration: BoxDecoration( color: _nodeColors[index], borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey), ), child: Center( child: Text( '${index + 1}', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ), ), ), ); }), ), const SizedBox(height: 8), Text( 'Нажмите на цвет чтобы изменить', style: TextStyle(color: Colors.grey[600], fontSize: 12), ), ], ), const SizedBox(height: 32), _buildSection( 'Экспорт / Импорт', [ Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: _exportMindMap, icon: const Icon(Icons.download), label: const Text('Экспорт JSON'), ), ), const SizedBox(width: 16), Expanded( child: OutlinedButton.icon( onPressed: _importMindMap, icon: const Icon(Icons.upload), label: const Text('Импорт JSON'), ), ), ], ), ], ), const SizedBox(height: 32), _buildResetButton(), ], ), ); } Widget _buildSection(String title, List children) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), ...children, ], ), ); } Widget _buildDropdown( String label, T value, List items, void Function(T?) onChanged, { Map? labels, }) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: DropdownButtonFormField( value: value, decoration: InputDecoration( labelText: label, border: const OutlineInputBorder(), ), items: items.map((item) { return DropdownMenuItem( value: item, child: Text(labels?[item] ?? item.toString().split('.').last), ); }).toList(), onChanged: onChanged, ), ); } void _showColorPicker(String type) async { final colors = [ const Color(0xFF2196F3), const Color(0xFF4CAF50), const Color(0xFFFF9800), const Color(0xFFE91E63), const Color(0xFF9C27B0), const Color(0xFF00BCD4), const Color(0xFFF44336), const Color(0xFF795548), const Color(0xFF607D8B), const Color(0xFFB0BEC5), ]; final selected = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Выберите цвет'), content: Wrap( spacing: 8, runSpacing: 8, children: colors.map((color) { return GestureDetector( onTap: () => Navigator.pop(context, color), child: Container( width: 50, height: 50, decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey), ), ), ); }).toList(), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Отмена'), ), ], ), ); if (selected != null) { setState(() { if (type == 'connection') { _connectionColor = selected; } else if (type.startsWith('node_')) { final index = int.parse(type.split('_')[1]); _nodeColors[index] = selected; } }); } } Widget _buildResetButton() { return OutlinedButton.icon( onPressed: () { setState(() { _defaultLayout = rm.MindMapLayout.radial; _defaultShape = rm.NodeShape.roundedRectangle; _defaultCamera = rm.CameraFocus.fitAll; _autoExpandNodes = true; _showConnections = true; _enableShadows = true; _connectionWidth = 2.0; _connectionColor = const Color(0xFFB0BEC5); _nodeColors = const [ Color(0xFF2196F3), Color(0xFF4CAF50), Color(0xFFFF9800), Color(0xFFE91E63), Color(0xFF9C27B0), ]; }); }, icon: const Icon(Icons.restore, color: Colors.red), label: const Text('Сбросить настройки', style: TextStyle(color: Colors.red)), ); } }