297 lines
9.1 KiB
Dart
297 lines
9.1 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:developer';
|
|
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:geolocator/geolocator.dart';
|
|
import 'package:google_maps_cluster_manager_2/google_maps_cluster_manager_2.dart';
|
|
import 'package:google_maps_flutter/google_maps_flutter.dart'
|
|
hide ClusterManager, Cluster;
|
|
import 'package:sfm_app/feature/devices/device_model.dart';
|
|
import 'package:sfm_app/bloc/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';
|
|
import 'package:sfm_app/product/utils/permission_handler.dart';
|
|
import '../../product/constant/enums/app_theme_enums.dart';
|
|
|
|
class MapScreen extends StatefulWidget {
|
|
const MapScreen({super.key});
|
|
|
|
@override
|
|
State<MapScreen> createState() => _MapScreenState();
|
|
}
|
|
|
|
class _MapScreenState extends State<MapScreen> 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<GoogleMapController>.broadcast();
|
|
List<Device> devices = [];
|
|
Completer<GoogleMapController> _controller = Completer();
|
|
List<String> 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<Marker> markersAll = {};
|
|
List<Marker> markers = [];
|
|
// LatLng myLocation = const LatLng(213761, 123123);
|
|
// Position? position;
|
|
// bool isAllowLocationPermission = false;
|
|
Timer? checkThemeTimer;
|
|
Timer? getMarker;
|
|
String themeMode = '';
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
mapBloc = BlocProvider.of(context);
|
|
_loadIcons();
|
|
getAllMarkers();
|
|
clusterManager = _initClusterManager();
|
|
const duration = Duration(seconds: 2);
|
|
checkThemeTimer = Timer.periodic(duration, (Timer t) => checkTheme());
|
|
const Duration markerDuration = Duration(seconds: 5);
|
|
getMarker = Timer.periodic(markerDuration, (Timer t) => getAllMarkers());
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
super.dispose();
|
|
checkThemeTimer?.cancel();
|
|
getMarker?.cancel();
|
|
_controller = Completer();
|
|
streamController.close();
|
|
}
|
|
|
|
void onMapCreated(GoogleMapController controller) {
|
|
_controller.complete(controller);
|
|
streamController.add(controller);
|
|
clusterManager.setMapId(controller.mapId);
|
|
checkTheme();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: Stack(
|
|
children: [
|
|
StreamBuilder<Set<Marker>>(
|
|
stream: mapBloc.streamAllMarker,
|
|
builder: (context, markerSnapshot) {
|
|
return StreamBuilder<List<LatLng>>(
|
|
stream: mapBloc.streamPolylines,
|
|
builder: (context, polylinesSnapshot) {
|
|
return StreamBuilder<String>(
|
|
stream: mapBloc.streamMapTheme,
|
|
builder: (context, mapThemeSnapshot) {
|
|
return GoogleMap(
|
|
initialCameraPosition: _myPosition,
|
|
// mapType: mapType,
|
|
onMapCreated: onMapCreated,
|
|
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,
|
|
),
|
|
},
|
|
style: mapThemeSnapshot.data,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void checkTheme() async {
|
|
String theme = await apiServices.checkTheme();
|
|
if (theme == AppThemes.DARK.name) {
|
|
themeMode = await _getFileData('assets/map_themes/maps_dark_theme.json');
|
|
} else {
|
|
themeMode = '';
|
|
}
|
|
mapBloc.sinkMapTheme.add(themeMode);
|
|
}
|
|
|
|
Future<String> _getFileData(String path) async {
|
|
return rootBundle.loadString(path);
|
|
}
|
|
|
|
Future<void> _loadIcons() async {
|
|
List<Future<BitmapDescriptor>> iconFutures = imageAssets.map((asset) {
|
|
return BitmapDescriptor.asset(
|
|
const ImageConfiguration(size: Size(50, 50)), asset);
|
|
}).toList();
|
|
|
|
List<BitmapDescriptor> icons = await Future.wait(iconFutures);
|
|
|
|
normalIcon = icons[0];
|
|
offlineIcon = icons[1];
|
|
flameIcon = icons[2];
|
|
hospitalIcon = icons[3];
|
|
fireStationIcon = icons[4];
|
|
}
|
|
|
|
ClusterManager _initClusterManager() {
|
|
return ClusterManager<Device>(
|
|
devices,
|
|
_updateMarkers,
|
|
markerBuilder: _getmarkerBuilder(),
|
|
);
|
|
}
|
|
|
|
Future<Marker> Function(Cluster<Device>) _getmarkerBuilder() =>
|
|
(cluster) async {
|
|
return Marker(
|
|
markerId: MarkerId(
|
|
cluster.getId(),
|
|
),
|
|
position: cluster.location,
|
|
onTap: () async {
|
|
LocationPermission permission = await checkAndRequestPermission();
|
|
if (permission == LocationPermission.whileInUse || permission == LocationPermission.always) {
|
|
Position position = await Geolocator.getCurrentPosition();
|
|
onTapMarker(
|
|
// ignore: use_build_context_synchronously
|
|
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<Device> 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<Device> 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;
|
|
}
|
|
}
|
|
|
|
if (hasStateOne) {
|
|
return true; // flameIcon
|
|
} else if (hasOtherState) {
|
|
return false; // normalIcon
|
|
} else {
|
|
return true; // offlineIcon
|
|
}
|
|
}
|
|
|
|
void _updateMarkers(Set<Marker> marker) {
|
|
log("Update Marker");
|
|
checkTheme();
|
|
markersAll = marker;
|
|
mapBloc.sinkAllMarker.add(marker);
|
|
}
|
|
|
|
void getAllMarkers() async {
|
|
String response = await apiServices.getOwnerDevices();
|
|
if (response != "") {
|
|
final data = jsonDecode(response);
|
|
List<dynamic> result = data['items'];
|
|
if(result.isNotEmpty){
|
|
devices.clear();
|
|
final devicesList = Device.fromJsonDynamicList(result);
|
|
for (var device in devicesList) {
|
|
devices.add(device);
|
|
}
|
|
}else{
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Future<bool> checkLocationPermission(context) async {
|
|
// bool check = await LocationPermissionRequest.instance
|
|
// .checkLocationPermission(context);
|
|
// return check;
|
|
// }
|
|
}
|