diff --git a/FoodifyServer/app/build.gradle b/FoodifyServer/app/build.gradle
index f62faf0..ca7d8bc 100644
--- a/FoodifyServer/app/build.gradle
+++ b/FoodifyServer/app/build.gradle
@@ -1,5 +1,6 @@
plugins {
id 'com.android.application'
+ id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}
android {
@@ -54,6 +55,11 @@ dependencies {
implementation 'com.github.jd-alexander:android-flat-button:v1.1'
implementation 'com.rengwuxian.materialedittext:library:2.1.4'
implementation 'com.jaredrummler:material-spinner:1.1.0'
+ implementation 'com.google.android.gms:play-services-maps:10.2.0'
+ implementation 'com.google.android.gms:play-services:10.2.0'
+
+ implementation 'com.squareup.retrofit2:retrofit:2.3.0'
+ implementation 'com.squareup.retrofit2:converter-scalars:2.1.0'
}
apply plugin: 'com.google.gms.google-services'
\ No newline at end of file
diff --git a/FoodifyServer/app/src/main/AndroidManifest.xml b/FoodifyServer/app/src/main/AndroidManifest.xml
index 2bb91a4..907743a 100644
--- a/FoodifyServer/app/src/main/AndroidManifest.xml
+++ b/FoodifyServer/app/src/main/AndroidManifest.xml
@@ -3,6 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.waterbase.foodifyServer">
+
+
+
+
+
+
+
+
diff --git a/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/Common/Common.java b/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/Common/Common.java
index 36f0d0c..4e6ec61 100644
--- a/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/Common/Common.java
+++ b/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/Common/Common.java
@@ -1,14 +1,25 @@
package com.waterbase.foodifyServer.Common;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+
+import com.waterbase.foodifyServer.Model.Request;
import com.waterbase.foodifyServer.Model.User;
+import com.waterbase.foodifyServer.Remote.IGeoCoordinates;
+import com.waterbase.foodifyServer.Remote.RetrofitClient;
public class Common {
public static User currentUser;
+ public static Request currentRequest;
public static final String UPDATE = "Cập nhật";
public static final String DELETE = "Xoá";
public static final int PICK_IMAGE_REQUEST = 71;
+ public static final String baseUrl = "https://maps.googleapis.com";
+
public static String coverCodeToStatus(String code){
if(code.equals("0"))
return "Placed";
@@ -17,4 +28,25 @@ public class Common {
else
return "Shipped";
}
+
+ public static IGeoCoordinates getGeoCodeService() {
+ return RetrofitClient.getClient(baseUrl).create(IGeoCoordinates.class);
+ }
+
+ public static Bitmap scaleBitmap(Bitmap bitmap, int newWidth, int newHeight) {
+ Bitmap scaledBitmap = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
+
+ float scaleX = newWidth / (float)bitmap.getWidth();
+ float scaleY = newWidth / (float)bitmap.getHeight();
+ float pivotX = 0, pivotY = 0;
+
+ Matrix scaleMatrix = new Matrix();
+ scaleMatrix.setScale(scaleX, scaleY, pivotX, pivotY);
+
+ Canvas canvas = new Canvas(scaledBitmap);
+ canvas.setMatrix(scaleMatrix);
+ canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG));
+
+ return scaledBitmap;
+ }
}
diff --git a/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/Common/DirectionJSONParser.java b/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/Common/DirectionJSONParser.java
new file mode 100644
index 0000000..c58833d
--- /dev/null
+++ b/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/Common/DirectionJSONParser.java
@@ -0,0 +1,100 @@
+package com.waterbase.foodifyServer.Common;
+
+import com.google.android.gms.maps.model.LatLng;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class DirectionJSONParser {
+
+ /**
+ * Receives a JSONObject and returns a list of lists containing latitude and longitude
+ */
+ public List>> parse(JSONObject jObject) {
+
+ List>> routes = new ArrayList>>();
+ JSONArray jRoutes = null;
+ JSONArray jLegs = null;
+ JSONArray jSteps = null;
+
+ try {
+
+ jRoutes = jObject.getJSONArray("routes");
+
+ /** Traversing all routes */
+ for (int i = 0; i < jRoutes.length(); i++) {
+ jLegs = ((JSONObject) jRoutes.get(i)).getJSONArray("legs");
+ List path = new ArrayList>();
+
+ /** Traversing all legs */
+ for (int j = 0; j < jLegs.length(); j++) {
+ jSteps = ((JSONObject) jLegs.get(j)).getJSONArray("steps");
+
+ /** Traversing all steps */
+ for (int k = 0; k < jSteps.length(); k++) {
+ String polyline = "";
+ polyline = (String) ((JSONObject) ((JSONObject) jSteps.get(k)).get("polyline")).get("points");
+ List list = decodePoly(polyline);
+
+ /** Traversing all points */
+ for (int l = 0; l < list.size(); l++) {
+ HashMap hm = new HashMap();
+ hm.put("lat", Double.toString(((LatLng) list.get(l)).latitude));
+ hm.put("lng", Double.toString(((LatLng) list.get(l)).longitude));
+ path.add(hm);
+ }
+ }
+ routes.add(path);
+ }
+ }
+
+ } catch (JSONException e) {
+ e.printStackTrace();
+ } catch (Exception e) {
+ }
+
+ return routes;
+ }
+
+ /**
+ * Method to decode polyline points
+ * Courtesy : http://jeffreysambells.com/2010/05/27/decoding-polylines-from-google-maps-direction-api-with-java
+ */
+ private List decodePoly(String encoded) {
+ List poly = new ArrayList();
+ int index = 0, len = encoded.length();
+ int lat = 0, lng = 0;
+
+ while (index < len) {
+ int b, shift = 0, result = 0;
+ do {
+ b = encoded.charAt(index++) - 63;
+ result |= (b & 0x1f) << shift;
+ shift += 5;
+ } while (b >= 0x20);
+ int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
+ lat += dlat;
+
+ shift = 0;
+ result = 0;
+ do {
+ b = encoded.charAt(index++) - 63;
+ result |= (b & 0x1f) << shift;
+ shift += 5;
+ } while (b >= 0x20);
+ int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
+ lng += dlng;
+
+ LatLng p = new LatLng((((double) lat / 1E5)),
+ (((double) lng / 1E5)));
+ poly.add(p);
+ }
+
+ return poly;
+ }
+}
diff --git a/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/OrderStatus.java b/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/OrderStatus.java
index 66cdc1a..d9b509b 100644
--- a/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/OrderStatus.java
+++ b/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/OrderStatus.java
@@ -7,6 +7,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.DialogInterface;
+import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
@@ -68,7 +69,9 @@ public class OrderStatus extends AppCompatActivity {
viewHolder.setItemClickListener(new ItemClickListener() {
@Override
public void onClick(View view, int position, boolean isLongClick) {
-
+ Intent trackingOrder = new Intent(OrderStatus.this, TrackingOrder.class);
+ Common.currentRequest = model;
+ startActivity(trackingOrder);
}
});
}
diff --git a/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/Remote/IGeoCoordinates.java b/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/Remote/IGeoCoordinates.java
new file mode 100644
index 0000000..1ca1401
--- /dev/null
+++ b/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/Remote/IGeoCoordinates.java
@@ -0,0 +1,13 @@
+package com.waterbase.foodifyServer.Remote;
+
+import retrofit2.Call;
+import retrofit2.http.GET;
+import retrofit2.http.Query;
+
+public interface IGeoCoordinates {
+ @GET("maps/api/geocode/json")
+ Call getGeoCode(@Query("address") String address);
+
+ @GET("maps/api/directions/json")
+ Call getDirections(@Query("origin") String origin, @Query("destination") String destination);
+}
diff --git a/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/Remote/RetrofitClient.java b/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/Remote/RetrofitClient.java
new file mode 100644
index 0000000..609650c
--- /dev/null
+++ b/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/Remote/RetrofitClient.java
@@ -0,0 +1,19 @@
+package com.waterbase.foodifyServer.Remote;
+
+import retrofit2.Retrofit;
+import retrofit2.converter.scalars.ScalarsConverterFactory;
+
+public class RetrofitClient {
+ private static Retrofit retrofit = null;
+
+ public static Retrofit getClient(String baseUrl) {
+ if(retrofit == null) {
+ retrofit = new Retrofit.Builder()
+ .baseUrl(baseUrl)
+ .addConverterFactory(ScalarsConverterFactory.create())
+ .build();
+ }
+
+ return retrofit;
+ }
+}
diff --git a/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/TrackingOrder.java b/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/TrackingOrder.java
new file mode 100644
index 0000000..e1a5028
--- /dev/null
+++ b/FoodifyServer/app/src/main/java/com/waterbase/foodifyServer/TrackingOrder.java
@@ -0,0 +1,336 @@
+package com.waterbase.foodifyServer;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.app.ActivityCompat;
+import androidx.fragment.app.FragmentActivity;
+import androidx.loader.content.AsyncTaskLoader;
+
+import android.Manifest;
+import android.app.ProgressDialog;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.location.Location;
+
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.widget.Toast;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GooglePlayServicesUtil;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.location.LocationListener;
+import com.google.android.gms.location.LocationRequest;
+import com.google.android.gms.location.LocationServices;
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.OnMapReadyCallback;
+import com.google.android.gms.maps.SupportMapFragment;
+import com.google.android.gms.maps.model.BitmapDescriptorFactory;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.MarkerOptions;
+import com.google.android.gms.maps.model.PolylineOptions;
+import com.waterbase.foodifyServer.Common.Common;
+import com.waterbase.foodifyServer.Common.DirectionJSONParser;
+import com.waterbase.foodifyServer.Remote.IGeoCoordinates;
+import com.waterbase.foodifyServer.databinding.ActivityTrackingOrderBinding;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+public class TrackingOrder extends FragmentActivity implements OnMapReadyCallback,
+ GoogleApiClient.ConnectionCallbacks,
+ GoogleApiClient.OnConnectionFailedListener,
+ LocationListener {
+
+ private GoogleMap mMap;
+ private ActivityTrackingOrderBinding binding;
+
+ private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 1000;
+ private final static int LOCATION_PERMISSION_REQUEST = 1001;
+
+ private Location mLastLocation;
+
+ private GoogleApiClient mGoogleApiClient;
+ private LocationRequest mLocationRequest;
+
+ private static int UPDATE_INTERVAL = 1000;
+ private static int FATEST_INTERVAL = 5000;
+ private static int DISPLACEMENT = 10;
+
+ private IGeoCoordinates mService;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ binding = ActivityTrackingOrderBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ mService = Common.getGeoCodeService();
+
+ if(ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
+ && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ requestRuntimePermission();
+ } else {
+ if(checkPlayServices()) {
+ buildGoogleApiClient();
+ createLocationRequest();
+ }
+ }
+
+ // Obtain the SupportMapFragment and get notified when the map is ready to be used.
+ SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.map);
+ mapFragment.getMapAsync(this);
+ }
+
+ private void createLocationRequest() {
+ mLocationRequest = new LocationRequest();
+ mLocationRequest.setInterval(UPDATE_INTERVAL);
+ mLocationRequest.setFastestInterval(FATEST_INTERVAL);
+ mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
+ mLocationRequest.setSmallestDisplacement(DISPLACEMENT);
+ }
+ private void displayLocation() {
+ if(ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
+ && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ requestRuntimePermission();
+ } else {
+ mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
+ if(mLastLocation != null) {
+ double latitude = mLastLocation.getLatitude();
+ double longitude = mLastLocation.getLongitude();
+
+ //Add Marker in your location and move the camera
+ LatLng yourLocation = new LatLng(latitude, longitude);
+ mMap.addMarker(new MarkerOptions().position(yourLocation).title("Vị trí của bạn"));
+ mMap.moveCamera(CameraUpdateFactory.newLatLng(yourLocation));
+ mMap.animateCamera(CameraUpdateFactory.zoomTo(17.0f));
+
+ //After add Marker for your location, Add Marker for this Order and draw route
+ drawRoute(yourLocation, Common.currentRequest.getAddress());
+ } else {
+ Toast.makeText(this, "Không thể lấy được vị trí của bạn", Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ private void drawRoute(LatLng yourLocation, String address) {
+ mService.getGeoCode(address).enqueue(new Callback() {
+ @Override
+ public void onResponse(Call call, Response response) {
+ try {
+ JSONObject jsonObject = new JSONObject(response.body());
+
+ String lat = ((JSONArray)jsonObject.get("results"))
+ .getJSONObject(0)
+ .getJSONObject("geometry")
+ .getJSONObject("location")
+ .get("lat").toString();
+
+ String lng = ((JSONArray)jsonObject.get("results"))
+ .getJSONObject(0)
+ .getJSONObject("geometry")
+ .getJSONObject("location")
+ .get("lng").toString();
+
+ LatLng orderLocation = new LatLng(Double.parseDouble(lat), Double.parseDouble(lng));
+
+ Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.box);
+ bitmap = Common.scaleBitmap(bitmap, 70, 70);
+
+ MarkerOptions marker = new MarkerOptions().icon(BitmapDescriptorFactory.fromBitmap(bitmap))
+ .title("Đơn hàng " + Common.currentRequest.getPhone())
+ .position(orderLocation);
+ mMap.addMarker(marker);
+
+ //draw route
+ mService.getDirections(yourLocation.latitude + ", " + yourLocation.longitude,
+ orderLocation.latitude + ", " + orderLocation.longitude)
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(Call call, Response response) {
+ new ParserTask().execute(response.body().toString());
+ }
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+
+ }
+ });
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+
+ }
+ });
+ }
+
+ protected synchronized void buildGoogleApiClient() {
+ mGoogleApiClient = new GoogleApiClient.Builder(this)
+ .addConnectionCallbacks(this)
+ .addOnConnectionFailedListener(this)
+ .addApi(LocationServices.API).build();
+ }
+
+ private boolean checkPlayServices() {
+ int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
+ if(resultCode != ConnectionResult.SUCCESS){
+ if(GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
+ GooglePlayServicesUtil.getErrorDialog(resultCode, this, PLAY_SERVICES_RESOLUTION_REQUEST).show();
+ } else {
+ Toast.makeText(this, "This device is not support", Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ return false;
+ }
+ return true;
+ }
+
+ private void requestRuntimePermission() {
+ ActivityCompat.requestPermissions(this, new String[]
+ {
+ Manifest.permission.ACCESS_COARSE_LOCATION,
+ Manifest.permission.ACCESS_FINE_LOCATION
+ }, LOCATION_PERMISSION_REQUEST);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ switch (requestCode) {
+ case LOCATION_PERMISSION_REQUEST:
+ if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
+ if(checkPlayServices()) {
+ buildGoogleApiClient();
+ createLocationRequest();
+
+ displayLocation();
+ }
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void onMapReady(GoogleMap googleMap) {
+ mMap = googleMap;
+ }
+
+ @Override
+ public void onLocationChanged(@NonNull Location location) {
+ mLastLocation = location;
+ displayLocation();
+ }
+
+ @Override
+ public void onConnected(@Nullable Bundle bundle) {
+ displayLocation();
+ startLocationUpdates();
+ }
+
+ private void startLocationUpdates() {
+ if(ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
+ && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
+ }
+
+ @Override
+ public void onConnectionSuspended(int i) {
+ mGoogleApiClient.connect();
+ }
+
+ @Override
+ public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
+
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ checkPlayServices();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if(mGoogleApiClient != null)
+ mGoogleApiClient.connect();
+ }
+
+ private class ParserTask extends AsyncTask>>> {
+ ProgressDialog mDialog = new ProgressDialog(TrackingOrder.this);
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ mDialog.setMessage("Xin vui lòng đợi...");
+ mDialog.show();
+ }
+
+ @Override
+ protected List>> doInBackground(String... strings) {
+ JSONObject jsonObject;
+ List>> routes = null;
+ try {
+ jsonObject = new JSONObject(strings[0]);
+ DirectionJSONParser parser = new DirectionJSONParser();
+
+ routes = parser.parse(jsonObject);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return routes;
+ }
+
+ @Override
+ protected void onPostExecute(List>> lists) {
+ mDialog.dismiss();
+
+ ArrayList points;
+ PolylineOptions lineOptions = null;
+
+ for(int i = 0; i < lists.size(); i++){
+ points = new ArrayList();
+ lineOptions = new PolylineOptions();
+
+ List> path = lists.get(i);
+
+ for(int j = 0; j < path.size(); j++) {
+ HashMap point = path.get(j);
+
+ double lat = Double.parseDouble(point.get("lat"));
+ double lng = Double.parseDouble(point.get("lng"));
+ LatLng position = new LatLng(lat, lng);
+
+ points.add(position);
+ }
+
+ lineOptions.addAll(points);
+ lineOptions.width(12);
+ lineOptions.color(Color.RED);
+ lineOptions.geodesic(true);
+ }
+ mMap.addPolyline(lineOptions);
+ }
+ }
+}
\ No newline at end of file
diff --git a/FoodifyServer/app/src/main/res/drawable/box.png b/FoodifyServer/app/src/main/res/drawable/box.png
new file mode 100644
index 0000000..946781a
Binary files /dev/null and b/FoodifyServer/app/src/main/res/drawable/box.png differ
diff --git a/FoodifyServer/app/src/main/res/layout/activity_tracking_order.xml b/FoodifyServer/app/src/main/res/layout/activity_tracking_order.xml
new file mode 100644
index 0000000..2c59b6a
--- /dev/null
+++ b/FoodifyServer/app/src/main/res/layout/activity_tracking_order.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/FoodifyServer/app/src/main/res/values/strings.xml b/FoodifyServer/app/src/main/res/values/strings.xml
index 93dc8e7..6145b01 100644
--- a/FoodifyServer/app/src/main/res/values/strings.xml
+++ b/FoodifyServer/app/src/main/res/values/strings.xml
@@ -12,4 +12,5 @@
Home
Gallery
Slideshow
+ TrackingOrder
\ No newline at end of file
diff --git a/FoodifyServer/build.gradle b/FoodifyServer/build.gradle
index fc54f61..8ed7b54 100644
--- a/FoodifyServer/build.gradle
+++ b/FoodifyServer/build.gradle
@@ -12,6 +12,7 @@ buildscript {
plugins {
id 'com.android.application' version '7.2.0' apply false
id 'com.android.library' version '7.2.0' apply false
+ id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.0' apply false
}
allprojects {