|
|
|
|
|
import 'dart:math';
|
|
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
import 'package:geolocator/geolocator.dart';
|
|
|
|
|
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
|
|
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
|
|
|
|
|
|
|
|
|
|
class ServiceLocationsRadiusScreen extends StatefulWidget {
|
|
|
|
|
|
final LatLng? initialPosition;
|
|
|
|
|
|
final ValueListenable<double>? radiusKmListenable;
|
|
|
|
|
|
|
|
|
|
|
|
const ServiceLocationsRadiusScreen({
|
|
|
|
|
|
Key? key,
|
|
|
|
|
|
this.initialPosition,
|
|
|
|
|
|
this.radiusKmListenable,
|
|
|
|
|
|
}) : super(key: key);
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
State<ServiceLocationsRadiusScreen> createState() =>
|
|
|
|
|
|
_ServiceLocationsRadiusScreenState();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _ServiceLocationsRadiusScreenState
|
|
|
|
|
|
extends State<ServiceLocationsRadiusScreen> {
|
|
|
|
|
|
GoogleMapController? _controller;
|
|
|
|
|
|
LatLng? _currentPosition;
|
|
|
|
|
|
LatLng? _circleCenter;
|
|
|
|
|
|
bool _isMapReady = false;
|
|
|
|
|
|
Set<Circle> _circles = {};
|
|
|
|
|
|
|
|
|
|
|
|
final double _fallbackRadiusMeters = 10000; // 10 km
|
|
|
|
|
|
final LatLng _indiaCenter = const LatLng(20.5937, 78.9629);
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
void initState() {
|
|
|
|
|
|
super.initState();
|
|
|
|
|
|
if (widget.initialPosition == null) {
|
|
|
|
|
|
_checkLocationPermissionAndGetCurrentPosition();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
_circleCenter = widget.initialPosition;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
widget.radiusKmListenable?.addListener(_onRadiusChangedFromParent);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
void dispose() {
|
|
|
|
|
|
widget.radiusKmListenable?.removeListener(_onRadiusChangedFromParent);
|
|
|
|
|
|
super.dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🔹 When radius changes in parent
|
|
|
|
|
|
void _onRadiusChangedFromParent() {
|
|
|
|
|
|
final km =
|
|
|
|
|
|
widget.radiusKmListenable?.value ?? (_fallbackRadiusMeters / 1000.0);
|
|
|
|
|
|
final meters = km * 1000.0;
|
|
|
|
|
|
final center = _circleCenter ?? _currentPosition ?? _indiaCenter;
|
|
|
|
|
|
_addRadiusCircle(center, radiusInMeters: meters);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🔹 Get current location
|
|
|
|
|
|
Future<void> _checkLocationPermissionAndGetCurrentPosition() async {
|
|
|
|
|
|
var status = await Permission.location.status;
|
|
|
|
|
|
if (!status.isGranted) {
|
|
|
|
|
|
status = await Permission.location.request();
|
|
|
|
|
|
if (!status.isGranted) return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
|
|
|
|
|
if (!serviceEnabled) return;
|
|
|
|
|
|
|
|
|
|
|
|
final position = await Geolocator.getCurrentPosition();
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
_currentPosition = LatLng(position.latitude, position.longitude);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🔹 Add / update the circle
|
|
|
|
|
|
void _addRadiusCircle(LatLng center, {double? radiusInMeters}) async {
|
|
|
|
|
|
final meters = radiusInMeters ??
|
|
|
|
|
|
(widget.radiusKmListenable?.value ?? (_fallbackRadiusMeters / 1000.0)) *
|
|
|
|
|
|
1000.0;
|
|
|
|
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
_circleCenter = center;
|
|
|
|
|
|
_circles = {
|
|
|
|
|
|
Circle(
|
|
|
|
|
|
circleId: const CircleId("radius_circle"),
|
|
|
|
|
|
center: center,
|
|
|
|
|
|
radius: meters,
|
|
|
|
|
|
fillColor: Colors.blue.withOpacity(0.2),
|
|
|
|
|
|
strokeColor: Colors.blueAccent,
|
|
|
|
|
|
strokeWidth: 2,
|
|
|
|
|
|
),
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 👇 adjust zoom so full circle is visible even in small container
|
|
|
|
|
|
if (_controller != null) {
|
|
|
|
|
|
final zoom = _getZoomLevelForSmallMap(meters);
|
|
|
|
|
|
await _controller!.animateCamera(
|
|
|
|
|
|
CameraUpdate.newCameraPosition(
|
|
|
|
|
|
CameraPosition(target: center, zoom: zoom),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🔹 Calculate zoom for small map container (200–300 px)
|
|
|
|
|
|
double _getZoomLevelForSmallMap(double radiusMeters) {
|
|
|
|
|
|
// Adjust constant here if map size differs
|
|
|
|
|
|
final scale = radiusMeters / 200; // Smaller scale for small map
|
|
|
|
|
|
double zoomLevel = 16 - log(scale) / log(2);
|
|
|
|
|
|
if (zoomLevel > 18) zoomLevel = 18;
|
|
|
|
|
|
if (zoomLevel < 4) zoomLevel = 4;
|
|
|
|
|
|
return zoomLevel;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
final LatLng target =
|
|
|
|
|
|
widget.initialPosition ?? _currentPosition ?? _indiaCenter;
|
|
|
|
|
|
|
|
|
|
|
|
final initialCameraPosition = CameraPosition(
|
|
|
|
|
|
target: target,
|
|
|
|
|
|
zoom: 12,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return GoogleMap(
|
|
|
|
|
|
initialCameraPosition: initialCameraPosition,
|
|
|
|
|
|
mapType: MapType.normal,
|
|
|
|
|
|
circles: _circles,
|
|
|
|
|
|
zoomControlsEnabled: false,
|
|
|
|
|
|
myLocationEnabled: false,
|
|
|
|
|
|
myLocationButtonEnabled: false,
|
|
|
|
|
|
onMapCreated: (controller) async {
|
|
|
|
|
|
_controller = controller;
|
|
|
|
|
|
_isMapReady = true;
|
|
|
|
|
|
|
|
|
|
|
|
await Future.delayed(const Duration(milliseconds: 500));
|
|
|
|
|
|
final center = widget.initialPosition ?? _currentPosition ?? _indiaCenter;
|
|
|
|
|
|
final km = widget.radiusKmListenable?.value ?? 10;
|
|
|
|
|
|
_addRadiusCircle(center, radiusInMeters: km * 1000.0);
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|