import 'dart:async'; import 'dart:convert'; import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import 'package:google_maps_cluster_manager/google_maps_cluster_manager.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:sfm_app/feature/devices/device_model.dart'; import 'package:sfm_app/feature/map/map_bloc.dart'; import 'package:sfm_app/feature/map/widget/on_tap_marker_widget.dart'; import 'package:sfm_app/product/base/bloc/base_bloc.dart'; import 'package:sfm_app/product/constant/icon/icon_constants.dart'; import 'package:sfm_app/product/permission/location_permission.dart'; import 'package:sfm_app/product/services/api_services.dart'; class MapScreen extends StatefulWidget { const MapScreen({super.key}); @override State createState() => _MapScreenState(); } class _MapScreenState extends State with WidgetsBindingObserver { late BitmapDescriptor normalIcon; late BitmapDescriptor offlineIcon; late BitmapDescriptor abnormalIcon; late BitmapDescriptor flameIcon; late BitmapDescriptor hospitalIcon; late BitmapDescriptor fireStationIcon; late MapBloc mapBloc; late ClusterManager clusterManager; MapType mapType = MapType.terrain; APIServices apiServices = APIServices(); final streamController = StreamController.broadcast(); List devices = []; Completer _controller = Completer(); List imageAssets = [ IconConstants.instance.getIcon("normal_icon"), IconConstants.instance.getIcon("offline_icon"), IconConstants.instance.getIcon("flame_icon"), IconConstants.instance.getIcon("hospital_marker"), IconConstants.instance.getIcon("fire_station_marker"), ]; static const CameraPosition _myPosition = CameraPosition( target: LatLng(20.976108, 105.791666), zoom: 12, ); Set markersAll = {}; List markers = []; LatLng myLocation = const LatLng(213761, 123123); Position? position; bool isAllowLocationPermission = false; @override void initState() { super.initState(); mapBloc = BlocProvider.of(context); _loadIcons(); getAllMarkers(); clusterManager = _initClusterManager(); } @override void dispose() { streamController.close(); _controller = Completer(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ StreamBuilder>( stream: mapBloc.streamAllMarker, builder: (context, markerSnapshot) { return StreamBuilder>( stream: mapBloc.streamPolylines, builder: (context, polylinesSnapshot) { return GoogleMap( initialCameraPosition: _myPosition, mapType: mapType, onMapCreated: (GoogleMapController controller) { if (!_controller.isCompleted) { _controller.complete(controller); } streamController.sink.add(controller); clusterManager.setMapId(controller.mapId); }, markers: markerSnapshot.data ?? markersAll ..addAll(markers), zoomControlsEnabled: true, myLocationEnabled: true, mapToolbarEnabled: false, onCameraMove: (position) { clusterManager.onCameraMove(position); }, onCameraIdle: () { clusterManager.updateMap(); }, polylines: { Polyline( polylineId: const PolylineId('router'), points: polylinesSnapshot.data ?? [], color: Colors.deepPurpleAccent, width: 8, ), }, ); }, ); }, ), ], ), ); } Future _loadIcons() async { List> iconFutures = imageAssets.map((asset) { return BitmapDescriptor.fromAssetImage(const ImageConfiguration(), asset); }).toList(); List icons = await Future.wait(iconFutures); normalIcon = icons[0]; offlineIcon = icons[1]; flameIcon = icons[2]; hospitalIcon = icons[3]; fireStationIcon = icons[4]; } ClusterManager _initClusterManager() { return ClusterManager( devices, _updateMarkers, markerBuilder: _getmarkerBuilder(), ); } Future Function(Cluster) _getmarkerBuilder() => (cluster) async { return Marker( markerId: MarkerId( cluster.getId(), ), position: cluster.location, onTap: () async { bool check = await checkLocationPermission(context); if (check == true) { Position position = await Geolocator.getCurrentPosition(); // ignore: use_build_context_synchronously onTapMarker( context, _controller, mapBloc, LatLng(position.latitude, position.longitude), cluster.items, imageAssets, markers, hospitalIcon, fireStationIcon, ); } else { // ignore: use_build_context_synchronously ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("Cannot get your Location"))); } }, icon: getMarkerIcon(cluster), ); }; BitmapDescriptor getMarkerIcon(Cluster cluster) { if (cluster.items.length == 1) { Device item = cluster.items.first; if (item.state == 0) { return normalIcon; } else if (item.state == 1) { return flameIcon; } else { return offlineIcon; } } bool hasStateOne = false; bool hasOtherState = false; for (var item in cluster.items) { if (item.state == 1) { hasStateOne = true; break; } else if (item.state != 0) { hasOtherState = true; } } // log("Has state = 1: $hasStateOne, Has other state: $hasOtherState"); if (hasStateOne) { return flameIcon; // flameIcon } else if (hasOtherState) { return normalIcon; // normalIcon } else { return offlineIcon; // offlineIcon } } bool checkStateMarker(Cluster cluster) { bool hasStateOne = false; bool hasOtherState = false; for (var item in cluster.items) { if (item.state == 1) { hasStateOne = true; break; } else if (item.state != 0) { hasOtherState = true; } } // log("Has state = 1: $hasStateOne, Has other state: $hasOtherState"); if (hasStateOne) { return true; // flameIcon } else if (hasOtherState) { return false; // normalIcon } else { return true; // offlineIcon } } void _updateMarkers(Set marker) { log("Update Marker"); markersAll = marker; mapBloc.sinkAllMarker.add(marker); } void getAllMarkers() async { String response = await apiServices.getOwnerDevices(); if (response != "") { final data = jsonDecode(response); List result = data['items']; final devicesList = Device.fromJsonDynamicList(result); for (var device in devicesList) { devices.add(device); } } } Future checkLocationPermission(context) async { bool check = await LocationPermissionRequest.instance .checkLocationPermission(context); return check; } }