diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 645db97d..a18d9e51 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -632,7 +632,7 @@ class URLs { // Asset Tracking APIs static get getAssetsFromPenguin => "$_baseUrl/PenguinIN/GetAssetsFromPenguin"; - + static get getPenguinZones => "$_baseUrl/PenguinIN/zone/"; static get getPenguinSites => "$_baseUrl/PenguinIN/sites/"; static get getPenguinDepartments => "$_baseUrl/PenguinIN/departments"; static get getPenguinBuildings => "$_baseUrl/PenguinIN/buildings"; diff --git a/lib/modules/asset_tracking/asset_tracking_provider.dart b/lib/modules/asset_tracking/asset_tracking_provider.dart index fe99bd86..c8fd7718 100644 --- a/lib/modules/asset_tracking/asset_tracking_provider.dart +++ b/lib/modules/asset_tracking/asset_tracking_provider.dart @@ -3,10 +3,12 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:test_sa/controllers/api_routes/api_manager.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/modules/asset_tracking/models/coordinates_model.dart'; import 'models/asset_tracking_model.dart'; import 'models/floor_map_model.dart'; import 'models/penguin_site_model.dart'; +import 'models/zone_data_model.dart'; class AssetTrackingProvider extends ChangeNotifier { bool isLoading = false; @@ -17,6 +19,9 @@ class AssetTrackingProvider extends ChangeNotifier { List assetsList = []; List assetsOnTheFloorList = []; + List zonesList = []; + ZoneModel? selectedZone; + List sitesList = []; PenguinSiteModel? selectedSite; @@ -25,8 +30,24 @@ class AssetTrackingProvider extends ChangeNotifier { List floorList = []; PenguinSiteModel? selectedFloor; + List coordinates = []; + + void calculateCoordinates() { + coordinates.clear(); + coordinates.addAll(selectedBuilding!.coordinates!.map((e) => Coordinates(latitude: e.latitude, longitude: e.longitude)).toList()); + notify(); + } + + // List getFilteredZones() { + // if (zonesList.isEmpty || selectedSite == null || selectedBuilding == null) { + // return []; + // } else { + // return zonesList.where((zone) => zone.siteId == selectedSite!.id).toList(); + // } + // } + String get getExpansionHeader => - (selectedBuilding == null || selectedBuilding == null || selectedFloor == null) ? "Search Floor Map" : "${selectedSite!.label} > ${selectedBuilding!.label} > ${selectedFloor!.label}"; + (selectedBuilding == null || selectedBuilding == null) ? "Search Floor Map" : "${selectedSite!.label} > ${selectedBuilding!.label} > ${selectedFloor?.label ?? "Select Floor"}"; reset() { isLoading = false; @@ -86,14 +107,53 @@ class AssetTrackingProvider extends ChangeNotifier { } } + Future getBuildingMapFromPenguin() async { + if (zonesList.isNotEmpty) { + await Future.delayed(const Duration(milliseconds: 250)); + // return; + } + try { + final response = await ApiManager.instance.get(URLs.getPenguinBuildings + ""); + if (response.statusCode >= 200 && response.statusCode < 300) { + final Map jsonData = json.decode(response.body); + List list = jsonData["data"]; + zonesList.addAll(list.map((item) => ZoneModel.fromJson(item)).toList()); + notify(); + } + } catch (error) { + print("getZoneFromPenguin:Error:$error"); + } + } + + Future getZoneFromPenguin() async { + if (zonesList.isNotEmpty) { + await Future.delayed(const Duration(milliseconds: 250)); + return; + } + try { + final response = await ApiManager.instance.get(URLs.getPenguinZones); + if (response.statusCode >= 200 && response.statusCode < 300) { + final Map jsonData = json.decode(response.body); + List list = jsonData["data"]; + zonesList.addAll(list.map((item) => ZoneModel.fromJson(item)).toList()); + notify(); + } + } catch (error) { + print("getZoneFromPenguin:Error:$error"); + } + } + Future getAssetsFromPenguinByFloor() async { try { assetsOnTheFloorList.clear(); - final response = await ApiManager.instance.post(URLs.getAssetsFromPenguin, body: { + Map body = { "campusId": selectedSite!.id, - "venueIds": [selectedBuilding!.id], - "floorIds": [selectedFloor!.id] - }); + "venueIds": [selectedBuilding!.id] + }; + if (selectedFloor != null) { + body["floorIds"] = [selectedFloor!.id]; + } + final response = await ApiManager.instance.post(URLs.getAssetsFromPenguin, body: body); if (response.statusCode >= 200 && response.statusCode < 300) { final Map jsonData = json.decode(response.body); List list = jsonData["data"]; diff --git a/lib/modules/asset_tracking/map_tracking_page.dart b/lib/modules/asset_tracking/map_tracking_page.dart index f7e5047d..bd1987a6 100644 --- a/lib/modules/asset_tracking/map_tracking_page.dart +++ b/lib/modules/asset_tracking/map_tracking_page.dart @@ -5,11 +5,15 @@ 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/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/helper/utils.dart'; +import 'package:test_sa/modules/asset_tracking/models/coordinates_model.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/new_views/common_widgets/multiple_item_drop_down_menu.dart'; import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart'; @@ -21,6 +25,7 @@ import 'asset_tracking_provider.dart'; import 'models/asset_tracking_model.dart'; import 'models/floor_map_model.dart'; import 'models/penguin_site_model.dart'; +import 'models/zone_data_model.dart'; class MapTrackingPage extends StatefulWidget { final AssetTrackingModel? assetTrackingData; @@ -62,6 +67,12 @@ class _MapTrackingPageState extends State { }); } + @override + void dispose() { + _polygonHitNotifier.dispose(); + super.dispose(); + } + void getPenguinSites() async { Utils.showLoading(context); await assetTrackingProvider.getPenguinSites(); @@ -166,18 +177,43 @@ class _MapTrackingPageState extends State { final minChildSize = 0.25; final maxChildSize = 1.0; + // final LayerHitNotifier> _polygonHitNotifier = ValueNotifier(null); + final LayerHitNotifier _polygonHitNotifier = ValueNotifier(null); + @override Widget build(BuildContext context) { Widget mapWidget = FlutterMap( mapController: mapController, - options: MapOptions(initialCenter: _fallbackCenter, initialZoom: 16.0), + options: const 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), + GestureDetector( + onTap: () { + // final hitResult = _polygonHitNotifier.value; + // + // if (hitResult == null || hitResult.hitValues.isEmpty) return; + // print("hitResult:$hitResult"); + // final hitValue = hitResult.hitValues.first; + // + // // Narrow safely instead of assuming the type. + // if (hitValue is Map) { + // print(hitValue); + // } else { + // // ignore: avoid_print + // print('Unexpected hit value type: ${hitValue.runtimeType} -> $hitValue'); + // } + }, + child: GeoJsonLayer.memory( + _geojson!, + hitNotifier: _polygonHitNotifier, + styleDefaults: GeoJsonStyleDefaults.leaflet, + ), + ), + // if (!inAppView || ) if (markers.isNotEmpty) MarkerLayer(markers: markers), if (inAppView) @@ -245,6 +281,27 @@ class _MapTrackingPageState extends State { }), ), ], + if (inAppView) + Selector>( + selector: (_, assetTPro) => assetTPro.coordinates, + builder: (_, coordinates, __) { + if (coordinates.isEmpty) return const SizedBox(); + + List list = [ + for (var coordinate in (coordinates)) LatLng(coordinate.latitude!, coordinate.longitude!), + ]; + List zonesPolygons = [ + Polygon( + label: assetTrackingProvider.selectedBuilding?.label ?? "", + labelStyle: AppTextStyles.bodyText2.copyWith(color: AppColor.textColor(context)), + points: list, + color: AppColor.red30.withOpacity(0.3), + borderColor: AppColor.red30, + borderStrokeWidth: 2.0, + ), + ]; + return PolygonLayer(polygons: zonesPolygons); + }), Positioned( top: 70, right: 8, @@ -357,12 +414,31 @@ class _MapTrackingPageState extends State { onSelect: (value) { if (value != null) { assetProvider.selectedFloor = value; - expansibleController.collapse(); - getFloorMap(value.id!); - assetTrackingProvider.getAssetsFromPenguinByFloor(); } }, ), + 8.height, + Align( + alignment: AlignmentGeometry.centerRight, + child: AppTextButton( + label: "View", + disableButton: assetProvider.selectedSite == null || assetProvider.selectedBuilding == null, + onPressed: () { + expansibleController.collapse(); + assetTrackingProvider.coordinates.clear(); + assetTrackingProvider.assetsOnTheFloorList.clear(); + assetTrackingProvider.notify(); + + assetTrackingProvider.getAssetsFromPenguinByFloor(); + if (assetProvider.selectedFloor != null) { + getFloorMap(assetProvider.selectedFloor!.id!); + assetTrackingProvider.getZoneFromPenguin(); + } else { + assetTrackingProvider.calculateCoordinates(); + } + }, + ), + ), 16.height, ], ); diff --git a/lib/modules/asset_tracking/models/coordinates_model.dart b/lib/modules/asset_tracking/models/coordinates_model.dart new file mode 100644 index 00000000..051b2899 --- /dev/null +++ b/lib/modules/asset_tracking/models/coordinates_model.dart @@ -0,0 +1,27 @@ +class Coordinates { + double? x; + double? y; + double? longitude; + double? latitude; + int? orderIndex; + + Coordinates({this.x, this.y, this.longitude, this.latitude, this.orderIndex}); + + Coordinates.fromJson(Map json) { + x = json['x']; + y = json['y']; + longitude = json['longitude']; + latitude = json['latitude']; + orderIndex = json['orderIndex']; + } + + Map toJson() { + final Map data = new Map(); + data['x'] = this.x; + data['y'] = this.y; + data['longitude'] = this.longitude; + data['latitude'] = this.latitude; + data['orderIndex'] = this.orderIndex; + return data; + } +} diff --git a/lib/modules/asset_tracking/models/penguin_site_model.dart b/lib/modules/asset_tracking/models/penguin_site_model.dart index cbb387ef..d9a0415d 100644 --- a/lib/modules/asset_tracking/models/penguin_site_model.dart +++ b/lib/modules/asset_tracking/models/penguin_site_model.dart @@ -1,11 +1,14 @@ import 'package:test_sa/models/base.dart'; +import 'coordinates_model.dart'; + class PenguinSiteModel extends Base { int? id; String? label; + List? coordinates; bool? isMapped; - PenguinSiteModel({this.id, this.label}) : super(identifier: id?.toString(), name: label); + PenguinSiteModel({this.id, this.label, this.coordinates}) : super(identifier: id?.toString(), name: label); PenguinSiteModel.fromJson(Map json) { id = json['id']; @@ -13,6 +16,15 @@ class PenguinSiteModel extends Base { label = json['label']; name = json['label']; isMapped = json['isMapped']; + if (json['coordinates'] != null) { + coordinates = []; + json['coordinates'].forEach((v) { + coordinates!.add(new Coordinates.fromJson(v)); + }); + } + if (this.coordinates != null) { + json['coordinates'] = this.coordinates!.map((v) => v.toJson()).toList(); + } } Map toJson() { diff --git a/lib/modules/asset_tracking/models/zone_data_model.dart b/lib/modules/asset_tracking/models/zone_data_model.dart new file mode 100644 index 00000000..443f65cc --- /dev/null +++ b/lib/modules/asset_tracking/models/zone_data_model.dart @@ -0,0 +1,86 @@ +import 'coordinates_model.dart'; + +class ZoneModel { + int? id; + String? referenceId; + String? name; + ZoneType? zoneType; + Location? location; + List? coordinates; + + ZoneModel({this.id, this.referenceId, this.name, this.zoneType, this.location, this.coordinates}); + + ZoneModel.fromJson(Map json) { + id = json['id']; + referenceId = json['referenceId']; + name = json['name']; + zoneType = json['zoneType'] != null ? new ZoneType.fromJson(json['zoneType']) : null; + location = json['location'] != null ? new Location.fromJson(json['location']) : null; + if (json['coordinates'] != null) { + coordinates = []; + json['coordinates'].forEach((v) { + coordinates!.add(new Coordinates.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['referenceId'] = this.referenceId; + data['name'] = this.name; + if (this.zoneType != null) { + data['zoneType'] = this.zoneType!.toJson(); + } + if (this.location != null) { + data['location'] = this.location!.toJson(); + } + if (this.coordinates != null) { + data['coordinates'] = this.coordinates!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class ZoneType { + int? id; + String? name; + + ZoneType({this.id, this.name}); + + ZoneType.fromJson(Map json) { + id = json['id']; + name = json['name']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['name'] = this.name; + return data; + } +} + +class Location { + int? id; + String? name; + String? label; + + Location({this.id, this.name, this.label}); + + Location.fromJson(Map json) { + id = json['id']; + name = json['name']; + label = json['label']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['name'] = this.name; + data['label'] = this.label; + return data; + } +} + + diff --git a/lib/new_views/common_widgets/app_filled_button.dart b/lib/new_views/common_widgets/app_filled_button.dart index 1456eea4..fc5dce55 100644 --- a/lib/new_views/common_widgets/app_filled_button.dart +++ b/lib/new_views/common_widgets/app_filled_button.dart @@ -78,3 +78,58 @@ class AppFilledButton extends StatelessWidget { ).onPress(disableButton ? null : (loading ? null : onPressed)); } } + +class AppTextButton extends StatelessWidget { + final VoidCallback? onPressed; + final String label; + final bool maxWidth, loading; + final double? fontSize; + final double? height; + final bool showIcon; + final String? iconString; + final Color? buttonColor; + final bool showBorder; + final double radius; + final bool disableButton; + + const AppTextButton( + {this.onPressed, + required this.label, + this.maxWidth = false, + this.showIcon = false, + this.iconString, + this.fontSize, + this.height, + this.loading = false, + this.showBorder = false, + this.disableButton = false, + this.buttonColor, + this.radius = 35, + Key? key}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + height: height, + width: label.length * 15, + alignment: Alignment.center, + decoration: BoxDecoration( + color: disableButton ? AppColor.disableFieldBackground(context) : buttonColor ?? AppColor.primary10, + borderRadius: BorderRadius.circular(radius), + ), + child: Row( + spacing: 8, + mainAxisSize: MainAxisSize.min, + children: [ + if (showIcon && iconString != null) iconString!.toSvgAsset(height: 20, width: 20, color: AppColor.icon2Color(context)), + Text( + label, + style: AppTextStyles.bodyText2.copyWith(color: AppColor.textStyleColor(context)), + ), + ], + ), + ).onPress(disableButton ? null : onPressed); + } +}