org.croudtrip.fragments.offer.MyTripDriverFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.croudtrip.fragments.offer.MyTripDriverFragment.java

Source

/*
 * The CroudTrip! application aims at revolutionizing the car-ride-sharing market with its easy,
 * user-friendly and highly automated way of organizing shared Trips. Copyright (C) 2015  Nazeeh Ammari,
 *  Philipp Eichhorn, Ricarda Hohn, Vanessa Lange, Alexander Popp, Frederik Simon, Michael Weber
 * This program is free software: you can redistribute it and/or modify  it under the terms of the GNU
 *  Affero General Public License as published by the Free Software Foundation, either version 3 of the
 *   License, or (at your option) any later version.
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Affero General Public License for more details.
 *  You should have received a copy of the GNU Affero General Public License along with this program.
 *    If not, see http://www.gnu.org/licenses/.
 */

package org.croudtrip.fragments.offer;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.Location;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

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.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.pnikosis.materialishprogress.ProgressWheel;

import org.croudtrip.Constants;
import org.croudtrip.R;
import org.croudtrip.api.TripsResource;
import org.croudtrip.api.directions.NavigationResult;
import org.croudtrip.api.directions.RouteLocation;
import org.croudtrip.api.trips.JoinTripRequest;
import org.croudtrip.api.trips.JoinTripRequestUpdate;
import org.croudtrip.api.trips.JoinTripRequestUpdateType;
import org.croudtrip.api.trips.JoinTripStatus;
import org.croudtrip.api.trips.TripOffer;
import org.croudtrip.api.trips.TripOfferDescription;
import org.croudtrip.api.trips.TripOfferUpdate;
import org.croudtrip.api.trips.UserWayPoint;
import org.croudtrip.fragments.SubscriptionFragment;
import org.croudtrip.location.LocationUpdater;
import org.croudtrip.trip.MyTripDriverPassengersAdapter;
import org.croudtrip.trip.OnDiversionUpdateListener;
import org.croudtrip.utils.CrashCallback;
import org.croudtrip.utils.DefaultTransformer;
import org.croudtrip.utils.SwipeListener;

import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

import javax.inject.Inject;

import it.neokree.materialnavigationdrawer.MaterialNavigationDrawer;
import it.neokree.materialnavigationdrawer.elements.MaterialSection;
import roboguice.inject.InjectView;
import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
import timber.log.Timber;

/**
 * This class shows a screen for the driver after he has offered a trip and hence is currently
 * already on his way. He is shown a map, his earnings and the passengers that he has accepted.
 *
 * @author Frederik Simon, Vanessa Lange
 */
public class MyTripDriverFragment extends SubscriptionFragment {

    //************************* Variables ****************************//

    // Route/Navigation
    public static final String ARG_ACTION = "ARG_ACTION";
    public static final String ACTION_LOAD = "ACTION_LOAD";
    public static final String ACTION_CREATE = "ACTION_CREATE";

    private GoogleMap googleMap;

    private NfcAdapter nfcAdapter;
    private NdefMessage ndefMessage;

    private long offerID = -1;

    // Passengers list
    private MyTripDriverPassengersAdapter adapter;
    private SwipeListener touchListener;

    @InjectView(R.id.rv_my_trip_driver_passengers)
    private RecyclerView recyclerView;

    private ProgressWheel mapProgressBar;
    private ProgressWheel passengersProgressBar;
    private ProgressWheel finishProgressBar;
    private ProgressWheel cancelProgressBar;
    private ProgressWheel generalProgressBar;

    @InjectView(R.id.iv_transparent_image)
    private ImageView transparentImageView;

    @Inject
    private TripsResource tripsResource;
    @Inject
    private LocationUpdater locationUpdater;

    private Button finishButton;
    private Button cancelButton;

    // Detect if a passenger cancels his trip or has reached his destination
    private BroadcastReceiver passengersChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // Load the whole offer incl. passengers etc. again
            loadOffer();
        }
    };

    //************************* Methods ****************************//

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);

        setHasOptionsMenu(true);

        nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
        NdefRecord ndefRecord = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], null);
        ndefMessage = new NdefMessage(new NdefRecord[] { ndefRecord });

        return inflater.inflate(R.layout.fragment_my_trip_driver, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        // Fill the passengers list
        View header = view.findViewById(R.id.ll_my_trip_driver_info);
        adapter = new MyTripDriverPassengersAdapter(this, header);
        AcceptDeclineRequestListener acceptDeclineListener = new AcceptDeclineRequestListener();
        this.touchListener = new SwipeListener(recyclerView, acceptDeclineListener);
        adapter.setOnRequestAcceptDeclineListener(acceptDeclineListener);

        mapProgressBar = (ProgressWheel) adapter.getHeader().findViewById(R.id.pb_my_trip_map_progressBar);
        passengersProgressBar = (ProgressWheel) adapter.getHeader()
                .findViewById(R.id.pb_my_trip_passengers_progressBar);
        finishProgressBar = (ProgressWheel) adapter.getHeader().findViewById(R.id.pb_my_trip_finish);
        cancelProgressBar = (ProgressWheel) adapter.getHeader().findViewById(R.id.pb_my_trip_cancel);
        generalProgressBar = (ProgressWheel) view.findViewById(R.id.pb_my_trip_progressBar);

        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity());
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setAdapter(adapter);
        recyclerView.setOnTouchListener(touchListener);
        recyclerView.setOnScrollListener(touchListener.makeScrollListener());

        setupCancelButton(header);
        setupFinishButton(header);

        // Get the route to display it on the map
        SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager()
                .findFragmentById(R.id.f_my_trip_driver_map);

        // TODO: Make it asynchronously
        googleMap = mapFragment.getMap();
        Bundle bundle = getArguments();
        if (bundle == null) {
            bundle = new Bundle();
        }

        String action = bundle.getString(ARG_ACTION, ACTION_LOAD);
        if (action.equals(ACTION_CREATE)) {
            Timber.d("Create Offer");
            createOffer(bundle);
        } else {
            Timber.d("Load Offer");
            loadOffer();
        }

        transparentImageView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction();
                switch (action) {
                case MotionEvent.ACTION_DOWN:
                    recyclerView.requestDisallowInterceptTouchEvent(true);
                    return false;

                case MotionEvent.ACTION_UP:
                    recyclerView.requestDisallowInterceptTouchEvent(false);
                    return true;

                case MotionEvent.ACTION_MOVE:
                    recyclerView.requestDisallowInterceptTouchEvent(true);
                    return false;

                default:
                    return true;
                }
            }
        });

        // Remove the header from the layout. Otherwise it exists twice
        ((ViewManager) view).removeView(header);
    }

    @Override
    public void onResume() {
        super.onResume();

        if (nfcAdapter != null) {
            nfcAdapter.setNdefPushMessage(ndefMessage, getActivity());
        }

        // Get notified if a passenger cancels his trip or has reached his destination
        IntentFilter filter = new IntentFilter();
        filter.addAction(Constants.EVENT_PASSENGER_CANCELLED_TRIP);
        filter.addAction(Constants.EVENT_PASSENGER_REACHED_DESTINATION);
        filter.addAction(Constants.EVENT_PASSENGER_ENTERED_CAR);
        filter.addAction(Constants.EVENT_NEW_JOIN_REQUEST);
        LocalBroadcastManager.getInstance(getActivity().getApplicationContext())
                .registerReceiver(passengersChangeReceiver, filter);
    }

    @Override
    public void onPause() {
        super.onPause();
        LocalBroadcastManager.getInstance(getActivity().getApplicationContext())
                .unregisterReceiver(passengersChangeReceiver);
    }

    private void setupCancelButton(View header) {

        cancelButton = ((Button) (header.findViewById(R.id.btn_my_trip_driver_cancel_trip)));
        cancelButton.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                cancelButton.setEnabled(false);
                cancelProgressBar.setVisibility(View.VISIBLE);

                // Tell the server to cancel this trip
                subscriptions.add(tripsResource.updateOffer(offerID, TripOfferUpdate.createCancelUpdate())
                        .compose(new DefaultTransformer<TripOffer>()).subscribe(new Action1<TripOffer>() {
                            @Override
                            public void call(TripOffer offer) {
                                // After the server has been contacted successfully, clean
                                // up the SharedPref and show "Offer Trip" screen again
                                removeRunningTripOfferState();
                            }

                        }, new Action1<Throwable>() {
                            @Override
                            public void call(Throwable throwable) {
                                Toast.makeText(getActivity(), R.string.join_trip_results_error, Toast.LENGTH_SHORT)
                                        .show();
                                cancelButton.setEnabled(true);
                                cancelProgressBar.setVisibility(View.GONE);
                            }
                        }));
            }
        });
    }

    private void setupFinishButton(View header) {

        finishButton = ((Button) (header.findViewById(R.id.btn_my_trip_driver_finish_trip)));
        finishButton.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                finishButton.setEnabled(false);
                finishProgressBar.setVisibility(View.VISIBLE);

                // Tell the server to finish this trip
                subscriptions.add(tripsResource.updateOffer(offerID, TripOfferUpdate.createFinishUpdate())
                        .compose(new DefaultTransformer<TripOffer>()).subscribe(new Action1<TripOffer>() {
                            @Override
                            public void call(TripOffer offer) {
                                // After the server has been contacted successfully, clean
                                // up the SharedPref and show "Offer Trip" screen again
                                removeRunningTripOfferState();
                            }

                        }, new Action1<Throwable>() {
                            @Override
                            public void call(Throwable throwable) {
                                Toast.makeText(getActivity(), R.string.join_trip_results_error, Toast.LENGTH_SHORT)
                                        .show();
                                finishButton.setEnabled(true);
                                finishProgressBar.setVisibility(View.GONE);
                            }
                        }));
            }
        });
    }

    /**
     * Downloads the current offer and processes its information with the {@link LoadOfferSubscriber}
     */
    private synchronized void loadOffer() {

        // UI
        mapProgressBar.setVisibility(View.VISIBLE);
        // Don't show spinner again, looks ugly with already filled adapter
        // passengersProgressBar.setVisibility(View.VISIBLE);

        subscriptions.add(tripsResource.getActiveOffers().compose(new DefaultTransformer<List<TripOffer>>())
                .flatMap(new Func1<List<TripOffer>, Observable<TripOffer>>() {

                    @Override
                    public Observable<TripOffer> call(List<TripOffer> tripOffers) {

                        if (tripOffers.isEmpty()) {
                            // Inform user
                            String errorMsg = getString(R.string.navigation_error_no_offer);
                            Toast.makeText(getActivity(), errorMsg, Toast.LENGTH_LONG).show();
                            removeRunningTripOfferState();
                            throw new NoSuchElementException(errorMsg);
                        }

                        return Observable.just(tripOffers.get(0));
                    }
                }).subscribe(new LoadOfferSubscriber()));
    }

    private void loadPassengers() {
        subscriptions
                .add(tripsResource.getJoinRequests(false).compose(new DefaultTransformer<List<JoinTripRequest>>())
                        .subscribe(new ImportantPassengersSubscriber()));
    }

    public void informAboutDiversion(final JoinTripRequest joinRequest, final OnDiversionUpdateListener listener,
            final TextView textView) {

        // Ask the server for the diversion
        subscriptions.add(tripsResource.getDiversionInSecondsForJoinRequest(joinRequest.getId())
                .compose(new DefaultTransformer<Long>()).subscribe(new Action1<Long>() {
                    @Override
                    public void call(Long diversionInSeconds) {
                        int diversionInMinutes = (int) (diversionInSeconds / 60);
                        listener.onDiversionUpdate(joinRequest, textView, diversionInMinutes);
                    }

                }, new CrashCallback(getActivity(), "failed to get diversion")));
    }

    private void removeRunningTripOfferState() {
        SharedPreferences prefs = getActivity().getSharedPreferences(Constants.SHARED_PREF_FILE_PREFERENCES,
                Context.MODE_PRIVATE);
        prefs.edit().remove(Constants.SHARED_PREF_KEY_RUNNING_TRIP_OFFER).apply();

        // Change "My Trip" (driver) to "Offer Trip" in navigation drawer
        MaterialNavigationDrawer drawer = ((MaterialNavigationDrawer) getActivity());

        // Find "last" "My Trip", so we don't accidentally rename the join-trip-my trip
        List<MaterialSection> sections = drawer.getSectionList();
        MaterialSection section = null;
        for (MaterialSection s : sections) {
            if (s.getTitle().equals(getString(R.string.menu_my_trip))) {
                section = s;
            }
        }
        section.setTitle(getString(R.string.menu_offer_trip));

        // The next fragment shows the "Offer trip" screen
        drawer.setFragment(new OfferTripFragment(), getString(R.string.menu_offer_trip));
    }

    private void generateRouteOnMap(TripOffer offer, NavigationResult navigationResult) {

        // only one route will be shown (old route will be deleted
        googleMap.clear();

        // get the polyline that should be shown as a list of RouteLocation objects and convert
        // it to LatLng objects. We have to do it this way, because the models can not import
        // the android maps libraries since the models also exist on the server.
        List<RouteLocation> polyline = navigationResult.getRoute().getPolylineWaypointsForUser(offer.getDriver(),
                navigationResult.getUserWayPoints());
        List<LatLng> polylinePoints = new ArrayList<LatLng>();
        for (RouteLocation loc : polyline)
            polylinePoints.add(new LatLng(loc.getLat(), loc.getLng()));

        //Timber.d("polyline: " + polyline);
        // Show route information on the map
        googleMap.addPolyline(new PolylineOptions().addAll(polylinePoints));
        googleMap.setMyLocationEnabled(true);

        for (UserWayPoint userWp : navigationResult.getUserWayPoints()) {
            if (!userWp.getUser().equals(offer.getDriver())) {
                googleMap.addMarker(new MarkerOptions()
                        .position(new LatLng(userWp.getLocation().getLat(), userWp.getLocation().getLng()))
                        .icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_marker)).anchor(0.5f, 0.5f)
                        .flat(true));
            }
        }

        // Move camera to current position
        Location location = locationUpdater.getLastLocation();
        if (location == null)
            return;

        LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
        CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLng, 10);
        googleMap.animateCamera(cameraUpdate);

        mapProgressBar.setVisibility(View.GONE);
    }

    private void createOffer(Bundle arguments) {

        int maxDiversion = arguments.getInt("maxDiversion");
        int pricePerKilometer = arguments.getInt("pricePerKilometer");

        double fromLat = arguments.getDouble("fromLat");
        double fromLng = arguments.getDouble("fromLng");

        double toLat = arguments.getDouble("toLat");
        double toLng = arguments.getDouble("toLng");

        long vehicleId = arguments.getLong("vehicle_id");

        TripOfferDescription tripOffer = new TripOfferDescription(new RouteLocation(fromLat, fromLng),
                new RouteLocation(toLat, toLng), maxDiversion * 1000L, pricePerKilometer, vehicleId);

        tripsResource.addOffer(tripOffer).compose(new DefaultTransformer<TripOffer>()).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<TripOffer>() {

                    @Override
                    public void call(TripOffer tripOffer) {
                        // SUCCESS
                        Timber.d("Your offer was successfully sent to the server");
                        offerID = tripOffer.getId();

                        // show route information on the map
                        generateRouteOnMap(tripOffer,
                                NavigationResult.createNavigationResultForDriverRoute(tripOffer));

                        loadPassengers();

                        // Remember that a trip was offered to show "My Trip" instead of "Offer Trip"
                        // in the Navigation drawer
                        getActivity()
                                .getSharedPreferences(Constants.SHARED_PREF_FILE_PREFERENCES, Context.MODE_PRIVATE)
                                .edit().putBoolean(Constants.SHARED_PREF_KEY_RUNNING_TRIP_OFFER, true).apply();

                    }

                }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        // ERROR
                        Timber.e(throwable.getMessage());

                        // Inform user
                        Toast.makeText(getActivity(), getString(R.string.offer_trip_failed), Toast.LENGTH_LONG)
                                .show();
                        removeRunningTripOfferState();
                    }
                });
    }

    //*************************** Inner classes ********************************//

    /**
     * This Subscriber loads the current route on the map and load the passengers with the help of
     * {@link ImportantPassengersSubscriber}
     */
    private class LoadOfferSubscriber extends Subscriber<TripOffer> {

        @Override
        public void onNext(final TripOffer offer) {

            if (offer == null) {
                throw new NoSuchElementException(getString(R.string.navigation_error_no_offer));
            }

            Timber.d("Received Offer with ID " + offer.getId());

            // Remember the offerID for later and load the passengers for this offer
            offerID = offer.getId();
            loadPassengers();

            // Load the Route
            tripsResource.computeNavigationResultForOffer(offerID)
                    .compose(new DefaultTransformer<NavigationResult>()).subscribe(new Action1<NavigationResult>() {

                        @Override
                        public void call(NavigationResult navigationResult) {

                            if (navigationResult == null) {
                                throw new NoSuchElementException("No route available");
                            }

                            generateRouteOnMap(offer, navigationResult);
                        }

                    }, new Action1<Throwable>() {
                        @Override
                        public void call(Throwable throwable) {
                            onError(throwable);
                        }
                    });

        }

        @Override
        public void onCompleted() {
        }

        @Override
        public void onError(Throwable throwable) {
            mapProgressBar.setVisibility(View.GONE);
            passengersProgressBar.setVisibility(View.GONE);
            Timber.e(throwable.getMessage());
            Toast.makeText(getActivity(), getString(R.string.load_trip_failed), Toast.LENGTH_LONG).show();
        }
    }

    /**
     * This Subscriber checks if there are still "important" passengers to take care of. That is,
     * e.g. there are still passengers sitting in the car or there are still passengers accepted.
     * If there are none, the driver may click the finishButton. Furthermore, all
     * relevant passengers (all that were not declined or canceled), will be shown in the RecyclerView-list.
     */
    private class ImportantPassengersSubscriber extends Subscriber<List<JoinTripRequest>> {

        @Override
        public synchronized void onNext(List<JoinTripRequest> joinTripRequests) {

            // Only allow finish if there are no passengers in the car or accepted
            boolean allowFinish = true;
            boolean allowCancel = true;

            for (JoinTripRequest joinTripRequest : joinTripRequests) {

                // TODO: Filter already on server
                if (joinTripRequest.getOffer().getId() != offerID) {
                    continue;
                }

                JoinTripStatus status = joinTripRequest.getStatus();
                if (status == JoinTripStatus.PASSENGER_ACCEPTED) {
                    adapter.updatePendingPassenger(joinTripRequest);

                } else {
                    // Passenger should not appear in pending requests list
                    adapter.removePendingPassenger(joinTripRequest.getId());
                }

                if (status != JoinTripStatus.DRIVER_DECLINED && status != JoinTripStatus.PASSENGER_ACCEPTED) {

                    if (status == JoinTripStatus.PASSENGER_CANCELLED || status == JoinTripStatus.DRIVER_CANCELLED) {
                        // Remove any cancelled passengers from the adapter
                        adapter.removePassenger(joinTripRequest.getId());

                    } else if (status == JoinTripStatus.DRIVER_ACCEPTED || status == JoinTripStatus.PASSENGER_IN_CAR
                            || status == JoinTripStatus.PASSENGER_AT_DESTINATION) {

                        // Simply update (or implicitly add) this passenger request
                        adapter.updatePassenger(joinTripRequest);

                        if (status == JoinTripStatus.DRIVER_ACCEPTED || status == JoinTripStatus.PASSENGER_IN_CAR) {

                            Timber.d(
                                    "Finishing trip not allowed: there is still an important passenger: " + status);
                            allowFinish = false;

                            if (status == JoinTripStatus.PASSENGER_IN_CAR) {
                                allowCancel = false;
                                Timber.d("Cancelling trip not allowed: there is still a passenger in the car");
                            }
                        }
                    }
                }
            }

            // Dis-/Allow the driver to finish the trip
            finishButton.setEnabled(allowFinish);
            cancelButton.setEnabled(allowCancel);

            adapter.maintainOrder();
            adapter.updateEarnings(); // notifyDataSetChanged is called automatically
        }

        @Override
        public void onCompleted() {
            passengersProgressBar.setVisibility(View.GONE);
        }

        @Override
        public void onError(Throwable e) {
            passengersProgressBar.setVisibility(View.GONE);
            Timber.e("Receiving Passengers (JoinTripRequest) failed:\n" + e.getMessage());
            Toast.makeText(getActivity(), getString(R.string.load_passengers_failed), Toast.LENGTH_LONG).show();
        }
    }

    private class AcceptDeclineRequestListener implements SwipeListener.DismissCallbacks,
            MyTripDriverPassengersAdapter.OnRequestAcceptDeclineListener {

        /**
         * Listener to listen for any driver decisions to accept or decline
         * a pending JoinTripRequest. As soon as such a decision is received, the server
         * is contacted.
         *
         * @param accept   if this method should handle "accept" (true) or "decline" (false)
         * @param position the position of the clicked JoinTripRequest in the adapter
         */
        private synchronized void handleAcceptDecline(final boolean accept, final int position) {

            Timber.d("Swiped position: " + position);

            String task;
            if (accept) {
                task = "Accepting";
            } else {
                task = "Declining";
            }

            final JoinTripRequest request = adapter.getPendingPassenger(position - 1); // -1 because of header
            Timber.i(task + " Request with ID " + request.getId());

            // UI
            generalProgressBar.setVisibility(View.VISIBLE);

            // Don't allow other user clicks while the task is performed
            recyclerView.setOnTouchListener(null);

            //Get a list of current Join trip requests from the server
            //and make sure that the request is still active (hasn't expired)
            subscriptions.add(
                    tripsResource.getJoinRequests(true).compose(new DefaultTransformer<List<JoinTripRequest>>())
                            .subscribe(new Action1<List<JoinTripRequest>>() {
                                @Override
                                public void call(List<JoinTripRequest> requests) {

                                    if (requests.size() > 0) {

                                        for (int i = 0; i < requests.size(); i++) {
                                            if (request.getId() == requests.get(i).getId()) {

                                                //Inform server only if the request is still active (not expired)
                                                JoinTripRequestUpdate requestUpdate;

                                                if (accept) {
                                                    requestUpdate = new JoinTripRequestUpdate(
                                                            JoinTripRequestUpdateType.ACCEPT_PASSENGER);
                                                } else {
                                                    requestUpdate = new JoinTripRequestUpdate(
                                                            JoinTripRequestUpdateType.DECLINE_PASSENGER);
                                                }

                                                subscriptions.add(tripsResource
                                                        .updateJoinRequest(request.getId(), requestUpdate)
                                                        .compose(new DefaultTransformer<JoinTripRequest>())
                                                        .subscribe(new AcceptDeclineRequestSubscriber(accept)));

                                                Timber.i("Request has not expired");
                                                break;
                                            }

                                            //If the request wasn't found in the list, show a toast to the driver
                                            // and remove the card from the list
                                            if (i == requests.size() - 1) {
                                                Timber.d("Request has expired");
                                                Toast.makeText(getActivity(),
                                                        getResources()
                                                                .getString(R.string.offer_trip_request_expired),
                                                        Toast.LENGTH_SHORT).show();

                                                //Enable clicking the list items again and remove the progress bar
                                                recyclerView.setOnTouchListener(touchListener);
                                                generalProgressBar.setVisibility(View.GONE);

                                                adapter.removePendingPassenger(request.getId());
                                            }
                                        }

                                    } else {
                                        //If the expired request was the last one in the list, size() will be 0
                                        //This snippet takes care of this case
                                        Timber.d("No requests found (Request has expired)");
                                        Toast.makeText(getActivity(),
                                                getResources().getString(R.string.offer_trip_request_expired),
                                                Toast.LENGTH_SHORT).show();

                                        //Enable clicking the list items again and remove the progress bar
                                        recyclerView.setOnTouchListener(touchListener);
                                        generalProgressBar.setVisibility(View.GONE);

                                        adapter.removePendingPassenger(request.getId());
                                    }

                                    adapter.maintainOrder();
                                    adapter.notifyDataSetChanged();

                                }
                            }, new Action1<Throwable>() {

                                @Override
                                public void call(Throwable throwable) {
                                    Toast.makeText(getActivity(), R.string.join_trip_results_error,
                                            Toast.LENGTH_SHORT).show();
                                    generalProgressBar.setVisibility(View.GONE);
                                    recyclerView.setOnTouchListener(touchListener);

                                }
                            }));
        }

        @Override
        public void onJoinRequestDecline(View view, int position) {
            handleAcceptDecline(false, position);
        }

        @Override
        public void onJoinRequestAccept(View view, int position) {
            handleAcceptDecline(true, position);
        }

        @Override
        public boolean canDismiss(int position) {
            return adapter.isPositionPendingPassenger(position);
        }

        @Override
        public void onSwipeLeft(RecyclerView recyclerView, int[] dismissedItems) {
            // Decline only the first item and ignore the rest
            if (dismissedItems != null && dismissedItems.length > 0) {
                handleAcceptDecline(false, dismissedItems[0]);
            }

        }

        @Override
        public void onSwipeRight(RecyclerView recyclerView, int[] dismissedItems) {
            // Accept only the first item and ignore the rest
            if (dismissedItems != null && dismissedItems.length > 0) {
                handleAcceptDecline(true, dismissedItems[0]);
            }
        }
    }

    /**
     * A simple Subscriber that removes the previously accepted/declined request
     * from the adapter
     */
    private class AcceptDeclineRequestSubscriber extends Subscriber<JoinTripRequest> {

        private boolean accept;

        protected AcceptDeclineRequestSubscriber(boolean accept) {
            super();
            this.accept = accept;
        }

        @Override
        public void onNext(JoinTripRequest joinTripRequest) {
            // SUCCESS
            Timber.i("Successfully informed the server about a request with ID " + joinTripRequest.getId());

            // Everything worked out, so remove the request from the adapter
            adapter.removePendingPassenger(joinTripRequest.getId());
            adapter.notifyDataSetChanged();

            if (accept) {
                loadOffer();
            }
        }

        @Override
        public void onError(Throwable e) {
            // ERROR
            String task;

            if (accept) {
                task = "accepting";
            } else {
                task = "declining";
            }

            Timber.e("Error when " + task + " a JoinTripRequest: " + e.getMessage());
            onDone();
        }

        @Override
        public void onCompleted() {
            onDone();
        }

        private void onDone() {

            // Allow clicks on trips again
            //adapter.setOnRequestAcceptDeclineListener(new AcceptDeclineRequestListener());
            recyclerView.setOnTouchListener(touchListener);

            // UI
            generalProgressBar.setVisibility(View.GONE);
        }
    }

}