228 lines
8.0 KiB
Dart
228 lines
8.0 KiB
Dart
import 'dart:math';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:latlong2/latlong.dart';
|
|
import 'package:flutter_map/flutter_map.dart';
|
|
import 'package:oc_front/core/services/specialized_services/datacenter_service.dart';
|
|
import 'package:oc_front/main.dart';
|
|
import 'package:oc_front/models/resources/compute.dart';
|
|
import 'package:oc_front/models/resources/resources.dart';
|
|
import 'package:oc_front/models/resources/storage.dart';
|
|
import 'package:oc_front/pages/abstract_page.dart';
|
|
import 'package:oc_front/widgets/items/item_row.dart';
|
|
|
|
|
|
class MapFactory implements AbstractFactory {
|
|
@override GlobalKey getKey() { return key; }
|
|
@override String? getSearch() { return ""; }
|
|
@override void back(BuildContext context) { }
|
|
static GlobalKey<MapPageWidgetState> key = GlobalKey<MapPageWidgetState>();
|
|
@override bool searchFill() { return false; }
|
|
@override Widget factory(GoRouterState state, List<String> args) { return MapPageWidget(); }
|
|
@override void search(BuildContext context, bool special) { }
|
|
}
|
|
double menuSize = 0;
|
|
class MapPageWidget extends StatefulWidget {
|
|
bool isShowed = false;
|
|
final DatacenterService _service = DatacenterService();
|
|
MapPageWidget(): super(key: MapFactory.key);
|
|
@override MapPageWidgetState createState() => MapPageWidgetState();
|
|
static void search(BuildContext context) { }
|
|
static Widget factory() { return MapPageWidget(); }
|
|
}
|
|
class MapPageWidgetState extends State<MapPageWidget> {
|
|
double currentZoom = 2.0;
|
|
LatLng currentCenter = const LatLng(51.5, -0.09);
|
|
static final MapController _mapController = MapController();
|
|
bool selected = true;
|
|
|
|
@override Widget build(BuildContext context) {
|
|
return FutureBuilder(future: widget._service.all(context), builder: (BuildContext context, AsyncSnapshot snapshot) {
|
|
Map<String, Map<AbstractItem, LatLng>> coordinates = {};
|
|
List<Marker> markerCoordinates = [];
|
|
if (snapshot.data != null&& snapshot.data!.data != null) {
|
|
for (var element in snapshot.data!.data!.values) {
|
|
if (element["type"] == "storage") {
|
|
StorageItem resource = StorageItem().deserialize(element);
|
|
var instance = resource.getSelectedInstance();
|
|
if (instance == null || instance.location == null) { continue; }
|
|
if (coordinates[resource.topic] == null) { coordinates[resource.topic] = {}; }
|
|
coordinates[resource.topic]![resource] = LatLng(instance.location!.latitude ?? 0, instance.location!.longitude ?? 0);
|
|
} else if (element["type"] == "compute") {
|
|
ComputeItem resource = ComputeItem().deserialize(element);
|
|
var instance = resource.getSelectedInstance();
|
|
if (instance == null || instance.location == null) { continue; }
|
|
if (coordinates[resource.topic] == null) { coordinates[resource.topic] = {}; }
|
|
var lr = Random().nextInt(max(1, 100)) / 10000;
|
|
var lfr = Random().nextInt(max(1, 100)) / 10000;
|
|
coordinates[resource.topic]![resource] = LatLng((instance.location!.latitude ?? 0) + lr, (instance.location!.longitude ?? 0) + lfr);
|
|
}
|
|
}
|
|
for (var topic in coordinates.keys) {
|
|
for (var coord in coordinates[topic]!.keys) {
|
|
markerCoordinates.add(Marker(
|
|
width: 25,
|
|
height: 30,
|
|
point: coordinates[topic]![coord]!,
|
|
child: HoverMenu( width: 110, title: Container( alignment: Alignment.center,
|
|
constraints: BoxConstraints( maxHeight: 100, maxWidth: 100 ),
|
|
child: Icon(FontAwesomeIcons.locationDot,
|
|
shadows: <Shadow>[Shadow(color: Color.fromRGBO(0, 0, 0, 1), blurRadius: 10.0)],
|
|
color: getColor(topic)) ),
|
|
items: [ Container(color: Colors.white,
|
|
child: ItemRowWidget(low: true, contextWidth: 290, item: coord)) ]
|
|
),
|
|
));
|
|
}
|
|
}
|
|
Rect rect = Rect.fromCenter( center: MediaQuery.of(context).size.center(Offset.zero),
|
|
width: selected ? menuSize : 0, height: (getMainHeight(context) - 50) > 0 ? (getMainHeight(context) - 50) : 0);
|
|
return Expanded(
|
|
child : FlutterMap(
|
|
mapController: _mapController,
|
|
options: MapOptions(
|
|
initialCenter: currentCenter,
|
|
initialZoom: currentZoom,
|
|
),
|
|
children: [
|
|
TileLayer(
|
|
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
|
userAgentPackageName: 'dev.fleaflet.flutter_map.example',
|
|
),
|
|
MarkerLayer(
|
|
markers: markerCoordinates,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
} else {
|
|
return const CircularProgressIndicator();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
class HoverMenuController {
|
|
HoverMenuState? currentState;
|
|
|
|
void hideSubMenu() {
|
|
currentState?.hideSubMenu();
|
|
}
|
|
}
|
|
|
|
class HoverMenu extends StatefulWidget {
|
|
final Widget title;
|
|
final double? width;
|
|
final List<Widget> items;
|
|
final HoverMenuController? controller;
|
|
bool isHovered = false;
|
|
|
|
|
|
HoverMenu({
|
|
Key? key,
|
|
required this.title,
|
|
this.items = const [],
|
|
this.width,
|
|
this.controller,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
HoverMenuState createState() => HoverMenuState();
|
|
}
|
|
|
|
class HoverMenuState extends State<HoverMenu> {
|
|
OverlayEntry? _overlayEntry;
|
|
final _focusNode = FocusNode();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_focusNode.addListener(_onFocusChanged);
|
|
|
|
if (widget.controller != null) {
|
|
widget.controller?.currentState = this;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_focusNode.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _onFocusChanged() {
|
|
if (_focusNode.hasFocus) {
|
|
_overlayEntry = _createOverlayEntry();
|
|
Overlay.of(context).insert(_overlayEntry!);
|
|
} else {
|
|
_overlayEntry?.remove();
|
|
_removeOverlay();
|
|
}
|
|
}
|
|
|
|
void _removeOverlay() {
|
|
widget.isHovered = false;
|
|
}
|
|
|
|
void hideSubMenu() {
|
|
_focusNode.unfocus();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return TextButton(
|
|
style: TextButton.styleFrom(
|
|
elevation: 0.0,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
|
splashFactory: NoSplash.splashFactory,
|
|
padding: EdgeInsets.zero,
|
|
foregroundColor: Colors.transparent,
|
|
surfaceTintColor: Colors.transparent,
|
|
backgroundColor: Colors.transparent,
|
|
shadowColor: Colors.transparent,
|
|
),
|
|
focusNode: _focusNode,
|
|
onHover: (isHovered) {
|
|
if (isHovered && !widget.isHovered) {
|
|
_focusNode.requestFocus();
|
|
isHovered = true;
|
|
} else {
|
|
_focusNode.unfocus();
|
|
widget.isHovered = false;
|
|
}
|
|
},
|
|
onPressed: () {},
|
|
child: widget.title,
|
|
);
|
|
}
|
|
|
|
OverlayEntry _createOverlayEntry() {
|
|
final renderBox = context.findRenderObject() as RenderBox;
|
|
final size = renderBox.size;
|
|
final offset = renderBox.localToGlobal(Offset.zero);
|
|
|
|
return OverlayEntry(
|
|
maintainState: true,
|
|
builder: (context) => Positioned(
|
|
left: offset.dx - 300,
|
|
top: offset.dy + size.height,
|
|
width: 300,
|
|
child: TextButton(
|
|
style: TextButton.styleFrom(
|
|
padding: EdgeInsets.zero,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
|
splashFactory: NoSplash.splashFactory,
|
|
backgroundColor: Colors.transparent,
|
|
),
|
|
onPressed: () {},
|
|
child: ListView(
|
|
padding: EdgeInsets.zero,
|
|
shrinkWrap: true,
|
|
children: widget.items)),
|
|
),
|
|
);
|
|
}
|
|
} |