package rectangledbmi.com.pittsburghrealtimetracker;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Color;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.squareup.leakcanary.RefWatcher;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.TimeZone;
import rectangledbmi.com.pittsburghrealtimetracker.handlers.Constants;
import rectangledbmi.com.pittsburghrealtimetracker.selection.RouteSelection;
import rectangledbmi.com.pittsburghrealtimetracker.world.Route;
import rx.Observable;
import rx.subjects.BehaviorSubject;
/**
* Fragment used for managing interactions for and presentation of a navigation drawer.
* See the <a href="https://developer.android.com/design/patterns/navigation-drawer.html#Interaction">
* design guidelines</a> for a complete explanation of the behaviors implemented here.
*/
public class NavigationDrawerFragment extends Fragment {
/**
* Per the design guidelines, you should show the drawer on launch until the user manually
* expands it. This shared preference tracks this.
*/
private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned";
/**
* pointer to the current selected bus for the current callbacks instance (Activity)
*
* @since 43
*/
private BusListCallbacks busCallbacks;
/**
* Saved instance of the buses that are selected
*/
private final static String BUS_SELECT_STATE = "busesSelected";
/**
* Helper component that ties the action bar to the navigation drawer.
*/
private ActionBarDrawerToggle mDrawerToggle;
private DrawerLayout mDrawerLayout;
private View mFragmentContainerView;
private BusRouteAdapter busListAdapter;
private boolean mFromSavedInstanceState;
private boolean mUserLearnedDrawer;
private BehaviorSubject<RouteSelection> routeSelectionPublishSubject;
/**
* State of selected routes
* <p/>
* public because we want to clear this list...
*/
private Set<String> selectedRoutes;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Read in the flag indicating whether or not the user has demonstrated awareness of the
// drawer. See PREF_USER_LEARNED_DRAWER for details.
Constants.DEFAULT_DATE_PARSE_FORMAT.setTimeZone(TimeZone.getTimeZone("EST"));
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);
if (savedInstanceState != null) {
mFromSavedInstanceState = true;
}
selectedRoutes = new HashSet<>(sp.getStringSet(BUS_SELECT_STATE,
Collections.synchronizedSet(
new HashSet<>(getResources().getInteger(R.integer.max_checked)))));
routeSelectionPublishSubject = BehaviorSubject.create(RouteSelection.create(selectedRoutes));
// Select either the default item (0) or the last selected item.
// selectItem(mCurrentSelectedPosition);
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Indicate that this fragment would like to influence the set of actions in the action bar.
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View v = inflater.inflate(R.layout.navigation_drawer_layout, container, false);
busListAdapter = new BusRouteAdapter();
RecyclerView busListRecyclerView = (RecyclerView) v.findViewById(R.id.bus_list_recyclerview);
busListRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
busListAdapter.setHasStableIds(true);
busListRecyclerView.setHasFixedSize(true);
busListRecyclerView.setAdapter(busListAdapter);
return v;
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
@Override
public void onDestroy() {
if (getActivity() != null) {
RefWatcher refWatcher = PATTrackApplication.getRefWatcher(getActivity());
refWatcher.watch(this);
}
super.onDestroy();
}
public boolean isDrawerOpen() {
return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mFragmentContainerView);
}
/**
* Users of this fragment must call this method to set up the navigation drawer interactions.
*
* @param fragmentId The android:id of this fragment in its activity's layout.
* @param drawerLayout The DrawerLayout containing this fragment's UI.
*/
public void setUp(int fragmentId, DrawerLayout drawerLayout) {
mFragmentContainerView = getActivity().findViewById(fragmentId);
mDrawerLayout = drawerLayout;
// set a custom shadow that overlays the main content when the drawer opens
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
// ActionBarDrawerToggle ties together the the proper interactions
// between the navigation drawer and the action bar app icon.
mDrawerToggle = new ActionBarDrawerToggle(
getActivity(), /* host Activity */
mDrawerLayout, /* DrawerLayout object */
R.string.navigation_drawer_open, /* "open drawer" description for accessibility */
R.string.navigation_drawer_close /* "close drawer" description for accessibility */
) {
@Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
if (!isAdded()) {
return;
}
getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu()
}
@Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
if (!isAdded()) {
return;
}
if (!mUserLearnedDrawer) {
// The user manually opened the drawer; store this flag to prevent auto-showing
// the navigation drawer automatically in the future.
mUserLearnedDrawer = true;
SharedPreferences sp = PreferenceManager
.getDefaultSharedPreferences(getActivity());
sp.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).apply();
}
getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu()
}
};
// If the user hasn't 'learned' about the drawer, open it to introduce them to the drawer,
// per the navigation drawer design guidelines.
if (!mUserLearnedDrawer && !mFromSavedInstanceState) {
mDrawerLayout.openDrawer(mFragmentContainerView);
}
// Defer code dependent on restoration of previous instance state.
mDrawerLayout.post(mDrawerToggle::syncState);
mDrawerLayout.addDrawerListener(mDrawerToggle);
}
public ActionBarDrawerToggle getActionBarDrawerToggle() {
return mDrawerToggle;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
busCallbacks = (BusListCallbacks) context;
} catch (ClassCastException e) {
throw new ClassCastException("Activity must implement NavigationDrawerCallbacks.");
}
}
public boolean closeDrawer() {
if(isDrawerOpen()) {
mDrawerLayout.closeDrawer(mFragmentContainerView);
return true;
}
return false;
}
public boolean openDrawer() {
if(!isDrawerOpen()) {
mDrawerLayout.openDrawer(mFragmentContainerView);
return true;
}
return false;
}
@Override
public void onDetach() {
super.onDetach();
busCallbacks = null;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Forward the new configuration the drawer toggle component.
mDrawerToggle.onConfigurationChanged(newConfig);
}
/**
* If the Clear Buses button is clicked, clears the selection and the selected buses
*/
public void clearSelection() {
busListAdapter.clearSelection();
routeSelectionPublishSubject.onNext(RouteSelection.create(selectedRoutes));
Toast.makeText(getActivity(), getString(R.string.cleared), Toast.LENGTH_SHORT).show();
}
/**
* Place to save preferences....
*/
private void savePreferences() {
if(getActivity() != null && busListAdapter != null) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
SharedPreferences.Editor spe = sp.edit();
spe.putStringSet(BUS_SELECT_STATE, busListAdapter.getSelectedRoutes());
spe.apply();
}
}
@Override
public void onPause() {
savePreferences();
super.onPause();
}
/**
* @since 43
* @param rt - the route number
* @return the route information by rt
*/
public Route getSelectedRoute(String rt) {
if(busListAdapter != null)
return busListAdapter.getRouteMap().get(rt);
return null;
}
public Set<String> getSelectedRoutes() {
if(busListAdapter != null)
return busListAdapter.getSelectedRoutes();
return null;
}
/**
* This takes the bus route information to the main activity {@link SelectTransit}.
*
* @since 43
* @author Jeremy Jao
*/
public interface BusListCallbacks {
/**
* This bus route has been selected
*
* @param route the bus route selected
*/
void onSelectBusRoute(Route route);
/**
* Do when the bus route has been deselected
*
* @param route the bus route deselected
*/
void onDeselectBusRoute(Route route);
//TODO: Fix this to use observables or designate callback function for selection or deselection
}
private class BusRouteAdapter extends RecyclerView.Adapter<BusRouteAdapter.BusRouteHolder> {
/**
* Routes dataset
*/
private Route[] routes;
/**
* Map of routes by hashmap
*/
private HashMap<String, Route> routeHashMap;
public BusRouteAdapter() {
super();
createRoutes();
}
/**
* Creates an array of routes for the recycler view and a reverse mapping
*
* TODO: This is rather slow and takes up some time according to the Android Studio debugger
* @return the routes to be made for the recycler view
*/
private Route[] createRoutes() {
String[] numbers, descriptions, colors;
numbers = getResources().getStringArray(R.array.buses);
descriptions = getResources().getStringArray(R.array.bus_description);
colors = getResources().getStringArray(R.array.buscolors);
routes = new Route[numbers.length];
routeHashMap = new HashMap<>(numbers.length);
Route currentRoute;
for(int i=0;i<numbers.length;++i) {
currentRoute = new Route(numbers[i], descriptions[i], colors[i], i, selectedRoutes.contains(numbers[i]));
routes[i] = currentRoute;
routeHashMap.put(currentRoute.getRoute(), currentRoute);
}
return routes;
}
/**
*
* @return a set of currently selected routes from the recycler view
*/
public Set<String> getSelectedRoutes() {
return selectedRoutes;
}
public HashMap<String, Route> getRouteMap() {
return routeHashMap;
}
@Override
public BusRouteHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View row = LayoutInflater.from(parent.getContext())
.inflate(R.layout.bus_route_recycler_item, parent, false);
return new BusRouteHolder(row);
}
@Override
public void onBindViewHolder(BusRouteHolder holder, int position) {
holder.bindBusRoute(routes[position]);
}
@Override
public int getItemCount() {
return routes.length;
}
public void clearSelection() {
for(String s : selectedRoutes) {
routeHashMap.get(s).setSelected(false);
}
selectedRoutes.clear();
notifyDataSetChanged();
}
public long getItemId(int position) {
return routes[position].getRoute().hashCode();
}
public class BusRouteHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private Route mRoute;
private TextView routeIcon;
private TextView routeDescription;
private View itemView;
public BusRouteHolder(View itemView) {
super(itemView);
this.itemView = itemView;
itemView.setOnClickListener(this);
routeDescription = (TextView) itemView.findViewById(R.id.bus_route_text);
routeIcon = (TextView) itemView.findViewById(R.id.bus_route_icon);
}
public void onClick(View v) {
if (mRoute == null) return;
if (!mRoute.isSelected() &&
selectedRoutes.size() == getResources().getInteger(R.integer.max_checked)) {
Toast.makeText(getActivity(), getString(R.string.max_selected_routes), Toast.LENGTH_SHORT).show();
return;
}
boolean selected = mRoute.toggleSelection();
if (selected) {
selectedRoutes.add(mRoute.getRoute());
busCallbacks.onSelectBusRoute(mRoute);
} else {
selectedRoutes.remove(mRoute.getRoute());
busCallbacks.onDeselectBusRoute(mRoute);
}
routeSelectionPublishSubject.onNext(RouteSelection.create(mRoute, selectedRoutes));
notifyItemChanged(getAdapterPosition());
}
private void generateIcon() {
routeIcon.setBackgroundColor(mRoute.getRouteColor());
routeIcon.setText(mRoute.getRoute());
routeIcon.setTextColor(isLight(mRoute.getRouteColor())
? Color.BLACK
: Color.WHITE);
}
public void bindBusRoute(Route busRoute) {
mRoute = busRoute;
routeDescription.setText(busRoute.getRouteInfo());
generateIcon();
itemView.setActivated(mRoute.isSelected());
}
/**
* Decides whether or not the color (background color) is light or not.
* <p>
* Formula was taken from here:
* http://stackoverflow.com/questions/24260853/check-if-color-is-dark-or-light-in-android
*
* @param color the background color being fed
* @return whether or not the background color is light or not (.345 is the current threshold)
* @since 47
*/
private boolean isLight(int color) {
return 1.0 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255 < .5;
}
}
}
public Observable<RouteSelection> getListObservable() {
return routeSelectionPublishSubject.asObservable();
}
}