From f16c2a2eee5d0d4617ea1c250e0e2fb1845e7dfa Mon Sep 17 00:00:00 2001 From: serso Date: Fri, 1 Apr 2016 23:05:01 +0200 Subject: [PATCH] Main menu implementation --- .../android/calculator/AppComponent.java | 1 + .../calculator/CalculatorActivity.java | 39 ++++---- .../solovyev/android/calculator/MainMenu.java | 94 +++++++++++++++++++ .../calculator/keyboard/KeyboardUi.java | 20 ++-- .../android/widget/menu/CustomPopupMenu.java | 61 ++++++------ .../widget/menu/CustomPopupMenuHelper.java | 56 +++++++++-- .../main/res/layout-land/activity_main.xml | 2 +- .../main/res/layout-land/cpp_app_keyboard.xml | 8 +- app/src/main/res/layout/activity_main.xml | 2 +- ...ctivity_main_editor_with_overflow_menu.xml | 22 +++++ .../activity_main_editor_with_toolbar.xml | 19 ---- app/src/main/res/layout/cpp_app_keyboard.xml | 8 +- .../res/layout/popup_menu_item_layout.xml | 35 +++++++ app/src/main/res/menu/main.xml | 12 +-- app/src/main/res/values/ids.xml | 2 + 15 files changed, 277 insertions(+), 104 deletions(-) create mode 100644 app/src/main/java/org/solovyev/android/calculator/MainMenu.java create mode 100644 app/src/main/res/layout/activity_main_editor_with_overflow_menu.xml delete mode 100644 app/src/main/res/layout/activity_main_editor_with_toolbar.xml create mode 100644 app/src/main/res/layout/popup_menu_item_layout.xml diff --git a/app/src/main/java/org/solovyev/android/calculator/AppComponent.java b/app/src/main/java/org/solovyev/android/calculator/AppComponent.java index 20d23489..0bb90f2a 100644 --- a/app/src/main/java/org/solovyev/android/calculator/AppComponent.java +++ b/app/src/main/java/org/solovyev/android/calculator/AppComponent.java @@ -70,4 +70,5 @@ public interface AppComponent { void inject(PreferencesFragment fragment); void inject(WizardFragment fragment); void inject(FloatingCalculatorBroadcastReceiver receiver); + void inject(MainMenu.ViewProvider viewProvider); } diff --git a/app/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java b/app/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java index 61df72cf..e4e16a1a 100644 --- a/app/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java +++ b/app/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java @@ -26,14 +26,13 @@ import android.content.SharedPreferences; import android.os.Bundle; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; -import android.support.v7.widget.Toolbar; +import android.support.v7.widget.PopupMenu; import android.view.KeyEvent; -import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.FrameLayout; +import android.widget.ImageButton; import butterknife.Bind; -import butterknife.ButterKnife; import org.solovyev.android.calculator.converter.ConverterFragment; import org.solovyev.android.calculator.history.History; import org.solovyev.android.calculator.keyboard.PartialKeyboardUi; @@ -42,8 +41,10 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.inject.Inject; -public class CalculatorActivity extends BaseActivity implements Toolbar.OnMenuItemClickListener { +public class CalculatorActivity extends BaseActivity implements View.OnClickListener, PopupMenu.OnMenuItemClickListener { + @Nonnull + private final MainMenu mainMenu = new MainMenu(this); @Inject PreferredPreferences preferredPreferences; @Inject @@ -59,10 +60,10 @@ public class CalculatorActivity extends BaseActivity implements Toolbar.OnMenuIt @Nullable @Bind(R.id.partial_keyboard) View partialKeyboard; - @Bind(R.id.toolbar) - Toolbar toolbar; @Bind(R.id.editor) FrameLayout editor; + @Bind(R.id.main_menu) + ImageButton mainMenuButton; private boolean useBackAsPrevious; public CalculatorActivity() { @@ -73,8 +74,6 @@ public class CalculatorActivity extends BaseActivity implements Toolbar.OnMenuIt public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - ButterKnife.bind(this); - if (savedInstanceState == null) { final FragmentManager fm = getSupportFragmentManager(); final FragmentTransaction t = fm.beginTransaction(); @@ -88,9 +87,7 @@ public class CalculatorActivity extends BaseActivity implements Toolbar.OnMenuIt partialKeyboardUi.onCreateView(this, partialKeyboard); } - toolbar.inflateMenu(R.menu.main); - toolbar.setOnMenuItemClickListener(this); - updateModeMenuItem(); + mainMenuButton.setOnClickListener(this); useBackAsPrevious = Preferences.Gui.useBackAsPrevious.getPreference(preferences); if (savedInstanceState == null) { @@ -100,13 +97,6 @@ public class CalculatorActivity extends BaseActivity implements Toolbar.OnMenuIt preferredPreferences.check(this, false); } - private void updateModeMenuItem() { - final Menu menu = toolbar.getMenu(); - final MenuItem modeMenuItem = menu.findItem(R.id.menu_mode); - final String modeName = getString(getActivityMode().name); - modeMenuItem.setTitle(getString(R.string.cpp_mode) + ": " + modeName); - } - @Override protected void inject(@Nonnull AppComponent component) { super.inject(component); @@ -169,15 +159,24 @@ public class CalculatorActivity extends BaseActivity implements Toolbar.OnMenuIt case R.id.menu_about: launcher.showAbout(); return true; - case R.id.menu_mode_engineer: +/* case R.id.menu_mode_engineer: Preferences.Gui.mode.putPreference(preferences, Preferences.Gui.Mode.engineer); restartIfModeChanged(); return true; case R.id.menu_mode_simple: Preferences.Gui.mode.putPreference(preferences, Preferences.Gui.Mode.simple); restartIfModeChanged(); - return true; + return true;*/ } return false; } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.main_menu: + mainMenu.toggle(); + break; + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/solovyev/android/calculator/MainMenu.java b/app/src/main/java/org/solovyev/android/calculator/MainMenu.java new file mode 100644 index 00000000..119b4547 --- /dev/null +++ b/app/src/main/java/org/solovyev/android/calculator/MainMenu.java @@ -0,0 +1,94 @@ +package org.solovyev.android.calculator; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.support.v4.view.ActionProvider; +import android.support.v7.view.menu.ListMenuItemView; +import android.support.v7.view.menu.MenuItemImpl; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import org.solovyev.android.Check; +import org.solovyev.android.widget.menu.CustomPopupMenu; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.inject.Inject; + +final class MainMenu { + + @NonNull + private CalculatorActivity activity; + @Nullable + private CustomPopupMenu popup; + + public MainMenu(@Nonnull CalculatorActivity activity) { + this.activity = activity; + } + + public void toggle() { + if (popup == null) { + popup = new CustomPopupMenu(activity, activity.mainMenuButton); + popup.inflate(R.menu.main); + final int menuButtonHeight = activity.mainMenuButton.getLayoutParams().height; + Check.isTrue(menuButtonHeight > 0); + popup.setVerticalOffset(menuButtonHeight); + popup.setOnMenuItemClickListener(activity); + } + if (popup.isShowing()) { + popup.dismiss(); + } else { + popup.show(); + } + } + + public static final class ViewProvider extends ActionProvider { + + @Nonnull + private final LayoutInflater inflater; + + @Inject + SharedPreferences preferences; + + public ViewProvider(Context context) { + super(context); + inflater = LayoutInflater.from(context); + App.cast(context).getComponent().inject(this); + } + + @Override + public View onCreateActionView() { + throw new UnsupportedOperationException(); + } + + @Override + public View onCreateActionView(MenuItem menuItem) { + switch (menuItem.getItemId()) { + case R.id.menu_mode: + return makeModeView(menuItem); + } + throw new IllegalArgumentException("Can't create a view for menu item: " + menuItem); + } + + @Nonnull + private View makeModeView(@Nonnull MenuItem menuItem) { + final ViewGroup view = makeDefaultView(menuItem); + final TextView subtitle = (TextView) view.findViewById(R.id.subtitle); + final Preferences.Gui.Mode mode = Preferences.Gui.mode.getPreference(preferences); + subtitle.setText(getContext().getString(mode.name)); + return view; + } + + @SuppressLint("InflateParams") + private ViewGroup makeDefaultView(@Nonnull MenuItem menuItem) { + final ViewGroup view = (ViewGroup) inflater.inflate(R.layout.popup_menu_item_layout, null); + final ListMenuItemView listItemView = (ListMenuItemView) view.findViewById(R.id.menu_list_item_view); + listItemView.initialize((MenuItemImpl) menuItem, 0); + return view; + } + } +} diff --git a/app/src/main/java/org/solovyev/android/calculator/keyboard/KeyboardUi.java b/app/src/main/java/org/solovyev/android/calculator/keyboard/KeyboardUi.java index a71e717a..51dde3bd 100644 --- a/app/src/main/java/org/solovyev/android/calculator/keyboard/KeyboardUi.java +++ b/app/src/main/java/org/solovyev/android/calculator/keyboard/KeyboardUi.java @@ -85,8 +85,10 @@ public class KeyboardUi extends BaseKeyboardUi { DirectionDragButton periodButton; @Bind(R.id.cpp_button_round_brackets) DirectionDragButton bracketsButton; + @Nullable @Bind(R.id.cpp_button_copy) NumeralBasesButton copyButton; + @Nullable @Bind(R.id.cpp_button_paste) AngleUnitsButton pasteButton; @Nullable @@ -144,10 +146,14 @@ public class KeyboardUi extends BaseKeyboardUi { prepareButton(button8); prepareButton(button9); - prepareButton(copyButton); - copyButton.setNumeralBase(numeralBase.getPreference(preferences)); - prepareButton(pasteButton); - pasteButton.setAngleUnit(angleUnit.getPreference(preferences)); + if (copyButton != null) { + prepareButton(copyButton); + copyButton.setNumeralBase(numeralBase.getPreference(preferences)); + } + if (pasteButton != null) { + prepareButton(pasteButton); + pasteButton.setAngleUnit(angleUnit.getPreference(preferences)); + } prepareButton(likeButton); prepareButton(memoryButton); @@ -179,12 +185,14 @@ public class KeyboardUi extends BaseKeyboardUi { @Override public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { super.onSharedPreferenceChanged(preferences, key); - if (angleUnit.isSameKey(key)) { + if (angleUnit.isSameKey(key) && pasteButton != null) { pasteButton.setAngleUnit(angleUnit.getPreference(preferences)); } if (numeralBase.isSameKey(key)) { toggleNumericDigits(); - copyButton.setNumeralBase(numeralBase.getPreference(preferences)); + if (copyButton != null) { + copyButton.setNumeralBase(numeralBase.getPreference(preferences)); + } } if (multiplicationSign.isSameKey(key)) { multiplicationButton.setText(multiplicationSign.getPreference(preferences)); diff --git a/app/src/main/java/org/solovyev/android/widget/menu/CustomPopupMenu.java b/app/src/main/java/org/solovyev/android/widget/menu/CustomPopupMenu.java index 41027687..b1189966 100644 --- a/app/src/main/java/org/solovyev/android/widget/menu/CustomPopupMenu.java +++ b/app/src/main/java/org/solovyev/android/widget/menu/CustomPopupMenu.java @@ -25,6 +25,7 @@ import android.support.v7.view.menu.MenuPopupHelper; import android.support.v7.view.menu.MenuPresenter; import android.support.v7.view.menu.SubMenuBuilder; import android.support.v7.widget.ListPopupWindow; +import android.support.v7.widget.PopupMenu; import android.view.*; /** @@ -40,8 +41,8 @@ public class CustomPopupMenu implements MenuBuilder.Callback, MenuPresenter.Call private MenuBuilder mMenu; private View mAnchor; private CustomPopupMenuHelper mPopup; - private OnMenuItemClickListener mMenuItemClickListener; - private OnDismissListener mDismissListener; + private PopupMenu.OnMenuItemClickListener mMenuItemClickListener; + private PopupMenu.OnDismissListener mDismissListener; private View.OnTouchListener mDragListener; /** @@ -119,6 +120,22 @@ public class CustomPopupMenu implements MenuBuilder.Callback, MenuPresenter.Call mPopup.setGravity(gravity); } + public int getHorizontalOffset() { + return mPopup.getHorizontalOffset(); + } + + public void setVerticalOffset(int offset) { + mPopup.setVerticalOffset(offset); + } + + public int getVerticalOffset() { + return mPopup.getVerticalOffset(); + } + + public void setHorizontalOffset(int offset) { + mPopup.setHorizontalOffset(offset); + } + /** * Returns an {@link android.view.View.OnTouchListener} that can be added to the anchor view * to implement drag-to-open behavior. @@ -208,12 +225,19 @@ public class CustomPopupMenu implements MenuBuilder.Callback, MenuPresenter.Call mPopup.dismiss(); } + /** + * @return {@code true} if the popup is currently showing, {@code false} otherwise. + */ + public boolean isShowing() { + return mPopup.isShowing(); + } + /** * Set a listener that will be notified when the user selects an item from the menu. * * @param listener Listener to notify */ - public void setOnMenuItemClickListener(OnMenuItemClickListener listener) { + public void setOnMenuItemClickListener(PopupMenu.OnMenuItemClickListener listener) { mMenuItemClickListener = listener; } @@ -222,7 +246,7 @@ public class CustomPopupMenu implements MenuBuilder.Callback, MenuPresenter.Call * * @param listener Listener to notify */ - public void setOnDismissListener(OnDismissListener listener) { + public void setOnDismissListener(PopupMenu.OnDismissListener listener) { mDismissListener = listener; } @@ -235,7 +259,7 @@ public class CustomPopupMenu implements MenuBuilder.Callback, MenuPresenter.Call public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { if (mDismissListener != null) { - mDismissListener.onDismiss(this); + mDismissListener.onDismiss(null); } } @@ -256,31 +280,4 @@ public class CustomPopupMenu implements MenuBuilder.Callback, MenuPresenter.Call public void onMenuModeChange(MenuBuilder menu) { } - - /** - * Callback interface used to notify the application that the menu has closed. - */ - public interface OnDismissListener { - /** - * Called when the associated menu has been dismissed. - * - * @param menu The PopupMenu that was dismissed. - */ - void onDismiss(CustomPopupMenu menu); - } - - /** - * Interface responsible for receiving menu item click events if the items themselves - * do not have individual item click listeners. - */ - public interface OnMenuItemClickListener { - /** - * This method will be invoked when a menu item is clicked if the item itself did - * not already handle the event. - * - * @param item {@link MenuItem} that was clicked - * @return true if the event was handled, false otherwise. - */ - boolean onMenuItemClick(MenuItem item); - } } \ No newline at end of file diff --git a/app/src/main/java/org/solovyev/android/widget/menu/CustomPopupMenuHelper.java b/app/src/main/java/org/solovyev/android/widget/menu/CustomPopupMenuHelper.java index 4d5f14c7..7e8fb1d2 100644 --- a/app/src/main/java/org/solovyev/android/widget/menu/CustomPopupMenuHelper.java +++ b/app/src/main/java/org/solovyev/android/widget/menu/CustomPopupMenuHelper.java @@ -20,6 +20,8 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; import android.os.Parcelable; +import android.support.v4.view.ActionProvider; +import android.support.v4.view.MenuItemCompat; import android.support.v7.appcompat.R; import android.support.v7.view.menu.*; import android.support.v7.widget.ListPopupWindow; @@ -27,6 +29,7 @@ import android.view.*; import android.view.View.MeasureSpec; import android.widget.*; +import javax.annotation.Nonnull; import java.util.ArrayList; /** @@ -38,6 +41,9 @@ public class CustomPopupMenuHelper implements AdapterView.OnItemClickListener, V ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener, MenuPresenter { + private static final int DEFAULT_VIEW_TAG_KEY = org.solovyev.android.calculator.R.id.cpm_default_view_tag_key; + private static final Object DEFAULT_VIEW_TAG = new Object(); + private final Context mContext; private final LayoutInflater mInflater; private final MenuBuilder mMenu; @@ -63,7 +69,9 @@ public class CustomPopupMenuHelper implements AdapterView.OnItemClickListener, V */ private int mContentWidth; - private int mDropDownGravity = Gravity.NO_GRAVITY; + private int mGravity = Gravity.NO_GRAVITY; + private int mVerticalOffset; + private int mHorizontalOffset; public CustomPopupMenuHelper(Context context, MenuBuilder menu) { this(context, menu, null, false, R.attr.popupMenuStyle); @@ -107,11 +115,27 @@ public class CustomPopupMenuHelper implements AdapterView.OnItemClickListener, V } public int getGravity() { - return mDropDownGravity; + return mGravity; } public void setGravity(int gravity) { - mDropDownGravity = gravity; + mGravity = gravity; + } + + public void setVerticalOffset(int offset) { + mVerticalOffset = offset; + } + + public int getVerticalOffset() { + return mVerticalOffset; + } + + public void setHorizontalOffset(int offset) { + mHorizontalOffset = offset; + } + + public int getHorizontalOffset() { + return mHorizontalOffset; } public void show() { @@ -137,7 +161,9 @@ public class CustomPopupMenuHelper implements AdapterView.OnItemClickListener, V mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this); mPopup.setAnchorView(anchor); - mPopup.setDropDownGravity(mDropDownGravity); + mPopup.setDropDownGravity(mGravity); + mPopup.setHorizontalOffset(mHorizontalOffset); + mPopup.setVerticalOffset(mVerticalOffset); } else { return false; } @@ -361,15 +387,31 @@ public class CustomPopupMenuHelper implements AdapterView.OnItemClickListener, V } public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { + final MenuItemImpl item = getItem(position); + final ActionProvider actionProvider = MenuItemCompat.getActionProvider(item); + if (actionProvider != null) { + return actionProvider.onCreateActionView(item); + } + final View actionView = MenuItemCompat.getActionView(item); + if (actionView != null) { + ((MenuView.ItemView) actionView).initialize(item, 0); + return actionView; + } + return getDefaultView(item, convertView, parent); + } + + @Nonnull + private View getDefaultView(MenuItemImpl item, View convertView, ViewGroup parent) { + if (convertView == null || convertView.getTag(DEFAULT_VIEW_TAG_KEY) == DEFAULT_VIEW_TAG) { convertView = mInflater.inflate(R.layout.abc_popup_menu_item_layout, parent, false); + convertView.setTag(DEFAULT_VIEW_TAG_KEY, DEFAULT_VIEW_TAG); } - MenuView.ItemView itemView = (MenuView.ItemView) convertView; + final MenuView.ItemView itemView = (MenuView.ItemView) convertView; if (mForceShowIcon) { ((ListMenuItemView) convertView).setForceShowIcon(true); } - itemView.initialize(getItem(position), 0); + itemView.initialize(item, 0); return convertView; } diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index ae54929d..0a2d67d9 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -27,7 +27,7 @@ xmlns:a="http://schemas.android.com/apk/res/android" a:orientation="vertical"> - + - + - + @@ -84,9 +84,9 @@ a:baselineAligned="false" a:orientation="horizontal"> - + - + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 345dcf70..11e6e204 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -30,7 +30,7 @@ a:layout_height="match_parent" a:orientation="vertical"> - + + + + + + + diff --git a/app/src/main/res/layout/activity_main_editor_with_toolbar.xml b/app/src/main/res/layout/activity_main_editor_with_toolbar.xml deleted file mode 100644 index 1b5a959b..00000000 --- a/app/src/main/res/layout/activity_main_editor_with_toolbar.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/layout/cpp_app_keyboard.xml b/app/src/main/res/layout/cpp_app_keyboard.xml index 82df8998..f0b54318 100644 --- a/app/src/main/res/layout/cpp_app_keyboard.xml +++ b/app/src/main/res/layout/cpp_app_keyboard.xml @@ -32,9 +32,9 @@ a:layout_height="0dp" a:layout_weight="1"> - + - + @@ -75,7 +75,7 @@ - + @@ -92,7 +92,7 @@ - + diff --git a/app/src/main/res/layout/popup_menu_item_layout.xml b/app/src/main/res/layout/popup_menu_item_layout.xml new file mode 100644 index 00000000..7e8dc4ba --- /dev/null +++ b/app/src/main/res/layout/popup_menu_item_layout.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml index 26c3105f..f13e0b2a 100644 --- a/app/src/main/res/menu/main.xml +++ b/app/src/main/res/menu/main.xml @@ -5,16 +5,8 @@ - - - - - + app:actionProviderClass="org.solovyev.android.calculator.MainMenu$ViewProvider" + app:showAsAction="never" /> + + \ No newline at end of file