Files
trombon_ip_browser/lib/widgets/movable_widget.dart
T
2026-05-10 02:54:27 +03:00

310 lines
9.7 KiB
Dart

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<Offset> 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<MovableWidget> createState() => _MovableWidgetState();
}
class _MovableWidgetState extends State<MovableWidget> {
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),
),
],
],
),
),
],
),
),
);
}
}