package com.thebluealliance.androidclient.fragments; import com.squareup.picasso.Picasso; import com.squareup.picasso.Transformation; import com.thebluealliance.androidclient.R; import com.thebluealliance.androidclient.TbaLogger; import com.thebluealliance.androidclient.accounts.AccountController; import com.thebluealliance.androidclient.adapters.NavigationDrawerAdapter; import com.thebluealliance.androidclient.auth.AuthProvider; import com.thebluealliance.androidclient.auth.User; import com.thebluealliance.androidclient.auth.firebase.MigrateLegacyUserToFirebase; import com.thebluealliance.androidclient.di.components.HasMyTbaComponent; import com.thebluealliance.androidclient.listitems.DividerListItem; import com.thebluealliance.androidclient.listitems.ListItem; import com.thebluealliance.androidclient.listitems.NavDrawerItem; import com.thebluealliance.androidclient.listitems.SpacerListItem; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ComposeShader; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Shader; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import javax.inject.Named; import de.hdodenhof.circleimageview.CircleImageView; /** * Fragment used for managing interactions for and presentation of a navigation drawer. * <p> * Opening or closing the drawer will trigger a call to onPrepareOptionsMenu(). * <p> * Activities containing a NavigationDrawerFragment <strong>must</strong> implement {@link * com.thebluealliance.androidclient.fragments.NavigationDrawerFragment.NavigationDrawerListener}. * <p> * See the <a href="https://developer.android.com/design/patterns/navigation-drawer.html#Interaction"> * design guidelines</a> for an explanation of the behaviors implemented here. * * @author tanis7x */ 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"; private static final int PROFILE_PIC_SIZE = 200; private static final List<ListItem> NAVIGATION_ITEMS = new ArrayList<>(); static { NAVIGATION_ITEMS.add(new SpacerListItem()); NAVIGATION_ITEMS.add(new NavDrawerItem(R.id.nav_item_my_tba, R.string.nav_drawer_mytba_title, R.drawable.ic_star_black_24dp, R.layout.nav_drawer_item)); NAVIGATION_ITEMS.add(new NavDrawerItem(R.id.nav_item_gameday, R.string.nav_drawer_gameday_title, R.drawable.ic_videocam_black_24dp, R.layout.nav_drawer_item)); NAVIGATION_ITEMS.add(new NavDrawerItem(R.id.nav_item_events, R.string.nav_drawer_events_title, R.drawable.ic_event_black_24dp, R.layout.nav_drawer_item)); NAVIGATION_ITEMS.add(new NavDrawerItem(R.id.nav_item_districts, R.string.nav_drawer_districts_title, R.drawable.ic_assignment_black_24dp, R.layout.nav_drawer_item)); NAVIGATION_ITEMS.add(new NavDrawerItem(R.id.nav_item_teams, R.string.nav_drawer_teams_title, R.drawable.ic_group_black_24dp, R.layout.nav_drawer_item)); NAVIGATION_ITEMS.add(new NavDrawerItem(R.id.nav_item_notifications, R.string.nav_drawer_recent_notifications_title, R.drawable.ic_notifications_black_24dp, R.layout.nav_drawer_item)); NAVIGATION_ITEMS.add(new SpacerListItem()); NAVIGATION_ITEMS.add(new DividerListItem()); NAVIGATION_ITEMS.add(new SpacerListItem()); NAVIGATION_ITEMS.add(new NavDrawerItem(R.id.nav_item_settings, R.string.nav_drawer_settings_title, R.drawable.ic_settings_black_24dp, R.layout.nav_drawer_item)); NAVIGATION_ITEMS.add(new SpacerListItem()); } private ActionBarDrawerToggle mDrawerToggle; private DrawerLayout mDrawerLayout; private ListView mDrawerListView; private View mNavHeaderWithoutAccount; private View mNavHeaderWithAccount; private CircleImageView mProfilePicture; private TextView mProfileName; private View mFragmentContainerView; private NavigationDrawerAdapter mNavigationAdapter; private NavigationDrawerListener mListener; private Picasso mPicasso; private boolean mFromSavedInstanceState; private boolean mUserLearnedDrawer; private boolean mUseActionBarToggle; @Inject AccountController mAccountController; @Inject @Named("firebase_auth") AuthProvider mAuthProvider; // Required empty constructor public NavigationDrawerFragment() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Activity activity = getActivity(); if (activity instanceof HasMyTbaComponent) { ((HasMyTbaComponent) activity).getMyTbaComponent().inject(this); } // Read in the flag indicating whether or not the user has demonstrated awareness of the // drawer. See PREF_USER_LEARNED_DRAWER for details. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity()); mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false); mFromSavedInstanceState = (savedInstanceState == null ? true : false); mNavigationAdapter = new NavigationDrawerAdapter(getActivity(), NAVIGATION_ITEMS); mPicasso = Picasso.with(getActivity()); } @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) { View v = inflater.inflate(R.layout.fragment_navigation_drawer, container, false); mDrawerListView = (ListView) v.findViewById(R.id.left_drawer); mDrawerListView.setOnItemClickListener((parent, view, position, id) -> navigationItemClicked(position)); mDrawerListView.setAdapter(mNavigationAdapter); mNavHeaderWithAccount = v.findViewById(R.id.header_with_account); mNavHeaderWithoutAccount = v.findViewById(R.id.header_without_account); mProfileName = (TextView) v.findViewById(R.id.profile_name); mProfilePicture = (CircleImageView) v.findViewById(R.id.profile_image); setupNavDrawerHeader(); return v; } public void setupNavDrawerHeader() { boolean hasAccountDetails = false; if (mAccountController.isMyTbaEnabled()) { User currentUser = mAuthProvider.getCurrentUser(); if (currentUser != null) { String displayName = currentUser.getName(); if (!displayName.isEmpty()) { mProfileName.setText(displayName); hasAccountDetails = true; } Uri profilePic = currentUser.getProfilePicUrl(); if (profilePic != null) { String personPhotoUrl = profilePic.toString(); personPhotoUrl = personPhotoUrl.substring(0, personPhotoUrl.length() - 2) + PROFILE_PIC_SIZE; TbaLogger.d("Profile photo url: " + personPhotoUrl); mPicasso.load(personPhotoUrl).into(mProfilePicture); hasAccountDetails = true; } } else { TbaLogger.w("No current user found"); // If myTBA /should/ be enabled, but we don't have a registered Firebase user, // then we're probably upgrading from a pre-firebase version. Try and migrate Activity activity = getActivity(); activity.startService(new Intent(activity, MigrateLegacyUserToFirebase.class)); } } if (hasAccountDetails) { mNavHeaderWithAccount.setVisibility(View.VISIBLE); mNavHeaderWithoutAccount.setVisibility(View.GONE); } else { mNavHeaderWithAccount.setVisibility(View.GONE); mNavHeaderWithoutAccount.setVisibility(View.VISIBLE); } } 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. * @param encourageLearning True to encourage the user learning how to use the navigation * drawer by showing the drawer automatically when this method is * called until the user has demonstrated knowledge of the drawer's * existence by opening the drawer. False will disable this behavior * and only show the drawer when manually opened. * @param useActionBarToggle True if the Action Bar home button should be used to open the * navigation drawer; false if otherwise. Some hosts may want to use * up navigation so we provide the option to disable this. */ public void setUp(int fragmentId, DrawerLayout drawerLayout, boolean encourageLearning, boolean useActionBarToggle) { mFragmentContainerView = getActivity().findViewById(fragmentId); mDrawerLayout = drawerLayout; mUseActionBarToggle = useActionBarToggle; // set a custom shadow that overlays the main content when the drawer opens mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); // set up the drawer's list view with items and click listener if (mUseActionBarToggle) { ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setHomeButtonEnabled(true); // 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.drawer_open, /* "open drawer" description for accessibility */ R.string.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() } }; mDrawerLayout.setDrawerListener(mDrawerToggle); // Defer code dependent on restoration of previous instance state. mDrawerLayout.post(() -> mDrawerToggle.syncState()); } else { mDrawerLayout.setDrawerListener(new DrawerLayout.SimpleDrawerListener() { @Override public void onDrawerOpened(View 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(); } } @Override public void onDrawerClosed(View drawerView) { // Do nothing } }); } // If the user hasn't 'learned' about the drawer, open it to introduce them to the drawer, // per the navigation drawer design guidelines. if (encourageLearning && !mUserLearnedDrawer && !mFromSavedInstanceState) { mDrawerLayout.openDrawer(mFragmentContainerView); } } /** * Called when an item in the navigation drawer is clicked * * @param position The position of the clicked item */ private void navigationItemClicked(int position) { ListItem item = mNavigationAdapter.getItem(position); // Ignore clicks on items like dividers and spacers if (!(item instanceof NavDrawerItem)) { return; } if (mDrawerListView != null) { mDrawerListView.setItemChecked(position, true); } mListener.onNavDrawerItemClicked((NavDrawerItem) item); if (mDrawerLayout != null) { mDrawerLayout.closeDrawer(mFragmentContainerView); } } /** * Set the currently selected item in the drawer * <p> * This will NOT trigger the OnNavigationDrawerListener callbacks or close the drawer. * * @param itemId The ID of the item to select */ public void setItemSelected(int itemId) { if (mDrawerListView != null) { int position = mNavigationAdapter.getPositionForId(itemId); mDrawerListView.setItemChecked(position, true); } } @Override public void onAttach(Context context) { super.onAttach(context); Activity activity = getActivity(); if (activity instanceof NavigationDrawerListener) { mListener = (NavigationDrawerListener) activity; } else { throw new IllegalStateException("Activities hosting a NavigationDrawerFragment must implement OnNavigationDrawerListener"); } } @Override public void onDetach() { super.onDetach(); mListener = null; } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (mDrawerToggle != null) { // Forward the new configuration the drawer toggle component. mDrawerToggle.onConfigurationChanged(newConfig); } } @Override public boolean onOptionsItemSelected(MenuItem item) { if (mUseActionBarToggle && mDrawerToggle != null) { return mDrawerToggle.onOptionsItemSelected(item); } return super.onOptionsItemSelected(item); } @Nullable private ActionBar getSupportActionBar() { return getActivity() != null ? ((AppCompatActivity) getActivity()).getSupportActionBar() : null; } /** * Interface for receiving navigation drawer callbacks */ public interface NavigationDrawerListener { /** * Called when a NavDrawerItem in the navigation drawer is clicked * * @param item The item that was clicked */ void onNavDrawerItemClicked(NavDrawerItem item); } public static class LinearGradientTransformation implements Transformation { @Override public Bitmap transform(Bitmap source) { Bitmap outBitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(), source.getConfig()); // Create shaders Shader bitmapShader = new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); Shader linearGradient = new LinearGradient(0, source.getHeight() / 2, 0, source.getHeight(), Color.TRANSPARENT, 0xB4000000, Shader.TileMode.CLAMP); // create a shader that combines both effects ComposeShader shader = new ComposeShader(bitmapShader, linearGradient, PorterDuff.Mode.DST_OUT); Paint p = new Paint(); p.setAntiAlias(true); p.setShader(shader); Paint black = new Paint(); black.setColor(Color.BLACK); Canvas c = new Canvas(outBitmap); c.drawRect(0, 0, source.getWidth(), source.getHeight(), black); c.drawPaint(p); source.recycle(); return outBitmap; } @Override public String key() { return "linear_gradient"; } } /** * Called when the insets of the nav drawer are changed. This allows us to properly place the * contents so that they don't flow under the status bar. */ public void onInsetsChanged(Rect insets) { RelativeLayout navDrawereHeader = (RelativeLayout) getView().findViewById(R.id.nav_drawer_header); int pBottom = navDrawereHeader.getPaddingBottom(); int pLeft = navDrawereHeader.getPaddingLeft(); int pRight = navDrawereHeader.getPaddingRight(); navDrawereHeader.setPadding(pLeft, insets.top, pRight, pBottom); } }