Commit 3793966a authored by Matt Jones's avatar Matt Jones Committed by Commit Bot

Add ModelList concept for the ModelListAdapter

This patch makes the ModelListAdapter compliant with MVC by
introducing a "ListObservable" ModelList. This allows the adapter to
know about changes to the list without explicitly being told. As a
result, coordinators and mediators no longer need to hold references
to the adapter to make changes.

Downstream component here: https://chrome-internal-review.googlesource.com/c/clank/internal/apps/+/1377764

Bug: 909779
Change-Id: I626f513f1e63bc8a1808390d01d4058b631ca18d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1652423
Commit-Queue: Matthew Jones <mdjones@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#683203}
parent 60ae9f5b
...@@ -19,12 +19,13 @@ import org.chromium.chrome.R; ...@@ -19,12 +19,13 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.share.ShareHelper; import org.chromium.chrome.browser.share.ShareHelper;
import org.chromium.chrome.browser.share.ShareParams; import org.chromium.chrome.browser.share.ShareParams;
import org.chromium.chrome.browser.widget.ContextMenuDialog; import org.chromium.chrome.browser.widget.ContextMenuDialog;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.ModelListAdapter; import org.chromium.ui.modelutil.ModelListAdapter;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
...@@ -80,7 +81,10 @@ public class RevampedContextMenuCoordinator implements ContextMenuUi { ...@@ -80,7 +81,10 @@ public class RevampedContextMenuCoordinator implements ContextMenuUi {
mHeaderCoordinator = new RevampedContextMenuHeaderCoordinator(activity, params); mHeaderCoordinator = new RevampedContextMenuHeaderCoordinator(activity, params);
ModelListAdapter adapter = new ModelListAdapter() { // The Integer here specifies the {@link ListItemType}.
ModelList listItems = getItemList(activity, items, params);
ModelListAdapter adapter = new ModelListAdapter(listItems) {
@Override @Override
public boolean areAllItemsEnabled() { public boolean areAllItemsEnabled() {
return false; return false;
...@@ -96,8 +100,8 @@ public class RevampedContextMenuCoordinator implements ContextMenuUi { ...@@ -96,8 +100,8 @@ public class RevampedContextMenuCoordinator implements ContextMenuUi {
public long getItemId(int position) { public long getItemId(int position) {
if (getItemViewType(position) == ListItemType.CONTEXT_MENU_ITEM if (getItemViewType(position) == ListItemType.CONTEXT_MENU_ITEM
|| getItemViewType(position) == ListItemType.CONTEXT_MENU_SHARE_ITEM) { || getItemViewType(position) == ListItemType.CONTEXT_MENU_SHARE_ITEM) {
return ((Pair<Integer, PropertyModel>) getItem(position)) return ((ListItem) getItem(position))
.second.get(RevampedContextMenuItemProperties.MENU_ID); .model.get(RevampedContextMenuItemProperties.MENU_ID);
} }
return INVALID_ITEM_ID; return INVALID_ITEM_ID;
} }
...@@ -129,10 +133,6 @@ public class RevampedContextMenuCoordinator implements ContextMenuUi { ...@@ -129,10 +133,6 @@ public class RevampedContextMenuCoordinator implements ContextMenuUi {
RevampedContextMenuShareItemViewBinder::bind); RevampedContextMenuShareItemViewBinder::bind);
// clang-format on // clang-format on
// The Integer here specifies the {@link ListItemType}.
List<Pair<Integer, PropertyModel>> itemList = getItemList(activity, items, params);
adapter.updateModels(itemList);
mListView.setOnItemClickListener((p, v, pos, id) -> { mListView.setOnItemClickListener((p, v, pos, id) -> {
assert id != INVALID_ITEM_ID; assert id != INVALID_ITEM_ID;
...@@ -166,17 +166,17 @@ public class RevampedContextMenuCoordinator implements ContextMenuUi { ...@@ -166,17 +166,17 @@ public class RevampedContextMenuCoordinator implements ContextMenuUi {
} }
@VisibleForTesting @VisibleForTesting
List<Pair<Integer, PropertyModel>> getItemList(Activity activity, ModelList getItemList(Activity activity, List<Pair<Integer, List<ContextMenuItem>>> items,
List<Pair<Integer, List<ContextMenuItem>>> items, ContextMenuParams params) { ContextMenuParams params) {
List<Pair<Integer, PropertyModel>> itemList = new ArrayList<>(); ModelList itemList = new ModelList();
// TODO(sinansahin): We should be able to remove this conversion once we can get the items // TODO(sinansahin): We should be able to remove this conversion once we can get the items
// in the desired format. // in the desired format.
itemList.add(new Pair<>(ListItemType.HEADER, mHeaderCoordinator.getModel())); itemList.add(new ListItem(ListItemType.HEADER, mHeaderCoordinator.getModel()));
for (Pair<Integer, List<ContextMenuItem>> group : items) { for (Pair<Integer, List<ContextMenuItem>> group : items) {
// Add a divider // Add a divider
itemList.add(new Pair<>(ListItemType.DIVIDER, new PropertyModel())); itemList.add(new ListItem(ListItemType.DIVIDER, new PropertyModel()));
for (ContextMenuItem item : group.second) { for (ContextMenuItem item : group.second) {
PropertyModel itemModel; PropertyModel itemModel;
...@@ -197,7 +197,7 @@ public class RevampedContextMenuCoordinator implements ContextMenuUi { ...@@ -197,7 +197,7 @@ public class RevampedContextMenuCoordinator implements ContextMenuUi {
.with(RevampedContextMenuShareItemProperties.CLICK_LISTENER, .with(RevampedContextMenuShareItemProperties.CLICK_LISTENER,
getShareItemClickListener(activity, shareItem, params)) getShareItemClickListener(activity, shareItem, params))
.build(); .build();
itemList.add(new Pair<>(ListItemType.CONTEXT_MENU_SHARE_ITEM, itemModel)); itemList.add(new ListItem(ListItemType.CONTEXT_MENU_SHARE_ITEM, itemModel));
} else { } else {
itemModel = itemModel =
new PropertyModel.Builder(RevampedContextMenuItemProperties.ALL_KEYS) new PropertyModel.Builder(RevampedContextMenuItemProperties.ALL_KEYS)
...@@ -206,7 +206,7 @@ public class RevampedContextMenuCoordinator implements ContextMenuUi { ...@@ -206,7 +206,7 @@ public class RevampedContextMenuCoordinator implements ContextMenuUi {
.with(RevampedContextMenuItemProperties.TEXT, .with(RevampedContextMenuItemProperties.TEXT,
item.getTitle(activity)) item.getTitle(activity))
.build(); .build();
itemList.add(new Pair<>(ListItemType.CONTEXT_MENU_ITEM, itemModel)); itemList.add(new ListItem(ListItemType.CONTEXT_MENU_ITEM, itemModel));
} }
} }
} }
......
...@@ -19,14 +19,15 @@ import org.chromium.chrome.R; ...@@ -19,14 +19,15 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.ResourceId; import org.chromium.chrome.browser.ResourceId;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties; import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.DialogListItemProperties; import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.DialogListItemProperties;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.ListItemType;
import org.chromium.chrome.browser.util.FeatureUtilities; import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.ui.modaldialog.ModalDialogProperties; import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.text.NoUnderlineClickableSpan; import org.chromium.ui.text.NoUnderlineClickableSpan;
import org.chromium.ui.widget.ButtonCompat; import org.chromium.ui.widget.ButtonCompat;
import java.util.ArrayList;
/** /**
* This infobar appears when ads are being blocked on the page. This occurs after proceeding through * This infobar appears when ads are being blocked on the page. This occurs after proceeding through
* an interstitial warning that the site shows deceptive content, or when the site is known to show * an interstitial warning that the site shows deceptive content, or when the site is known to show
...@@ -138,17 +139,18 @@ public class AdsBlockedInfoBar extends ConfirmInfoBar implements OnCheckedChange ...@@ -138,17 +139,18 @@ public class AdsBlockedInfoBar extends ConfirmInfoBar implements OnCheckedChange
model.set(ModalDialogProperties.TITLE, res.getString(R.string.blocked_ads_prompt_title)); model.set(ModalDialogProperties.TITLE, res.getString(R.string.blocked_ads_prompt_title));
model.set(ModalDialogProperties.MESSAGE, res.getString(R.string.intrusive_ads_information)); model.set(ModalDialogProperties.MESSAGE, res.getString(R.string.intrusive_ads_information));
ArrayList<PropertyModel> options = new ArrayList<>(); ModelList listItems = new ModelList();
options.add(new PropertyModel.Builder(DialogListItemProperties.ALL_KEYS) listItems.add(new ListItem(ListItemType.DEFAULT,
.with(DialogListItemProperties.TEXT, res.getString(R.string.learn_more)) new PropertyModel.Builder(DialogListItemProperties.ALL_KEYS)
.with(DialogListItemProperties.CLICK_LISTENER, (v) -> onLinkClicked()) .with(DialogListItemProperties.TEXT, res.getString(R.string.learn_more))
.with(DialogListItemProperties.ICON, .with(DialogListItemProperties.CLICK_LISTENER, (v) -> onLinkClicked())
ApiCompatibilityUtils.getDrawable( .with(DialogListItemProperties.ICON,
res, R.drawable.ic_info_outline_grey)) ApiCompatibilityUtils.getDrawable(
.build()); res, R.drawable.ic_info_outline_grey))
.build()));
// TODO(973601): We should have a better string for "always allow"; at least one that is // TODO(973601): We should have a better string for "always allow"; at least one that is
// specific to this feature. // specific to this feature.
options.add( listItems.add(new ListItem(ListItemType.DEFAULT,
new PropertyModel.Builder(DialogListItemProperties.ALL_KEYS) new PropertyModel.Builder(DialogListItemProperties.ALL_KEYS)
.with(DialogListItemProperties.TEXT, .with(DialogListItemProperties.TEXT,
res.getString(R.string.always_allow_redirects)) res.getString(R.string.always_allow_redirects))
...@@ -159,11 +161,9 @@ public class AdsBlockedInfoBar extends ConfirmInfoBar implements OnCheckedChange ...@@ -159,11 +161,9 @@ public class AdsBlockedInfoBar extends ConfirmInfoBar implements OnCheckedChange
}) })
.with(DialogListItemProperties.ICON, .with(DialogListItemProperties.ICON,
ApiCompatibilityUtils.getDrawable(res, R.drawable.ic_check_circle)) ApiCompatibilityUtils.getDrawable(res, R.drawable.ic_check_circle))
.build()); .build()));
PropertyModel[] optionModels = new PropertyModel[options.size()]; model.set(TouchlessDialogProperties.LIST_MODELS, listItems);
options.toArray(optionModels);
model.set(TouchlessDialogProperties.LIST_MODELS, optionModels);
// The alt action matches cancel but with a different name. // The alt action matches cancel but with a different name.
model.get(TouchlessDialogProperties.ACTION_NAMES).alt = R.string.ok; model.get(TouchlessDialogProperties.ACTION_NAMES).alt = R.string.ok;
......
...@@ -14,10 +14,11 @@ import org.chromium.chrome.R; ...@@ -14,10 +14,11 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.ResourceId; import org.chromium.chrome.browser.ResourceId;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties; import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.DialogListItemProperties; import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.DialogListItemProperties;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.ListItemType;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
import java.util.ArrayList;
/** /**
* An infobar that presents the user with several buttons. * An infobar that presents the user with several buttons.
* *
...@@ -80,9 +81,9 @@ public class ConfirmInfoBar extends InfoBar { ...@@ -80,9 +81,9 @@ public class ConfirmInfoBar extends InfoBar {
public PropertyModel createModel() { public PropertyModel createModel() {
PropertyModel model = super.createModel(); PropertyModel model = super.createModel();
ArrayList<PropertyModel> options = new ArrayList<>(); ModelList options = new ModelList();
if (!TextUtils.isEmpty(mPrimaryButtonText)) { if (!TextUtils.isEmpty(mPrimaryButtonText)) {
options.add( options.add(new ListItem(ListItemType.DEFAULT,
new PropertyModel.Builder(DialogListItemProperties.ALL_KEYS) new PropertyModel.Builder(DialogListItemProperties.ALL_KEYS)
.with(DialogListItemProperties.TEXT, mPrimaryButtonText) .with(DialogListItemProperties.TEXT, mPrimaryButtonText)
.with(DialogListItemProperties.CLICK_LISTENER, .with(DialogListItemProperties.CLICK_LISTENER,
...@@ -90,11 +91,11 @@ public class ConfirmInfoBar extends InfoBar { ...@@ -90,11 +91,11 @@ public class ConfirmInfoBar extends InfoBar {
.with(DialogListItemProperties.ICON, .with(DialogListItemProperties.ICON,
ApiCompatibilityUtils.getDrawable(getContext().getResources(), ApiCompatibilityUtils.getDrawable(getContext().getResources(),
R.drawable.ic_check_circle)) R.drawable.ic_check_circle))
.build()); .build()));
} }
if (!TextUtils.isEmpty(mSecondaryButtonText)) { if (!TextUtils.isEmpty(mSecondaryButtonText)) {
options.add( options.add(new ListItem(ListItemType.DEFAULT,
new PropertyModel.Builder(DialogListItemProperties.ALL_KEYS) new PropertyModel.Builder(DialogListItemProperties.ALL_KEYS)
.with(DialogListItemProperties.TEXT, mSecondaryButtonText) .with(DialogListItemProperties.TEXT, mSecondaryButtonText)
.with(DialogListItemProperties.CLICK_LISTENER, .with(DialogListItemProperties.CLICK_LISTENER,
...@@ -102,12 +103,10 @@ public class ConfirmInfoBar extends InfoBar { ...@@ -102,12 +103,10 @@ public class ConfirmInfoBar extends InfoBar {
.with(DialogListItemProperties.ICON, .with(DialogListItemProperties.ICON,
ApiCompatibilityUtils.getDrawable(getContext().getResources(), ApiCompatibilityUtils.getDrawable(getContext().getResources(),
R.drawable.ic_cancel_circle)) R.drawable.ic_cancel_circle))
.build()); .build()));
} }
PropertyModel[] optionModels = new PropertyModel[options.size()]; model.set(TouchlessDialogProperties.LIST_MODELS, options);
options.toArray(optionModels);
model.set(TouchlessDialogProperties.LIST_MODELS, optionModels);
return model; return model;
} }
......
...@@ -37,6 +37,7 @@ import org.chromium.chrome.browser.util.KeyNavigationUtil; ...@@ -37,6 +37,7 @@ import org.chromium.chrome.browser.util.KeyNavigationUtil;
import org.chromium.ui.ViewProvider; import org.chromium.ui.ViewProvider;
import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modelutil.LazyConstructionPropertyMcp; import org.chromium.ui.modelutil.LazyConstructionPropertyMcp;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.ModelListAdapter; import org.chromium.ui.modelutil.ModelListAdapter;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
...@@ -68,7 +69,10 @@ public class AutocompleteCoordinatorImpl implements AutocompleteCoordinator { ...@@ -68,7 +69,10 @@ public class AutocompleteCoordinatorImpl implements AutocompleteCoordinator {
listModel.set(SuggestionListProperties.EMBEDDER, listEmbedder); listModel.set(SuggestionListProperties.EMBEDDER, listEmbedder);
listModel.set(SuggestionListProperties.VISIBLE, false); listModel.set(SuggestionListProperties.VISIBLE, false);
ViewProvider<SuggestionListViewHolder> viewProvider = createViewProvider(context); ModelList listItems = new ModelList();
listModel.set(SuggestionListProperties.SUGGESTION_MODELS, listItems);
ViewProvider<SuggestionListViewHolder> viewProvider =
createViewProvider(context, listItems);
viewProvider.whenLoaded((holder) -> { mListView = holder.listView; }); viewProvider.whenLoaded((holder) -> { mListView = holder.listView; });
LazyConstructionPropertyMcp.create(listModel, SuggestionListProperties.VISIBLE, LazyConstructionPropertyMcp.create(listModel, SuggestionListProperties.VISIBLE,
viewProvider, SuggestionListViewBinder::bind); viewProvider, SuggestionListViewBinder::bind);
...@@ -83,7 +87,8 @@ public class AutocompleteCoordinatorImpl implements AutocompleteCoordinator { ...@@ -83,7 +87,8 @@ public class AutocompleteCoordinatorImpl implements AutocompleteCoordinator {
mMediator = null; mMediator = null;
} }
private ViewProvider<SuggestionListViewHolder> createViewProvider(Context context) { private ViewProvider<SuggestionListViewHolder> createViewProvider(
Context context, ModelList modelList) {
return new ViewProvider<SuggestionListViewHolder>() { return new ViewProvider<SuggestionListViewHolder>() {
private List<Callback<SuggestionListViewHolder>> mCallbacks = new ArrayList<>(); private List<Callback<SuggestionListViewHolder>> mCallbacks = new ArrayList<>();
private SuggestionListViewHolder mHolder; private SuggestionListViewHolder mHolder;
...@@ -101,7 +106,7 @@ public class AutocompleteCoordinatorImpl implements AutocompleteCoordinator { ...@@ -101,7 +106,7 @@ public class AutocompleteCoordinatorImpl implements AutocompleteCoordinator {
// Start with visibility GONE to ensure that show() is called. // Start with visibility GONE to ensure that show() is called.
// http://crbug.com/517438 // http://crbug.com/517438
list.setVisibility(View.GONE); list.setVisibility(View.GONE);
ModelListAdapter adapter = new ModelListAdapter(); ModelListAdapter adapter = new ModelListAdapter(modelList);
list.setAdapter(adapter); list.setAdapter(adapter);
list.setClipToPadding(false); list.setClipToPadding(false);
...@@ -131,7 +136,7 @@ public class AutocompleteCoordinatorImpl implements AutocompleteCoordinator { ...@@ -131,7 +136,7 @@ public class AutocompleteCoordinatorImpl implements AutocompleteCoordinator {
EntitySuggestionViewBinder::bind); EntitySuggestionViewBinder::bind);
// clang-format on // clang-format on
mHolder = new SuggestionListViewHolder(container, list, adapter); mHolder = new SuggestionListViewHolder(container, list);
for (int i = 0; i < mCallbacks.size(); i++) { for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onResult(mHolder); mCallbacks.get(i).onResult(mHolder);
......
...@@ -12,7 +12,6 @@ import android.os.SystemClock; ...@@ -12,7 +12,6 @@ import android.os.SystemClock;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
...@@ -48,6 +47,8 @@ import org.chromium.ui.base.WindowAndroid; ...@@ -48,6 +47,8 @@ import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modaldialog.DialogDismissalCause; import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties; import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
...@@ -231,12 +232,12 @@ class AutocompleteMediator ...@@ -231,12 +232,12 @@ class AutocompleteMediator
@Override @Override
public void notifyPropertyModelsChanged() { public void notifyPropertyModelsChanged() {
if (mPreventSuggestionListPropertyChanges) return; if (mPreventSuggestionListPropertyChanges) return;
List<Pair<Integer, PropertyModel>> models = new ArrayList<>(mCurrentModels.size()); ModelList suggestions = mListPropertyModel.get(SuggestionListProperties.SUGGESTION_MODELS);
suggestions.clear();
for (int i = 0; i < mCurrentModels.size(); i++) { for (int i = 0; i < mCurrentModels.size(); i++) {
PropertyModel model = mCurrentModels.get(i).model; PropertyModel model = mCurrentModels.get(i).model;
models.add(new Pair<>(mCurrentModels.get(i).processor.getViewTypeId(), model)); suggestions.add(new ListItem(mCurrentModels.get(i).processor.getViewTypeId(), model));
} }
mListPropertyModel.set(SuggestionListProperties.SUGGESTION_MODELS, models);
} }
@Override @Override
...@@ -244,7 +245,7 @@ class AutocompleteMediator ...@@ -244,7 +245,7 @@ class AutocompleteMediator
return mDataProvider != null ? mDataProvider.getProfile() : null; return mDataProvider != null ? mDataProvider.getProfile() : null;
} }
/** /**m
* Sets the data provider for the toolbar. * Sets the data provider for the toolbar.
*/ */
void setToolbarDataProvider(ToolbarDataProvider provider) { void setToolbarDataProvider(ToolbarDataProvider provider) {
......
...@@ -4,15 +4,11 @@ ...@@ -4,15 +4,11 @@
package org.chromium.chrome.browser.omnibox.suggestions; package org.chromium.chrome.browser.omnibox.suggestions;
import android.util.Pair; import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey; import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey; import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
import java.util.List;
/** /**
* The properties controlling the state of the list of suggestion items. * The properties controlling the state of the list of suggestion items.
*/ */
...@@ -24,9 +20,12 @@ public class SuggestionListProperties { ...@@ -24,9 +20,12 @@ public class SuggestionListProperties {
public static final WritableObjectPropertyKey<OmniboxSuggestionListEmbedder> EMBEDDER = public static final WritableObjectPropertyKey<OmniboxSuggestionListEmbedder> EMBEDDER =
new WritableObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** The list of models controlling the state of the suggestion items. */ /**
public static final WritableObjectPropertyKey<List<Pair<Integer, PropertyModel>>> * The list of models controlling the state of the suggestion items. This should never be
SUGGESTION_MODELS = new WritableObjectPropertyKey<>(true); * bound to the same view more than once.
*/
public static final WritableObjectPropertyKey<ModelList> SUGGESTION_MODELS =
new WritableObjectPropertyKey<>(true);
/** Whether the suggestion list should have a dark background. */ /** Whether the suggestion list should have a dark background. */
public static final WritableBooleanPropertyKey IS_INCOGNITO = new WritableBooleanPropertyKey(); public static final WritableBooleanPropertyKey IS_INCOGNITO = new WritableBooleanPropertyKey();
......
...@@ -4,11 +4,12 @@ ...@@ -4,11 +4,12 @@
package org.chromium.chrome.browser.omnibox.suggestions; package org.chromium.chrome.browser.omnibox.suggestions;
import android.support.annotation.Nullable;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.chromium.ui.UiUtils; import org.chromium.ui.UiUtils;
import org.chromium.ui.modelutil.ModelListAdapter; import org.chromium.ui.modelutil.ListObservable;
import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
...@@ -22,13 +23,10 @@ class SuggestionListViewBinder { ...@@ -22,13 +23,10 @@ class SuggestionListViewBinder {
public static class SuggestionListViewHolder { public static class SuggestionListViewHolder {
public final ViewGroup container; public final ViewGroup container;
public final OmniboxSuggestionsList listView; public final OmniboxSuggestionsList listView;
public final ModelListAdapter adapter;
public SuggestionListViewHolder( public SuggestionListViewHolder(ViewGroup container, OmniboxSuggestionsList list) {
ViewGroup container, OmniboxSuggestionsList list, ModelListAdapter adapter) {
this.container = container; this.container = container;
this.listView = list; this.listView = list;
this.adapter = adapter;
} }
} }
...@@ -53,8 +51,15 @@ class SuggestionListViewBinder { ...@@ -53,8 +51,15 @@ class SuggestionListViewBinder {
} else if (SuggestionListProperties.EMBEDDER.equals(propertyKey)) { } else if (SuggestionListProperties.EMBEDDER.equals(propertyKey)) {
view.listView.setEmbedder(model.get(SuggestionListProperties.EMBEDDER)); view.listView.setEmbedder(model.get(SuggestionListProperties.EMBEDDER));
} else if (SuggestionListProperties.SUGGESTION_MODELS.equals(propertyKey)) { } else if (SuggestionListProperties.SUGGESTION_MODELS.equals(propertyKey)) {
view.adapter.updateModels(model.get(SuggestionListProperties.SUGGESTION_MODELS)); // This should only ever be bound once.
view.listView.setSelection(0); model.get(SuggestionListProperties.SUGGESTION_MODELS)
.addObserver(new ListObservable.ListObserver<Void>() {
@Override
public void onItemRangeChanged(ListObservable<Void> source, int index,
int count, @Nullable Void payload) {
view.listView.setSelection(0);
}
});
} else if (SuggestionListProperties.IS_INCOGNITO.equals(propertyKey)) { } else if (SuggestionListProperties.IS_INCOGNITO.equals(propertyKey)) {
view.listView.refreshPopupBackground(model.get(SuggestionListProperties.IS_INCOGNITO)); view.listView.refreshPopupBackground(model.get(SuggestionListProperties.IS_INCOGNITO));
} }
......
...@@ -22,6 +22,8 @@ import org.chromium.chrome.browser.widget.AlertDialogEditText; ...@@ -22,6 +22,8 @@ import org.chromium.chrome.browser.widget.AlertDialogEditText;
import org.chromium.ui.modaldialog.DialogDismissalCause; import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties; import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
/** A touchless variation of the {@link AddToHomescreenDialog}. */ /** A touchless variation of the {@link AddToHomescreenDialog}. */
...@@ -30,6 +32,7 @@ public class TouchlessAddToHomescreenDialog extends AddToHomescreenDialog { ...@@ -30,6 +32,7 @@ public class TouchlessAddToHomescreenDialog extends AddToHomescreenDialog {
private Delegate mDelegate; private Delegate mDelegate;
private ModalDialogManager mDialogManager; private ModalDialogManager mDialogManager;
private PropertyModel mModel; private PropertyModel mModel;
private PropertyModel mAddToHomescreenOption;
public TouchlessAddToHomescreenDialog( public TouchlessAddToHomescreenDialog(
Activity activity, ModalDialogManager dialogManager, Delegate delegate) { Activity activity, ModalDialogManager dialogManager, Delegate delegate) {
...@@ -77,8 +80,19 @@ public class TouchlessAddToHomescreenDialog extends AddToHomescreenDialog { ...@@ -77,8 +80,19 @@ public class TouchlessAddToHomescreenDialog extends AddToHomescreenDialog {
model.set(TouchlessDialogProperties.CANCEL_ACTION, model.set(TouchlessDialogProperties.CANCEL_ACTION,
(v) -> mDialogManager.dismissDialog(model, DialogDismissalCause.UNKNOWN)); (v) -> mDialogManager.dismissDialog(model, DialogDismissalCause.UNKNOWN));
model.set(TouchlessDialogProperties.LIST_MODELS, mAddToHomescreenOption = new PropertyModel.Builder(DialogListItemProperties.ALL_KEYS)
new PropertyModel[] {buildListItemModel("")}); .with(DialogListItemProperties.TEXT,
res.getString(org.chromium.chrome.R.string.add))
.with(DialogListItemProperties.ICON,
ApiCompatibilityUtils.getDrawable(res,
org.chromium.chrome.R.drawable.ic_add))
.with(DialogListItemProperties.MULTI_CLICKABLE, false)
.with(DialogListItemProperties.CLICK_LISTENER, null)
.build();
ModelList modelList = new ModelList();
modelList.add(new ListItem(
TouchlessDialogProperties.ListItemType.DEFAULT, mAddToHomescreenOption));
model.set(TouchlessDialogProperties.LIST_MODELS, modelList);
model.set(ModalDialogProperties.CUSTOM_VIEW, model.set(ModalDialogProperties.CUSTOM_VIEW,
mActivity.getLayoutInflater().inflate(R.layout.touchless_add_to_apps, null)); mActivity.getLayoutInflater().inflate(R.layout.touchless_add_to_apps, null));
...@@ -86,37 +100,6 @@ public class TouchlessAddToHomescreenDialog extends AddToHomescreenDialog { ...@@ -86,37 +100,6 @@ public class TouchlessAddToHomescreenDialog extends AddToHomescreenDialog {
return model; return model;
} }
/**
* Build the list item that adds the app or site to the home screen.
* @param title The title of the app or site.
* @return The list item for the dialog model.
*/
private PropertyModel buildListItemModel(final String title) {
Resources res = mActivity.getResources();
return new PropertyModel.Builder(DialogListItemProperties.ALL_KEYS)
.with(DialogListItemProperties.TEXT,
res.getString(org.chromium.chrome.R.string.add))
.with(DialogListItemProperties.ICON,
ApiCompatibilityUtils.getDrawable(
res, org.chromium.chrome.R.drawable.ic_add))
.with(DialogListItemProperties.MULTI_CLICKABLE, false)
.with(DialogListItemProperties.CLICK_LISTENER,
(v) -> {
ViewGroup group =
(ViewGroup) mModel.get(ModalDialogProperties.CUSTOM_VIEW);
String customTitle =
((AlertDialogEditText) group.findViewById(R.id.app_title))
.getText()
.toString();
if (TextUtils.isEmpty(customTitle)) customTitle = title;
if (TextUtils.isEmpty(customTitle)) return;
mDelegate.addToHomescreen(customTitle);
mDialogManager.dismissDialog(mModel, DialogDismissalCause.UNKNOWN);
})
.build();
}
/** /**
* Update the custom view shown for the touchless dialog. This shows the name of the app, icon, * Update the custom view shown for the touchless dialog. This shows the name of the app, icon,
* and site. * and site.
...@@ -139,8 +122,16 @@ public class TouchlessAddToHomescreenDialog extends AddToHomescreenDialog { ...@@ -139,8 +122,16 @@ public class TouchlessAddToHomescreenDialog extends AddToHomescreenDialog {
@Override @Override
public void onUserTitleAvailable(String title, String url, boolean isWebapp) { public void onUserTitleAvailable(String title, String url, boolean isWebapp) {
updateModelCustomView(null, title, url); updateModelCustomView(null, title, url);
mModel.set(TouchlessDialogProperties.LIST_MODELS, mAddToHomescreenOption.set(DialogListItemProperties.CLICK_LISTENER, (v) -> {
new PropertyModel[] {buildListItemModel(title)}); ViewGroup group = (ViewGroup) mModel.get(ModalDialogProperties.CUSTOM_VIEW);
String customTitle =
((AlertDialogEditText) group.findViewById(R.id.app_title)).getText().toString();
if (TextUtils.isEmpty(customTitle)) customTitle = title;
if (TextUtils.isEmpty(customTitle)) return;
mDelegate.addToHomescreen(customTitle);
mDialogManager.dismissDialog(mModel, DialogDismissalCause.UNKNOWN);
});
} }
@Override @Override
......
...@@ -28,6 +28,8 @@ import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate; ...@@ -28,6 +28,8 @@ import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
import org.chromium.chrome.test.ui.DummyUiActivityTestCase; import org.chromium.chrome.test.ui.DummyUiActivityTestCase;
import org.chromium.chrome.test.util.RenderTestRule; import org.chromium.chrome.test.util.RenderTestRule;
import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.ModelListAdapter; import org.chromium.ui.modelutil.ModelListAdapter;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
...@@ -49,6 +51,7 @@ public class RevampedContextMenuRenderTest extends DummyUiActivityTestCase { ...@@ -49,6 +51,7 @@ public class RevampedContextMenuRenderTest extends DummyUiActivityTestCase {
public RenderTestRule mRenderTestRule = new RenderTestRule(); public RenderTestRule mRenderTestRule = new RenderTestRule();
private ModelListAdapter mAdapter; private ModelListAdapter mAdapter;
private ModelList mListItems;
private View mView; private View mView;
private View mFrame; private View mFrame;
...@@ -60,7 +63,8 @@ public class RevampedContextMenuRenderTest extends DummyUiActivityTestCase { ...@@ -60,7 +63,8 @@ public class RevampedContextMenuRenderTest extends DummyUiActivityTestCase {
@Override @Override
public void setUpTest() throws Exception { public void setUpTest() throws Exception {
super.setUpTest(); super.setUpTest();
mAdapter = new ModelListAdapter(); mListItems = new ModelList();
mAdapter = new ModelListAdapter(mListItems);
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
getActivity().setContentView(R.layout.revamped_context_menu); getActivity().setContentView(R.layout.revamped_context_menu);
mView = getActivity().findViewById(android.R.id.content); mView = getActivity().findViewById(android.R.id.content);
...@@ -97,6 +101,7 @@ public class RevampedContextMenuRenderTest extends DummyUiActivityTestCase { ...@@ -97,6 +101,7 @@ public class RevampedContextMenuRenderTest extends DummyUiActivityTestCase {
@Override @Override
public void tearDownTest() throws Exception { public void tearDownTest() throws Exception {
NightModeTestUtils.tearDownNightModeForDummyUiActivity(); NightModeTestUtils.tearDownNightModeForDummyUiActivity();
TestThreadUtils.runOnUiThreadBlocking(() -> mListItems.clear());
super.tearDownTest(); super.tearDownTest();
} }
...@@ -105,20 +110,17 @@ public class RevampedContextMenuRenderTest extends DummyUiActivityTestCase { ...@@ -105,20 +110,17 @@ public class RevampedContextMenuRenderTest extends DummyUiActivityTestCase {
@Feature({"RenderTest"}) @Feature({"RenderTest"})
public void testRevampedContextMenuViewWithLink() throws IOException { public void testRevampedContextMenuViewWithLink() throws IOException {
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
List<Pair<Integer, PropertyModel>> itemList = new ArrayList<>(); mListItems.add(
itemList.add( new ListItem(ListItemType.HEADER, getHeaderModel("", "www.google.com", false)));
new Pair<>(ListItemType.HEADER, getHeaderModel("", "www.google.com", false))); mListItems.add(new ListItem(ListItemType.DIVIDER, new PropertyModel()));
itemList.add(new Pair<>(ListItemType.DIVIDER, new PropertyModel())); mListItems.add((
itemList.add( new ListItem(ListItemType.CONTEXT_MENU_ITEM, getItemModel("Open in new tab"))));
(new Pair<>(ListItemType.CONTEXT_MENU_ITEM, getItemModel("Open in new tab")))); mListItems.add((new ListItem(
itemList.add((new Pair<>(
ListItemType.CONTEXT_MENU_ITEM, getItemModel("Open in incognito tab")))); ListItemType.CONTEXT_MENU_ITEM, getItemModel("Open in incognito tab"))));
itemList.add(( mListItems.add((new ListItem(
new Pair<>(ListItemType.CONTEXT_MENU_ITEM, getItemModel("Copy link address")))); ListItemType.CONTEXT_MENU_ITEM, getItemModel("Copy link address"))));
itemList.add((new Pair<>( mListItems.add((new ListItem(
ListItemType.CONTEXT_MENU_SHARE_ITEM, getShareItemModel("Share link")))); ListItemType.CONTEXT_MENU_SHARE_ITEM, getShareItemModel("Share link"))));
mAdapter.updateModels(itemList);
}); });
mRenderTestRule.render(mFrame, "revamped_context_menu_with_link"); mRenderTestRule.render(mFrame, "revamped_context_menu_with_link");
} }
...@@ -129,26 +131,25 @@ public class RevampedContextMenuRenderTest extends DummyUiActivityTestCase { ...@@ -129,26 +131,25 @@ public class RevampedContextMenuRenderTest extends DummyUiActivityTestCase {
public void testRevampedContextMenuViewWithImageLink() throws IOException { public void testRevampedContextMenuViewWithImageLink() throws IOException {
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
List<Pair<Integer, PropertyModel>> itemList = new ArrayList<>(); List<Pair<Integer, PropertyModel>> itemList = new ArrayList<>();
itemList.add(new Pair<>( mListItems.add(new ListItem(
ListItemType.HEADER, getHeaderModel("Capybara", "www.google.com", true))); ListItemType.HEADER, getHeaderModel("Capybara", "www.google.com", true)));
itemList.add(new Pair<>(ListItemType.DIVIDER, new PropertyModel())); mListItems.add(new ListItem(ListItemType.DIVIDER, new PropertyModel()));
itemList.add( mListItems.add((
(new Pair<>(ListItemType.CONTEXT_MENU_ITEM, getItemModel("Open in new tab")))); new ListItem(ListItemType.CONTEXT_MENU_ITEM, getItemModel("Open in new tab"))));
itemList.add((new Pair<>( mListItems.add((new ListItem(
ListItemType.CONTEXT_MENU_ITEM, getItemModel("Open in incognito tab")))); ListItemType.CONTEXT_MENU_ITEM, getItemModel("Open in incognito tab"))));
itemList.add(( mListItems.add((new ListItem(
new Pair<>(ListItemType.CONTEXT_MENU_ITEM, getItemModel("Copy link address")))); ListItemType.CONTEXT_MENU_ITEM, getItemModel("Copy link address"))));
itemList.add((new Pair<>( mListItems.add((new ListItem(
ListItemType.CONTEXT_MENU_SHARE_ITEM, getShareItemModel("Share link")))); ListItemType.CONTEXT_MENU_SHARE_ITEM, getShareItemModel("Share link"))));
itemList.add(new Pair<>(ListItemType.DIVIDER, new PropertyModel())); mListItems.add(new ListItem(ListItemType.DIVIDER, new PropertyModel()));
itemList.add((new Pair<>( mListItems.add((new ListItem(
ListItemType.CONTEXT_MENU_ITEM, getItemModel("Open image in new tab")))); ListItemType.CONTEXT_MENU_ITEM, getItemModel("Open image in new tab"))));
itemList.add( mListItems.add(
(new Pair<>(ListItemType.CONTEXT_MENU_ITEM, getItemModel("Download image")))); (new ListItem(ListItemType.CONTEXT_MENU_ITEM, getItemModel("Download image"))));
itemList.add((new Pair<>( mListItems.add((new ListItem(
ListItemType.CONTEXT_MENU_SHARE_ITEM, getShareItemModel("Share image")))); ListItemType.CONTEXT_MENU_SHARE_ITEM, getShareItemModel("Share image"))));
mAdapter.updateModels(itemList);
}); });
mRenderTestRule.render(mFrame, "revamped_context_menu_with_image_link"); mRenderTestRule.render(mFrame, "revamped_context_menu_with_image_link");
} }
......
...@@ -21,7 +21,7 @@ import org.chromium.chrome.R; ...@@ -21,7 +21,7 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.contextmenu.ChromeContextMenuItem.Item; import org.chromium.chrome.browser.contextmenu.ChromeContextMenuItem.Item;
import org.chromium.chrome.browser.contextmenu.ChromeContextMenuPopulator.ContextMenuGroup; import org.chromium.chrome.browser.contextmenu.ChromeContextMenuPopulator.ContextMenuGroup;
import org.chromium.chrome.browser.contextmenu.RevampedContextMenuCoordinator.ListItemType; import org.chromium.chrome.browser.contextmenu.RevampedContextMenuCoordinator.ListItemType;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -62,19 +62,18 @@ public class RevampedContextMenuCoordinatorTest { ...@@ -62,19 +62,18 @@ public class RevampedContextMenuCoordinatorTest {
rawItems.add(new Pair<>(ContextMenuGroup.IMAGE, groupTwo)); rawItems.add(new Pair<>(ContextMenuGroup.IMAGE, groupTwo));
mCoordinator.initializeHeaderCoordinatorForTesting(mActivity, params); mCoordinator.initializeHeaderCoordinatorForTesting(mActivity, params);
List<Pair<Integer, PropertyModel>> itemList = ModelList itemList = mCoordinator.getItemList(mActivity, rawItems, params);
mCoordinator.getItemList(mActivity, rawItems, params);
assertThat(itemList.get(0).type, equalTo(ListItemType.HEADER));
assertThat(itemList.get(0).first, equalTo(ListItemType.HEADER)); assertThat(itemList.get(1).type, equalTo(ListItemType.DIVIDER));
assertThat(itemList.get(1).first, equalTo(ListItemType.DIVIDER)); assertThat(itemList.get(2).type, equalTo(ListItemType.CONTEXT_MENU_ITEM));
assertThat(itemList.get(2).first, equalTo(ListItemType.CONTEXT_MENU_ITEM)); assertThat(itemList.get(3).type, equalTo(ListItemType.CONTEXT_MENU_ITEM));
assertThat(itemList.get(3).first, equalTo(ListItemType.CONTEXT_MENU_ITEM)); assertThat(itemList.get(4).type, equalTo(ListItemType.CONTEXT_MENU_ITEM));
assertThat(itemList.get(4).first, equalTo(ListItemType.CONTEXT_MENU_ITEM)); assertThat(itemList.get(5).type, equalTo(ListItemType.CONTEXT_MENU_SHARE_ITEM));
assertThat(itemList.get(5).first, equalTo(ListItemType.CONTEXT_MENU_SHARE_ITEM)); assertThat(itemList.get(6).type, equalTo(ListItemType.DIVIDER));
assertThat(itemList.get(6).first, equalTo(ListItemType.DIVIDER)); assertThat(itemList.get(7).type, equalTo(ListItemType.CONTEXT_MENU_ITEM));
assertThat(itemList.get(7).first, equalTo(ListItemType.CONTEXT_MENU_ITEM)); assertThat(itemList.get(8).type, equalTo(ListItemType.CONTEXT_MENU_ITEM));
assertThat(itemList.get(8).first, equalTo(ListItemType.CONTEXT_MENU_ITEM)); assertThat(itemList.get(9).type, equalTo(ListItemType.CONTEXT_MENU_SHARE_ITEM));
assertThat(itemList.get(9).first, equalTo(ListItemType.CONTEXT_MENU_SHARE_ITEM));
} }
@Test @Test
...@@ -96,15 +95,14 @@ public class RevampedContextMenuCoordinatorTest { ...@@ -96,15 +95,14 @@ public class RevampedContextMenuCoordinatorTest {
rawItems.add(new Pair<>(ContextMenuGroup.LINK, groupOne)); rawItems.add(new Pair<>(ContextMenuGroup.LINK, groupOne));
mCoordinator.initializeHeaderCoordinatorForTesting(mActivity, params); mCoordinator.initializeHeaderCoordinatorForTesting(mActivity, params);
List<Pair<Integer, PropertyModel>> itemList = ModelList itemList = mCoordinator.getItemList(mActivity, rawItems, params);
mCoordinator.getItemList(mActivity, rawItems, params);
assertThat(itemList.get(0).type, equalTo(ListItemType.HEADER));
assertThat(itemList.get(0).first, equalTo(ListItemType.HEADER)); assertThat(itemList.get(1).type, equalTo(ListItemType.DIVIDER));
assertThat(itemList.get(1).first, equalTo(ListItemType.DIVIDER)); assertThat(itemList.get(2).type, equalTo(ListItemType.CONTEXT_MENU_ITEM));
assertThat(itemList.get(2).first, equalTo(ListItemType.CONTEXT_MENU_ITEM)); assertThat(itemList.get(3).type, equalTo(ListItemType.CONTEXT_MENU_ITEM));
assertThat(itemList.get(3).first, equalTo(ListItemType.CONTEXT_MENU_ITEM)); assertThat(itemList.get(4).type, equalTo(ListItemType.CONTEXT_MENU_ITEM));
assertThat(itemList.get(4).first, equalTo(ListItemType.CONTEXT_MENU_ITEM)); assertThat(itemList.get(5).type, equalTo(ListItemType.CONTEXT_MENU_SHARE_ITEM));
assertThat(itemList.get(5).first, equalTo(ListItemType.CONTEXT_MENU_SHARE_ITEM));
} }
@Test @Test
...@@ -118,11 +116,10 @@ public class RevampedContextMenuCoordinatorTest { ...@@ -118,11 +116,10 @@ public class RevampedContextMenuCoordinatorTest {
rawItems.add(new Pair<>(ContextMenuGroup.LINK, groupOne)); rawItems.add(new Pair<>(ContextMenuGroup.LINK, groupOne));
mCoordinator.initializeHeaderCoordinatorForTesting(mActivity, params); mCoordinator.initializeHeaderCoordinatorForTesting(mActivity, params);
List<Pair<Integer, PropertyModel>> itemList = ModelList itemList = mCoordinator.getItemList(mActivity, rawItems, params);
mCoordinator.getItemList(mActivity, rawItems, params);
assertThat(itemList.get(0).first, equalTo(ListItemType.HEADER)); assertThat(itemList.get(0).type, equalTo(ListItemType.HEADER));
assertThat(itemList.get(1).first, equalTo(ListItemType.DIVIDER)); assertThat(itemList.get(1).type, equalTo(ListItemType.DIVIDER));
assertThat(itemList.get(2).first, equalTo(ListItemType.CONTEXT_MENU_ITEM)); assertThat(itemList.get(2).type, equalTo(ListItemType.CONTEXT_MENU_ITEM));
} }
} }
...@@ -17,13 +17,14 @@ import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate; ...@@ -17,13 +17,14 @@ import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties; import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.ActionNames; import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.ActionNames;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.DialogListItemProperties; import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.DialogListItemProperties;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.ListItemType;
import org.chromium.chrome.touchless.R; import org.chromium.chrome.touchless.R;
import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties; import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
import java.util.ArrayList;
/** /**
* Handles context menu creation for native pages on touchless devices. Utilizes functionality in * Handles context menu creation for native pages on touchless devices. Utilizes functionality in
* ContextMenuManager and Delegate to select items to show and lookup their labels. * ContextMenuManager and Delegate to select items to show and lookup their labels.
...@@ -90,7 +91,7 @@ public class TouchlessContextMenuManager extends ContextMenuManager { ...@@ -90,7 +91,7 @@ public class TouchlessContextMenuManager extends ContextMenuManager {
return; return;
} }
ArrayList<PropertyModel> menuItems = new ArrayList<>(); ModelList menuItems = new ModelList();
for (@ContextMenuItemId int itemId = 0; itemId < ContextMenuItemId.NUM_ENTRIES; itemId++) { for (@ContextMenuItemId int itemId = 0; itemId < ContextMenuItemId.NUM_ENTRIES; itemId++) {
if (!shouldShowItem(itemId, delegate)) continue; if (!shouldShowItem(itemId, delegate)) continue;
...@@ -98,13 +99,11 @@ public class TouchlessContextMenuManager extends ContextMenuManager { ...@@ -98,13 +99,11 @@ public class TouchlessContextMenuManager extends ContextMenuManager {
// itemId of this item is maintained. // itemId of this item is maintained.
PropertyModel menuItem = PropertyModel menuItem =
buildMenuItem(itemId, new TouchlessItemClickListener(delegate, itemId)); buildMenuItem(itemId, new TouchlessItemClickListener(delegate, itemId));
menuItems.add(menuItem); menuItems.add(new ListItem(ListItemType.DEFAULT, menuItem));
} }
if (menuItems.size() == 0) return; if (menuItems.size() == 0) return;
PropertyModel[] menuItemsArray = new PropertyModel[menuItems.size()]; mTouchlessMenuModel = buildMenuModel(delegate.getContextMenuTitle(), menuItems);
menuItemsArray = menuItems.toArray(menuItemsArray);
mTouchlessMenuModel = buildMenuModel(delegate.getContextMenuTitle(), menuItemsArray);
mModalDialogManager = modalDialogManager; mModalDialogManager = modalDialogManager;
mModalDialogManager.showDialog(mTouchlessMenuModel, ModalDialogManager.ModalDialogType.APP); mModalDialogManager.showDialog(mTouchlessMenuModel, ModalDialogManager.ModalDialogType.APP);
...@@ -187,7 +186,7 @@ public class TouchlessContextMenuManager extends ContextMenuManager { ...@@ -187,7 +186,7 @@ public class TouchlessContextMenuManager extends ContextMenuManager {
} }
/** Builds PropertyModel for context menu from list of PropertyModels for individual items. */ /** Builds PropertyModel for context menu from list of PropertyModels for individual items. */
private PropertyModel buildMenuModel(String title, PropertyModel[] menuItems) { private PropertyModel buildMenuModel(String title, ModelList menuItems) {
PropertyModel.Builder builder = PropertyModel.Builder builder =
new PropertyModel.Builder(TouchlessDialogProperties.ALL_DIALOG_KEYS); new PropertyModel.Builder(TouchlessDialogProperties.ALL_DIALOG_KEYS);
ActionNames names = new ActionNames(); ActionNames names = new ActionNames();
......
...@@ -7,7 +7,6 @@ package org.chromium.chrome.browser.touchless.dialog; ...@@ -7,7 +7,6 @@ package org.chromium.chrome.browser.touchless.dialog;
import android.app.Dialog; import android.app.Dialog;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewCompat;
import android.util.Pair;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
...@@ -31,8 +30,6 @@ import org.chromium.ui.modelutil.PropertyModel; ...@@ -31,8 +30,6 @@ import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor; import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import org.chromium.ui.widget.ChromeImageView; import org.chromium.ui.widget.ChromeImageView;
import java.util.ArrayList;
/** A modal dialog presenter that is specific to touchless dialogs. */ /** A modal dialog presenter that is specific to touchless dialogs. */
public class TouchlessDialogPresenter extends Presenter { public class TouchlessDialogPresenter extends Presenter {
/** An activity to attach dialogs to. */ /** An activity to attach dialogs to. */
...@@ -43,8 +40,8 @@ public class TouchlessDialogPresenter extends Presenter { ...@@ -43,8 +40,8 @@ public class TouchlessDialogPresenter extends Presenter {
private Dialog mDialog; private Dialog mDialog;
/** The model change processor for the currently shown dialog. */ /** The model change processor for the currently shown dialog. */
private PropertyModelChangeProcessor<PropertyModel, Pair<ViewGroup, ModelListAdapter>, private PropertyModelChangeProcessor<PropertyModel, ViewGroup, PropertyKey>
PropertyKey> mModelChangeProcessor; mModelChangeProcessor;
public TouchlessDialogPresenter( public TouchlessDialogPresenter(
ChromeActivity activity, TouchlessModelCoordinator modelCoordinator) { ChromeActivity activity, TouchlessModelCoordinator modelCoordinator) {
...@@ -92,7 +89,8 @@ public class TouchlessDialogPresenter extends Presenter { ...@@ -92,7 +89,8 @@ public class TouchlessDialogPresenter extends Presenter {
}); });
ViewGroup dialogView = (ViewGroup) LayoutInflater.from(mDialog.getContext()) ViewGroup dialogView = (ViewGroup) LayoutInflater.from(mDialog.getContext())
.inflate(R.layout.touchless_dialog_view, null); .inflate(R.layout.touchless_dialog_view, null);
ModelListAdapter adapter = new ModelListAdapter(); ModelListAdapter adapter =
new ModelListAdapter(model.get(TouchlessDialogProperties.LIST_MODELS));
adapter.registerType(ListItemType.DEFAULT, adapter.registerType(ListItemType.DEFAULT,
() -> LayoutInflater.from(mActivity).inflate(R.layout.dialog_list_item, null), () -> LayoutInflater.from(mActivity).inflate(R.layout.dialog_list_item, null),
TouchlessDialogPresenter::bindListItem); TouchlessDialogPresenter::bindListItem);
...@@ -100,7 +98,7 @@ public class TouchlessDialogPresenter extends Presenter { ...@@ -100,7 +98,7 @@ public class TouchlessDialogPresenter extends Presenter {
dialogOptions.setAdapter(adapter); dialogOptions.setAdapter(adapter);
dialogOptions.setItemsCanFocus(true); dialogOptions.setItemsCanFocus(true);
mModelChangeProcessor = PropertyModelChangeProcessor.create( mModelChangeProcessor = PropertyModelChangeProcessor.create(
model, Pair.create(dialogView, adapter), TouchlessDialogPresenter::bind); model, dialogView, TouchlessDialogPresenter::bind);
mDialog.setContentView(dialogView); mDialog.setContentView(dialogView);
// If the modal dialog is not specified to be fullscreen, wrap content and place at the // If the modal dialog is not specified to be fullscreen, wrap content and place at the
...@@ -143,10 +141,7 @@ public class TouchlessDialogPresenter extends Presenter { ...@@ -143,10 +141,7 @@ public class TouchlessDialogPresenter extends Presenter {
* @param view The view to apply the model to. * @param view The view to apply the model to.
* @param propertyKey The property that changed. * @param propertyKey The property that changed.
*/ */
private static void bind( private static void bind(PropertyModel model, ViewGroup view, PropertyKey propertyKey) {
PropertyModel model, Pair<ViewGroup, ModelListAdapter> view, PropertyKey propertyKey) {
ViewGroup dialogView = view.first;
ModelListAdapter optionsAdapter = view.second;
assert !model.get(ModalDialogProperties.FILTER_TOUCH_FOR_SECURITY); assert !model.get(ModalDialogProperties.FILTER_TOUCH_FOR_SECURITY);
assert propertyKey != ModalDialogProperties.FILTER_TOUCH_FOR_SECURITY; assert propertyKey != ModalDialogProperties.FILTER_TOUCH_FOR_SECURITY;
// TODO(mdjones): If the default buttons are used assert no list items and convert the // TODO(mdjones): If the default buttons are used assert no list items and convert the
...@@ -154,38 +149,31 @@ public class TouchlessDialogPresenter extends Presenter { ...@@ -154,38 +149,31 @@ public class TouchlessDialogPresenter extends Presenter {
if (TouchlessDialogProperties.IS_FULLSCREEN == propertyKey) { if (TouchlessDialogProperties.IS_FULLSCREEN == propertyKey) {
// TODO(mdjones): Implement fullscreen/non-fullscreen modes. // TODO(mdjones): Implement fullscreen/non-fullscreen modes.
} else if (ModalDialogProperties.TITLE_ICON == propertyKey) { } else if (ModalDialogProperties.TITLE_ICON == propertyKey) {
ChromeImageView imageView = dialogView.findViewById(R.id.touchless_dialog_icon); ChromeImageView imageView = view.findViewById(R.id.touchless_dialog_icon);
imageView.setImageDrawable(model.get(ModalDialogProperties.TITLE_ICON)); imageView.setImageDrawable(model.get(ModalDialogProperties.TITLE_ICON));
imageView.setVisibility(View.VISIBLE); imageView.setVisibility(View.VISIBLE);
} else if (ModalDialogProperties.TITLE == propertyKey) { } else if (ModalDialogProperties.TITLE == propertyKey) {
TextView textView = dialogView.findViewById(R.id.touchless_dialog_title); TextView textView = view.findViewById(R.id.touchless_dialog_title);
textView.setText(model.get(ModalDialogProperties.TITLE)); textView.setText(model.get(ModalDialogProperties.TITLE));
textView.setVisibility(View.VISIBLE); textView.setVisibility(View.VISIBLE);
} else if (ModalDialogProperties.MESSAGE == propertyKey) { } else if (ModalDialogProperties.MESSAGE == propertyKey) {
TextView textView = dialogView.findViewById(R.id.touchless_dialog_description); TextView textView = view.findViewById(R.id.touchless_dialog_description);
textView.setText(model.get(ModalDialogProperties.MESSAGE)); textView.setText(model.get(ModalDialogProperties.MESSAGE));
textView.setVisibility(View.VISIBLE); textView.setVisibility(View.VISIBLE);
} else if (ModalDialogProperties.CUSTOM_VIEW == propertyKey) { } else if (ModalDialogProperties.CUSTOM_VIEW == propertyKey) {
ViewGroup customGroup = dialogView.findViewById(R.id.custom); ViewGroup customGroup = view.findViewById(R.id.custom);
customGroup.addView(model.get(ModalDialogProperties.CUSTOM_VIEW)); customGroup.addView(model.get(ModalDialogProperties.CUSTOM_VIEW));
customGroup.setVisibility(View.VISIBLE); customGroup.setVisibility(View.VISIBLE);
} else if (TouchlessDialogProperties.LIST_MODELS == propertyKey) {
PropertyModel[] models = model.get(TouchlessDialogProperties.LIST_MODELS);
ArrayList<Pair<Integer, PropertyModel>> modelPairs = new ArrayList<>();
for (int i = 0; i < models.length; i++) {
modelPairs.add(Pair.create(0, models[i]));
}
optionsAdapter.updateModels(modelPairs);
} else if (TouchlessDialogProperties.FORCE_SINGLE_LINE_TITLE == propertyKey) { } else if (TouchlessDialogProperties.FORCE_SINGLE_LINE_TITLE == propertyKey) {
TextView textView = dialogView.findViewById(R.id.touchless_dialog_title); TextView textView = view.findViewById(R.id.touchless_dialog_title);
textView.setMaxLines(model.get(TouchlessDialogProperties.FORCE_SINGLE_LINE_TITLE) textView.setMaxLines(model.get(TouchlessDialogProperties.FORCE_SINGLE_LINE_TITLE)
? 1 ? 1
: Integer.MAX_VALUE); : Integer.MAX_VALUE);
} else if (TouchlessDialogProperties.TITLE_DIRECTION == propertyKey) { } else if (TouchlessDialogProperties.TITLE_DIRECTION == propertyKey) {
TextView textView = dialogView.findViewById(R.id.touchless_dialog_title); TextView textView = view.findViewById(R.id.touchless_dialog_title);
textView.setTextDirection(model.get(TouchlessDialogProperties.TITLE_DIRECTION)); textView.setTextDirection(model.get(TouchlessDialogProperties.TITLE_DIRECTION));
} else if (TouchlessDialogProperties.TITLE_ELLIPSIZE == propertyKey) { } else if (TouchlessDialogProperties.TITLE_ELLIPSIZE == propertyKey) {
TextView textView = dialogView.findViewById(R.id.touchless_dialog_title); TextView textView = view.findViewById(R.id.touchless_dialog_title);
textView.setEllipsize(model.get(TouchlessDialogProperties.TITLE_ELLIPSIZE)); textView.setEllipsize(model.get(TouchlessDialogProperties.TITLE_ELLIPSIZE));
} }
} }
......
...@@ -13,6 +13,7 @@ import android.view.View; ...@@ -13,6 +13,7 @@ import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import org.chromium.ui.modaldialog.ModalDialogProperties; import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModel.ReadableBooleanPropertyKey; import org.chromium.ui.modelutil.PropertyModel.ReadableBooleanPropertyKey;
...@@ -93,7 +94,7 @@ public class TouchlessDialogProperties { ...@@ -93,7 +94,7 @@ public class TouchlessDialogProperties {
* The list of options to be displayed in the dialog. These models should be using the * The list of options to be displayed in the dialog. These models should be using the
* {@link DialogListItemProperties} properties. * {@link DialogListItemProperties} properties.
*/ */
public static final WritableObjectPropertyKey<PropertyModel[]> LIST_MODELS = public static final WritableObjectPropertyKey<ModelList> LIST_MODELS =
new WritableObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** The names for the Actions. */ /** The names for the Actions. */
......
...@@ -16,9 +16,12 @@ import org.chromium.base.ApiCompatibilityUtils; ...@@ -16,9 +16,12 @@ import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.permissions.PermissionDialogDelegate; import org.chromium.chrome.browser.permissions.PermissionDialogDelegate;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties; import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.ListItemType;
import org.chromium.ui.UiUtils; import org.chromium.ui.UiUtils;
import org.chromium.ui.modaldialog.DialogDismissalCause; import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogProperties; import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
/** /**
...@@ -53,29 +56,33 @@ public class TouchlessPermissionDialogModel { ...@@ -53,29 +56,33 @@ public class TouchlessPermissionDialogModel {
.with(ModalDialogProperties.CONTENT_DESCRIPTION, delegate.getMessageText()) .with(ModalDialogProperties.CONTENT_DESCRIPTION, delegate.getMessageText())
.build(); .build();
PropertyModel[] items = new PropertyModel[]{ ModelList items = new ModelList();
items.add(new ListItem(ListItemType.DEFAULT,
new PropertyModel new PropertyModel
.Builder(TouchlessDialogProperties.DialogListItemProperties.ALL_KEYS) .Builder(TouchlessDialogProperties.DialogListItemProperties.ALL_KEYS)
.with(TouchlessDialogProperties.DialogListItemProperties.TEXT, .with(TouchlessDialogProperties.DialogListItemProperties.TEXT,
delegate.getPrimaryButtonText()) delegate.getPrimaryButtonText())
.with(TouchlessDialogProperties.DialogListItemProperties.CLICK_LISTENER, .with(TouchlessDialogProperties.DialogListItemProperties.CLICK_LISTENER,
(v) -> controller.onClick(model, (v)
ModalDialogProperties.ButtonType.POSITIVE)) -> controller.onClick(
model, ModalDialogProperties.ButtonType.POSITIVE))
.with(TouchlessDialogProperties.DialogListItemProperties.ICON, .with(TouchlessDialogProperties.DialogListItemProperties.ICON,
ApiCompatibilityUtils.getDrawable( ApiCompatibilityUtils.getDrawable(
resources, R.drawable.ic_check_circle)) resources, R.drawable.ic_check_circle))
.build(), .build()));
items.add(new ListItem(ListItemType.DEFAULT,
new PropertyModel new PropertyModel
.Builder(TouchlessDialogProperties.DialogListItemProperties.ALL_KEYS) .Builder(TouchlessDialogProperties.DialogListItemProperties.ALL_KEYS)
.with(TouchlessDialogProperties.DialogListItemProperties.TEXT, .with(TouchlessDialogProperties.DialogListItemProperties.TEXT,
delegate.getSecondaryButtonText()) delegate.getSecondaryButtonText())
.with(TouchlessDialogProperties.DialogListItemProperties.CLICK_LISTENER, .with(TouchlessDialogProperties.DialogListItemProperties.CLICK_LISTENER,
(v) -> controller.onClick(model, (v)
ModalDialogProperties.ButtonType.NEGATIVE)) -> controller.onClick(
model, ModalDialogProperties.ButtonType.NEGATIVE))
.with(TouchlessDialogProperties.DialogListItemProperties.ICON, .with(TouchlessDialogProperties.DialogListItemProperties.ICON,
ApiCompatibilityUtils.getDrawable( ApiCompatibilityUtils.getDrawable(
resources, R.drawable.ic_cancel_circle)) resources, R.drawable.ic_cancel_circle))
.build()}; .build()));
model.set(TouchlessDialogProperties.LIST_MODELS, items); model.set(TouchlessDialogProperties.LIST_MODELS, items);
model.set(TouchlessDialogProperties.CANCEL_ACTION, model.set(TouchlessDialogProperties.CANCEL_ACTION,
...@@ -103,20 +110,20 @@ public class TouchlessPermissionDialogModel { ...@@ -103,20 +110,20 @@ public class TouchlessPermissionDialogModel {
.with(TouchlessDialogProperties.ACTION_NAMES, names) .with(TouchlessDialogProperties.ACTION_NAMES, names)
.with(TouchlessDialogProperties.ALT_ACTION, null) .with(TouchlessDialogProperties.ALT_ACTION, null)
.build(); .build();
model.set(TouchlessDialogProperties.LIST_MODELS, ModelList items = new ModelList();
new PropertyModel[] { items.add(new ListItem(ListItemType.DEFAULT,
new PropertyModel new PropertyModel
.Builder( .Builder(TouchlessDialogProperties.DialogListItemProperties.ALL_KEYS)
TouchlessDialogProperties.DialogListItemProperties.ALL_KEYS) .with(TouchlessDialogProperties.DialogListItemProperties.TEXT, resources,
.with(TouchlessDialogProperties.DialogListItemProperties.TEXT, R.string.infobar_update_permissions_button_text)
resources, R.string.infobar_update_permissions_button_text) .with(TouchlessDialogProperties.DialogListItemProperties.ICON, context,
.with(TouchlessDialogProperties.DialogListItemProperties.ICON, R.drawable.ic_check_circle)
context, R.drawable.ic_check_circle) .with(TouchlessDialogProperties.DialogListItemProperties.CLICK_LISTENER,
.with(TouchlessDialogProperties.DialogListItemProperties (v)
.CLICK_LISTENER, -> controller.onClick(
(v) -> controller.onClick(model, model, ModalDialogProperties.ButtonType.POSITIVE))
ModalDialogProperties.ButtonType.POSITIVE)) .build()));
.build()}); model.set(TouchlessDialogProperties.LIST_MODELS, items);
return model; return model;
} }
} }
...@@ -7,9 +7,12 @@ package org.chromium.chrome.browser.touchless.dialog; ...@@ -7,9 +7,12 @@ package org.chromium.chrome.browser.touchless.dialog;
import android.view.View; import android.view.View;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.DialogListItemProperties; import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.DialogListItemProperties;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.ListItemType;
import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties; import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
...@@ -57,14 +60,15 @@ public class TouchlessDialogTestUtils { ...@@ -57,14 +60,15 @@ public class TouchlessDialogTestUtils {
}); });
} }
private static PropertyModel[] generateItems( private static ModelList generateItems(
String[] itemTitles, View.OnClickListener[] itemListeners) { String[] itemTitles, View.OnClickListener[] itemListeners) {
PropertyModel[] items = new PropertyModel[itemTitles.length]; ModelList items = new ModelList();
for (int i = 0; i < items.length; i++) { for (int i = 0; i < itemTitles.length; i++) {
items[i] = new PropertyModel.Builder(DialogListItemProperties.ALL_KEYS) items.add(new ListItem(ListItemType.DEFAULT,
.with(DialogListItemProperties.TEXT, itemTitles[i]) new PropertyModel.Builder(DialogListItemProperties.ALL_KEYS)
.with(DialogListItemProperties.CLICK_LISTENER, itemListeners[i]) .with(DialogListItemProperties.TEXT, itemTitles[i])
.build(); .with(DialogListItemProperties.CLICK_LISTENER, itemListeners[i])
.build()));
} }
return items; return items;
} }
......
...@@ -169,4 +169,9 @@ public class ListModelBase<T, P> extends ListObservableImpl<P> implements Simple ...@@ -169,4 +169,9 @@ public class ListModelBase<T, P> extends ListObservableImpl<P> implements Simple
} }
notifyItemMoved(curIndex, newIndex); notifyItemMoved(curIndex, newIndex);
} }
/** Clear all items from the list. */
public void clear() {
if (size() > 0) removeRange(0, size());
}
} }
...@@ -13,10 +13,10 @@ import android.widget.BaseAdapter; ...@@ -13,10 +13,10 @@ import android.widget.BaseAdapter;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
import org.chromium.ui.R; import org.chromium.ui.R;
import org.chromium.ui.modelutil.ListObservable.ListObserver;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor.ViewBinder;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List;
/** /**
* Adapter for providing data and views to a ListView. * Adapter for providing data and views to a ListView.
...@@ -24,10 +24,8 @@ import java.util.List; ...@@ -24,10 +24,8 @@ import java.util.List;
* To use, register a {@link PropertyModelChangeProcessor.ViewBinder} and {@link ViewBuilder} * To use, register a {@link PropertyModelChangeProcessor.ViewBinder} and {@link ViewBuilder}
* for each view type in the list using * for each view type in the list using
* {@link #registerType(int, ViewBuilder, PropertyModelChangeProcessor.ViewBinder)}. * {@link #registerType(int, ViewBuilder, PropertyModelChangeProcessor.ViewBinder)}.
* Then call {@link #updateModels(List)} to provide a list of items (represented by PropertyModels) * The constructor takes a {@link ListObservable} list in the form of a {@link ModelList}. Any
* to display in the list. If the items in the list change (e.g. items are added, removed, or * changes that occur in the list will be automatically updated in the view.
* change order), call #updateModels again with the new list of items. NOTE: There are plans to
* change the API surface to work with a PropertyObservable instead.
* *
* When creating a new view, ModelListAdapter will bind all set properties. When reusing/rebinding * When creating a new view, ModelListAdapter will bind all set properties. When reusing/rebinding
* a view, in addition to binding all properties set on the new model, properties that were * a view, in addition to binding all properties set on the new model, properties that were
...@@ -38,32 +36,36 @@ import java.util.List; ...@@ -38,32 +36,36 @@ import java.util.List;
* Additionally, ModelListAdapter will hook up a {@link PropertyModelChangeProcessor} when binding * Additionally, ModelListAdapter will hook up a {@link PropertyModelChangeProcessor} when binding
* views to ensure that changes to the PropertyModel for that list item are bound to the view. * views to ensure that changes to the PropertyModel for that list item are bound to the view.
*/ */
public class ModelListAdapter extends BaseAdapter { public class ModelListAdapter extends BaseAdapter implements MVCListAdapter {
/** private final ModelList mModelList;
* An interface to provide a means to build specific view types. private final SparseArray<Pair<ViewBuilder, ViewBinder>> mViewBuilderMap = new SparseArray<>();
* @param <T> The type of view that the implementor will build. private final ListObserver<Void> mListObserver;
*/
public interface ViewBuilder<T extends View> { public ModelListAdapter(ModelList data) {
/** mModelList = data;
* @return A new view to show in the list. mListObserver = new ListObserver<Void>() {
*/ @Override
T buildView(); public void onItemRangeInserted(ListObservable source, int index, int count) {
} notifyDataSetChanged();
}
private final List<Pair<Integer, PropertyModel>> mModelList = new ArrayList<>(); @Override
private final SparseArray<Pair<ViewBuilder, PropertyModelChangeProcessor.ViewBinder>> public void onItemRangeRemoved(ListObservable source, int index, int count) {
mViewBuilderMap = new SparseArray<>(); notifyDataSetChanged();
}
/** @Override
* Update the visible models (list items). public void onItemRangeChanged(
* @param models A list of {@link PropertyModel}s to display. The Integer property in the pair ListObservable<Void> source, int index, int count, @Nullable Void payload) {
* indicates the view type, while the PropertyModel contains the properties for the item notifyDataSetChanged();
* to display. }
*/
public void updateModels(List<Pair<Integer, PropertyModel>> models) { @Override
mModelList.clear(); public void onItemMoved(ListObservable source, int curIndex, int newIndex) {
mModelList.addAll(models); notifyDataSetChanged();
notifyDataSetChanged(); }
};
mModelList.addObserver(mListObserver);
} }
@Override @Override
...@@ -81,22 +83,16 @@ public class ModelListAdapter extends BaseAdapter { ...@@ -81,22 +83,16 @@ public class ModelListAdapter extends BaseAdapter {
return position; return position;
} }
/** @Override
* Register a new view type that this adapter knows how to show. public <T extends View> void registerType(
* @param typeId The ID of the view type. This should not match any other view type registered int typeId, ViewBuilder<T> builder, ViewBinder<PropertyModel, T, PropertyKey> binder) {
* in this adapter.
* @param builder A mechanism for building new views of the specified type.
* @param binder A means of binding a model to the provided view.
*/
public <T extends View> void registerType(int typeId, ViewBuilder<T> builder,
PropertyModelChangeProcessor.ViewBinder<PropertyModel, T, PropertyKey> binder) {
assert mViewBuilderMap.get(typeId) == null; assert mViewBuilderMap.get(typeId) == null;
mViewBuilderMap.put(typeId, new Pair<>(builder, binder)); mViewBuilderMap.put(typeId, new Pair<>(builder, binder));
} }
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
return mModelList.get(position).first; return mModelList.get(position).type;
} }
@Override @Override
...@@ -118,7 +114,7 @@ public class ModelListAdapter extends BaseAdapter { ...@@ -118,7 +114,7 @@ public class ModelListAdapter extends BaseAdapter {
PropertyModel oldModel = null; PropertyModel oldModel = null;
if (convertView == null || convertView.getTag(R.id.view_type) == null if (convertView == null || convertView.getTag(R.id.view_type) == null
|| (int) convertView.getTag(R.id.view_type) != getItemViewType(position)) { || (int) convertView.getTag(R.id.view_type) != getItemViewType(position)) {
int modelTypeId = mModelList.get(position).first; int modelTypeId = mModelList.get(position).type;
convertView = mViewBuilderMap.get(modelTypeId).first.buildView(); convertView = mViewBuilderMap.get(modelTypeId).first.buildView();
// Since the view type returned by getView is not guaranteed to return a view of that // Since the view type returned by getView is not guaranteed to return a view of that
...@@ -130,9 +126,9 @@ public class ModelListAdapter extends BaseAdapter { ...@@ -130,9 +126,9 @@ public class ModelListAdapter extends BaseAdapter {
oldModel = (PropertyModel) convertView.getTag(R.id.view_model); oldModel = (PropertyModel) convertView.getTag(R.id.view_model);
} }
PropertyModel model = mModelList.get(position).second; PropertyModel model = mModelList.get(position).model;
PropertyModelChangeProcessor.ViewBinder binder = PropertyModelChangeProcessor.ViewBinder binder =
mViewBuilderMap.get(mModelList.get(position).first).second; mViewBuilderMap.get(mModelList.get(position).type).second;
// 3. Attach a PropertyModelChangeProcessor and PropertyModel to the view (for #1/2 above // 3. Attach a PropertyModelChangeProcessor and PropertyModel to the view (for #1/2 above
// when re-using a view). // when re-using a view).
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
package org.chromium.ui.modelutil; package org.chromium.ui.modelutil;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair;
import android.view.View; import android.view.View;
import org.junit.Assert; import org.junit.Assert;
...@@ -21,8 +20,6 @@ import org.chromium.base.test.BaseRobolectricTestRunner; ...@@ -21,8 +20,6 @@ import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.CallbackHelper; import org.chromium.base.test.util.CallbackHelper;
import org.chromium.ui.R; import org.chromium.ui.R;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
/** /**
...@@ -92,16 +89,13 @@ public class ModelListAdapterTest { ...@@ -92,16 +89,13 @@ public class ModelListAdapterTest {
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mModelListAdapter = new ModelListAdapter(); ModelListAdapter.ModelList testData = new ModelListAdapter.ModelList();
mModelListAdapter.registerType(VIEW_TYPE_1, new TestViewBuilder(), new TestViewBinder());
mModelListAdapter.registerType(VIEW_TYPE_2, new TestViewBuilder(), new TestViewBinder());
mModel = new PropertyModel(BOOLEAN_PROPERTY, FLOAT_PROPERTY, INT_PROPERTY, OBJECT_PROPERTY); mModel = new PropertyModel(BOOLEAN_PROPERTY, FLOAT_PROPERTY, INT_PROPERTY, OBJECT_PROPERTY);
testData.add(new ModelListAdapter.ListItem(VIEW_TYPE_1, mModel));
List<Pair<Integer, PropertyModel>> testData = new ArrayList<>(); mModelListAdapter = new ModelListAdapter(testData);
testData.add(new Pair(VIEW_TYPE_1, mModel)); mModelListAdapter.registerType(VIEW_TYPE_1, new TestViewBuilder(), new TestViewBinder());
mModelListAdapter.registerType(VIEW_TYPE_2, new TestViewBuilder(), new TestViewBinder());
mModelListAdapter.updateModels(testData);
} }
@Test @Test
......
...@@ -26,7 +26,7 @@ import java.util.List; ...@@ -26,7 +26,7 @@ import java.util.List;
@RunWith(BaseRobolectricTestRunner.class) @RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE) @Config(manifest = Config.NONE)
public class PropertyListModelTest implements ListObservable.ListObserver<PropertyKey> { public class PropertyListModelTest implements ListObservable.ListObserver<PropertyKey> {
private static final int METHOD_COUNT = 31; private static final int METHOD_COUNT = 32;
private static final PropertyModel.WritableIntPropertyKey INTEGER_KEY = private static final PropertyModel.WritableIntPropertyKey INTEGER_KEY =
new PropertyModel.WritableIntPropertyKey(); new PropertyModel.WritableIntPropertyKey();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment