/*
* 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.trip;
import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.croudtrip.R;
import org.croudtrip.api.account.User;
import org.croudtrip.api.directions.RouteLocation;
import org.croudtrip.api.trips.JoinTripRequest;
import org.croudtrip.api.trips.JoinTripStatus;
import org.croudtrip.api.trips.TripQuery;
import org.croudtrip.fragments.offer.MyTripDriverFragment;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import timber.log.Timber;
/**
* This adapter manages all accepted passengers in a list such that the driver can scroll
* through them in his "My Trip" view.
*
* @author Vanessa Lange
*/
public class MyTripDriverPassengersAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
implements OnDiversionUpdateListener {
//************************** Variables ***************************//
private View header; // map and earnings are in the header view
private LinearLayout space;
private static final int TYPE_HEADER = 0; // header element
private static final int TYPE_ITEM = 1; // normal passenger element
private static final int TYPE_PENDING_ITEM = 2; // pending passenger element
private static final int TYPE_SPACE_ITEM = 3; // space element to separate normal from pending passengers
private List<JoinTripRequest> passengers;
private List<JoinMatch> pendingPassengers;
protected OnRequestAcceptDeclineListener listener;
private MyTripDriverFragment fragment; // needs to be informed after swipe
//************************** Constructors ***************************//
public MyTripDriverPassengersAdapter(MyTripDriverFragment fragment, View header) {
this.passengers = new ArrayList<JoinTripRequest>();
this.pendingPassengers = new ArrayList<JoinMatch>();
this.header = header;
this.fragment = fragment;
updateEarnings();
}
//**************************** Methods *****************************//
public View getHeader() {
return header;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// Inflate item layout and pass it to view holder
if (viewType == TYPE_ITEM) {
return new ItemViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.cardview_my_trip_driver_passengers, parent, false));
} else if (viewType == TYPE_HEADER) {
return new HeaderViewHolder(header);
} else if (viewType == TYPE_PENDING_ITEM) {
return new PendingItemViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.cardview_join_trip_requests, parent, false));
} else if (viewType == TYPE_SPACE_ITEM) {
return new SpaceViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.space, parent, false));
}
throw new RuntimeException("There is no type that matches the type " + viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder h, int position) {
if (h instanceof ItemViewHolder) {
ItemViewHolder holder = (ItemViewHolder) h;
JoinTripRequest joinRequest = passengers.get(position - 1 - pendingPassengers.size() - getSpaceCount());
TripQuery query = joinRequest.getSuperTrip().getQuery();
// Passenger name
User passenger = query.getPassenger();
holder.tvPassengerName.setText(passenger.getFirstName() + " " + passenger.getLastName());
// Passenger image/avatar
String avatarURL = passenger.getAvatarUrl();
if (avatarURL != null) {
Context context = holder.ivAvatar.getContext();
Picasso.with(context).load(avatarURL).into(holder.ivAvatar);
} else {
holder.ivAvatar.setImageResource(R.drawable.profile);
}
// Passenger location
showPassengerLocation(holder.tvPassengerLocation, query.getStartLocation());
// Earnings for driver
showEarning(holder.tvEarnings, joinRequest.getTotalPriceInCents());
// Change background color and show green check mark if destination reached
int color = 0;
if (joinRequest.getStatus() == JoinTripStatus.PASSENGER_AT_DESTINATION) {
color = R.color.my_trip_driver_passenger_destination_reached;
holder.checkmark.setVisibility(View.VISIBLE);
} else if(joinRequest.getStatus() == JoinTripStatus.PASSENGER_IN_CAR){
color = R.color.my_trip_driver_passenger_destination_reached;
holder.checkmark.setVisibility(View.GONE);
} else {
color = R.color.my_trip_driver_passenger;
holder.checkmark.setVisibility(View.GONE);
}
color = holder.card.getContext().getResources().getColor(color);
holder.card.setCardBackgroundColor(color);
} else if (h instanceof MyTripDriverPassengersAdapter.HeaderViewHolder) {
MyTripDriverPassengersAdapter.HeaderViewHolder holder = (MyTripDriverPassengersAdapter.HeaderViewHolder) h;
holder.view = header;
} else if (h instanceof MyTripDriverPassengersAdapter.SpaceViewHolder) {
MyTripDriverPassengersAdapter.SpaceViewHolder holder = (MyTripDriverPassengersAdapter.SpaceViewHolder) h;
holder.space = space;
} else if (h instanceof MyTripDriverPassengersAdapter.PendingItemViewHolder) {
PendingItemViewHolder holder = (PendingItemViewHolder) h;
JoinMatch joinMatch = pendingPassengers.get(position - 1); // -1 because of header
TripQuery query = joinMatch.joinRequest.getSuperTrip().getQuery();
// Passenger name
User passenger = query.getPassenger();
holder.tvPassengerName.setText(passenger.getFirstName() + " " + passenger.getLastName());
// Passenger image/avatar
String avatarURL = passenger.getAvatarUrl();
if (avatarURL != null) {
Context context = holder.ivAvatar.getContext();
Picasso.with(context).load(avatarURL).into(holder.ivAvatar);
} else {
holder.ivAvatar.setImageResource(R.drawable.profile);
}
// Earnings for driver
showEarning(holder.tvEarnings, joinMatch.joinRequest.getTotalPriceInCents());
// Diversion to pick up passenger
int diversionInMinutes = joinMatch.diversionInMinutes;
if (diversionInMinutes == -1) {
// no data yet -> ask server
Timber.d("Asking server for diversion");
fragment.informAboutDiversion(joinMatch.joinRequest, this, holder.tvDiversion);
} else {
Timber.d("Used cached result for diversion");
showDiversion(holder.tvDiversion, diversionInMinutes);
}
}
}
private void showPassengerLocation(TextView tvLocation, RouteLocation location) {
tvLocation.setVisibility(View.VISIBLE);
// Receive addresses for Latitude/Longitude
Geocoder geocoder;
List<Address> addresses;
geocoder = new Geocoder(tvLocation.getContext(), Locale.getDefault());
try {
addresses = geocoder.getFromLocation(location.getLat(), location.getLng(), 1);
String city = addresses.get(0).getLocality();
String street = addresses.get(0).getThoroughfare();
if (city == null && street == null) {
// no data -> hide TextView
tvLocation.setVisibility(View.GONE);
} else if (city != null && street != null) {
// both data
tvLocation.setText(city + ", " + street);
} else {
// either only city of street
String loc = (city != null) ? city : street;
tvLocation.setText(loc);
}
} catch (IOException e) {
e.printStackTrace();
tvLocation.setVisibility(View.GONE);
}
}
private void showEarning(TextView textView, int earningsInCents) {
String pEuros = (earningsInCents / 100) + "";
String pCents;
// Format cents correctly
int cents = (earningsInCents % 100);
if (cents == 0) {
pCents = "00";
} else if (cents < 10) {
pCents = "0" + cents;
} else {
pCents = cents + "";
}
textView.setText(textView.getContext().getString(R.string.my_trip_driver_my_earnings,
pEuros, pCents));
}
private void showDiversion(TextView textView, int diversionInMinutes) {
String minutes;
int min = diversionInMinutes % 60;
if (diversionInMinutes >= 60) {
String hours = diversionInMinutes / 60 + "";
if (min == 0) {
minutes = "00";
} else if (min < 10) {
minutes = "0" + min;
} else {
minutes = min + "";
}
textView.setText(textView.getContext().getString(R.string.join_trip_requests_diversion_hmin,
hours, minutes));
} else {
textView.setText(textView.getContext().getString(R.string.join_trip_requests_diversion_min,
min));
}
}
@Override
public void onDiversionUpdate(JoinTripRequest joinRequest, TextView textView, int diversionInMinutes) {
showDiversion(textView, diversionInMinutes);
// Cache the result
for (JoinMatch match : pendingPassengers) {
if (match.joinRequest.equals(joinRequest)) {
match.diversionInMinutes = diversionInMinutes;
break;
}
}
notifyDataSetChanged();
}
private int getSpaceCount() {
int spaceCount = 0;
if (pendingPassengers.size() > 0 && passengers.size() > 0) {
spaceCount = 1;
}
return spaceCount;
}
@Override
public int getItemCount() {
return passengers.size() + getPendingPassengersCount() + 1 + getSpaceCount(); // 1 for header
}
public int getPassengersCount() {
return passengers.size();
}
public int getPendingPassengersCount() {
return pendingPassengers.size();
}
/**
* Returns the JoinTripRequest at the specific position
*
* @param position the nth pending passenger
* @return the JoinTripRequest at the specific position
*/
public JoinTripRequest getPendingPassenger(int position) {
if (position < 0 || position >= pendingPassengers.size()) {
return null;
}
return pendingPassengers.get(position).joinRequest;
}
/**
* Searches for a JoinTripRequest (pending passenger) with the same ID as additionalPendingPassenger
* and replaces the request with the given one. If the request isn't in the list yet, it is
* simply added.
*
* @param additionalPendingPassenger new elements to add to the adapter
*/
public void updatePendingPassenger(JoinTripRequest additionalPendingPassenger) {
if (additionalPendingPassenger == null) {
return;
}
boolean requestFound = false;
for (int i = 0; i < pendingPassengers.size(); i++) {
JoinTripRequest r = pendingPassengers.get(i).joinRequest;
if (r.getId() == additionalPendingPassenger.getId()) {
if (r.equals(additionalPendingPassenger)) {
// Nothing changed, no need to update
return;
}
pendingPassengers.set(i, new JoinMatch(additionalPendingPassenger));
requestFound = true;
break;
}
}
if (!requestFound) {
pendingPassengers.add(new JoinMatch(additionalPendingPassenger));
}
}
/**
* Removes the JoinTripRequest with the same requestID from the adapter.
*
* @param requestID
*/
public void removePendingPassenger(long requestID) {
boolean foundRequest = false;
int index = 0;
for (JoinMatch request : pendingPassengers) {
if (request.joinRequest.getId() == requestID) {
foundRequest = true;
break;
}
index++;
}
if (foundRequest) {
pendingPassengers.remove(index);
}
}
/**
* Removes the given JoinTripRequest with the same ID from the adapter.
*
* @param requestID the ID of the JoinTripRequest to remove
*/
public void removePassenger(long requestID) {
boolean foundRequest = false;
int index = 0;
for (JoinTripRequest request : passengers) {
if (request.getId() == requestID) {
foundRequest = true;
break;
}
index++;
}
if (foundRequest) {
passengers.remove(index);
}
}
/**
* Searches for a JoinTripRequest with the same ID as the given request
* and replaces the request with the given one. If the request isn't in the list yet, it is
* simply added.
*
* @param request the request to update
*/
public void updatePassenger(JoinTripRequest request) {
if (request == null) {
return;
}
boolean requestFound = false;
for (int i = 0; i < passengers.size(); i++) {
JoinTripRequest r = passengers.get(i);
if (r.getId() == request.getId()) {
if (r.equals(request)) {
// Nothing changed, no need to update
return;
}
passengers.set(i, request);
requestFound = true;
break;
}
}
if (!requestFound) {
passengers.add(request);
}
}
/**
* Sorts the entries in the adapter according to the order of the trip status in the enum. I.e.
* Passengers waiting to be picked up are first in the list, next up are passengers in the car,
* followed by passengers already dropped of. Other kinds of passengers should not be in the list.
*/
public void maintainOrder() {
Collections.sort(passengers, new Comparator<JoinTripRequest>() {
@Override
public int compare(JoinTripRequest request, JoinTripRequest other) {
return request.getStatus().ordinal() - other.getStatus().ordinal();
}
});
}
/**
* Shows the total earnings to the driver
*
* @param totalEarningsInCent
*/
private void setTotalEarnings(int totalEarningsInCent) {
TextView earnings = (TextView) header.findViewById(R.id.tv_my_trip_driver_earnings);
showEarning(earnings, totalEarningsInCent);
notifyDataSetChanged();
}
public void updateEarnings() {
int totalEarnings = 0;
if (passengers != null) {
for (JoinTripRequest request : passengers) {
totalEarnings += request.getTotalPriceInCents();
}
}
setTotalEarnings(totalEarnings);
}
private boolean isPositionHeader(int position) {
return position == 0;
}
public boolean isPositionPendingPassenger(int position) {
return position - 1 >= 0 && position - 1 < pendingPassengers.size();
}
public boolean isPositionSpace(int position) {
if (pendingPassengers.size() == 0 || passengers.size() == 0) {
return false;
}
return position - 1 == pendingPassengers.size(); // - 1 because of header
}
@Override
public int getItemViewType(int position) {
if (isPositionHeader(position)) {
return TYPE_HEADER;
} else if (isPositionPendingPassenger(position)) {
return TYPE_PENDING_ITEM;
} else if (isPositionSpace(position)) {
return TYPE_SPACE_ITEM;
}
return TYPE_ITEM;
}
//************************** Inner classes ***************************//
/**
* Provides a reference to the views for each data item.
*/
class ItemViewHolder extends RecyclerView.ViewHolder {
protected TextView tvPassengerName;
protected TextView tvPassengerLocation;
protected TextView tvEarnings;
protected ImageView ivAvatar;
protected CardView card;
protected ImageView checkmark;
public ItemViewHolder(View view) {
super(view);
this.tvPassengerName = (TextView)
view.findViewById(R.id.tv_my_trip_driver_passengers_passenger_name);
this.tvPassengerLocation = (TextView)
view.findViewById(R.id.tv_my_trip_driver_passengers_passenger_location);
this.tvEarnings = (TextView)
view.findViewById(R.id.tv_my_trip_driver_passengers_passenger_earnings);
this.ivAvatar = (ImageView)
view.findViewById(R.id.iv_my_trip_driver_passengers_user_image);
this.card = (CardView)
view.findViewById(R.id.cv_my_trip_driver_passengers);
this.checkmark = (ImageView)
view.findViewById(R.id.iv_my_trip_driver_passengers_reached_destination);
}
}
/**
* Provides a reference to the header view
*/
class HeaderViewHolder extends RecyclerView.ViewHolder {
protected View view;
public HeaderViewHolder(View view) {
super(view);
this.view = view;
}
}
/**
* Provides a reference to the space view
*/
class SpaceViewHolder extends RecyclerView.ViewHolder {
protected LinearLayout space;
public SpaceViewHolder(View view) {
super(view);
this.space = (LinearLayout) view.findViewById(R.id.space);
}
}
public interface OnRequestAcceptDeclineListener {
void onJoinRequestDecline(View view, int position);
void onJoinRequestAccept(View view, int position);
}
public void setOnRequestAcceptDeclineListener(OnRequestAcceptDeclineListener listener) {
this.listener = listener;
}
/**
* Provides a reference to a pending passenger
*/
class PendingItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
protected TextView tvPassengerName;
protected TextView tvEarnings;
protected TextView tvDiversion;
protected ImageView ivAvatar;
public PendingItemViewHolder(View view) {
super(view);
this.tvPassengerName = (TextView)
view.findViewById(R.id.tv_join_trip_requests_passenger_name);
this.tvEarnings = (TextView)
view.findViewById(R.id.tv_join_trip_requests_earnings);
this.tvDiversion = (TextView)
view.findViewById(R.id.tv_join_trip_requests_diversion);
this.ivAvatar = (ImageView)
view.findViewById(R.id.iv_join_trip_requests_user_image);
// Get notified if the user accepts or declines a request
ImageButton acceptButton = (ImageButton)
view.findViewById(R.id.btn_join_trip_request_yes);
ImageButton declineButton = (ImageButton)
view.findViewById(R.id.btn_join_trip_request_no);
acceptButton.setOnClickListener(this);
declineButton.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (listener == null) {
return;
}
if (view.getId() == R.id.btn_join_trip_request_yes) {
// Accept
listener.onJoinRequestAccept(view, getPosition());
} else if (view.getId() == R.id.btn_join_trip_request_no) {
// Decline
listener.onJoinRequestDecline(view, getPosition());
} else {
Timber.e("Received click from unknown View with ID: " + view.getId());
}
}
}
/**
* A simple class to keep data together
*/
private class JoinMatch {
private JoinTripRequest joinRequest;
private int diversionInMinutes;
public JoinMatch(JoinTripRequest joinRequest) {
this.joinRequest = joinRequest;
this.diversionInMinutes = -1;
}
}
}