diff --git a/.gitignore b/.gitignore index 24476c5..fdd6e81 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,7 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + + + +lib/constant/credential.dart \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 59d2002..7a9e91d 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,33 +1,24 @@ - - + + + + + + - + + + - + diff --git a/android/build.gradle b/android/build.gradle index f7eb7f6..5264885 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.9.0' repositories { google() mavenCentral() diff --git a/assets/images/picker.png b/assets/images/picker.png new file mode 100644 index 0000000..6a35bf3 Binary files /dev/null and b/assets/images/picker.png differ diff --git a/lib/constant/credential.dart b/lib/constant/credential.dart deleted file mode 100644 index 3b74e96..0000000 --- a/lib/constant/credential.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'dart:convert'; - -class AppCredintials { - static const String basicAuthCredentials = 'hamzaayedphp:malDEV@2101'; - static const String serverAPI = - 'AAAAinYllCo:APA91bF1shTpzSsSxqbfY6c60D8zs1ZsdIsl9ix6nl7GDdjCqWPRK0G0ub5SqFdb1jDpQDvQPxGg-697MWLo0sy3oYImBwBLObyhk0GjtNzyr0PbE3hI-pOvhf8Vp1xgUgBmofbZYXkH'; - // static const String mapAPIKEY = 'AIzaSyC1pjEgB78OFz_-2nwTvGltHjXho0y99MY'; - static const String mapAPIKEY = 'AIzaSyCyfwRXTwSTLOFQSQgN5p7QZgGJVZnEKq0'; - String getBasicAuthCredentials() { - return base64Encode(utf8.encode(basicAuthCredentials)); - } -} diff --git a/lib/constant/links.dart b/lib/constant/links.dart index fdf82b4..a66836f 100644 --- a/lib/constant/links.dart +++ b/lib/constant/links.dart @@ -1,5 +1,6 @@ class AppLink { static const String server = 'https://ride.mobile-app.store/'; + static const String googleMapsLink = 'https://maps.googleapis.com/maps/api/'; static const String test = "$server/test.php"; diff --git a/lib/controller/functions/crud.dart b/lib/controller/functions/crud.dart index 9738188..427628d 100644 --- a/lib/controller/functions/crud.dart +++ b/lib/controller/functions/crud.dart @@ -38,6 +38,7 @@ class CRUD { 'Basic ${base64Encode(utf8.encode(AppCredintials.basicAuthCredentials))}', }, ); + if (response.statusCode == 200) { var jsonData = jsonDecode(response.body); if (jsonData['status'] == 'success') { @@ -55,6 +56,26 @@ class CRUD { return (response.body); } + Future getGoogleApi({ + required String link, + Map? payload, + }) async { + var url = Uri.parse( + link, + ); + var response = await http.post( + url, + body: payload, + ); + print(response.request); + var jsonData = jsonDecode(response.body); + // print(jsonData); + if (jsonData['status'] == 'OK') { + return jsonData; + } + return (jsonData['status']); + } + Future update({ required String endpoint, required Map data, diff --git a/lib/controller/home/map_page_controller.dart b/lib/controller/home/map_page_controller.dart new file mode 100644 index 0000000..6987250 --- /dev/null +++ b/lib/controller/home/map_page_controller.dart @@ -0,0 +1,273 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'dart:math' as math; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:google_polyline_algorithm/google_polyline_algorithm.dart'; +import 'package:ride/constant/colors.dart'; +import 'package:ride/constant/credential.dart'; +import 'package:ride/constant/links.dart'; +import 'package:ride/constant/style.dart'; +import 'package:ride/controller/functions/crud.dart'; + +class MapController extends GetxController { + bool isloading = true; + TextEditingController placeController = TextEditingController(); + List data = []; + List bounds = []; + List places = []; + LatLngBounds? boundsdata; + List markers = []; + List polylines = []; + LatLng mylocation = const LatLng(32.111965, 36.067427); + LatLng mydestination = const LatLng(32.115295, 36.064773); + final List polylineCoordinates = []; + BitmapDescriptor markerIcon = BitmapDescriptor.defaultMarker; + double height = 200; + + changeHeight() { + if (places.isEmpty) { + height = 0; + update(); + } + height = 200; + update(); + } + + hidePlaces() { + height = 0; + + update(); + } + + Future getPlaces() async { + var url = + '${AppLink.googleMapsLink}place/nearbysearch/json?keyword=${placeController.text}&location=32.111946,${mylocation.longitude}&radius=10000&type=restaurant&language=ar&key=${AppCredintials.mapAPIKEY}'; + + var response = await CRUD().getGoogleApi(link: url, payload: {}); + + places = response['results']; + print(places); + update(); + } + + LatLng fromString(String location) { + List parts = location.split(','); + double lat = double.parse(parts[0]); + double lng = double.parse(parts[1]); + return LatLng(lat, lng); + } + + void clearpolyline() { + polylines = []; + polylineCoordinates.clear(); + update(); + } + + void addCustomPicker() { + ImageConfiguration config = const ImageConfiguration( + size: Size(20, 20), + // scale: 1.0, + ); + BitmapDescriptor.fromAssetImage(config, 'assets/images/picker.png') + .then((value) { + markerIcon = value; + update(); + }); + } + + GoogleMapController? mapController; + void onMapCreated(GoogleMapController controller) { + mapController = controller; + controller.getVisibleRegion(); + update(); + } + + getMap(String origin, destination) async { + var origin1 = fromString(origin); + var destination1 = fromString(destination); + isloading = false; + mydestination = destination1; + mylocation = origin1; + update(); + + var url = + ('${AppLink.googleMapsLink}directions/json?&language=en&avoid=tolls|ferries&destination=$destination&origin=$origin&key=${AppCredintials.mapAPIKEY}'); + + var response = await CRUD().getGoogleApi(link: url, payload: {}); + data = response['routes'][0]['legs']; + + final points = + decodePolyline(response["routes"][0]["overview_polyline"]["points"]); + for (int i = 0; i < points.length; i++) { + double lat = points[i][0].toDouble(); + double lng = points[i][1].toDouble(); + polylineCoordinates.add(LatLng(lat, lng)); + } +// // Define the northeast and southwest coordinates + final bounds = response["routes"][0]["bounds"]; +// LatLng northeast = +// LatLng(bounds['northeast']['lat'], bounds['northeast']['lng']); +// LatLng southwest = +// LatLng(bounds['southwest']['lat'], bounds['southwest']['lng']); + +// // Create the LatLngBounds using the coordinates +// boundsdata = LatLngBounds(northeast: northeast, southwest: southwest); +// update(); + // print(boundsdata); +//////////////////////////////////////////// + // Create the northeast and southwest LatLng objects + LatLng northeast = + LatLng(bounds['northeast']['lat'], bounds['northeast']['lng']); + LatLng southwest = + LatLng(bounds['southwest']['lat'], bounds['southwest']['lng']); + + // Create the LatLngBounds object + LatLngBounds boundsData = + LatLngBounds(northeast: northeast, southwest: southwest); + + // Calculate padding for the bounding box + double distance = math.sqrt( + math.pow(northeast.latitude - southwest.latitude, 2) + + math.pow(northeast.longitude - southwest.longitude, 2), + ); + + // Define the map padding + final double padding = 50.0; + + // Get the screen dimensions + final screenSize = Get.size; + print(screenSize.width); + print('================'); + // Adjust the bounding box to include padding + LatLngBounds adjustedBounds = LatLngBounds( + southwest: LatLng(boundsData.southwest.latitude - padding, + boundsData.southwest.longitude - padding), + northeast: LatLng(boundsData.northeast.latitude + padding, + boundsData.northeast.longitude + padding), + ); +// Calculate the zoom level based on the distance and screen size + double zoomLevel = getZoomLevel(distance, screenSize.width); + // Animate the camera to the adjusted bounds + mapController!.animateCamera(CameraUpdate.newLatLngBounds( + adjustedBounds, + screenSize.width, + )); + + if (polylines.isNotEmpty) { + clearpolyline(); + } else { + var polyline = Polyline( + polylineId: PolylineId(response["routes"][0]["summary"]), + points: polylineCoordinates, + width: 10, + color: Colors.blue, + ); + + polylines.add(polyline); + update(); + } + } + + double getZoomLevel(double distance, double screenWidth) { + const double zoomFactor = 15.0; + const double pixelRatio = + 156543.03392; // Earth circumference in pixels at zoom level 0 + + double zoomLevel = + (math.log(pixelRatio * screenWidth / (distance * zoomFactor))) / + math.log(2); + return zoomLevel; + } + + double getDistanceFromText(String distanceText) { + // Remove any non-digit characters from the distance text + String distanceValue = distanceText.replaceAll(RegExp(r'[^0-9.]+'), ''); + + // Parse the extracted numerical value as a double + double distance = double.parse(distanceValue); + + return distance; + } + + void bottomSheet() { + String distanceText = data[0]['distance']['text']; + String durationText = data[0]['duration']['text']; + double distance = getDistanceFromText(distanceText); + double duration = getDistanceFromText(durationText); + double cost = distance * 0.21; + double costDuration = duration * 0.05; + double totalPassenger = cost + costDuration; + Get.bottomSheet( + Container( + height: 130, + color: AppColor.secondaryColor, + child: data.isEmpty + ? Center( + child: Text( + 'Where are you want to go..', + style: AppStyle.title, + )) + : Center( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + 'distance is ${data[0]['distance']['text']}', + style: AppStyle.title, + ), + Text( + 'duration is ${data[0]['duration']['text']}', + style: AppStyle.title, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + 'Cost for .21/km $cost ', + style: AppStyle.title, + ), + Text( + 'Cost duration .05/m $costDuration ', + style: AppStyle.title, + ), + ], + ), + Text( + 'Total cost $totalPassenger ', + style: AppStyle.title, + ), + ], + ), + ), + ), + elevation: 6, + enableDrag: true, + isDismissible: true, + useRootNavigator: true, + backgroundColor: AppColor.secondaryColor, + barrierColor: AppColor.accentColor.withOpacity(.4), + persistent: true, + ); + } + + List polylineCoordinate = []; + double calculateCost(double distance) { + const double costRate = 0.27; + // double distanceInKm = distance / 1000; // convert distance to kilometers + double cost = costRate * distance; + return cost; + } + + @override + void onInit() { + // getPolyLine(); + // getMap(); + addCustomPicker(); + + super.onInit(); + } +} diff --git a/lib/main.dart b/lib/main.dart index 3823fd0..2d56ff0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,7 +8,7 @@ import 'constant/box_name.dart'; import 'controller/local/local_controller.dart'; import 'controller/local/translations.dart'; import 'firebase_options.dart'; -import 'views/home/auth/login_page.dart'; +import 'views/home/map_page.dart'; final box = GetStorage(); void main() async { @@ -30,7 +30,7 @@ class MyApp extends StatelessWidget { LocaleController controller = Get.put(LocaleController()); return GetMaterialApp( - title: 'Fleek-tech', + title: 'Ride', translations: MyTranslation(), debugShowCheckedModeBanner: false, locale: controller.language, @@ -40,6 +40,6 @@ class MyApp extends StatelessWidget { home: box.read(BoxName.lang).toString() != 'ar' && box.read(BoxName.lang).toString() != 'en' ? const Language() - : LoginPage()); + : const MapPage()); } } diff --git a/lib/views/home/auth/login_page.dart b/lib/views/auth/login_page.dart similarity index 96% rename from lib/views/home/auth/login_page.dart rename to lib/views/auth/login_page.dart index 1b5b38f..48b79e6 100644 --- a/lib/views/home/auth/login_page.dart +++ b/lib/views/auth/login_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import '../../../controller/auth/login_controller.dart'; +import '../../controller/auth/login_controller.dart'; class LoginPage extends StatelessWidget { final controller = Get.put(LoginController()); diff --git a/lib/views/home/map_page.dart b/lib/views/home/map_page.dart new file mode 100644 index 0000000..ab37549 --- /dev/null +++ b/lib/views/home/map_page.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:ride/constant/colors.dart'; +import 'package:ride/controller/home/map_page_controller.dart'; + +class MapPage extends StatelessWidget { + const MapPage({super.key}); + + @override + Widget build(BuildContext context) { + Get.put(MapController()); + return Scaffold( + body: GetBuilder( + builder: (controller) => Stack( + children: [ + GoogleMap( + onMapCreated: controller.onMapCreated, + cameraTargetBounds: CameraTargetBounds(controller.boundsdata), + minMaxZoomPreference: const MinMaxZoomPreference(12, 18), + onLongPress: (argument) { + Get.defaultDialog( + title: 'Are you want to go to this site', + content: Column( + children: [ + Text('${argument.latitude},${argument.longitude}'), + ], + ), + onConfirm: () { + controller.clearpolyline(); + controller.getMap('32.111946, 36.067396', + '${argument.latitude.toString()},${argument.longitude.toString()}'); + + Get.back(); + controller.bottomSheet(); + }, + ); + }, + onTap: (argument) { + controller.hidePlaces(); + controller.bottomSheet(); + }, + initialCameraPosition: CameraPosition( + target: controller.mylocation, + zoom: 15, + ), + markers: { + Marker( + markerId: const MarkerId('MyLocation'), + position: controller.mylocation, + draggable: true, + icon: controller.markerIcon, + onDragEnd: (value) { + print(value); + }, + infoWindow: const InfoWindow(title: 'my location'), + ), + Marker( + markerId: const MarkerId('destination'), + position: controller.mydestination), + }, + polylines: { + Polyline( + polylineId: const PolylineId('route'), + points: controller.polylineCoordinates, + color: AppColor.primaryColor, + width: 3, + ) + }, + mapType: MapType.normal, + myLocationButtonEnabled: true, + indoorViewEnabled: true, + trafficEnabled: true, + ), + Positioned( + top: 10, + left: 0, + right: 0, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Container( + decoration: + const BoxDecoration(color: AppColor.secondaryColor), + child: TextField( + decoration: const InputDecoration( + suffixIcon: Icon(Icons.search)), + controller: controller.placeController, + onChanged: (value) { + if (controller.placeController.text.length > 6) { + controller.getPlaces(); + controller.changeHeight(); + } + }, + // onEditingComplete: () => controller.changeHeight(), + ), + ), + ), + Container( + height: + controller.places.isNotEmpty ? controller.height : 0, + color: AppColor.secondaryColor, + child: ListView.builder( + itemCount: controller.places.length, + itemBuilder: (BuildContext context, int index) { + var res = controller.places[index]; + return TextButton( + onPressed: () { + controller.changeHeight(); + Get.defaultDialog( + title: 'Are You sure to ride to ${res['name']}', + middleText: '', + onConfirm: () { + controller.getMap( + '${controller.mylocation.latitude.toString()},${controller.mylocation.longitude.toString()}', + "${res['geometry']['location']['lat']},${res['geometry']['location']['lng']}"); + controller.places = []; + Get.back(); + }, + ); + }, + child: Text( + res['name'].toString(), + ), + ); + }, + ), + ) + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 31901d0..ef77a02 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,7 +8,7 @@ import Foundation import firebase_core import firebase_messaging import flutter_local_notifications -import geolocator_apple +import location import path_provider_foundation import sqflite import url_launcher_macos @@ -17,7 +17,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) - GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) + LocationPlugin.register(with: registry.registrar(forPlugin: "LocationPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 26bb0f7..9eb847f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -97,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + custom_searchable_dropdown: + dependency: "direct main" + description: + name: custom_searchable_dropdown + sha256: c2676b1ee55f0b71a7cd890ae473cf97651f018b2695b3bf57c8c28a14e2fa95 + url: "https://pub.dev" + source: hosted + version: "2.1.1" dbus: dependency: transitive description: @@ -214,14 +222,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.15" - flutter_polyline_points: - dependency: "direct main" - description: - name: flutter_polyline_points - sha256: "02699e69142f51a248d784b6e3eec524194467fca5f7c4da19699ce2368b6980" - url: "https://pub.dev" - source: hosted - version: "1.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -232,54 +232,6 @@ packages: description: flutter source: sdk version: "0.0.0" - geolocator: - dependency: "direct main" - description: - name: geolocator - sha256: "5c23f3613f50586c0bbb2b8f970240ae66b3bd992088cf60dd5ee2e6f7dde3a8" - url: "https://pub.dev" - source: hosted - version: "9.0.2" - geolocator_android: - dependency: transitive - description: - name: geolocator_android - sha256: "835ff5b4888a2f8eba128996494faf9c5d422785322a81dc0565b99e0f6c379d" - url: "https://pub.dev" - source: hosted - version: "4.2.2" - geolocator_apple: - dependency: transitive - description: - name: geolocator_apple - sha256: "36527c555f4c425f7d8fa8c7c07d67b78e3ff7590d40448051959e1860c1cfb4" - url: "https://pub.dev" - source: hosted - version: "2.2.7" - geolocator_platform_interface: - dependency: transitive - description: - name: geolocator_platform_interface - sha256: af4d69231452f9620718588f41acc4cb58312368716bfff2e92e770b46ce6386 - url: "https://pub.dev" - source: hosted - version: "4.0.7" - geolocator_web: - dependency: transitive - description: - name: geolocator_web - sha256: f68a122da48fcfff68bbc9846bb0b74ef651afe84a1b1f6ec20939de4d6860e1 - url: "https://pub.dev" - source: hosted - version: "2.1.6" - geolocator_windows: - dependency: transitive - description: - name: geolocator_windows - sha256: f5911c88e23f48b598dd506c7c19eff0e001645bdc03bb6fecb9f4549208354d - url: "https://pub.dev" - source: hosted - version: "0.1.1" get: dependency: "direct main" description: @@ -360,6 +312,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.3" + google_polyline_algorithm: + dependency: "direct main" + description: + name: google_polyline_algorithm + sha256: "357874f00d3f93c3ba1bf4b4d9a154aa9ee87147c068238c1e8392012b686a03" + url: "https://pub.dev" + source: hosted + version: "3.1.0" google_sign_in: dependency: "direct main" description: @@ -456,6 +416,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + location: + dependency: "direct main" + description: + name: location + sha256: c8e0c8cd92c6ce8b42760f9aafd6d80592459e2e0cd702bc336061c738ec4715 + url: "https://pub.dev" + source: hosted + version: "5.0.2+1" + location_platform_interface: + dependency: transitive + description: + name: location_platform_interface + sha256: a211a41b1fdfaddaf02996750dd703abcc7ac1d4fd90e978c8773ccf2260af68 + url: "https://pub.dev" + source: hosted + version: "3.1.1" + location_web: + dependency: transitive + description: + name: location_web + sha256: acde3e95c4dec2b82cbdc3490bf010eeaf66e793e20f62364887e072bd701d49 + url: "https://pub.dev" + source: hosted + version: "4.1.1" lottie: dependency: "direct main" description: @@ -757,14 +741,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.7" - uuid: - dependency: transitive - description: - name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" - url: "https://pub.dev" - source: hosted - version: "3.0.7" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2e42249..96eb025 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,9 +38,7 @@ dependencies: firebase_messaging: ^14.6.5 firebase_core: ^2.15.0 flutter_local_notifications: ^15.1.0+1 - geolocator: ^9.0.2 google_maps_flutter: ^2.4.0 - flutter_polyline_points: ^1.0.0 sqflite: ^2.3.0 path: ^1.8.3 google_sign_in: ^6.1.4 @@ -51,6 +49,9 @@ dependencies: get: ^4.6.5 get_storage: ^2.1.1 url_launcher: ^6.1.12 + location: ^5.0.2+1 + google_polyline_algorithm: ^3.1.0 + custom_searchable_dropdown: ^2.1.1 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 16039c1..ec8e8d4 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,14 +7,11 @@ #include "generated_plugin_registrant.h" #include -#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { FirebaseCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); - GeolocatorWindowsRegisterWithRegistrar( - registry->GetRegistrarForPlugin("GeolocatorWindows")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index e30096f..02d26c3 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,7 +4,6 @@ list(APPEND FLUTTER_PLUGIN_LIST firebase_core - geolocator_windows url_launcher_windows )