package org.holoeverywhere.slider; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import org.holoeverywhere.HoloEverywhere; import org.holoeverywhere.LayoutInflater; import org.holoeverywhere.ThemeManager; import org.holoeverywhere.addon.AddonSlider.AddonSliderA; import org.holoeverywhere.addon.IAddonThemes; import org.holoeverywhere.app.Activity; import org.holoeverywhere.app.Fragment; import org.holoeverywhere.app.ListFragment; import org.holoeverywhere.widget.ListView; import org.holoeverywhere.widget.TextView; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.os.BadParcelableException; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager.OnBackStackChangedListener; import android.support.v4.app.FragmentTransaction; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import android.widget.Adapter; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.BaseAdapter; import com.actionbarsherlock.app.ActionBar; public class SliderMenu implements OnBackStackChangedListener { public static enum SelectionBehavior { BackgroundWhenSelected, Default, OnlyBackground, OnlyHandler; } public static class SliderItem implements Parcelable { public static final Creator<SliderItem> CREATOR = new Creator<SliderMenu.SliderItem>() { @Override public SliderItem createFromParcel(Parcel source) { try { return new SliderItem(source); } catch (Exception e) { throw new BadParcelableException(e); } } @Override public SliderItem[] newArray(int size) { return new SliderItem[size]; } }; private int mBackgroundColor = 0; private int mCustomLayout = 0; private Bundle mFragmentArguments; private Class<? extends Fragment> mFragmentClass; private CharSequence mLabel; private WeakReference<Fragment> mLastFragment; private Fragment.SavedState mSavedState; private boolean mSaveState = true; private int mSelectionHandlerColor = 0; private SliderMenu mSliderMenu; private String mTag; private int mTextAppereance = 0; private int mTextAppereanceInverse = 0; public SliderItem() { } @SuppressWarnings("unchecked") protected SliderItem(Parcel source) throws Exception { String classname = source.readString(); if (classname != null) { mFragmentClass = (Class<? extends Fragment>) Class.forName(classname); } mSavedState = source.readParcelable(Fragment.SavedState.class.getClassLoader()); mSaveState = source.readInt() == 1; mCustomLayout = source.readInt(); mBackgroundColor = source.readInt(); mSelectionHandlerColor = source.readInt(); mTextAppereance = source.readInt(); mTextAppereanceInverse = source.readInt(); mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); } @Override public int describeContents() { return 0; } public int getBackgroundColor() { return mBackgroundColor; } public int getCustomLayout() { return mCustomLayout; } public Bundle getFragmentArguments() { return mFragmentArguments; } public Class<? extends Fragment> getFragmentClass() { return mFragmentClass; } public CharSequence getLabel() { return mLabel; } public int getSelectionHandlerColor() { return mSelectionHandlerColor; } public String getTag() { return mTag; } public int getTextAppereance() { return mTextAppereance; } public int getTextAppereanceInverse() { return mTextAppereanceInverse; } private void invalidate() { if (mSliderMenu != null) { mSliderMenu.invalidate(); } } public boolean isSaveState() { return mSaveState; } public void setBackgroundColor(int backgroundColor) { mBackgroundColor = backgroundColor; } public void setCustomLayout(int customLayout) { mCustomLayout = customLayout; } public void setFragmentArguments(Bundle fragmentArguments) { mFragmentArguments = fragmentArguments; } public void setFragmentClass(Class<? extends Fragment> fragmentClass) { if (mFragmentClass == fragmentClass) { return; } mFragmentClass = fragmentClass; mSavedState = null; } public void setLabel(CharSequence label) { mLabel = label; invalidate(); } public void setSaveState(boolean saveState) { if (mSaveState == saveState) { return; } mSaveState = saveState; if (!saveState) { mSavedState = null; } } public void setSelectionHandlerColor(int selectionHandlerColor) { mSelectionHandlerColor = selectionHandlerColor; } public void setTag(String tag) { mTag = tag; } public void setTextAppereance(int textAppereance) { mTextAppereance = textAppereance; } public void setTextAppereanceInverse(int textAppereanceInverse) { mTextAppereanceInverse = textAppereanceInverse; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mFragmentClass == null ? null : mFragmentClass.getName()); dest.writeParcelable(mSaveState ? mSavedState : null, flags); dest.writeInt(mSaveState ? 1 : 0); dest.writeInt(mCustomLayout); dest.writeInt(mBackgroundColor); dest.writeInt(mSelectionHandlerColor); dest.writeInt(mTextAppereance); dest.writeInt(mTextAppereanceInverse); TextUtils.writeToParcel(mLabel, dest, flags); } } private final class SliderMenuAdapter extends BaseAdapter implements OnItemClickListener { private final int mDefaultTextAppearance; private final int mDefaultTextAppearanceInverse; private final LayoutInflater mLayoutInflater; private SliderMenuAdapter(Context context) { context = sThemes.context(context); mLayoutInflater = LayoutInflater.from(context); TypedArray a = context.obtainStyledAttributes(R.styleable.SliderMenu); mDefaultTextAppearance = a.getResourceId( R.styleable.SliderMenu_textAppearanceSliderItem, R.style.Holo_TextAppearance_Medium); mDefaultTextAppearanceInverse = a.getResourceId( R.styleable.SliderMenu_textAppearanceSliderItemInverse, R.style.Holo_TextAppearance_Medium_Inverse); a.recycle(); } @Override public int getCount() { return mItems.size(); } @Override public SliderItem getItem(int position) { return mItems.get(position); } @Override public long getItemId(int position) { return position; } @Override public int getItemViewType(int position) { return getItem(position).mCustomLayout != 0 ? position : Adapter.IGNORE_ITEM_VIEW_TYPE; } @Override public View getView(int position, View convertView, ViewGroup parent) { final SliderItem item = getItem(position); if (convertView == null) { convertView = mLayoutInflater.inflate(item.mCustomLayout != 0 ? item.mCustomLayout : R.layout.slider_menu_item, parent, false); } return bindView(item, convertView, mCurrentPage == position, mDefaultTextAppearance, mDefaultTextAppearanceInverse); } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { setCurrentPage(position, false); } } public static final int[] BLUE = new int[] { R.color.holo_blue_dark, R.color.holo_blue_light }; public static final int[] GREEN = new int[] { R.color.holo_green_dark, R.color.holo_green_light }; private static final String KEY_CURRENT_PAGE = ":slider:currentPage"; public static final int[] ORANGE = new int[] { R.color.holo_orange_dark, R.color.holo_orange_light }; public static final int[] PURPLE = new int[] { R.color.holo_purple_dark, R.color.holo_purple_light }; public static final int[] RED = new int[] { R.color.holo_red_dark, R.color.holo_red_light }; private static final IAddonThemes sThemes; public static final int THEME_FLAG; static { sThemes = new IAddonThemes(); THEME_FLAG = sThemes.getThemeFlag(); map(R.style.Holo_Internal_SliderTheme, R.style.Holo_Internal_SliderTheme_Light); } /** * Remap all SliderMenu themes */ public static void map(int theme) { map(theme, theme, theme); } /** * Remap SliderMenu themes, splited by dark and light color scheme. For * mixed color scheme will be using light theme */ public static void map(int darkTheme, int lightTheme) { map(darkTheme, lightTheme, lightTheme); } /** * Remap SliderMenu themes, splited by color scheme */ public static void map(int darkTheme, int lightTheme, int mixedTheme) { sThemes.map(darkTheme, lightTheme, mixedTheme); } private static void setTextAppearance(TextView textView, int resid) { if (resid != 0) { textView.setTextAppearance(textView.getContext(), resid); } } private ActionBar mActionBar; private SliderMenuAdapter mAdapter; private final AddonSliderA mAddon; private int mCurrentPage = -1; private final FragmentManager mFragmentManager; private int mFuturePosition = -1; private boolean mHandleHomeKey; private boolean mIgnoreBackStack = false; private int mInitialPage = 0; private boolean mInverseTextColorWhenSelected = false; private final List<SliderItem> mItems; private SelectionBehavior mSelectionBehavior = SelectionBehavior.Default; public SliderMenu(AddonSliderA addon) { mAddon = addon; mActionBar = addon.get().getSupportActionBar(); mFragmentManager = mAddon.get().getSupportFragmentManager(); mItems = new ArrayList<SliderItem>(); } public SliderItem add(CharSequence label, Class<? extends Fragment> fragmentClass) { return add(label, fragmentClass, null, null); } public SliderItem add(CharSequence label, Class<? extends Fragment> fragmentClass, Bundle fragmentArguments) { return add(label, fragmentClass, fragmentArguments, null); } public SliderItem add(CharSequence label, Class<? extends Fragment> fragmentClass, Bundle fragmentArguments, int[] colors) { SliderItem item = new SliderItem(); item.setLabel(label); item.setFragmentClass(fragmentClass); item.setFragmentArguments(fragmentArguments); if (colors != null && colors.length >= 2) { final Resources res = mAddon.get().getResources(); item.setBackgroundColor(res.getColor(colors[0])); item.setSelectionHandlerColor(res.getColor(colors[1])); } return add(item); } public SliderItem add(CharSequence label, Class<? extends Fragment> fragmentClass, int[] colors) { return add(label, fragmentClass, null, colors); } public SliderItem add(int label, Class<? extends Fragment> fragmentClass) { return add(label, fragmentClass, null, null); } public SliderItem add(int label, Class<? extends Fragment> fragmentClass, Bundle fragmentArguments) { return add(label, fragmentClass, fragmentArguments, null); } public SliderItem add(int label, Class<? extends Fragment> fragmentClass, Bundle fragmentArguments, int[] colors) { return add(mAddon.get().getText(label), fragmentClass, fragmentArguments, colors); } public SliderItem add(int label, Class<? extends Fragment> fragmentClass, int[] colors) { return add(label, fragmentClass, null, colors); } public SliderItem add(SliderItem item) { if (item.mSliderMenu != null) { throw new IllegalArgumentException("Item already has a parent: " + item + " (" + item.mSliderMenu + ")"); } item.mSliderMenu = this; mItems.add(item); notifyChanged(); return item; } public SliderItem add(SliderItem item, int position) { if (item.mSliderMenu != null) { throw new IllegalArgumentException("Item already has a parent: " + item + " (" + item.mSliderMenu + ")"); } item.mSliderMenu = this; mItems.add(position, item); notifyChanged(); return item; } public void bind(ListFragment listFragment) { bind(listFragment, null); } public void bind(ListFragment listFragment, Context context) { bind(listFragment.getListView(), context); } public void bind(ListView listView) { bind(listView, null); } public void bind(ListView listView, Context context) { if (mAdapter != null) { throw new IllegalStateException("No more than one view allowed"); } mAdapter = new SliderMenuAdapter(context == null ? listView.getContext() : context); listView.setAdapter(mAdapter); listView.setOnItemClickListener(mAdapter); if (mFuturePosition >= 0) { setCurrentPage(mFuturePosition, true); mFuturePosition = -1; } } public void bindOnLeftPanel() { bindOnLeftPanel(null); } public void bindOnLeftPanel(Context context) { tryToBind(mAddon.getLeftView(), context); } public void bindOnRightPanel() { bindOnRightPanel(null); } public void bindOnRightPanel(Context context) { tryToBind(mAddon.getRightView(), context); } @SuppressWarnings("deprecation") public View bindView(SliderItem item, View view, boolean selected, int defaultTextAppearance, int defaultTextAppearanceInverse) { TextView labelView = (TextView) view.findViewById(android.R.id.text1); if (labelView != null) { labelView.setText(item.mLabel); } final int textAppereance = item.mTextAppereance != 0 ? item.mTextAppereance : defaultTextAppearance; final int textAppereanceInverse = item.mTextAppereanceInverse != 0 ? item.mTextAppereanceInverse : defaultTextAppearanceInverse; setTextAppearance(labelView, mInverseTextColorWhenSelected ? selected ? textAppereanceInverse : textAppereance : textAppereance); if (mSelectionBehavior == null) { return view; } View selectionHandlerView = view.findViewById(R.id.selectionHandler); switch (mSelectionBehavior) { case Default: if (selected) { view.setBackgroundColor(item.mBackgroundColor); if (selectionHandlerView != null) { selectionHandlerView.setBackgroundColor(item.mSelectionHandlerColor); } } else { view.setBackgroundDrawable(null); if (selectionHandlerView != null) { selectionHandlerView.setBackgroundDrawable(null); } } break; case BackgroundWhenSelected: if (selectionHandlerView != null) { selectionHandlerView.setBackgroundColor(item.mSelectionHandlerColor); } if (selected) { view.setBackgroundColor(item.mBackgroundColor); } else { view.setBackgroundDrawable(null); } break; case OnlyBackground: if (selected) { view.setBackgroundColor(item.mBackgroundColor); } else { view.setBackgroundDrawable(null); } break; case OnlyHandler: if (selectionHandlerView != null) { if (selected) { selectionHandlerView.setBackgroundColor(item.mSelectionHandlerColor); } else { selectionHandlerView.setBackgroundDrawable(null); } } break; } return view; } protected void changePage(int position) { changePage(position, mItems.get(position)); } private void changePage(int position, SliderItem item) { mIgnoreBackStack = true; if (mCurrentPage >= 0) { final SliderItem lastItem = mAdapter.getItem(mCurrentPage); final WeakReference<Fragment> ref = lastItem.mLastFragment; final Fragment fragment = ref == null ? null : ref.get(); if (fragment != null && fragment.isAdded()) { if (!fragment.isDetached()) { mFragmentManager.beginTransaction().detach(fragment).commit(); mFragmentManager.executePendingTransactions(); } if (lastItem.mSaveState) { lastItem.mSavedState = mFragmentManager.saveFragmentInstanceState(fragment); } } } mCurrentPage = position; mAdapter.notifyDataSetInvalidated(); while (mFragmentManager.popBackStackImmediate()) { } final Fragment fragment = Fragment.instantiate(item.mFragmentClass); if (item.mSavedState != null) { fragment.setInitialSavedState(item.mSavedState); } if (item.mFragmentArguments != null) { item.mFragmentArguments.setClassLoader(HoloEverywhere.class.getClassLoader()); fragment.setArguments(item.mFragmentArguments); } item.mLastFragment = new WeakReference<Fragment>(fragment); final String tag; if (item.mTag != null) { tag = item.mTag; } else { tag = "fragment-" + fragment.hashCode(); } clearBackStack(); replaceFragment(mFragmentManager, fragment, tag, false); mIgnoreBackStack = false; } private void clearBackStack() { while (mFragmentManager.popBackStackImmediate()) { ; } } public int getInitialPage() { return mInitialPage; } public SelectionBehavior getSelectionBehavior() { return mSelectionBehavior; } public int indexOfItem(SliderItem item) { return mItems.indexOf(item); } public void invalidate() { if (mAdapter != null) { mAdapter.notifyDataSetInvalidated(); } } public boolean isHandleHomeKey() { return mHandleHomeKey; } public boolean isInverseTextColorWhenSelected() { return mInverseTextColorWhenSelected; } public void makeDefaultMenu() { makeDefaultMenu(false); } public void makeDefaultMenu(boolean useActionBarStyle) { final Activity activity = mAddon.get(); makeDefaultMenu(useActionBarStyle ? activity.getSupportActionBarContext() : activity); } public void makeDefaultMenu(Context context) { setInverseTextColorWhenSelected(ThemeManager.getThemeType(mAddon.get()) != ThemeManager.LIGHT); ListFragment menuFragment = (ListFragment) mFragmentManager.findFragmentById(R.id.leftView); if (menuFragment == null) { menuFragment = (ListFragment) mFragmentManager.findFragmentById(R.id.rightView); } if (menuFragment == null) { throw new IllegalStateException("Couldn't find ListFragment for menu"); } bind(menuFragment, context); } private void notifyChanged() { if (mAdapter != null) { mAdapter.notifyDataSetChanged(); } } @Override public void onBackStackChanged() { if (mHandleHomeKey) { mActionBar.setDisplayHomeAsUpEnabled(mAddon.isAddonEnabled() ? true : mFragmentManager.getBackStackEntryCount() > 0); } if (mIgnoreBackStack || mCurrentPage < 0 || mCurrentPage >= mItems.size()) { return; } SliderItem item = mItems.get(mCurrentPage); WeakReference<Fragment> ref = item.mLastFragment; Fragment fragment = ref == null ? null : ref.get(); if (fragment == null || !item.mSaveState) { return; } if (fragment.isAdded() && fragment.isDetached() && !fragment.isVisible()) { item.mSavedState = mFragmentManager.saveFragmentInstanceState(fragment); } } public boolean onHomePressed() { if (mHandleHomeKey) { if (mAddon.isAddonEnabled() && mFragmentManager.getBackStackEntryCount() == 0) { mAddon.toggle(); } else { mAddon.get().onBackPressed(); } return true; } return false; } public void onPostCreate(Bundle savedInstanceState) { if (savedInstanceState != null) { mCurrentPage = savedInstanceState.getInt(KEY_CURRENT_PAGE, 0); } mIgnoreBackStack = true; mAddon.get().getSupportFragmentManager().addOnBackStackChangedListener(this); onBackStackChanged(); mIgnoreBackStack = false; } public void onResume() { if (mCurrentPage < 0 && mItems.size() > 0) { setCurrentPage(mInitialPage, true); } } public void onSaveInstanceState(Bundle outState) { outState.putInt(KEY_CURRENT_PAGE, mCurrentPage); } public void remove(int position) { mItems.remove(position).mSliderMenu = null; notifyChanged(); } public void remove(SliderItem item) { if (mItems.remove(item)) { item.mSliderMenu = null; } notifyChanged(); } public void replaceFragment(Fragment newFragment) { replaceFragment(newFragment, null, true); } public void replaceFragment(Fragment newFragment, String tag, boolean addToBackStack) { if (!addToBackStack) { clearBackStack(); } replaceFragment(mFragmentManager, newFragment, tag, addToBackStack); } private void replaceFragment(FragmentManager fm, Fragment fragment, String tag, boolean addToBackStack) { FragmentTransaction ft = fm.beginTransaction(); ft.replace(R.id.contentView, fragment, tag); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); if (addToBackStack) { ft.addToBackStack(tag); ft.commitAllowingStateLoss(); } else { ft.commit(); } } public void setCurrentPage(int position) { setCurrentPage(position, true); } private void setCurrentPage(int position, boolean force) { if (mAdapter == null) { mFuturePosition = position; } if (position < 0 || position >= mItems.size()) { throw new IndexOutOfBoundsException(); } if (force || mCurrentPage != position || mAddon.get().getSupportFragmentManager().getBackStackEntryCount() > 0) { changePage(position, mAdapter.getItem(position)); mCurrentPage = position; } if (mAddon.isAddonEnabled()) { mAddon.openContentViewDelayed(40); } } public void setHandleHomeKey(boolean handleHomeKey) { mHandleHomeKey = handleHomeKey; } public void setInitialPage(int initialPage) { mInitialPage = initialPage; } public void setInverseTextColorWhenSelected(boolean inverseTextColorWhenSelected) { mInverseTextColorWhenSelected = inverseTextColorWhenSelected; } public void setSelectionBehavior(SelectionBehavior selectionBehavior) { mSelectionBehavior = selectionBehavior; } private void tryToBind(View view, Context context) { if (view instanceof ListView) { bind((ListView) view, context); return; } final ListView listView = (ListView) view.findViewById(android.R.id.list); if (listView != null) { bind(listView, context); return; } throw new IllegalStateException("Couldn't find ListView on panel"); } }