From 353378123c71aa4f329369778d3126bad0ce921b Mon Sep 17 00:00:00 2001 From: donchenko_r Date: Wed, 17 May 2017 17:39:11 +0300 Subject: [PATCH] show disabled items in menu of CompositeAction --- actionhandler/build.gradle | 6 +- .../actionhandler/action/CompositeAction.java | 136 ++++++++++++++++-- .../res/layout/item_menu_composit_action.xml | 2 + actionhandler/src/main/res/values/ids.xml | 20 +++ .../viewmodel/MainActivityViewModel.java | 48 ++++--- 5 files changed, 174 insertions(+), 38 deletions(-) create mode 100644 actionhandler/src/main/res/values/ids.xml diff --git a/actionhandler/build.gradle b/actionhandler/build.gradle index 747c880..7913c8a 100644 --- a/actionhandler/build.gradle +++ b/actionhandler/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'com.android.library' ext { libraryVersionMajor = 1 libraryVersionMinor = 1 - libraryVersionRevision = 1 + libraryVersionRevision = 2 libraryVersion = libraryVersionMajor + '.' + libraryVersionMinor + '.' + libraryVersionRevision @@ -58,10 +58,10 @@ dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) // testCompile 'junit:junit:4.12' // javadocDeps 'com.android.support:support-annotations:23.4.0' - compile 'com.android.support:support-annotations:25.1.0' + compile 'com.android.support:support-annotations:25.3.1' // provided 'com.android.support:support-compat:24.2.1' // compile 'com.android.support:support-v4:24.2.0' - compile 'com.android.support:appcompat-v7:25.1.0' + compile 'com.android.support:appcompat-v7:25.3.1' provided 'io.reactivex:rxjava:1.2.1' provided 'io.reactivex:rxandroid:1.2.1' diff --git a/actionhandler/src/main/java/com/drextended/actionhandler/action/CompositeAction.java b/actionhandler/src/main/java/com/drextended/actionhandler/action/CompositeAction.java index 8f309e3..74ec39e 100644 --- a/actionhandler/src/main/java/com/drextended/actionhandler/action/CompositeAction.java +++ b/actionhandler/src/main/java/com/drextended/actionhandler/action/CompositeAction.java @@ -35,6 +35,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.Window; +import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; @@ -47,6 +48,7 @@ import java.security.InvalidParameterException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -76,7 +78,10 @@ public class CompositeAction extends BaseAction implements OnActionFiredLi // Flag for settings how a single action item should be fired. // True for show a menu, false for fire action directly. - private boolean mDisplayDialogForSingleAction = true; + protected boolean mDisplayDialogForSingleAction = true; + + // True for show non accepted actions in menu as disabled, false to hide them. + protected boolean mShowNonAcceptedActions = false; /** * Specific type of action which can contain a few other actions, show them as menu items, @@ -89,7 +94,24 @@ public class CompositeAction extends BaseAction implements OnActionFiredLi * if there is only single action in a menu. */ public CompositeAction(@StringRes int titleResId, boolean displayDialogForSingleAction, ActionItem... actions) { - this(new SimpleTitleProvider(titleResId), displayDialogForSingleAction, actions); + this(new SimpleTitleProvider(titleResId), displayDialogForSingleAction, false, actions); + } + + /** + * Specific type of action which can contain a few other actions, show them as menu items, + * and fire an action, if corresponding item clicked. + * + * @param titleResId resource id for corresponding menu item's title + * @param actions action item, which contains menu item titles and actions, + * which will be fired if corresponding menu item selected + * @param displayDialogForSingleAction True for show a menu, false for fire action directly + * if there is only single action in a menu. + * @param showNonAcceptedActions true for show non accepted actions in menu as disabled, + * false to hide them + */ + public CompositeAction(@StringRes int titleResId, boolean displayDialogForSingleAction, + boolean showNonAcceptedActions, ActionItem... actions) { + this(new SimpleTitleProvider(titleResId), displayDialogForSingleAction, showNonAcceptedActions, actions); } /** @@ -101,7 +123,7 @@ public CompositeAction(@StringRes int titleResId, boolean displayDialogForSingle * which will be fired if corresponding menu item selected */ public CompositeAction(@StringRes int titleResId, ActionItem... actions) { - this(new SimpleTitleProvider(titleResId), actions); + this(new SimpleTitleProvider(titleResId), true, false, actions); } /** @@ -113,7 +135,7 @@ public CompositeAction(@StringRes int titleResId, ActionItem... actions) { * which will be fired if corresponding menu item selected */ public CompositeAction(TitleProvider titleProvider, ActionItem... actions) { - this(titleProvider, true, actions); + this(titleProvider, true, false, actions); } /** @@ -127,10 +149,28 @@ public CompositeAction(TitleProvider titleProvider, ActionItem... actions) { * if there is only single action in a menu. */ public CompositeAction(TitleProvider titleProvider, boolean displayDialogForSingleAction, ActionItem... actions) { + this(titleProvider, displayDialogForSingleAction, false, actions); + } + + /** + * Specific type of action which can contain a few other actions, show them as menu items, + * and fire an action, if corresponding item clicked. + * + * @param titleProvider provider for corresponding menu item's title + * @param actions action item, which contains menu item titles and actions, + * which will be fired if corresponding menu item selected + * @param displayDialogForSingleAction True for show a menu, false for fire action directly + * if there is only single action in a menu. + * @param showNonAcceptedActions true for show non accepted actions in menu as disabled, + * false to hide them + */ + public CompositeAction(TitleProvider titleProvider, boolean displayDialogForSingleAction, + boolean showNonAcceptedActions, ActionItem... actions) { if (actions == null) throw new InvalidParameterException("Provide at least one action"); mActions = actions; mTitleProvider = titleProvider; mDisplayDialogForSingleAction = displayDialogForSingleAction; + mShowNonAcceptedActions = showNonAcceptedActions; for (ActionItem item : mActions) { if (item.action instanceof BaseAction) { @@ -154,6 +194,22 @@ public void setShowAsPopupMenuEnabled(boolean showAsPopupMenuEnabled) { mShowAsPopupMenuEnabled = showAsPopupMenuEnabled; } + /** + * Flag for settings how a single action item should be fired. + * @param displayDialogForSingleAction true for show a menu, false for fire action directly. + */ + public void setDisplayDialogForSingleAction(boolean displayDialogForSingleAction) { + mDisplayDialogForSingleAction = displayDialogForSingleAction; + } + + /** + * Flag for settings how non accepted action item should be showed in the menu. + * @param showNonAcceptedActions true for show non accepted actions in menu as disabled, false to hide them. + */ + public void setShowNonAcceptedActions(boolean showNonAcceptedActions) { + mShowNonAcceptedActions = showNonAcceptedActions; + } + /** * Check if there is at least one action which can handle given model * @@ -236,10 +292,23 @@ private void showMenu(final Context context, final View view, String actionType, String title = mTitleProvider.getTitle(context, model); AlertDialog.Builder builder = buildAlertDialog(context, view, actionType, model, title, menuItems); final AlertDialog dialog = builder.create(); + if (mShowNonAcceptedActions) { + final AdapterView.OnItemClickListener clickListener = dialog.getListView().getOnItemClickListener(); + if (clickListener != null) { + dialog.getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (menuItems.get(position).action.isModelAccepted(model)) { + clickListener.onItemClick(parent, view, position, id); + } + } + }); + } + } if (title == null) { dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); } - builder.show(); + dialog.show(); } } @@ -251,6 +320,7 @@ private void showMenu(final Context context, final View view, String actionType, */ @NonNull protected List prepareMenuListItems(M model) { + if (mShowNonAcceptedActions) return Arrays.asList(mActions); int count = mActions.length; final List menuItems = new ArrayList<>(count); for (int index = 0; index < count; index++) { @@ -281,6 +351,9 @@ protected PopupMenu buildPopupMenu(final Context context, final View view, final final ActionItem item = menuItems.get(index); //noinspection unchecked menu.add(0, index, 0, item.titleProvider.getTitle(context, model)); + if (mShowNonAcceptedActions) { + menu.getItem(index).setEnabled(item.action.isModelAccepted(model)); + } } final AtomicBoolean activated = new AtomicBoolean(false); popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @@ -288,7 +361,11 @@ protected PopupMenu buildPopupMenu(final Context context, final View view, final public boolean onMenuItemClick(MenuItem item) { activated.set(true); final ActionItem actionItem = menuItems.get(item.getItemId()); - fireActionItem(context, view, actionItem.actionType, model, actionItem); + if (item.isEnabled()) { + fireActionItem(context, view, actionItem.actionType, model, actionItem); + } else { + notifyOnActionDismiss("The model is not accepted for selected action", view, actionType, model); + } return true; } }); @@ -318,11 +395,15 @@ protected AlertDialog.Builder buildAlertDialog(final Context context, final View final AlertDialog.Builder builder = new AlertDialog.Builder(context) .setTitle(title); - builder.setAdapter(new MenuItemsAdapter(getMenuItemLayoutResId(), menuItems, model), new DialogInterface.OnClickListener() { + builder.setAdapter(new MenuItemsAdapter(getMenuItemLayoutResId(), menuItems, model, mShowNonAcceptedActions), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final ActionItem actionItem = menuItems.get(which); - fireActionItem(context, view, actionItem.actionType, model, actionItem); + if (actionItem.action.isModelAccepted(model)) { + fireActionItem(context, view, actionItem.actionType, model, actionItem); + } else { + notifyOnActionDismiss("Model is not acceptable for this action", view, actionType, model); + } } }).setOnCancelListener(new DialogInterface.OnCancelListener() { @Override @@ -476,12 +557,14 @@ private static class MenuItemsAdapter extends BaseAdapter { private final int mItemLayoutResId; private final List mItems; private final Object mModel; + private final boolean mShowNonAcceptedActions; private final boolean mHasIcons; - public MenuItemsAdapter(@LayoutRes int itemLayoutResId, List menuItems, Object model) { + public MenuItemsAdapter(@LayoutRes int itemLayoutResId, List menuItems, Object model, boolean showNonAcceptedActions) { mItemLayoutResId = itemLayoutResId; mItems = menuItems; mModel = model; + mShowNonAcceptedActions = showNonAcceptedActions; mHasIcons = checkHasIcons(mItems); } @@ -495,30 +578,41 @@ private boolean checkHasIcons(List items) { @Override public View getView(int position, View convertView, ViewGroup parent) { Context context = parent.getContext(); + ViewHolder viewHolder; if (convertView == null) { convertView = LayoutInflater.from(context).inflate(mItemLayoutResId, parent, false); + viewHolder = new ViewHolder(convertView); + convertView.setTag(R.id.viewHolder, viewHolder); + } else { + viewHolder = (ViewHolder) convertView.getTag(R.id.viewHolder); } ActionItem item = mItems.get(position); + + boolean modelAccepted = true; + if (mShowNonAcceptedActions) { + modelAccepted = item.action.isModelAccepted(mModel); + viewHolder.itemView.setEnabled(modelAccepted); + } //noinspection unchecked final String label = item.titleProvider.getTitle(context, mModel); - ((TextView) convertView.findViewById(android.R.id.text1)).setText(label); - ImageView imageView = (ImageView) convertView.findViewById(android.R.id.icon); + viewHolder.textView.setText(label); if (item.iconResId != 0) { - imageView.setVisibility(View.VISIBLE); + viewHolder.imageView.setVisibility(View.VISIBLE); if (item.iconTintColorResId != 0) { final Drawable iconDrawable = ContextCompat.getDrawable(context, item.iconResId); if (iconDrawable != null) { Drawable drawable = DrawableCompat.wrap(iconDrawable.mutate()); DrawableCompat.setTint(drawable, ContextCompat.getColor(context, item.iconTintColorResId)); - imageView.setImageDrawable(drawable); + viewHolder.imageView.setImageDrawable(drawable); } } else { - imageView.setImageResource(item.iconResId); + viewHolder.imageView.setImageResource(item.iconResId); } + if (mShowNonAcceptedActions) viewHolder.imageView.setAlpha(modelAccepted ? 1.0f : 0.3f); } else { - imageView.setVisibility(mHasIcons ? View.INVISIBLE : View.GONE); + viewHolder.imageView.setVisibility(mHasIcons ? View.INVISIBLE : View.GONE); } return convertView; @@ -543,5 +637,17 @@ public boolean hasStableIds() { public long getItemId(int position) { return position; } + + private static class ViewHolder { + final View itemView; + final TextView textView; + final ImageView imageView; + + public ViewHolder(View itemView) { + this.itemView = itemView; + this.textView = (TextView) itemView.findViewById(android.R.id.text1); + this.imageView = (ImageView) itemView.findViewById(android.R.id.icon); + } + } } } diff --git a/actionhandler/src/main/res/layout/item_menu_composit_action.xml b/actionhandler/src/main/res/layout/item_menu_composit_action.xml index d04bd81..f61437a 100644 --- a/actionhandler/src/main/res/layout/item_menu_composit_action.xml +++ b/actionhandler/src/main/res/layout/item_menu_composit_action.xml @@ -27,6 +27,7 @@ android:layout_height="24dp" android:layout_gravity="center_vertical" android:layout_marginLeft="24dp" + android:duplicateParentState="true" tools:src="@android:drawable/ic_menu_edit"/> + + + + + \ No newline at end of file diff --git a/samples/databinding/src/main/java/com/drextended/databinding/viewmodel/MainActivityViewModel.java b/samples/databinding/src/main/java/com/drextended/databinding/viewmodel/MainActivityViewModel.java index c8dea16..d250117 100644 --- a/samples/databinding/src/main/java/com/drextended/databinding/viewmodel/MainActivityViewModel.java +++ b/samples/databinding/src/main/java/com/drextended/databinding/viewmodel/MainActivityViewModel.java @@ -65,6 +65,33 @@ private void refreshModel() { } private ActionHandler buildActionHandler() { + + CompositeAction menuAction = new CompositeAction<>(new CompositeAction.TitleProvider() { + @Override + public String getTitle(Context context, String model) { + return "Title (" + model + ")"; + } + }, true, true, + new ActionItem<>(ActionType.OPEN_NEW_SCREEN, new OpenSecondActivity(), R.drawable.ic_touch_app_black_24dp, 0, + new CompositeAction.TitleProvider() { + @Override + public String getTitle(Context context, String model) { + // There you can return any title for menu item using some fields from model + return context.getString(R.string.fire_intent_action); + } + }), + new ActionItem(ActionType.FIRE_ACTION, new ShowToastAction(), R.drawable.ic_announcement_black_24dp, R.color.greenLight, R.string.fire_simple_action), + new ActionItem(ActionType.FIRE_DIALOG_ACTION, DialogAction.wrap(getString(R.string.action_dialog_message), new ShowToastAction()), R.drawable.ic_announcement_black_24dp, R.color.amber, R.string.fire_dialog_action), + new ActionItem(ActionType.FIRE_REQUEST_ACTION, new SampleRequestAction() { + @Override + public boolean isModelAccepted(Object model) { + return super.isModelAccepted(model) && mClickCount % 3 == 0; + } + }, R.drawable.ic_cloud_upload_black_24dp, R.color.red, R.string.fire_request_action), + new ActionItem(ActionType.FIRE_RX_REQUEST_ACTION, new SampleRxRequestAction(), 0, 0, R.string.fire_rx_request_action) + ); + menuAction.setShowAsPopupMenuEnabled(false); + return new ActionHandler.Builder() .addAction(null, new SimpleAnimationAction()) // Applied for any actionType .addAction(null, new TrackAction()) // Applied for any actionType @@ -72,26 +99,7 @@ private ActionHandler buildActionHandler() { .addAction(ActionType.FIRE_ACTION, new ShowToastAction()) .addAction(ActionType.FIRE_DIALOG_ACTION, DialogAction.wrap(getString(R.string.action_dialog_message), new ShowToastAction())) .addAction(ActionType.FIRE_REQUEST_ACTION, new SampleRequestAction()) - .addAction(ActionType.FIRE_COMPOSITE_ACTION, - new CompositeAction<>(new CompositeAction.TitleProvider() { - @Override - public String getTitle(Context context, String model) { - return "Title (" + model + ")"; - } - }, - new ActionItem<>(ActionType.OPEN_NEW_SCREEN, new OpenSecondActivity(), R.drawable.ic_touch_app_black_24dp, 0, - new CompositeAction.TitleProvider() { - @Override - public String getTitle(Context context, String model) { - // There you can return any title for menu item using some fields from model - return context.getString(R.string.fire_intent_action); - } - }), - new ActionItem(ActionType.FIRE_ACTION, new ShowToastAction(), R.drawable.ic_announcement_black_24dp, R.color.greenLight, R.string.fire_simple_action), - new ActionItem(ActionType.FIRE_DIALOG_ACTION, DialogAction.wrap(getString(R.string.action_dialog_message), new ShowToastAction()), R.drawable.ic_announcement_black_24dp, R.color.amber, R.string.fire_dialog_action), - new ActionItem(ActionType.FIRE_REQUEST_ACTION, new SampleRequestAction(), R.drawable.ic_cloud_upload_black_24dp, R.color.red, R.string.fire_request_action), - new ActionItem(ActionType.FIRE_RX_REQUEST_ACTION, new SampleRxRequestAction(), 0, 0, R.string.fire_rx_request_action) - )) + .addAction(ActionType.FIRE_COMPOSITE_ACTION, menuAction) .addActionInterceptor(this) .addActionFiredListener(this) .addActionErrorListener(this)