From 2a560abcb3fd6811e432d6ffcbdcdf214ab8d5c4 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 13 Sep 2026 17:08:13 +0300 Subject: [PATCH] asset tracking list view & map view added. --- lib/controllers/api_routes/urls.dart | 20 +- lib/extensions/widget_extensions.dart | 12 + lib/main.dart | 2 + .../asset_tracking/asset_tracking_page.dart | 188 ++++++++++++--- .../asset_tracking_provider.dart | 80 +++++++ .../asset_tracking/map_tracking_page.dart | 159 +++++++++++++ .../models/asset_tracking_model.dart | 218 ++++++++++++++++++ .../models/floor_map_model.dart | 151 ++++++++++++ lib/new_views/common_widgets/app_drawer.dart | 3 + .../widgets/item_views/info_text_widget.dart | 6 +- pubspec.lock | 160 +++++++++++++ pubspec.yaml | 1 + 12 files changed, 968 insertions(+), 32 deletions(-) create mode 100644 lib/modules/asset_tracking/asset_tracking_provider.dart create mode 100644 lib/modules/asset_tracking/map_tracking_page.dart create mode 100644 lib/modules/asset_tracking/models/asset_tracking_model.dart create mode 100644 lib/modules/asset_tracking/models/floor_map_model.dart diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index f45e772e..59fc9299 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -3,13 +3,13 @@ class URLs { static const String appReleaseBuildNumber = "53"; - static const host1 = "https://atomsm.hmg.com"; // production url - // static const host1 = "https://atomsmdev.hmg.com"; // local DEV url + // static const host1 = "https://atomsm.hmg.com"; // production url + static const host1 = "https://atomsmdev.hmg.com"; // local DEV url // static const host1 = "https://atomsmuat.hmg.com"; // local UAT url - // static final String _baseUrl = "$_host/mobile"; // host local UAT + static final String _baseUrl = "$_host/mobile"; // host local UAT // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis - static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM + // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module @@ -55,11 +55,17 @@ class URLs { // SignUp APIs static get signUpGetEmployeeFromERP => "$_baseUrl/SignUpRequest/GetEmployeeFromERP"; + static get createSignUpRequest => "$_baseUrl/SignUpRequest/CreateSignUpRequest"; + static get signUpGetAssetGroups => "$_baseUrl/AssetGroup/GetAssetGroupsAutoComplete"; + static get signUpGetCompaniesByAssetGroup => "$_baseUrl/Company/GetCompaniesByAssetGroup"; + static get signUpGetSiteList => "$_baseUrl/Customer/GetSiteList"; + static get signUpGetDepartmentAutoComplete => "$_baseUrl/Department/GetDepartmentAutoCompleteByAssetGroup"; + static get signUpGetDepartmentsBySitesAndAssetGroup => "$_baseUrl/Department/GetDepartmentsBySitesAndAssetGroup"; // Site Request APIs @@ -606,6 +612,7 @@ class URLs { // Notification Preference Center static String get getNotificationPreferences => "$_baseUrl/NotificationPreferenceCenter/GetTemplateApplyOnCurrentUser"; + static String get saveNotificationPreferences => "$_baseUrl/NotificationPreferenceCenter/SaveNotificationPreferenceCenter"; //4104 attachment type lookup @@ -621,4 +628,9 @@ class URLs { static get submitApplicationDemoRequest => "$_baseUrl/DemoUser/CreateDemoUser"; // post submit application demo request// get application demo request history static get sendDemoUserOtp => "$_baseUrl/DemoUser/EmailVerification"; // post send OTP to user static get verifyDemoUserOtp => "$_baseUrl/DemoUser/VerifyOtp"; // post verify OTP + + // Asset Tracking APIs + static get getAssetsFromPenguin => "$_baseUrl/PenguinIN/GetAssetsFromPenguin"; + + static get getFloorMap => "$_baseUrl/PenguinIN/floors/"; } diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index c058be38..8267264a 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -110,6 +110,18 @@ extension WidgetExtensions on Widget { ).toShadowContainer(context) : this; + Widget toAssetListShimmer(BuildContext context, bool isShow) => isShow + ? Column( + crossAxisAlignment: CrossAxisAlignment.start,spacing: 8, + children: [ + const SizedBox(width: 50, height: 16).toShimmer(isShow: isShow, context: context).toShimmer(isShow: isShow, context: context), + const SizedBox(width: 250, height: 20).toShimmer(isShow: isShow, context: context), + const SizedBox(width: 160, height: 14).toShimmer(isShow: isShow, context: context), + const SizedBox(width: 120, height: 14).toShimmer(isShow: isShow, context: context).toShimmer(isShow: isShow, context: context), + ], + ).toShadowContainer(context) + : this; + Widget toShadowContainer( BuildContext context, { bool showShadow = true, diff --git a/lib/main.dart b/lib/main.dart index 90efc744..9a50b7ae 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -136,6 +136,7 @@ import 'controllers/providers/api/user_provider.dart'; import 'controllers/providers/settings/setting_provider.dart'; import 'dashboard_latest/dashboard_provider.dart'; import 'l10n/app_localizations.dart'; +import 'modules/asset_tracking/asset_tracking_provider.dart'; import 'modules/demo_module/demo_document_lookup_provider.dart'; import 'modules/incident_module/create_incident_request_page.dart'; import 'modules/incident_module/incident_lookup_provider.dart'; @@ -403,6 +404,7 @@ class MyApp extends StatelessWidget { // UTILITY PROVIDERS (1) ChangeNotifierProvider(create: (_) => OracleCodeProvider(), lazy: true), + ChangeNotifierProvider(create: (_) => AssetTrackingProvider(), lazy: true), ], child: GestureDetector( onTap: () { diff --git a/lib/modules/asset_tracking/asset_tracking_page.dart b/lib/modules/asset_tracking/asset_tracking_page.dart index 6efaa284..dbc87427 100644 --- a/lib/modules/asset_tracking/asset_tracking_page.dart +++ b/lib/modules/asset_tracking/asset_tracking_page.dart @@ -3,7 +3,22 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_geojson2/flutter_map_geojson2.dart'; import 'package:latlong2/latlong.dart'; +import 'package:mapbox_maps_flutter/mapbox_maps_flutter.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/modules/asset_tracking/asset_tracking_provider.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/views/widgets/item_views/info_header_widget.dart'; +import 'package:test_sa/views/widgets/item_views/info_text_widget.dart'; +import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; + +import 'map_tracking_page.dart'; class AssetTrackingPage extends StatefulWidget { AssetTrackingPage({Key? key}) : super(key: key); @@ -14,42 +29,163 @@ class AssetTrackingPage extends StatefulWidget { } } -class _AssetTrackingPageState extends State { - // Bounds computed from assets/floor.geojson so the floor plan is visible immediately. - static final LatLngBounds _floorBounds = LatLngBounds( - const LatLng(24.81155194782686, 46.62383802969581), - const LatLng(24.81245295363478, 46.62470917292456), - ); +class _AssetTrackingPageState extends State with SingleTickerProviderStateMixin { + int selectedIndex = 1; + late AnimationController _animationController; + late AssetTrackingProvider assetTrackingProvider; + + @override + void initState() { + super.initState(); + assetTrackingProvider = Provider.of(context, listen: false); + _animationController = AnimationController( + duration: const Duration(milliseconds: 1200), + vsync: this, + ); + _animationController.forward(); + WidgetsBinding.instance.addPostFrameCallback((_) { + assetTrackingProvider.reset(); + assetTrackingProvider.getAssetsFromPenguin(); + }); + } @override Widget build(BuildContext context) { return Scaffold( appBar: const DefaultAppBar(title: "Live Asset Tracking"), - body: FlutterMap( - options: MapOptions( - initialCenter: const LatLng(24.81200245073082, 46.624273601310186), - initialZoom: 16.0, - initialCameraFit: CameraFit.bounds(bounds: _floorBounds, padding: const EdgeInsets.all(24)), - ), + body: Column( + // crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ - TileLayer( - urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - userAgentPackageName: 'com.hmg.atoms', - ), - GeoJsonLayer.asset( - 'assets/floor.geojson', - styleDefaults: GeoJsonStyleDefaults.leaflet, - ), - RichAttributionWidget( - attributions: [ - TextSourceAttribution( - 'OpenStreetMap contributors', - onTap: () {}, // (external) + SizedBox( + width: double.infinity, + child: SegmentedButton( + multiSelectionEnabled: false, + emptySelectionAllowed: false, + expandedInsets: const EdgeInsets.only(left: 16, top: 16, right: 16, bottom: 8), + style: SegmentedButton.styleFrom( + side: BorderSide(color: AppColor.borderColor(context)), + textStyle: AppTextStyles.bodyText, + backgroundColor: AppColor.background(context), + foregroundColor: context.isDark ? AppColor.neutral30 : AppColor.black20, + selectedForegroundColor: AppColor.addItemButtonColor(context), + selectedBackgroundColor: AppColor.primary10, ), - ], + segments: >[ + ButtonSegment( + value: 1, + label: const Text("List"), + icon: Icon(Icons.list_rounded, color: AppColor.icon2Color(context)), + ), + ButtonSegment( + value: 2, + label: const Text("Grid"), + icon: Icon(Icons.grid_view_rounded, color: AppColor.icon2Color(context)), + ), + ButtonSegment( + value: 3, + label: const Text("Map"), + icon: Icon(Icons.map, color: AppColor.icon2Color(context)), + ), + ], + selected: {selectedIndex}, + onSelectionChanged: (Set newSelection) { + setState(() { + selectedIndex = newSelection.first; + }); + }, + ), ), + SafeArea( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: selectedIndex == 3 ? MapTrackingPage(inAppView: true) : listView(), + ), + ).expanded, ], ), ); } + + Widget listView() { + return Consumer(builder: (context, provider, _) { + return (provider.assetsList.isEmpty && !provider.isLoading) + ? NoDataFound(message: context.translation.noDataFound).center + : ListView.separated( + key: const ValueKey('asset-list_view'), + padding: const EdgeInsets.all(16), + itemCount: provider.isLoading ? 6 : provider.assetsList.length, + separatorBuilder: (listContext, itemIndex) => 12.height, + itemBuilder: (listContext, itemIndex) { + if (provider.isLoading) return const SizedBox().toAssetListShimmer(listContext, provider.isLoading); + + // Calculate staggered animation delay + final delay = itemIndex * 0.08; + final animation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: _animationController, + curve: Interval( + delay.clamp(0.0, 0.7), + (delay + 0.4).clamp(0.0, 1.0), + curve: Curves.easeOut, + ), + ), + ); + + final slideAnimation = Tween( + begin: const Offset(0.0, 0.15), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: Interval( + delay.clamp(0.0, 0.7), + (delay + 0.4).clamp(0.0, 1.0), + curve: Curves.easeOutCubic, + ), + ), + ); + + return FadeTransition( + opacity: animation, + child: SlideTransition( + position: slideAnimation, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusLabel( + label: (provider.assetsList[itemIndex].location?.isLive ?? false) ? "Live" : "Not Live", + radius: 4, + textColor: AppColor.getPriorityStatusTextColor(context, (provider.assetsList[itemIndex].location?.isLive ?? false) ? 0 : 1), + backgroundColor: AppColor.getPriorityStatusColor(context, (provider.assetsList[itemIndex].location?.isLive ?? false) ? 0 : 1), + ), + 4.height, + InfoHeader16Widget(provider.assetsList[itemIndex].label ?? ""), + InfoTextWidget(label: "Serial No", value: provider.assetsList[itemIndex].label, showEmptyValue: true), + InfoTextWidget(label: "Type", value: provider.assetsList[itemIndex].type?.name, showEmptyValue: true), + InfoTextWidget(label: "Status", value: provider.assetsList[itemIndex].status?.name, showEmptyValue: true), + InfoTextWidget(label: "Department", value: provider.assetsList[itemIndex].department?.name, showEmptyValue: true), + InfoTextWidget( + label: "Last seen", + value: provider.assetsList[itemIndex].location?.lastSeen?.toServiceRequestDetailsFormat, + showEmptyValue: true, + showArrow: true, + arrowMsg: "View on map", + ), + ], + ).toShadowContainer(context)).onPress((){ + Navigator.push(context, MaterialPageRoute(builder: (context) => MapTrackingPage(assetTrackingData: provider.assetsList[itemIndex]))); + }), + ); + }, + ); + }); + +// Selector( +// selector: (_, myModel) => myModel, // Selects only the userName +// builder: (_, isLoading, __) { +// +// }); + } } diff --git a/lib/modules/asset_tracking/asset_tracking_provider.dart b/lib/modules/asset_tracking/asset_tracking_provider.dart new file mode 100644 index 00000000..3cd684fb --- /dev/null +++ b/lib/modules/asset_tracking/asset_tracking_provider.dart @@ -0,0 +1,80 @@ +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 'models/asset_tracking_model.dart'; +import 'models/floor_map_model.dart'; + +class AssetTrackingProvider extends ChangeNotifier { + bool isLoading = false; + bool isNextPageLoading = false; + final pageItemNumber = 12; + int pageNo = 1; + + List assetsList = []; + + reset() { + isLoading = false; + notifyListeners(); + } + + Future getAssetsFromPenguin({bool loadMore = false}) async { + if (isLoading || isNextPageLoading) return -2; + if (loadMore) { + isNextPageLoading = true; + pageNo += 1; + } else { + isLoading = true; + pageNo = 1; // reset pagination + } + notifyListeners(); + try { + final payload = {"pageSize": pageItemNumber, "pageNumber": pageNo}; + final response = await ApiManager.instance.post(URLs.getAssetsFromPenguin, body: payload); + if (response.statusCode >= 200 && response.statusCode < 300) { + final Map jsonData = json.decode(response.body); + List list = jsonData["data"]; + assetsList.addAll(list.map((item) => AssetTrackingModel.fromJson(item)).toList()); + if (jsonData["totalRows"] <= assetsList.length) { + // nextPage = true; + } + print("assetsList:${assetsList.length}"); + // if (loadMore) { + // assetInventoryResponse?.assetList?.addAll(newResponse.assetList ?? []); + // assetInventoryResponse?.totalRows = newResponse.totalRows; + // } else { + // assetInventoryResponse = newResponse; + // } + // nextPage = (assetInventoryResponse?.assetList?.length ?? 0) < (assetInventoryResponse?.totalRows ?? 0); + } else { + // nextPage = false; + } + isLoading = false; + isNextPageLoading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + isLoading = false; + isNextPageLoading = false; + // stateCode = -1; + // nextPage = false; + notifyListeners(); + return -1; + } + } + + Future getFloorMap(int floorId) async { + FloorMapModel? floorMapModel; + // try { + final response = await ApiManager.instance.get(URLs.getFloorMap + "$floorId/maps"); + if (response.statusCode >= 200 && response.statusCode < 300) { + floorMapModel = FloorMapModel.fromJson(json.decode(response.body)["data"]); + } + return floorMapModel; + // } catch (error) { + // return floorMapModel; + // } + } +} diff --git a/lib/modules/asset_tracking/map_tracking_page.dart b/lib/modules/asset_tracking/map_tracking_page.dart new file mode 100644 index 00000000..86a1ca18 --- /dev/null +++ b/lib/modules/asset_tracking/map_tracking_page.dart @@ -0,0 +1,159 @@ +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/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 'asset_tracking_provider.dart'; +import 'models/asset_tracking_model.dart'; +import 'models/floor_map_model.dart'; + +class MapTrackingPage extends StatefulWidget { + final AssetTrackingModel? assetTrackingData; + final bool inAppView; + + MapTrackingPage({Key? key, this.inAppView = false, this.assetTrackingData}) : super(key: key); + + @override + _MapTrackingPageState createState() { + return _MapTrackingPageState(); + } +} + +class _MapTrackingPageState extends State { + final MapController mapController = MapController(); + late AssetTrackingProvider assetTrackingProvider; + + FloorMapModel? floorMapModel; + Map? _geojson; + List markers = []; + LatLngBounds? _computedBounds; + + // Fallback shown only until the real floor plan bounds are computed. + static const LatLng _fallbackCenter = LatLng(24.81200245073082, 46.624273601310186); + + @override + void initState() { + super.initState(); + assetTrackingProvider = Provider.of(context, listen: false); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (widget.assetTrackingData != null) { + print('widget.assetTrackingData!.location!.floor!.id!: ${widget.assetTrackingData!.toJson()}'); + Utils.showLoading(context); + assetTrackingProvider.getFloorMap(widget.assetTrackingData!.location!.floor!.id!).then((floorMap) { + Utils.hideLoading(context); + _onFloorMapLoaded(floorMap); + }).catchError((error) { + print('Failed to load floor map: $error'); + Utils.hideLoading(context); + }); + } + }); + } + + 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 geojson) { + final features = (geojson['features'] as List? ?? []); + + 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?; + 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!), + ); + } + + @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, + ), + MarkerLayer(markers: markers), + ] + ], + ); + + return widget.inAppView + ? mapWidget + : Scaffold( + appBar: const DefaultAppBar(title: "Live Asset Tracking"), + body: Stack( + children: [mapWidget], + ), + ); + } +} diff --git a/lib/modules/asset_tracking/models/asset_tracking_model.dart b/lib/modules/asset_tracking/models/asset_tracking_model.dart new file mode 100644 index 00000000..5eb45b2c --- /dev/null +++ b/lib/modules/asset_tracking/models/asset_tracking_model.dart @@ -0,0 +1,218 @@ +class AssetTrackingModel { + int? id; + String? label; + String? serialNumber; + int? group; + Type? type; + Type? department; + Type? status; + Location? location; + Tag? tag; + Alerts? alerts; + + AssetTrackingModel({this.id, this.label, this.serialNumber, this.group, this.type, this.department, this.status, this.location, this.tag, this.alerts}); + + AssetTrackingModel.fromJson(Map json) { + id = json['id']; + label = json['label']; + serialNumber = json['serialNumber']; + group = json['group']; + type = json['type'] != null ? new Type.fromJson(json['type']) : null; + department = json['department'] != null ? new Type.fromJson(json['department']) : null; + status = json['status'] != null ? new Type.fromJson(json['status']) : null; + location = json['location'] != null ? new Location.fromJson(json['location']) : null; + tag = json['tag'] != null ? new Tag.fromJson(json['tag']) : null; + alerts = json['alerts'] != null ? new Alerts.fromJson(json['alerts']) : null; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['label'] = this.label; + data['serialNumber'] = this.serialNumber; + data['group'] = this.group; + if (this.type != null) { + data['type'] = this.type!.toJson(); + } + if (this.department != null) { + data['department'] = this.department!.toJson(); + } + if (this.status != null) { + data['status'] = this.status!.toJson(); + } + if (this.location != null) { + data['location'] = this.location!.toJson(); + } + if (this.tag != null) { + data['tag'] = this.tag!.toJson(); + } + if (this.alerts != null) { + data['alerts'] = this.alerts!.toJson(); + } + return data; + } +} + +class Type { + int? id; + String? name; + + Type({this.id, this.name}); + + Type.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 { + bool? isLive; + String? lastSeen; + Type? venue; + Floor? floor; + Zone? zone; + double? longitude; + double? latitude; + double? x; + double? y; + + Location({this.isLive, this.lastSeen, this.venue, this.floor, this.zone, this.longitude, this.latitude, this.x, this.y}); + + Location.fromJson(Map json) { + isLive = json['isLive']; + lastSeen = json['lastSeen']; + venue = json['venue'] != null ? new Type.fromJson(json['venue']) : null; + floor = json['floor'] != null ? new Floor.fromJson(json['floor']) : null; + zone = json['zone'] != null ? new Zone.fromJson(json['zone']) : null; + longitude = json['longitude']; + latitude = json['latitude']; + x = json['x']; + y = json['y']; + } + + Map toJson() { + final Map data = new Map(); + data['isLive'] = this.isLive; + data['lastSeen'] = this.lastSeen; + if (this.venue != null) { + data['venue'] = this.venue!.toJson(); + } + if (this.floor != null) { + data['floor'] = this.floor!.toJson(); + } + if (this.zone != null) { + data['zone'] = this.zone!.toJson(); + } + data['longitude'] = this.longitude; + data['latitude'] = this.latitude; + data['x'] = this.x; + data['y'] = this.y; + return data; + } +} + +class Floor { + int? id; + String? name; + String? label; + + Floor({this.id, this.name, this.label}); + + Floor.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; + } +} + +class Zone { + int? id; + String? name; + Type? zoneType; + + Zone({this.id, this.name, this.zoneType}); + + Zone.fromJson(Map json) { + id = json['id']; + name = json['name']; + zoneType = json['zoneType'] != null ? new Type.fromJson(json['zoneType']) : null; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['name'] = this.name; + if (this.zoneType != null) { + data['zoneType'] = this.zoneType!.toJson(); + } + return data; + } +} + +class Tag { + int? id; + String? macAddress; + int? batteryPercentage; + + Tag({this.id, this.macAddress, this.batteryPercentage}); + + Tag.fromJson(Map json) { + id = json['id']; + macAddress = json['macAddress']; + batteryPercentage = json['batteryPercentage']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['macAddress'] = this.macAddress; + data['batteryPercentage'] = this.batteryPercentage; + return data; + } +} + +class Alerts { + int? count; + String? lastCreationDate; + String? mostSevereCreationDate; + String? mostSevereTriggerType; + String? mostSeverity; + String? mostSevereRuleName; + + Alerts({this.count, this.lastCreationDate, this.mostSevereCreationDate, this.mostSevereTriggerType, this.mostSeverity, this.mostSevereRuleName}); + + Alerts.fromJson(Map json) { + count = json['count']; + lastCreationDate = json['lastCreationDate']; + mostSevereCreationDate = json['mostSevereCreationDate']; + mostSevereTriggerType = json['mostSevereTriggerType']; + mostSeverity = json['mostSeverity']; + mostSevereRuleName = json['mostSevereRuleName']; + } + + Map toJson() { + final Map data = new Map(); + data['count'] = this.count; + data['lastCreationDate'] = this.lastCreationDate; + data['mostSevereCreationDate'] = this.mostSevereCreationDate; + data['mostSevereTriggerType'] = this.mostSevereTriggerType; + data['mostSeverity'] = this.mostSeverity; + data['mostSevereRuleName'] = this.mostSevereRuleName; + return data; + } +} diff --git a/lib/modules/asset_tracking/models/floor_map_model.dart b/lib/modules/asset_tracking/models/floor_map_model.dart new file mode 100644 index 00000000..5383b317 --- /dev/null +++ b/lib/modules/asset_tracking/models/floor_map_model.dart @@ -0,0 +1,151 @@ +class FloorMapModel { + int? id; + String? mapUrl; + String? geojsonUrl; + String? mapBase64; + Map? geojsonData; + + FloorMapModel({this.id, this.mapUrl, this.geojsonUrl, this.mapBase64, this.geojsonData}); + + FloorMapModel.fromJson(Map json) { + id = json['id']; + mapUrl = json['mapUrl']; + geojsonUrl = json['geojsonUrl']; + mapBase64 = json['mapBase64']; + geojsonData = json['geojsonData']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['mapUrl'] = this.mapUrl; + data['geojsonUrl'] = this.geojsonUrl; + data['mapBase64'] = this.mapBase64; + if (this.geojsonData != null) { + // data['geojsonData'] = this.geojsonData!.toJson(); + } + return data; + } +} + +// class GeojsonData { +// String? type; +// List? features; +// +// GeojsonData({this.type, this.features}); +// +// GeojsonData.fromJson(Map json) { +// type = json['type']; +// if (json['features'] != null) { +// features = []; +// json['features'].forEach((v) { features!.add(new Features.fromJson(v)); }); +// } +// } +// +// Map toJson() { +// final Map data = new Map(); +// data['type'] = this.type; +// if (this.features != null) { +// data['features'] = this.features!.map((v) => v.toJson()).toList(); +// } +// return data; +// } +// } + +// class Features { +// String? type; +// Properties? properties; +// Geometry? geometry; +// +// Features({this.type, this.properties, this.geometry}); +// +// Features.fromJson(Map json) { +// type = json['type']; +// properties = json['properties'] != null ? new Properties.fromJson(json['properties']) : null; +// geometry = json['geometry'] != null ? new Geometry.fromJson(json['geometry']) : null; +// } +// +// Map toJson() { +// final Map data = new Map(); +// data['type'] = this.type; +// if (this.properties != null) { +// data['properties'] = this.properties!.toJson(); +// } +// if (this.geometry != null) { +// data['geometry'] = this.geometry!.toJson(); +// } +// return data; +// } +// } +// +// class Properties { +// int? ff; +// int? id; +// String? fill; +// int? fillOpacity; +// String? stroke; +// int? strokeOpacity; +// int? strokeWidth; +// +// Properties({this.ff, this.id, this.fill, this.fillOpacity, this.stroke, this.strokeOpacity, this.strokeWidth}); +// +// Properties.fromJson(Map json) { +// ff = json['ff']; +// id = json['id']; +// fill = json['fill']; +// fillOpacity = json['fillOpacity']; +// stroke = json['stroke']; +// strokeOpacity = json['strokeOpacity']; +// strokeWidth = json['strokeWidth']; +// } +// +// Map toJson() { +// final Map data = new Map(); +// data['ff'] = this.ff; +// data['id'] = this.id; +// data['fill'] = this.fill; +// data['fillOpacity'] = this.fillOpacity; +// data['stroke'] = this.stroke; +// data['strokeOpacity'] = this.strokeOpacity; +// data['strokeWidth'] = this.strokeWidth; +// return data; +// } +// } + +// class Geometry { +// String? type; +// List? coordinates; +// +// Geometry({this.type, this.coordinates}); +// +// Geometry.fromJson(Map json) { +// type = json['type']; +// if (json['coordinates'] != null) { +// coordinates = []; +// json['coordinates'].forEach((v) { coordinates!.add(new List.fromJson(v)); }); +// } +// } +// +// Map toJson() { +// final Map data = new Map(); +// data['type'] = this.type; +// if (this.coordinates != null) { +// data['coordinates'] = this.coordinates!.map((v) => v.toJson()).toList(); +// } +// return data; +// } +// } +// +// class Coordinates { +// +// +// Coordinates({}); +// +// Coordinates.fromJson(Map json) { +// } +// +// Map toJson() { +// final Map data = new Map(); +// return data; +// } +// } diff --git a/lib/new_views/common_widgets/app_drawer.dart b/lib/new_views/common_widgets/app_drawer.dart index 0f6c0a9f..14273ad7 100644 --- a/lib/new_views/common_widgets/app_drawer.dart +++ b/lib/new_views/common_widgets/app_drawer.dart @@ -5,6 +5,7 @@ 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/modules/asset_tracking/asset_tracking_page.dart'; import 'package:test_sa/modules/notification_setting/view/notification_setting_page.dart'; import 'package:test_sa/modules/site_request_module/screens/site_request_history_screen.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; @@ -74,6 +75,8 @@ class AppDrawer extends StatelessWidget { // 18.height, // drawerItem("rate_us", context.translation.rateUs, context), // 18.height, + drawerItem("setting", "Asset Tracking", context).onPress(() => Navigator.push(context, MaterialPageRoute(builder: (context) => AssetTrackingPage()))), + 18.height, drawerItem("setting", context.translation.settings, context).onPress(() => Navigator.of(context).pushNamed(SettingsPage.id)), 18.height, drawerItem("site_request_icon", "Site Request", context).onPress(() => Navigator.of(context).pushNamed(SiteRequestHistoryScreen.routeName)), diff --git a/lib/views/widgets/item_views/info_text_widget.dart b/lib/views/widgets/item_views/info_text_widget.dart index 29f3fba0..9de2b92d 100644 --- a/lib/views/widgets/item_views/info_text_widget.dart +++ b/lib/views/widgets/item_views/info_text_widget.dart @@ -12,11 +12,12 @@ class InfoTextWidget extends StatelessWidget { String? value; final String label; final bool showEmptyValue; + final String arrowMsg; final bool showArrow; final bool copyValue; final bool isMsg; - InfoTextWidget({required this.label, this.value, this.showEmptyValue = false, this.copyValue = false, this.showArrow = false, this.isMsg = false, Key? key}) : super(key: key); + InfoTextWidget({required this.label, this.value, this.arrowMsg = "", this.showEmptyValue = false, this.copyValue = false, this.showArrow = false, this.isMsg = false, Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -33,9 +34,10 @@ class InfoTextWidget extends StatelessWidget { text = Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, + spacing: 4, children: [ text.expanded, - const SizedBox(width: 4), + if (arrowMsg.isNotEmpty) Text(arrowMsg, style: AppTextStyles.tinyFont2.copyWith(color: AppColor.textStyleColor(context))), "arrow_right".toSvgAsset(width: 17, color: AppColor.primary10), ], ); diff --git a/pubspec.lock b/pubspec.lock index 8fa3ed1c..2bc741a5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -121,6 +121,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + benchmark: + dependency: transitive + description: + name: benchmark + sha256: cb3eeea01e3f054df76ee9775ca680f3afa5f19f39b2bb426ba78ba27654493b + url: "https://pub.dev" + source: hosted + version: "0.3.0" boolean_selector: dependency: transitive description: @@ -233,6 +241,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dart_earcut: + dependency: transitive + description: + name: dart_earcut + sha256: e485001bfc05dcbc437d7bfb666316182e3522d4c3f9668048e004d0eb2ce43b + url: "https://pub.dev" + source: hosted + version: "1.2.0" + dart_polylabel2: + dependency: transitive + description: + name: dart_polylabel2 + sha256: "7eeab15ce72894e4bdba6a8765712231fc81be0bd95247de4ad9966abc57adc6" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + dart_sort_queue: + dependency: transitive + description: + name: dart_sort_queue + sha256: f3353ba8b4850e072d3368757f62edb79af34a9703c3e3df9c59342721f5f5b1 + url: "https://pub.dev" + source: hosted + version: "0.0.2+3" date_picker_timeline: dependency: "direct main" description: @@ -524,6 +556,22 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_map: + dependency: "direct main" + description: + name: flutter_map + sha256: "9bbe5472f66ae648d2ca2efbce97fcaa04e71261fabd3e062f26728222d7f767" + url: "https://pub.dev" + source: hosted + version: "8.3.2" + flutter_map_geojson2: + dependency: "direct main" + description: + name: flutter_map_geojson2 + sha256: "7e05ffbf87cc777d872ec7de21142e5aca5ae65949265943d0e1bc704b81011e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -654,6 +702,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.3" + geotypes: + dependency: transitive + description: + name: geotypes + sha256: "5bedf57de92283133dd221e363812ef50eaaba414f0823b1974ef7d84b86991f" + url: "https://pub.dev" + source: hosted + version: "0.0.2" get: dependency: transitive description: @@ -894,6 +950,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.16" + latlong2: + dependency: "direct main" + description: + name: latlong2 + sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe" + url: "https://pub.dev" + source: hosted + version: "0.9.1" leak_tracker: dependency: transitive description: @@ -998,6 +1062,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.2" + mapbox_maps_flutter: + dependency: "direct main" + description: + name: mapbox_maps_flutter + sha256: c9888c9fed527a3ad7c65d2b34dc5a401ee3ddc1cb2c368899dfb40ba924f894 + url: "https://pub.dev" + source: hosted + version: "2.30.1" matcher: dependency: transitive description: @@ -1006,6 +1078,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.12.19" + material_color_names: + dependency: transitive + description: + name: material_color_names + sha256: "12bf9cdbe60002908730605854172d878ad726fd4f2dfdc665aea1b32895fc8e" + url: "https://pub.dev" + source: hosted + version: "1.0.0" material_color_utilities: dependency: transitive description: @@ -1030,6 +1110,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" + mgrs_dart: + dependency: transitive + description: + name: mgrs_dart + sha256: "385e7168ecc77eb545220223c49eef8ab249da7bf57f22781c40a04d23fb196f" + url: "https://pub.dev" + source: hosted + version: "3.0.0" mime: dependency: transitive description: @@ -1390,6 +1478,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.5.0" + proj4dart: + dependency: transitive + description: + name: proj4dart + sha256: ddcedc1f7876e62717de43ab3491e2829bdad0b028261805f94aa080967e5859 + url: "https://pub.dev" + source: hosted + version: "3.0.0" provider: dependency: "direct main" description: @@ -1414,6 +1510,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + rbush: + dependency: transitive + description: + name: rbush + sha256: "48b683421b4afb43a642f82c6aa31911e54f3069143d31c7d33cbe329df13403" + url: "https://pub.dev" + source: hosted + version: "1.1.1" record_mp3_plus: dependency: "direct main" description: @@ -1566,6 +1670,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.1" + simple_sparse_list: + dependency: transitive + description: + name: simple_sparse_list + sha256: aa648fd240fa39b49dcd11c19c266990006006de6699a412de485695910fbc1f + url: "https://pub.dev" + source: hosted + version: "0.1.4" sky_engine: dependency: transitive description: flutter @@ -1699,6 +1811,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + sweepline_intersections: + dependency: transitive + description: + name: sweepline_intersections + sha256: a665c707200a4f07140a4029b41a7c4883beb3f04322cd8e08ebf650f69e1176 + url: "https://pub.dev" + source: hosted + version: "0.0.4" synchronized: dependency: transitive description: @@ -1763,6 +1883,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + turf: + dependency: transitive + description: + name: turf + sha256: "7684654c561e751332bc2fa6e9bb7bcf0c71bad5315acf126206e7ecfef67256" + url: "https://pub.dev" + source: hosted + version: "0.0.12" + turf_equality: + dependency: transitive + description: + name: turf_equality + sha256: f0f44ffe389547941358e0d3d4a747db2bd56115b32ff1cede5e5bdf3126a3e2 + url: "https://pub.dev" + source: hosted + version: "0.1.0" + turf_pip: + dependency: transitive + description: + name: turf_pip + sha256: ba4fd414baffd5d7b30880658ad6db82461c49ec023f8ffd0c23d398ad8b14be + url: "https://pub.dev" + source: hosted + version: "0.0.2" typed_data: dependency: transitive description: @@ -1771,6 +1915,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + unicode: + dependency: transitive + description: + name: unicode + sha256: a6f7bcfc8ea1d5ce1f6c0b1c39117a9919f4953edd9fd7a64090a9796c499b57 + url: "https://pub.dev" + source: hosted + version: "1.1.9" url_launcher: dependency: "direct main" description: @@ -1923,6 +2075,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.15.0" + wkt_parser: + dependency: transitive + description: + name: wkt_parser + sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13" + url: "https://pub.dev" + source: hosted + version: "2.0.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1b382def..8f17ed02 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -67,6 +67,7 @@ dependencies: flutter_map: ^8.3.2 latlong2: ^0.9.0 flutter_map_geojson2: ^1.0.2 + mapbox_maps_flutter: ^2.30.1 flare_flutter: git: