310 lines
9.7 KiB
Dart
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),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|