You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
620 lines
22 KiB
620 lines
22 KiB
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'dart:math';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
import 'dart:async';
|
|
import 'package:flutter/services.dart' show PlatformException, rootBundle;
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:location/location.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:geolocator/geolocator.dart';
|
|
import 'package:teso/Classes/TesoShop.dart';
|
|
import 'package:teso/Pages/Sub_Pages/BusinessDetails.dart';
|
|
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
|
|
import 'package:teso/util/SizeConfig.dart';
|
|
import 'package:teso/util/consts.dart';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
class BusinessLocator extends StatefulWidget {
|
|
@override
|
|
_BusinessLocatorState createState() => _BusinessLocatorState();
|
|
}
|
|
|
|
class _BusinessLocatorState extends State<BusinessLocator> {
|
|
String mapstyle;
|
|
var _future;
|
|
static LatLng _initialPosition;
|
|
Set<Marker> markers = {};
|
|
List<TesoShop> shops;
|
|
GoogleMapController mapController;
|
|
static const double CAMERA_ZOOM = 13.499910354614258;
|
|
bool routing = false;
|
|
List<LatLng> polylineCoordinates = [];
|
|
Map<PolylineId, Polyline> polylines = {};
|
|
String selectedshop = "";
|
|
Location location = Location();
|
|
String routingMessage = "Finding shops....";
|
|
LocationData _location;
|
|
String _error;
|
|
String _placeDistance;
|
|
final startAddressController = TextEditingController();
|
|
final destinationAddressController = TextEditingController();
|
|
bool ios = false;
|
|
|
|
Future<LatLng> _determinePosition(context) async {
|
|
setState(() {
|
|
_error = null;
|
|
});
|
|
try {
|
|
final LocationData _locationResult = await location.getLocation();
|
|
setState(() {
|
|
_location = _locationResult;
|
|
_initialPosition = LatLng(_location.latitude, _location.longitude);
|
|
});
|
|
|
|
await getLocations();
|
|
return _initialPosition;
|
|
} on PlatformException catch (err) {
|
|
setState(() {
|
|
_error = err.code;
|
|
});
|
|
return _initialPosition;
|
|
}
|
|
}
|
|
|
|
getLocations() async {
|
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
Map<String, String> requestHeaders = {
|
|
'Content-type': 'application/json',
|
|
'Authorization': prefs.getString('tokensTeso')
|
|
};
|
|
|
|
var register2 = serverLocation + 'tesobusiness/available';
|
|
var client1 = await http.get(Uri.parse(register2), headers: requestHeaders);
|
|
|
|
if (client1.statusCode == 200) {
|
|
try {
|
|
var data = jsonDecode(client1.body);
|
|
shops = List<TesoShop>.from(
|
|
data.map((model) => TesoShop.fromJSON(model)).toList());
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
}
|
|
|
|
if (shops.length > 0 && shops != null)
|
|
shops.forEach((element) {
|
|
MarkerId markerId = MarkerId(element.shopID);
|
|
Marker marker = Marker(
|
|
markerId: markerId,
|
|
position: LatLng(element.latitude, element.longitude),
|
|
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan),
|
|
infoWindow: InfoWindow(
|
|
title: element.shopName,
|
|
snippet: element.shopAddress,
|
|
),
|
|
onTap: () => showModalBottomSheet(
|
|
context: context,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(30.0)),
|
|
),
|
|
builder: (BuildContext bc) {
|
|
return buildShopDetails(bc, element, navigateToShop);
|
|
},
|
|
),
|
|
);
|
|
if (mounted) {
|
|
setState(() {
|
|
markers.add(marker);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
ios = Platform.isIOS;
|
|
SharedPreferences.getInstance().then((prefs) {
|
|
String currentTheme = prefs.getString("theme");
|
|
if (currentTheme == "light") {
|
|
rootBundle.loadString('assets/styles/light.txt').then((string) {
|
|
mapstyle = string;
|
|
});
|
|
} else {
|
|
rootBundle.loadString('assets/styles/dark.txt').then((string) {
|
|
mapstyle = string;
|
|
});
|
|
}
|
|
});
|
|
_future = _determinePosition(context);
|
|
|
|
location.onLocationChanged.listen((LocationData cLoc) {
|
|
_initialPosition = LatLng(cLoc.latitude, cLoc.longitude);
|
|
});
|
|
}
|
|
|
|
void navigateToShop(TesoShop tesoShop) async {
|
|
Position _northeastCoordinates;
|
|
Position _southwestCoordinates;
|
|
|
|
MarkerId markerId = MarkerId(tesoShop.shopName + " Location");
|
|
Marker marker = Marker(
|
|
markerId: markerId,
|
|
position: LatLng(tesoShop.latitude, tesoShop.longitude),
|
|
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueGreen),
|
|
infoWindow: InfoWindow(
|
|
title: tesoShop.shopName,
|
|
snippet: tesoShop.shopAddress,
|
|
),
|
|
);
|
|
|
|
setState(() {
|
|
markers.clear();
|
|
markers.add(marker);
|
|
selectedshop = tesoShop.shopName;
|
|
destinationAddressController.text = selectedshop;
|
|
});
|
|
|
|
Position user = Position(
|
|
latitude: _initialPosition.latitude,
|
|
longitude: _initialPosition.longitude,
|
|
accuracy: 100,
|
|
altitude: 100,
|
|
heading: 100,
|
|
speed: 100,
|
|
speedAccuracy: 100,
|
|
timestamp: DateTime.now());
|
|
Position shopLoc = Position(
|
|
latitude: tesoShop.latitude,
|
|
longitude: tesoShop.longitude,
|
|
accuracy: 100,
|
|
altitude: 100,
|
|
heading: 100,
|
|
speed: 100,
|
|
speedAccuracy: 100,
|
|
timestamp: DateTime.now());
|
|
|
|
if (_initialPosition.latitude <= tesoShop.latitude) {
|
|
_southwestCoordinates = user;
|
|
_northeastCoordinates = shopLoc;
|
|
} else {
|
|
_southwestCoordinates = shopLoc;
|
|
_northeastCoordinates = user;
|
|
}
|
|
await createPolylines(user, shopLoc);
|
|
_calculateDistance(user, shopLoc);
|
|
|
|
mapController.animateCamera(
|
|
CameraUpdate.newLatLngBounds(
|
|
LatLngBounds(
|
|
northeast: LatLng(
|
|
_northeastCoordinates.latitude,
|
|
_northeastCoordinates.longitude,
|
|
),
|
|
southwest: LatLng(
|
|
_southwestCoordinates.latitude,
|
|
_southwestCoordinates.longitude,
|
|
),
|
|
),
|
|
100.0, // padding
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<bool> createPolylines(Position start, Position destination) async {
|
|
try {
|
|
setState(() {
|
|
routing = true;
|
|
routingMessage = "Calculating route....";
|
|
startAddressController.text = "Current Location ";
|
|
});
|
|
polylineCoordinates.clear();
|
|
PolylinePoints polylinePoints = PolylinePoints();
|
|
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
|
|
mapsKey,
|
|
PointLatLng(start.latitude, start.longitude),
|
|
PointLatLng(destination.latitude, destination.longitude),
|
|
travelMode: TravelMode.driving,
|
|
);
|
|
await Future.delayed(Duration(seconds: 5), () async {
|
|
if (result.points.isNotEmpty) {
|
|
result.points.forEach((PointLatLng point) {
|
|
polylineCoordinates.add(LatLng(point.latitude, point.longitude));
|
|
});
|
|
}
|
|
PolylineId id = PolylineId('poly');
|
|
|
|
Polyline polyline = Polyline(
|
|
polylineId: id,
|
|
color: Theme.of(context).colorScheme.secondary,
|
|
points: polylineCoordinates,
|
|
width: 5,
|
|
);
|
|
polylines[id] = polyline;
|
|
setState(() {
|
|
routing = false;
|
|
routingMessage = "Finding shops.....";
|
|
});
|
|
});
|
|
return true;
|
|
} catch (e) {
|
|
print(e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
if (mapController != null) mapController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
SizeConfig().init(context);
|
|
return new Scaffold(
|
|
body: FutureBuilder(
|
|
future: _future,
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.waiting || routing) {
|
|
return Stack(
|
|
children: [
|
|
ios
|
|
? Container(
|
|
margin: EdgeInsets.only(
|
|
top: (MediaQuery.of(context).size.height) -
|
|
(MediaQuery.of(context).size.height * 0.935),
|
|
left: 10),
|
|
child: Material(
|
|
elevation: 5,
|
|
color: Color.fromRGBO(0, 0, 0, 0.4),
|
|
borderRadius: BorderRadius.only(
|
|
bottomLeft: Radius.circular(25),
|
|
bottomRight: Radius.circular(25),
|
|
topLeft: Radius.circular(25),
|
|
topRight: Radius.circular(25),
|
|
),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(25.0),
|
|
child: IconButton(
|
|
icon: Icon(
|
|
Icons.arrow_back_ios,
|
|
size: 20,
|
|
),
|
|
color: Colors.white,
|
|
onPressed: () => Navigator.pop(context),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
: Container(),
|
|
Container(
|
|
padding: EdgeInsets.only(
|
|
top: MediaQuery.of(context).size.width * 0.7),
|
|
height: MediaQuery.of(context).size.height,
|
|
width: MediaQuery.of(context).size.width,
|
|
child: Center(
|
|
child: Column(
|
|
children: [
|
|
CupertinoActivityIndicator(
|
|
animating: true,
|
|
radius: 15,
|
|
),
|
|
Text(routingMessage),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
} else if (snapshot.data == null &&
|
|
snapshot.connectionState == ConnectionState.done) {
|
|
print(_error.toString());
|
|
Navigator.of(context).pop();
|
|
return Container();
|
|
} else {
|
|
return Stack(
|
|
children: [
|
|
GoogleMap(
|
|
padding: EdgeInsets.only(
|
|
top: 70.0,
|
|
),
|
|
zoomGesturesEnabled: true,
|
|
zoomControlsEnabled: false,
|
|
compassEnabled: true,
|
|
myLocationButtonEnabled: true,
|
|
myLocationEnabled: true,
|
|
markers: markers,
|
|
initialCameraPosition: CameraPosition(
|
|
target: _initialPosition,
|
|
zoom: CAMERA_ZOOM,
|
|
//bearing: CAMERA_BEARING,
|
|
),
|
|
onMapCreated: (GoogleMapController controller) {
|
|
controller.setMapStyle(mapstyle);
|
|
mapController = controller;
|
|
},
|
|
onCameraMove: (position) {
|
|
setState(() {
|
|
try {
|
|
_initialPosition = LatLng(position.target.latitude,
|
|
position.target.longitude);
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
});
|
|
},
|
|
polylines: Set<Polyline>.of(polylines.values),
|
|
),
|
|
Container(
|
|
margin: EdgeInsets.only(
|
|
top: (MediaQuery.of(context).size.height) -
|
|
(MediaQuery.of(context).size.height * 0.935),
|
|
left: 10),
|
|
child: Material(
|
|
elevation: 5,
|
|
color: Color.fromRGBO(0, 0, 0, 0.4),
|
|
borderRadius: BorderRadius.only(
|
|
bottomLeft: Radius.circular(25),
|
|
bottomRight: Radius.circular(25),
|
|
topLeft: Radius.circular(25),
|
|
topRight: Radius.circular(25),
|
|
),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(25.0),
|
|
child: IconButton(
|
|
icon: Icon(
|
|
Icons.arrow_back_ios,
|
|
size: 20,
|
|
),
|
|
color: Colors.white,
|
|
onPressed: () => Navigator.pop(context),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Visibility(
|
|
visible: selectedshop == null || selectedshop.isEmpty
|
|
? false
|
|
: true,
|
|
child: Align(
|
|
alignment: Alignment.topCenter,
|
|
child: SafeArea(
|
|
child: Align(
|
|
alignment: Alignment.topCenter,
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(top: 10.0),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white70,
|
|
borderRadius: BorderRadius.all(
|
|
Radius.circular(20.0),
|
|
),
|
|
),
|
|
width: SizeConfig.safeBlockHorizontal * 80,
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(
|
|
top: 10.0, bottom: 10.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
Text(
|
|
'Places',
|
|
style: TextStyle(
|
|
fontSize:
|
|
SizeConfig.blockSizeHorizontal * 4.0,
|
|
),
|
|
),
|
|
SizedBox(height: 10),
|
|
_textField(
|
|
label: 'Start',
|
|
hint: 'Choose starting point',
|
|
prefixIcon: Icon(
|
|
Icons.looks_one,
|
|
color: Colors.black,
|
|
),
|
|
controller: startAddressController,
|
|
width: SizeConfig.safeBlockHorizontal * 70,
|
|
),
|
|
SizedBox(height: 10),
|
|
_textField(
|
|
label: 'Destination',
|
|
hint: 'Choose destination',
|
|
prefixIcon: Icon(Icons.looks_two,
|
|
color: Colors.black),
|
|
controller: destinationAddressController,
|
|
width: SizeConfig.safeBlockHorizontal * 70,
|
|
),
|
|
SizedBox(height: 10),
|
|
Visibility(
|
|
visible:
|
|
_placeDistance == null ? false : true,
|
|
child: Text(
|
|
'DISTANCE: $_placeDistance km',
|
|
style: TextStyle(
|
|
fontSize:
|
|
SizeConfig.blockSizeHorizontal *
|
|
3.5,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
ElevatedButton(
|
|
onPressed: () async {
|
|
polylines.clear();
|
|
markers.clear();
|
|
await getLocations();
|
|
selectedshop = "";
|
|
},
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Text(
|
|
'Cancel'.toUpperCase(),
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize:
|
|
SizeConfig.blockSizeHorizontal *
|
|
3.5,
|
|
),
|
|
),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
primary: Colors.red,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius:
|
|
BorderRadius.circular(20.0),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
double _coordinateDistance(lat1, lon1, lat2, lon2) {
|
|
var p = 0.017453292519943295;
|
|
var c = cos;
|
|
var a = 0.5 -
|
|
c((lat2 - lat1) * p) / 2 +
|
|
c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p)) / 2;
|
|
return 12742 * asin(sqrt(a));
|
|
}
|
|
|
|
Future<bool> _calculateDistance(
|
|
Position startPos, Position destination) async {
|
|
try {
|
|
// Calculating to check that the position relative
|
|
// to the frame, and pan & zoom the camera accordingly.
|
|
double miny = (startPos.latitude <= destination.latitude)
|
|
? startPos.latitude
|
|
: destination.latitude;
|
|
double minx = (startPos.longitude <= destination.longitude)
|
|
? startPos.longitude
|
|
: destination.longitude;
|
|
double maxy = (startPos.latitude <= destination.latitude)
|
|
? destination.latitude
|
|
: startPos.latitude;
|
|
double maxx = (startPos.longitude <= destination.longitude)
|
|
? destination.longitude
|
|
: startPos.longitude;
|
|
|
|
double southWestLatitude = miny;
|
|
double southWestLongitude = minx;
|
|
|
|
double northEastLatitude = maxy;
|
|
double northEastLongitude = maxx;
|
|
|
|
// Accommodate the two locations within the
|
|
// camera view of the map
|
|
mapController.animateCamera(
|
|
CameraUpdate.newLatLngBounds(
|
|
LatLngBounds(
|
|
northeast: LatLng(northEastLatitude, northEastLongitude),
|
|
southwest: LatLng(southWestLatitude, southWestLongitude),
|
|
),
|
|
100.0,
|
|
),
|
|
);
|
|
|
|
// Calculating the distance between the start and the end positions
|
|
// with a straight path, without considering any route
|
|
// double distanceInMeters = await Geolocator.bearingBetween(
|
|
// startLatitude,
|
|
// startLongitude,
|
|
// destinationLatitude,
|
|
// destinationLongitude,
|
|
// );
|
|
|
|
await createPolylines(startPos, destination);
|
|
|
|
double totalDistance = 0.0;
|
|
|
|
// Calculating the total distance by adding the distance
|
|
// between small segments
|
|
for (int i = 0; i < polylineCoordinates.length - 1; i++) {
|
|
totalDistance += _coordinateDistance(
|
|
polylineCoordinates[i].latitude,
|
|
polylineCoordinates[i].longitude,
|
|
polylineCoordinates[i + 1].latitude,
|
|
polylineCoordinates[i + 1].longitude,
|
|
);
|
|
}
|
|
|
|
setState(() {
|
|
_placeDistance = totalDistance.toStringAsFixed(2);
|
|
print('DISTANCE: $_placeDistance km');
|
|
});
|
|
|
|
return true;
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Widget _textField({
|
|
TextEditingController controller,
|
|
FocusNode focusNode,
|
|
String label,
|
|
String hint,
|
|
double width,
|
|
Icon prefixIcon,
|
|
Widget suffixIcon,
|
|
}) {
|
|
return Container(
|
|
width: width * 0.8,
|
|
child: TextField(
|
|
controller: controller,
|
|
enabled: false,
|
|
focusNode: focusNode,
|
|
style: TextStyle(
|
|
color: Colors.black,
|
|
fontSize: SizeConfig.blockSizeHorizontal * 3.5,
|
|
),
|
|
decoration: new InputDecoration(
|
|
prefixIcon: prefixIcon,
|
|
suffixIcon: suffixIcon,
|
|
labelText: label,
|
|
filled: true,
|
|
fillColor: Colors.white,
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.all(
|
|
Radius.circular(10.0),
|
|
),
|
|
borderSide: BorderSide(
|
|
color: Colors.grey.shade400,
|
|
width: 2,
|
|
),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.all(
|
|
Radius.circular(10.0),
|
|
),
|
|
borderSide: BorderSide(
|
|
color: Colors.blue.shade300,
|
|
width: 2,
|
|
),
|
|
),
|
|
contentPadding: EdgeInsets.all(15),
|
|
hintText: hint,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|