package com.dozuki.ifixit.ui;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.actionbarsherlock.view.MenuItem;
import com.dozuki.ifixit.App;
import com.dozuki.ifixit.BuildConfig;
import com.dozuki.ifixit.R;
import com.dozuki.ifixit.model.user.LoginEvent;
import com.dozuki.ifixit.ui.gallery.GalleryActivity;
import com.dozuki.ifixit.ui.guide.create.GuideCreateActivity;
import com.dozuki.ifixit.ui.guide.create.StepEditActivity;
import com.dozuki.ifixit.ui.guide.view.FeaturedGuidesActivity;
import com.dozuki.ifixit.ui.guide.view.OfflineGuidesActivity;
import com.dozuki.ifixit.ui.guide.view.TeardownsActivity;
import com.dozuki.ifixit.ui.search.SearchActivity;
import com.dozuki.ifixit.ui.topic_view.TopicActivity;
import net.simonvt.menudrawer.MenuDrawer;
import net.simonvt.menudrawer.Position;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* Base activity that displays the menu drawer.
*/
public abstract class BaseMenuDrawerActivity extends BaseActivity
implements AdapterView.OnItemClickListener {
private static final String STATE_ACTIVE_POSITION =
"com.dozuki.ifixit.ui.BaseMenuDrawerActivity.activePosition";
private static final String PEEK_MENU = "PEEK_MENU_KEY";
private static final String INTERFACE_STATE = "IFIXIT_INTERFACE_STATE";
/**
* Slide Out Menu Drawer
*/
private MenuDrawer mMenuDrawer;
private int mActivePosition = -1;
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
if (savedState != null) {
mActivePosition = savedState.getInt(STATE_ACTIVE_POSITION);
}
mMenuDrawer = MenuDrawer.attach(this, MenuDrawer.Type.OVERLAY,
Position.LEFT, MenuDrawer.MENU_DRAG_CONTENT);
mMenuDrawer.setMenuSize(getResources().getDimensionPixelSize(R.dimen.menu_size));
mMenuDrawer.setTouchMode(MenuDrawer.TOUCH_MODE_BEZEL);
mMenuDrawer.setTouchBezelSize(getResources().getDimensionPixelSize(R.dimen.menu_bezel_size));
SharedPreferences prefs = getSharedPreferences(INTERFACE_STATE, MODE_PRIVATE);
if (!prefs.contains(PEEK_MENU)) {
prefs.edit().putBoolean(PEEK_MENU, false).commit();
mMenuDrawer.openMenu();
}
buildSliderMenu();
}
@Override
public void setContentView(int layoutResId) {
mMenuDrawer.setContentView(layoutResId);
}
@Override
public void onRestart() {
super.onRestart();
// Invalidate the options menu in case the user logged in/out in a child Activity.
buildSliderMenu();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
mMenuDrawer.toggleMenu();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onCustomMenuTitleClick(View v) {
// Rather than finishing the Activity and going "up", toggle the menu drawer.
mMenuDrawer.toggleMenu();
}
@Override
public void onLogin(LoginEvent.Login event) {
super.onLogin(event);
// Reload app to update the menu to include the user name and logout button.
buildSliderMenu();
}
@Override
public void onLogout(LoginEvent.Logout event) {
super.onLogout(event);
// Reload app to remove username and logout button from menu.
buildSliderMenu();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
String barcodeScannerResult = getBarcodeScannerResult(requestCode, resultCode, intent);
if (barcodeScannerResult != null) {
startActivity(IntentFilterActivity.viewUrl(this, barcodeScannerResult));
} else {
super.onActivityResult(requestCode, resultCode, intent);
}
}
private String getBarcodeScannerResult(int requestCode, int resultCode, Intent intent) {
// The classes below might not exist if barcode scanning isn't enabled.
if (!App.get().getSite().barcodeScanningEnabled()) {
return null;
}
try {
// Call IntentIntegrator.parseResult(requestCode, resultCode, intent);
Class<?> c = Class.forName("com.google.zxing.integration.android.IntentIntegrator");
Class[] argTypes = new Class[]{Integer.TYPE, Integer.TYPE, Intent.class};
Method parseResult = c.getDeclaredMethod("parseActivityResult", argTypes);
Object intentResult = parseResult.invoke(null, requestCode, resultCode, intent);
// The request code didn't match.
if (intentResult == null) {
return null;
}
// Call intentResult.getContents().
c = Class.forName("com.google.zxing.integration.android.IntentResult");
argTypes = new Class[]{};
Method getContents = c.getDeclaredMethod("getContents", argTypes);
Object contents = getContents.invoke(intentResult);
return (String) contents;
} catch (Exception e) {
Toast.makeText(this, "Failed to parse result.", Toast.LENGTH_SHORT).show();
Log.e("BaseMenuDrawerActivity", "Failure parsing activity result", e);
return null;
}
}
private void buildSliderMenu() {
// Add items to the menu. The order Items are added is the order they appear in the menu.
List<NavigationItem> items = new ArrayList<NavigationItem>();
for (NavigationItem item : NavigationItem.values()) {
if (item.shouldDisplay()) {
items.add(item);
}
}
// A custom ListView is needed so the drawer can be notified when it's scrolled. This is to update the position
// of the arrow indicator.
ListView menuList = new ListView(this);
MenuAdapter adapter = new MenuAdapter(items);
menuList.setAdapter(adapter);
menuList.setOnItemClickListener(this);
menuList.setCacheColorHint(Color.TRANSPARENT);
mMenuDrawer.setMenuView(menuList);
mMenuDrawer.setSlideDrawable(R.drawable.ic_drawer);
mMenuDrawer.setDrawerIndicatorEnabled(true);
mMenuDrawer.invalidate();
}
public void setMenuDrawerSlideDrawable(int drawable) {
mMenuDrawer.setSlideDrawable(drawable);
}
/**
* Close the menu drawer if back is pressed and the menu is open.
*/
@Override
public void onBackPressed() {
final int drawerState = mMenuDrawer.getDrawerState();
if (drawerState == MenuDrawer.STATE_OPEN
|| drawerState == MenuDrawer.STATE_OPENING) {
mMenuDrawer.closeMenu();
return;
}
super.onBackPressed();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_ACTIVE_POSITION, mActivePosition);
}
/**
* Represents an item or separator in the navigation drawer. Items are displayed
* in the order that they are defined.
*/
protected enum NavigationItem {
SITE_LIST(
R.string.back_to_site_list,
R.drawable.ic_action_list
) {
@Override
public boolean shouldDisplay() {
return App.isDozukiApp();
}
@Override
public void performNavigation(BaseMenuDrawerActivity activity) {
activity.returnToSiteList();
}
},
SEARCH(
R.string.search,
R.drawable.ic_action_search,
SearchActivity.class
),
SCAN_BARCODE(
R.string.slide_menu_barcode_scanner,
R.drawable.ic_action_qr_code
) {
@Override
public boolean shouldDisplay() {
return App.get().getSite().barcodeScanningEnabled();
}
@Override
public void performNavigation(BaseMenuDrawerActivity activity) {
activity.launchBarcodeScanner();
}
},
BROWSE_CONTENT_SEPARATOR(R.string.slide_menu_browse_content),
BROWSE_TOPICS(
R.string.slide_menu_browse_devices,
R.drawable.ic_action_list_2,
TopicActivity.class
) {
@Override
public String getTitle(Context context) {
return context.getString(mTitle,
App.get().getSite().getObjectNamePlural());
}
},
STORE(
R.string.parts_and_tools,
R.drawable.ic_action_basket,
"http://www.ifixit.com/Store"
) {
@Override
public boolean shouldDisplay() {
return App.get().getSite().isIfixit();
}
},
FEATURED_GUIDES(
R.string.featured_guides,
R.drawable.ic_action_star_10,
FeaturedGuidesActivity.class
) {
@Override
public boolean shouldDisplay() {
return App.get().getSite().isIfixit();
}
},
TEARDOWNS(
R.string.teardowns,
R.drawable.ic_menu_stack,
TeardownsActivity.class
) {
@Override
public boolean shouldDisplay() {
return App.get().getSite().isIfixit();
}
},
ACCOUNT_MENU_SEPARATOR() {
@Override
public String getTitle(Context context) {
App app = App.get();
boolean loggedIn = app.isUserLoggedIn();
if (loggedIn) {
String username = app.getUser().getUsername();
return context.getString(R.string.account_username_title, username);
} else {
return context.getString(R.string.account_menu_title);
}
}
},
// Note: This doesn't use live data but rather displays guides stored
// offline.
USER_FAVORITES(
R.string.slide_menu_favorite_guides,
R.drawable.ic_menu_favorite_light,
OfflineGuidesActivity.class
),
USER_GUIDES(
R.string.slide_menu_my_guides,
R.drawable.ic_menu_spinner_guides,
GuideCreateActivity.class
),
NEW_GUIDE(
R.string.slide_menu_create_new_guide,
R.drawable.ic_menu_add_guide,
StepEditActivity.class,
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
),
MEDIA_GALLERY(
R.string.slide_menu_media_gallery,
R.drawable.ic_menu_spinner_gallery,
GalleryActivity.class
),
LOGOUT(
R.string.slide_menu_logout,
R.drawable.ic_action_exit
) {
@Override
public boolean shouldDisplay() {
return App.get().isUserLoggedIn();
}
@Override
public void performNavigation(BaseMenuDrawerActivity activity) {
App.get().logout(activity);
}
},
IFIXIT_EVERYWHERE_SEPARATOR(
R.string.slide_menu_ifixit_everywhere
) {
@Override
public boolean shouldDisplay() {
return App.get().getSite().isIfixit();
}
},
YOUTUBE(
R.string.slide_menu_youtube,
R.drawable.ic_action_youtube,
"https://www.youtube.com/user/iFixitYourself"
) {
@Override
public boolean shouldDisplay() {
return App.get().getSite().isIfixit();
}
},
FACEBOOK(
R.string.slide_menu_facebook,
R.drawable.ic_action_facebook,
"https://www.facebook.com/iFixit"
) {
@Override
public boolean shouldDisplay() {
return App.get().getSite().isIfixit();
}
},
TWITTER(
R.string.slide_menu_twitter,
R.drawable.ic_action_twitter,
"https://twitter.com/iFixit"
) {
@Override
public boolean shouldDisplay() {
return App.get().getSite().isIfixit();
}
},
// Display a separator so we know that the app is in debug mode.
DEBUG_SEPARATOR(R.string.debug) {
@Override
public boolean shouldDisplay() {
return BuildConfig.DEBUG;
}
};
private static final int NO_RES = -1;
private static final int DEFAULT_FLAGS = Intent.FLAG_ACTIVITY_NO_ANIMATION;
/**
* Used for display.
*/
public final boolean mSeparator;
public final int mTitle;
public final int mIcon;
/**
* Used for default navigation actions.
*/
public final Class<? extends BaseActivity> mActivityClass;
public final int mIntentFlags;
public final String mUrl;
private NavigationItem() {
this(true, NO_RES, NO_RES, null, DEFAULT_FLAGS, null);
}
private NavigationItem(int title) {
this(true, title, NO_RES, null, DEFAULT_FLAGS, null);
}
private NavigationItem(int title, int icon) {
this(false, title, icon, null, DEFAULT_FLAGS, null);
}
private NavigationItem(int title, int icon, Class activityClass) {
this(title, icon, activityClass, DEFAULT_FLAGS);
}
private NavigationItem(int title, int icon,
Class<? extends BaseActivity> activityClass, int intentFlags) {
this(false, title, icon, activityClass, intentFlags, null);
}
private NavigationItem(int title, int icon, String url) {
this(false, title, icon, null, DEFAULT_FLAGS, url);
}
private NavigationItem(boolean separator, int title, int icon,
Class<? extends BaseActivity> activityClass, int intentFlags, String url) {
mSeparator = separator;
mTitle = title;
mIcon = icon;
mActivityClass = activityClass;
mIntentFlags = intentFlags;
mUrl = url;
}
/**
* Display all by default.
*/
public boolean shouldDisplay() {
return true;
}
public String getTitle(Context context) {
return context.getString(mTitle);
}
/**
* Enums can override this to run arbitrary code when the item is selected.
*/
public void performNavigation(BaseMenuDrawerActivity activity) {
if (mActivityClass != null) {
Intent intent = new Intent(activity, mActivityClass);
intent.setFlags(mIntentFlags);
activity.startActivity(intent);
} else if (mUrl != null) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(mUrl));
activity.startActivity(intent);
} else {
Log.e("BaseMenuDrawerActivity",
"Could not take action on NavigationItem: " + toString());
}
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
NavigationItem item = (NavigationItem)view.getTag();
AlertDialog navigationDialog = getNavigationAlertDialog(item);
if (navigationDialog != null) {
navigationDialog.show();
} else {
App.sendEvent("menu_action", "drawer_item_click", item.toString().toLowerCase(), null);
mMenuDrawer.closeMenu();
mActivePosition = position;
mMenuDrawer.setActiveView(view, position);
navigateMenuDrawer(item);
}
}
protected void navigateMenuDrawer(NavigationItem item) {
item.performNavigation(this);
}
private void returnToSiteList() {
try {
// We need to use reflection because SiteListActivity only exists for
// the dozuki build but this code is around for all builds.
Intent intent = new Intent(this,
Class.forName("com.dozuki.ifixit.ui.dozuki.SiteListActivity"));
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION |
Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
} catch (ClassNotFoundException e) {
Toast.makeText(this, "Failed to return to site list.", Toast.LENGTH_SHORT).show();
Log.e("BaseMenuDrawerActivity", "Cannot start SiteListActivity", e);
}
}
protected void launchBarcodeScanner() {
// We want to just call `IntentIntegrator.initiateScan(this);` but it doesn't
// compile unless the dependency exists.
try {
Class<?> c = Class.forName("com.google.zxing.integration.android.IntentIntegrator");
Class[] argTypes = new Class[]{android.app.Activity.class};
Method initiateScan = c.getDeclaredMethod("initiateScan", argTypes);
initiateScan.invoke(null, this);
} catch (Exception e) {
Toast.makeText(this, "Failed to launch QR code scanner.", Toast.LENGTH_SHORT).show();
Log.e("BaseMenuDrawerActivity", "Cannot launch barcode scanner", e);
}
}
private class MenuAdapter extends BaseAdapter {
private List<NavigationItem> mItems;
private static final int VIEW_TYPE_COUNT = 2;
public MenuAdapter(List<NavigationItem> items) {
mItems = items;
}
@Override
public int getCount() {
return mItems.size();
}
@Override
public Object getItem(int position) {
return mItems.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getItemViewType(int position) {
return mItems.get(position).mSeparator ? 0 : 1;
}
@Override
public int getViewTypeCount() {
return VIEW_TYPE_COUNT;
}
@Override
public boolean isEnabled(int position) {
return !mItems.get(position).mSeparator;
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
NavigationItem item = mItems.get(position);
if (item.mSeparator) {
if (view == null) {
view = getLayoutInflater().inflate(R.layout.menu_row_category, parent, false);
}
((TextView)view).setText(item.getTitle(BaseMenuDrawerActivity.this));
} else {
if (view == null) {
view = getLayoutInflater().inflate(R.layout.menu_row_item, parent, false);
}
TextView textView = (TextView)view;
textView.setText(item.getTitle(BaseMenuDrawerActivity.this));
textView.setCompoundDrawablesWithIntrinsicBounds(item.mIcon, 0, 0, 0);
textView.setTag(item);
}
view.setTag(R.id.mdActiveViewPosition, position);
if (position == mActivePosition) {
mMenuDrawer.setActiveView(view, position);
}
return view;
}
}
/**
* Returns an AlertDialog to warn the user before navigating away from the Activity.
* null is returned if the user shouldn't be warned.
*/
public AlertDialog getNavigationAlertDialog(NavigationItem item) {
return null;
}
}