package com.hypertrack.ridesharing.components.driver; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Point; import android.net.Uri; import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.LatLngBounds; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; import com.google.firebase.firestore.DocumentReference; import com.google.firebase.firestore.EventListener; import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.ListenerRegistration; import com.google.firebase.firestore.QueryDocumentSnapshot; import com.google.firebase.firestore.QuerySnapshot; import com.hypertrack.ridesharing.components.MapPresenter; import com.hypertrack.ridesharing.models.Order; import com.hypertrack.ridesharing.models.User; import com.hypertrack.ridesharing.parsers.OrderParser; import com.hypertrack.ridesharing.tracking.TrackingPresenter; import com.hypertrack.ridesharing.utils.TextFormatUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @SuppressWarnings("WeakerAccess") public class DriverMapPresenter extends MapPresenter<DriverMapPresenter.DriverView, DriverMapPresenter.DriverState> { private static final String TAG = "DriverMapPresenter"; private static final int ANIMATE_CAMERA_DURATION = 500; private final TrackingPresenter trackingPresenter; private final int mapCenterOffset; private ListenerRegistration newOrderListenerRegistration; public DriverMapPresenter(Context context, DriverMapPresenter.DriverView view) { super(context, view, new DriverState(context)); Resources r = context.getResources(); float paddingTop = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, r.getDisplayMetrics()); float paddingBottom = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 250, r.getDisplayMetrics()); mapCenterOffset = (int) ((paddingBottom - paddingTop) / 2); trackingPresenter = new TrackingPresenter(context, view); } @Override public void map(@NonNull GoogleMap googleMap) { super.map(googleMap); Log.d(TAG, "Device id: " + mState.getUser().deviceId); hyperTrackViews.subscribeToDeviceUpdates(mState.getUser().deviceId, this); hyperTrackMap.bind(hyperTrackViews, mState.getUser().deviceId); hyperTrackMap.adapter().addTripFilter(this); if (mState.getOrder() == null) { subscribeNewOrderUpdates(); mView.showDriverInfo(mState.getUser()); } } @Override protected boolean onMarkerClick(Marker marker) { if (marker.getTag() instanceof String) { String tag = (String) marker.getTag(); if (mState.newOrders.containsKey(tag)) { selectOrder(tag); return true; } } return false; } @Override protected void onOrderChanged() { super.onOrderChanged(); if (mState.getOrder() != null) { if (!TextUtils.isEmpty(mState.getOrder().tripId)) { hyperTrackMap.subscribeTrip(mState.getOrder().tripId); } mView.dismissDriverInfo(); if (!Order.COMPLETED.equals(mState.getOrder().status)) { mView.showOrderInfo(mState.getOrder().rider, TextFormatUtils.getDestinationName(mContext, mState.getOrder())); mView.hideInfoUserStartTripButton(); mView.hideInfoUserEndTripButton(); if (Order.REACHED_PICKUP.equals(mState.getOrder().status)) { mView.showInfoUserStartTripButton(); } else if (Order.REACHED_DROPOFF.equals(mState.getOrder().status)) { mView.showInfoUserEndTripButton(); } mView.showUI(); } else { mView.dismissOrderInfo(); } } else { mView.dismissOrderInfo(); mView.showDriverInfo(mState.getUser()); subscribeNewOrderUpdates(); } hyperTrackMap.adapter().notifyDataSetChanged(); } public void selectOrder(String orderId) { mState.selectedOrder = mState.newOrders.get(orderId); if (mState.selectedOrder != null) { moveToSelectedOrder(new GoogleMap.CancelableCallback() { @Override public void onFinish() { mView.showAcceptRide(mState.selectedOrder); } @Override public void onCancel() { } }); } } private void moveToSelectedOrder(GoogleMap.CancelableCallback callback) { if (googleMap != null && mState.selectedOrder != null) { Point locationInPx = googleMap.getProjection().toScreenLocation(mState.selectedOrder.pickup.getLatLng()); locationInPx.y = locationInPx.y + mapCenterOffset; LatLng latLng = googleMap.getProjection().fromScreenLocation(locationInPx); CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLng(latLng); googleMap.animateCamera(cameraUpdate, ANIMATE_CAMERA_DURATION, callback); } } public void acceptRide() { if (mState.selectedOrder != null) { mView.showProgressBar(); ObjectMapper mapper = new ObjectMapper(); DocumentReference washingtonRef = db.collection("orders").document(mState.selectedOrder.id); washingtonRef .update( "status", Order.ACCEPTED, "driver", mapper.convertValue(mState.getUser(), Map.class) ) .addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { mView.hideProgressBar(); if (newOrderListenerRegistration != null) { newOrderListenerRegistration.remove(); } mState.newOrders.remove(mState.selectedOrder.id); mState.selectedOrder.marker.remove(); mState.updateOrder(mState.selectedOrder); subscribeOrderUpdates(); Log.d(TAG, "DocumentSnapshot successfully updated!"); trackingPresenter.adjustTrackingState(); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { mView.hideProgressBar(); Log.w(TAG, "Error updating document", e); } }); } } public void startRide() { if (mState.getOrder() != null) { mView.showProgressBar(); DocumentReference washingtonRef = db.collection("orders").document(mState.getOrder().id); washingtonRef .update("status", Order.STARTED_RIDE) .addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { mView.hideProgressBar(); Log.d(TAG, "DocumentSnapshot successfully updated!"); trackingPresenter.adjustTrackingState(); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { mView.hideProgressBar(); Log.w(TAG, "Error updating document", e); } }); } } public void endRide() { if (mState.getOrder() != null) { mView.showProgressBar(); DocumentReference washingtonRef = db.collection("orders").document(mState.getOrder().id); washingtonRef .update("status", Order.COMPLETED) .addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { mView.hideProgressBar(); Log.d(TAG, "DocumentSnapshot successfully updated!"); trackingPresenter.adjustTrackingState(); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { mView.hideProgressBar(); Log.w(TAG, "Error updating document", e); } }); } } public void getDirections() { if (mState.getOrder() != null) { Uri gmmIntentUri; if (Order.PICKING_UP.equals(mState.getOrder().status)) { gmmIntentUri = Uri.parse("google.navigation:q=" + mState.getOrder().pickup.latitude + "," + mState.getOrder().pickup.longitude); } else { gmmIntentUri = Uri.parse("google.navigation:q=" + mState.getOrder().dropoff.latitude + "," + mState.getOrder().dropoff.longitude); } Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri); mapIntent.setPackage("com.google.android.apps.maps"); mContext.startActivity(mapIntent); } } private void subscribeNewOrderUpdates() { if (newOrderListenerRegistration != null) { newOrderListenerRegistration.remove(); } newOrderListenerRegistration = db.collection("orders") .whereIn("status", Arrays.asList(Order.NEW, Order.CANCELLED)) .addSnapshotListener(new EventListener<QuerySnapshot>() { @SuppressWarnings("ConstantConditions") @Override public void onEvent(@Nullable QuerySnapshot value, @Nullable FirebaseFirestoreException e) { if (e != null) { Log.w(TAG, "Listen failed.", e); return; } if (value != null) { List<Order> orders = new ArrayList<>(); OrderParser parser = new OrderParser(); for (QueryDocumentSnapshot doc : value) { if (Order.CANCELLED.equals(doc.getString("status"))) { if (mState.newOrders.containsKey(doc.getId())) { mState.newOrders.remove(doc.getId()).marker.remove(); } continue; } Order order = parser.parse(doc); orders.add(order); } updateNewOrders(orders); } } }); } private void updateNewOrders(List<Order> orders) { if (orders.isEmpty()) { return; } if (mState.newOrders.isEmpty()) { for (Order order : orders) { order.marker = mView.addMarker(order); order.marker.setTag(order.id); mState.newOrders.put(order.id, order); } // updateMapCamera(); } else { List<Order> newOrders = new ArrayList<>(); for (Order order : orders) { Order oldOrder = mState.newOrders.get(order.id); if (oldOrder == null) { order.marker = mView.addMarker(order); order.marker.setTag(order.id); mState.newOrders.put(order.id, order); newOrders.add(order); } else { oldOrder.update(order); oldOrder.marker.setPosition( new LatLng(order.pickup.latitude, order.pickup.longitude) ); } } mView.updateNotifications(newOrders); } } private void updateMapCamera() { if (!mState.newOrders.isEmpty()) { LatLngBounds.Builder builder = new LatLngBounds.Builder(); if (mState.currentLocation != null) { builder.include( new LatLng(mState.currentLocation.getLatitude(), mState.currentLocation.getLongitude()) ); } for (Order order : mState.newOrders.values()) { builder.include(order.pickup.getLatLng()); } animateCamera(builder.build()); } } @Override public void destroy() { super.destroy(); if (newOrderListenerRegistration != null) { newOrderListenerRegistration.remove(); } trackingPresenter.destroy(); } public interface DriverView extends MapPresenter.View, TrackingPresenter.View { Marker addMarker(Order order); void updateNotifications(Collection<Order> orders); void showAcceptRide(Order order); void showDriverInfo(User user); void dismissDriverInfo(); void showInfoUserStartTripButton(); void hideInfoUserStartTripButton(); void showInfoUserEndTripButton(); void hideInfoUserEndTripButton(); } public static class DriverState extends MapPresenter.State { public Map<String, Order> newOrders = new HashMap<>(); public Order selectedOrder; public DriverState(Context context) { super(context); } } }