import 'package:flutter/material.dart'; import 'package:trombon_ip_browser/views/doc_viewer.dart'; import 'package:trombon_ip_browser/widgets/movable_area_widget.dart' show DeviceKind; class MovableWidget extends StatefulWidget { const MovableWidget({ super.key, required this.name, required this.icon, required this.description, required this.areaKey, required this.maxWidth, required this.maxHeight, required this.x, required this.y, required this.onPositionChanged, required this.onDelete, required this.onTap, required this.onStartConnection, required this.onScenario, this.showScenarioButton = true, required this.isSelected, required this.deviceKind, }); final String name; final IconData icon; final String description; final DeviceKind deviceKind; final GlobalKey areaKey; final double maxWidth; final double maxHeight; final double x; final double y; final ValueChanged onPositionChanged; final VoidCallback onDelete; final VoidCallback onTap; final VoidCallback onStartConnection; final VoidCallback onScenario; final bool showScenarioButton; final bool isSelected; final double width = 100; final double height = 100; @override State createState() => _MovableWidgetState(); } class _MovableWidgetState extends State { static const double _controlsPanelHeight = 178; Offset _grabOffset = Offset.zero; bool _showControlsFlag = false; void _onDoubleClicked() => setState(() { _showControlsFlag = !_showControlsFlag; }); void _deleteItem() { widget.onDelete(); } void _showInfo(BuildContext context) { if (widget.deviceKind == DeviceKind.pzv) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const DocViewer( title: 'GPZV: руководство пользователя', imagePath: 'assets/gpzv/gpzv.webp', docPath: 'assets/gpzv/gpzv_doc.md', ), ), ); return; } if (widget.deviceKind == DeviceKind.panel) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const DocViewer( title: 'Panel: руководство пользователя', imagePath: 'assets/panel/panel.webp', docPath: 'assets/panel/panel_doc.md', ), ), ); return; } if (widget.deviceKind == DeviceKind.lmgPanel) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const DocViewer( title: 'ВП МГН: руководство пользователя', imagePath: 'assets/lmg_panel/lmg_panel.webp', docPath: 'assets/lmg_panel/lmg_panel_doc.md', ), ), ); return; } if (widget.deviceKind == DeviceKind.em) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const DocViewer( title: 'БО8: руководство пользователя', imagePath: 'assets/bo8/bo8.jpg', docPath: 'assets/bo8/bo8_doc.md', ), ), ); return; } if (widget.deviceKind == DeviceKind.emf) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const DocViewer( title: 'МО8: руководство пользователя', imagePath: 'assets/mo8/mo8.jpg', docPath: 'assets/mo8/mo8_doc.md', ), ), ); return; } if (widget.deviceKind == DeviceKind.emercom) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const DocViewer( title: 'МЧС: руководство пользователя', imagePath: 'assets/mchs/mchs.jpg', docPath: 'assets/mchs/mchs_doc.md', ), ), ); return; } if (widget.deviceKind == DeviceKind.extm) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const DocViewer( title: 'МР12: руководство пользователя', imagePath: 'assets/mr12/mr12.webp', docPath: 'assets/mr12/mr12_doc.md', ), ), ); return; } if (widget.deviceKind == DeviceKind.lwu) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const DocViewer( title: 'БСО: руководство пользователя', imagePath: 'assets/bso/bso.webp', docPath: 'assets/bso/bso_doc.md', ), ), ); return; } switch (widget.deviceKind) { case DeviceKind.ampl: case DeviceKind.ampl50: case DeviceKind.ampl120: case DeviceKind.ampl240: case DeviceKind.ampl600: Navigator.of(context).push( MaterialPageRoute( builder: (_) => const DocViewer( title: 'Ampl: руководство пользователя', imagePath: 'assets/ampl/ampl.jpg', docPath: 'assets/ampl/ampl_doc.md', ), ), ); return; default: break; } showDialog( context: context, builder: (context) => SimpleDialog( title: Center(child: Text(widget.name),), children: [ const Divider(), Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text(widget.description), ), ], ) ); } @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final maxX = (widget.maxWidth - widget.width - 52).clamp(0.0, double.infinity); final maxY = (widget.maxHeight - widget.height).clamp(0.0, double.infinity); return Positioned( left: widget.x.clamp(0.0, maxX), top: widget.y.clamp(0.0, maxY), child: SizedBox( width: widget.width + 52, height: _showControlsFlag ? (widget.height > (widget.showScenarioButton ? _controlsPanelHeight : 132) ? widget.height : (widget.showScenarioButton ? _controlsPanelHeight : 132)) : widget.height, child: Stack( clipBehavior: Clip.none, children: [ GestureDetector( onTap: widget.onTap, onPanStart: (details) { _grabOffset = details.localPosition; }, onPanUpdate: (details) { final areaContext = widget.areaKey.currentContext; if (areaContext == null) return; final areaBox = areaContext.findRenderObject() as RenderBox; final localPointer = areaBox.globalToLocal(details.globalPosition); final nextX = (localPointer.dx - _grabOffset.dx) .clamp(0.0, maxX); final nextY = (localPointer.dy - _grabOffset.dy) .clamp(0.0, maxY); widget.onPositionChanged(Offset(nextX, nextY)); }, onDoubleTap: _onDoubleClicked, child: Container( width: widget.width, height: widget.height, alignment: Alignment.center, decoration: BoxDecoration( color: cs.primaryContainer, borderRadius: BorderRadius.circular(15), border: Border.all( color: widget.isSelected ? cs.primary : Colors.transparent, width: 3, ), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(widget.icon, size: 50, color: cs.onPrimaryContainer), const SizedBox(height: 6), Padding( padding: const EdgeInsets.symmetric(horizontal: 6), child: Text( widget.name, maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: TextStyle( color: cs.onPrimaryContainer, fontSize: 12, fontWeight: FontWeight.w600, ), ), ), ], ), ), ), if (_showControlsFlag) Positioned( left: widget.width + 4, top: 0, child: Column( children: [ IconButton.filled( onPressed: widget.onStartConnection, icon: const Icon(Icons.link), ), const SizedBox(height: 6), IconButton.filled( onPressed: _deleteItem, icon: const Icon(Icons.delete), ), const SizedBox(height: 6), IconButton.filled( onPressed: () => _showInfo(context), icon: const Icon(Icons.info), ), if (widget.showScenarioButton) ...[ const SizedBox(height: 6), IconButton.filled( onPressed: widget.onScenario, icon: const Icon(Icons.play_circle_outline), ), ], ], ), ), ], ), ), ); } }