You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cloudsolutions-atoms/lib/modules/asset_tracking/map_tracking_page.dart

299 lines
11 KiB
Dart

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_geojson2/flutter_map_geojson2.dart';
import 'package:latlong2/latlong.dart';
import 'package:provider/provider.dart';
import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/helper/utils.dart';
import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart';
import 'package:test_sa/providers/loading_list_notifier.dart';
import 'package:test_sa/views/widgets/item_views/info_text_widget.dart';
import 'asset_tracking_item_view.dart';
import 'asset_tracking_provider.dart';
import 'models/asset_tracking_model.dart';
import 'models/floor_map_model.dart';
import 'models/penguin_site_model.dart';
class MapTrackingPage extends StatefulWidget {
final AssetTrackingModel? assetTrackingData;
MapTrackingPage({Key? key, this.assetTrackingData}) : super(key: key);
@override
_MapTrackingPageState createState() {
return _MapTrackingPageState();
}
}
class _MapTrackingPageState extends State<MapTrackingPage> {
final MapController mapController = MapController();
late AssetTrackingProvider assetTrackingProvider;
late bool inAppView;
FloorMapModel? floorMapModel;
Map<String, dynamic>? _geojson;
List<Marker> markers = [];
LatLngBounds? _computedBounds;
final ExpansibleController expansibleController = ExpansibleController();
static const LatLng _fallbackCenter = LatLng(24.81200245073082, 46.624273601310186);
@override
void initState() {
super.initState();
inAppView = widget.assetTrackingData == null;
assetTrackingProvider = Provider.of<AssetTrackingProvider>(context, listen: false);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!inAppView) {
getFloorMap(widget.assetTrackingData!.location!.floor!.id!);
} else {
getPenguinSites();
}
});
}
void getPenguinSites() async {
Utils.showLoading(context);
await assetTrackingProvider.getPenguinSites();
Utils.hideLoading(context);
}
void getPenguinBuildings(int id) async {
Utils.showLoading(context);
await assetTrackingProvider.getPenguinBuildings(id);
Utils.hideLoading(context);
}
void getPenguinFloors(int id) async {
Utils.showLoading(context);
await assetTrackingProvider.getPenguinFloors(id);
Utils.hideLoading(context);
}
void getFloorMap(int id) {
Utils.showLoading(context);
assetTrackingProvider.getFloorMap(id).then((floorMap) {
Utils.hideLoading(context);
_onFloorMapLoaded(floorMap);
}).catchError((error) {
Utils.hideLoading(context);
});
}
void getAssetsOnTheFloor(int id) {
assetTrackingProvider.getAssetsFromPenguinByFloorId(id);
}
void _onFloorMapLoaded(FloorMapModel? loadedFloorMap) {
floorMapModel = loadedFloorMap;
if (floorMapModel?.geojsonData == null) {
setState(() {});
return;
}
try {
final decoded = floorMapModel!.geojsonData!;
final bounds = _computeBoundsFromGeoJson(decoded);
setState(() {
_geojson = decoded;
_computedBounds = bounds;
markers = [
Marker(
point: LatLng(widget.assetTrackingData!.location!.latitude!, widget.assetTrackingData!.location!.longitude!),
child: const Icon(Icons.location_pin, color: AppColor.red30),
),
];
});
WidgetsBinding.instance.addPostFrameCallback((_) {
mapController.fitCamera(
CameraFit.bounds(bounds: bounds, padding: const EdgeInsets.all(24)),
);
});
} catch (e) {
print('Failed to parse/compute bounds for floor geojson: $e');
setState(() {}); // still show the map with whatever we have
}
}
LatLngBounds _computeBoundsFromGeoJson(Map<String, dynamic> geojson) {
final features = (geojson['features'] as List<dynamic>? ?? []);
double? minLng, minLat, maxLng, maxLat;
void visit(dynamic node) {
if (node is List && node.isNotEmpty && node.first is num) {
final lng = (node[0] as num).toDouble();
final lat = (node[1] as num).toDouble();
minLng = (minLng == null || lng < minLng!) ? lng : minLng;
maxLng = (maxLng == null || lng > maxLng!) ? lng : maxLng;
minLat = (minLat == null || lat < minLat!) ? lat : minLat;
maxLat = (maxLat == null || lat > maxLat!) ? lat : maxLat;
} else if (node is List) {
for (final child in node) {
visit(child);
}
}
}
for (final feature in features) {
final geometry = feature['geometry'] as Map<String, dynamic>?;
final coordinates = geometry?['coordinates'];
if (coordinates == null) continue;
visit(coordinates);
}
if (minLng == null || minLat == null || maxLng == null || maxLat == null) {
throw StateError('No coordinates found in floor geojson — cannot compute bounds.');
}
return LatLngBounds(
LatLng(minLat!, minLng!),
LatLng(maxLat!, maxLng!),
);
}
double _dragPosition = 0.5;
late double _sheetPosition = _dragPosition;
final minChildSize = 0.25;
final maxChildSize = 1.0;
@override
Widget build(BuildContext context) {
Widget mapWidget = FlutterMap(
mapController: mapController,
options: MapOptions(initialCenter: _fallbackCenter, initialZoom: 16.0),
children: [
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'com.hmg.atoms',
),
if (_geojson != null) ...[
GeoJsonLayer.memory(
_geojson!,
styleDefaults: GeoJsonStyleDefaults.leaflet,
),
if (!inAppView) MarkerLayer(markers: markers),
Selector<AssetTrackingProvider, List<AssetTrackingModel>>(
selector: (_, myModel) => myModel.assetsOnTheFloorList,
builder: (_, assetsOnTheFloorList, __) {
return assetsOnTheFloorList.isEmpty
? const SizedBox()
: DraggableScrollableSheet(
initialChildSize: _sheetPosition,
builder: (BuildContext context, ScrollController scrollController) {
return Flexible(
child: ListView.builder(
controller: scrollController,
itemCount: assetsOnTheFloorList.length,
itemBuilder: (BuildContext context, int index) {
return AssetTrackingListItemView(assetsOnTheFloorList[index]);
},
),
);
});
}),
]
],
);
return inAppView
? Stack(
alignment: AlignmentDirectional.topCenter,
children: [
mapWidget,
Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.only(right: 16, left: 16),
decoration: BoxDecoration(
color: AppColor.tabBarBackground(context),
border: Border.all(color: AppColor.tabBarBorder(context)),
borderRadius: BorderRadius.circular(20),
),
child: Theme(
data: ThemeData(splashColor: Colors.transparent, hoverColor: Colors.transparent, highlightColor: Colors.transparent),
child: Consumer<AssetTrackingProvider>(builder: (context, assetProvider, _) {
return ExpansionTile(
shape: Border.all(color: Colors.transparent, width: 0),
collapsedShape: Border.all(color: Colors.transparent, width: 0),
controller: expansibleController,
title: InfoTextLabelWidget(label: assetProvider.getExpansionHeader),
iconColor: AppColor.icon2Color(context),
collapsedIconColor: AppColor.icon2Color(context),
dense: true,
childrenPadding: const EdgeInsets.all(0),
tilePadding: const EdgeInsets.all(0),
children: <Widget>[
SingleItemDropDownMenu<PenguinSiteModel, NullableLoadingProvider>(
context: context,
title: "Select Site",
staticData: assetProvider.sitesList,
initialValue: assetProvider.selectedSite,
showShadow: false,
// backgroundColor: AppColor.fieldBgColor(context),
showAsBottomSheet: true,
onSelect: (value) {
if (value != null) {
assetProvider.selectedSite = value;
getPenguinBuildings(value.id!);
}
},
),
4.height,
SingleItemDropDownMenu<PenguinSiteModel, NullableLoadingProvider>(
context: context,
title: "Select Building",
staticData: assetProvider.buildList,
initialValue: assetProvider.selectedBuilding,
showShadow: false,
enabled: assetProvider.selectedSite != null,
// backgroundColor: AppColor.fieldBgColor(context),
showAsBottomSheet: true,
onSelect: (value) {
if (value != null) {
assetProvider.selectedBuilding = value;
getPenguinFloors(value.id!);
}
},
),
4.height,
SingleItemDropDownMenu<PenguinSiteModel, NullableLoadingProvider>(
context: context,
title: "Select Floor",
staticData: assetProvider.floorList,
initialValue: assetProvider.selectedFloor,
showShadow: false,
enabled: assetProvider.selectedBuilding != null,
// backgroundColor: AppColor.fieldBgColor(context),
showAsBottomSheet: true,
onSelect: (value) {
if (value != null) {
assetProvider.selectedFloor = value;
expansibleController.collapse();
getFloorMap(value.id!);
getAssetsOnTheFloor(value.id!);
}
},
),
16.height,
],
);
}),
),
)
],
)
: Scaffold(appBar: const DefaultAppBar(title: "Live Asset Tracking"), body: mapWidget);
}
}