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 {