From 86f3d604691b274026deee114afd6d9670058873 Mon Sep 17 00:00:00 2001 From: Patrick Noland Date: Tue, 2 Aug 2022 16:42:13 +0000 Subject: [PATCH] [Journeys] Add more progress spinner/button. This displays a spinner when content is loading, and a load more button when scroll to load is disabled. Scroll to load is disabled when accessibility is enabled or a hardware keyboard is attached. In this state, "infinite scroll" is disabled. Bug: 1303171, 1346662 Change-Id: Ic5f10e18f80c432f031045ed4dc1762d0d4fef88 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3781705 Reviewed-by: Gang Wu Commit-Queue: Patrick Noland Reviewed-by: Matthew Jones Cr-Commit-Position: refs/heads/main@{#1030608} --- .../browser/history/HistoryManager.java | 7 +- .../chrome/browser/ui/RootUiCoordinator.java | 6 +- .../HistoryClustersCoordinatorTest.java | 9 ++- .../HistoryClustersMediatorTest.java | 77 +++++++++++++++++-- .../HistoryClustersCoordinator.java | 21 +++-- .../HistoryClustersItemProperties.java | 6 +- .../HistoryClustersMediator.java | 59 +++++++++++--- .../HistoryClustersViewBinder.java | 14 ++++ 8 files changed, 169 insertions(+), 30 deletions(-) diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryManager.java b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryManager.java index 91dde174ac31b2..c181e0fdbe5bc6 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryManager.java @@ -52,6 +52,7 @@ import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar; import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager.SnackbarController; +import org.chromium.chrome.browser.util.ChromeAccessibilityUtil; import org.chromium.components.browser_ui.settings.SettingsLauncher; import org.chromium.components.browser_ui.widget.selectable_list.SelectableItemView; import org.chromium.components.browser_ui.widget.selectable_list.SelectableListLayout; @@ -256,9 +257,9 @@ public void onOptOut() { } }; - mHistoryClustersCoordinator = - new HistoryClustersCoordinator(Profile.getLastUsedRegularProfile(), activity, - TemplateUrlServiceFactory.get(), historyClustersDelegate); + mHistoryClustersCoordinator = new HistoryClustersCoordinator( + Profile.getLastUsedRegularProfile(), activity, TemplateUrlServiceFactory.get(), + historyClustersDelegate, ChromeAccessibilityUtil.get()); } // 1. Create selectable components. diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java index d5d2dd3ef12e3e..61a3b7f566c3d4 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java @@ -128,6 +128,7 @@ import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; import org.chromium.chrome.browser.ui.system.StatusBarColorController; import org.chromium.chrome.browser.ui.system.StatusBarColorController.StatusBarColorProvider; +import org.chromium.chrome.browser.util.ChromeAccessibilityUtil; import org.chromium.chrome.browser.vr.VrModuleProvider; import org.chromium.chrome.features.start_surface.StartSurface; import org.chromium.components.browser_ui.accessibility.PageZoomCoordinator; @@ -824,8 +825,9 @@ public ViewGroup getToggleView(ViewGroup parent) { } }; - mHistoryClustersCoordinator = new HistoryClustersCoordinator( - profile, mActivity, TemplateUrlServiceFactory.get(), historyClustersDelegate); + mHistoryClustersCoordinator = new HistoryClustersCoordinator(profile, mActivity, + TemplateUrlServiceFactory.get(), historyClustersDelegate, + ChromeAccessibilityUtil.get()); mHistoryClustersCoordinatorSupplier.set(mHistoryClustersCoordinator); } } diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinatorTest.java index 9e17544d203a89..bba917d6e62225 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinatorTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinatorTest.java @@ -59,6 +59,7 @@ import org.chromium.components.favicon.LargeIconBridgeJni; import org.chromium.components.search_engines.TemplateUrlService; import org.chromium.ui.display.DisplayAndroidManager; +import org.chromium.ui.util.AccessibilityUtil; import org.chromium.url.GURL; import java.io.Serializable; @@ -178,6 +179,8 @@ public void markVisitForRemoval(ClusterVisit clusterVisit) { private GURL mGurl2; @Mock private HistoryClustersMetricsLogger mMetricsLogger; + @Mock + private AccessibilityUtil mAccessibilityUtil; private ActivityScenario mActivityScenario; private HistoryClustersCoordinator mHistoryClustersCoordinator; @@ -214,9 +217,9 @@ public void setUp() { mActivityScenario = ActivityScenario.launch(ChromeTabbedActivity.class).onActivity(activity -> { mActivity = activity; - mHistoryClustersCoordinator = - new HistoryClustersCoordinator(mProfile, activity, mTemplateUrlService, - mHistoryClustersDelegate, mMetricsLogger, mSelectionDelegate); + mHistoryClustersCoordinator = new HistoryClustersCoordinator(mProfile, activity, + mTemplateUrlService, mHistoryClustersDelegate, mMetricsLogger, + mSelectionDelegate, mAccessibilityUtil); }); } diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediatorTest.java index cc46e8dae28e51..a7d5d90684e5f0 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediatorTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediatorTest.java @@ -5,6 +5,7 @@ package org.chromium.chrome.browser.history_clusters; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalMatchers.geq; @@ -18,6 +19,7 @@ import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Typeface; import android.net.Uri; @@ -42,6 +44,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.annotation.Config; @@ -59,6 +62,7 @@ import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.TabLaunchType; import org.chromium.chrome.browser.tabmodel.TabCreator; +import org.chromium.components.browser_ui.widget.MoreProgressButton.State; import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate; import org.chromium.components.favicon.LargeIconBridge; import org.chromium.components.search_engines.TemplateUrlService; @@ -67,6 +71,7 @@ import org.chromium.ui.modelutil.MVCListAdapter.ModelList; import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.shadows.ShadowAppCompatResources; +import org.chromium.ui.util.AccessibilityUtil; import org.chromium.url.GURL; import org.chromium.url.JUnitTestGURLs; import org.chromium.url.ShadowGURL; @@ -125,6 +130,10 @@ public class HistoryClustersMediatorTest { private TabCreator mTabCreator; @Mock private HistoryClustersMetricsLogger mMetricsLogger; + @Mock + private AccessibilityUtil mAccessibilityUtil; + @Mock + private Configuration mConfiguration; private ClusterVisit mVisit1; private ClusterVisit mVisit2; @@ -155,6 +164,8 @@ public void setUp() { doReturn(mResources).when(mContext).getResources(); doReturn(ITEM_URL_SPEC).when(mMockGurl).getSpec(); doReturn(mLayoutManager).when(mRecyclerView).getLayoutManager(); + mConfiguration.keyboard = Configuration.KEYBOARD_NOKEYS; + doReturn(mConfiguration).when(mResources).getConfiguration(); mModelList = new ModelList(); mToolbarModel = new PropertyModel(HistoryClustersToolbarProperties.ALL_KEYS); @@ -238,7 +249,7 @@ public void markVisitForRemoval(ClusterVisit clusterVisit) { mMediator = new HistoryClustersMediator(mBridge, mLargeIconBridge, mContext, mResources, mModelList, mToolbarModel, mHistoryClustersDelegate, mClock, mTemplateUrlService, - mSelectionDelegate, mMetricsLogger); + mSelectionDelegate, mMetricsLogger, mAccessibilityUtil); mVisit1 = new ClusterVisit(1.0F, mGurl1, "Title 1", "url1.com/", new ArrayList<>(), new ArrayList<>(), mGurl1, 123L, new ArrayList<>()); mVisit2 = new ClusterVisit(1.0F, mGurl2, "Title 2", "url2.com/", new ArrayList<>(), @@ -281,7 +292,11 @@ public void testQuery() { // call both. mMediator.setQueryState(QueryState.forQuery("query", "")); mMediator.startQuery("query"); - assertEquals(mModelList.size(), 0); + assertEquals(1, mModelList.size()); + ListItem spinnerItem = mModelList.get(0); + assertEquals(spinnerItem.type, ItemType.MORE_PROGRESS); + assertEquals(spinnerItem.model.get(HistoryClustersItemProperties.PROGRESS_BUTTON_STATE), + State.LOADING); fulfillPromise(promise, mHistoryClustersResultWithQuery); @@ -315,6 +330,55 @@ public void testQuery() { HistoryClustersItemProperties.RELATED_SEARCHES))); } + @Test + public void testScrollToLoadDisabled() { + mConfiguration.keyboard = Configuration.KEYBOARD_12KEY; + mMediator = new HistoryClustersMediator(mBridge, mLargeIconBridge, mContext, mResources, + mModelList, mToolbarModel, mHistoryClustersDelegate, mClock, mTemplateUrlService, + mSelectionDelegate, mMetricsLogger, mAccessibilityUtil); + + Promise promise = new Promise<>(); + doReturn(promise).when(mBridge).queryClusters("query"); + Promise secondPromise = new Promise(); + doReturn(secondPromise).when(mBridge).loadMoreClusters("query"); + + mMediator.setQueryState(QueryState.forQuery("query", "")); + mMediator.startQuery("query"); + + assertEquals(1, mModelList.size()); + ListItem spinnerItem = mModelList.get(0); + assertEquals(spinnerItem.type, ItemType.MORE_PROGRESS); + assertEquals(spinnerItem.model.get(HistoryClustersItemProperties.PROGRESS_BUTTON_STATE), + State.LOADING); + + fulfillPromise(promise, mHistoryClustersResultWithQuery); + + spinnerItem = mModelList.get(mModelList.size() - 1); + assertEquals(spinnerItem.type, ItemType.MORE_PROGRESS); + assertEquals(spinnerItem.model.get(HistoryClustersItemProperties.PROGRESS_BUTTON_STATE), + State.BUTTON); + + mMediator.onScrolled(mRecyclerView, 1, 1); + verify(mBridge, Mockito.never()).loadMoreClusters("query"); + + spinnerItem.model.get(HistoryClustersItemProperties.CLICK_HANDLER).onClick(null); + ShadowLooper.idleMainLooper(); + + verify(mBridge).loadMoreClusters("query"); + spinnerItem = mModelList.get(mModelList.size() - 1); + assertEquals(spinnerItem.type, ItemType.MORE_PROGRESS); + assertEquals(spinnerItem.model.get(HistoryClustersItemProperties.PROGRESS_BUTTON_STATE), + State.LOADING); + + fulfillPromise(secondPromise, mHistoryClustersFollowupResultWithQuery); + // There should no longer be a spinner or "load more" button once all possible results for + // the current query have been loaded. + for (int i = 0; i < mModelList.size(); i++) { + ListItem item = mModelList.get(i); + assertNotEquals(item.type, ItemType.MORE_PROGRESS); + } + } + @Test public void testEmptyQuery() { Promise promise = new Promise<>(); @@ -393,14 +457,17 @@ public void testSearchTextChanged() { mMediator.setQueryState(QueryState.forQuery("pan", "")); mMediator.onSearchTextChanged("pan"); - assertEquals(mModelList.size(), 0); + assertEquals(mModelList.size(), 1); + ListItem spinnerItem = mModelList.get(0); + assertEquals(spinnerItem.type, ItemType.MORE_PROGRESS); + assertEquals(spinnerItem.model.get(HistoryClustersItemProperties.PROGRESS_BUTTON_STATE), + State.LOADING); verify(mBridge).queryClusters("pan"); doReturn(new Promise<>()).when(mBridge).queryClusters(""); - mModelList.add(new ListItem(42, new PropertyModel())); + mMediator.onEndSearch(); - assertEquals(mModelList.size(), 0); verify(mBridge).queryClusters(""); } diff --git a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinator.java b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinator.java index 1f93ba85c25d34..56a56d663f832d 100644 --- a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinator.java +++ b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinator.java @@ -28,6 +28,7 @@ import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModelChangeProcessor; import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter; +import org.chromium.ui.util.AccessibilityUtil; /** * Root component for the HistoryClusters UI component, which displays lists of related history @@ -70,7 +71,8 @@ public boolean isSelectionEnabled() { HistoryClustersCoordinator(@NonNull Profile profile, @NonNull Activity activity, TemplateUrlService templateUrlService, HistoryClustersDelegate historyClustersDelegate, HistoryClustersMetricsLogger metricsLogger, - SelectionDelegate selectionDelegate) { + SelectionDelegate selectionDelegate, + AccessibilityUtil accessibilityUtil) { mActivity = activity; mDelegate = historyClustersDelegate; mModelList = new ModelList(); @@ -84,7 +86,7 @@ public boolean isSelectionEnabled() { mMediator = new HistoryClustersMediator(HistoryClustersBridge.getForProfile(profile), new LargeIconBridge(profile), mActivity, mActivity.getResources(), mModelList, mToolbarModel, mDelegate, System::currentTimeMillis, templateUrlService, - mSelectionDelegate, mMetricsLogger); + mSelectionDelegate, mMetricsLogger, accessibilityUtil); } /** @@ -93,12 +95,14 @@ public boolean isSelectionEnabled() { * @param activity Activity in which this UI resides. * @param historyClustersDelegate Delegate that provides functionality that must be implemented * externally, e.g. populating intents targeting activities we can't reference directly. + * @param accessibilityUtil Utility object that tells us about the current accessibility state. */ public HistoryClustersCoordinator(@NonNull Profile profile, @NonNull Activity activity, - TemplateUrlService templateUrlService, - HistoryClustersDelegate historyClustersDelegate) { + TemplateUrlService templateUrlService, HistoryClustersDelegate historyClustersDelegate, + AccessibilityUtil accessibilityUtil) { this(profile, activity, templateUrlService, historyClustersDelegate, - new HistoryClustersMetricsLogger(templateUrlService), new SelectionDelegate<>()); + new HistoryClustersMetricsLogger(templateUrlService), new SelectionDelegate<>(), + accessibilityUtil); } public void destroy() { @@ -157,6 +161,8 @@ void inflateActivityView() { HistoryClustersViewBinder::noopBindView); mAdapter.registerType(ItemType.CLEAR_BROWSING_DATA, mDelegate::getClearBrowsingDataView, HistoryClustersViewBinder::noopBindView); + mAdapter.registerType(ItemType.MORE_PROGRESS, this::buildMoreProgressView, + HistoryClustersViewBinder::bindMoreProgressView); LayoutInflater layoutInflater = LayoutInflater.from(mActivity); mActivityContentView = (ViewGroup) layoutInflater.inflate( @@ -192,6 +198,11 @@ void inflateActivityView() { mActivityViewInflated = true; } + private View buildMoreProgressView(ViewGroup parent) { + return LayoutInflater.from(parent.getContext()) + .inflate(R.layout.more_progress_button, parent, false); + } + private View buildClusterView(ViewGroup parent) { SelectableItemView clusterView = (SelectableItemView) LayoutInflater.from(parent.getContext()) diff --git a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersItemProperties.java b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersItemProperties.java index 634fd9f3df38ac..1846f669dc2ee5 100644 --- a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersItemProperties.java +++ b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersItemProperties.java @@ -21,7 +21,7 @@ class HistoryClustersItemProperties { @IntDef({HistoryClustersItemProperties.ItemType.VISIT, ItemType.CLUSTER, ItemType.RELATED_SEARCHES, ItemType.TOGGLE, ItemType.PRIVACY_DISCLAIMER, - ItemType.CLEAR_BROWSING_DATA}) + ItemType.CLEAR_BROWSING_DATA, ItemType.MORE_PROGRESS}) @Retention(RetentionPolicy.SOURCE) @interface ItemType { int VISIT = 1; @@ -30,6 +30,7 @@ class HistoryClustersItemProperties { int TOGGLE = 4; int PRIVACY_DISCLAIMER = 5; int CLEAR_BROWSING_DATA = 6; + int MORE_PROGRESS = 7; } static final WritableIntPropertyKey ACCESSIBILITY_STATE = new WritableIntPropertyKey(); @@ -48,6 +49,7 @@ class HistoryClustersItemProperties { static final WritableObjectPropertyKey ICON_DRAWABLE = new WritableObjectPropertyKey<>(); static final WritableObjectPropertyKey LABEL = new WritableObjectPropertyKey<>(); + static final WritableIntPropertyKey PROGRESS_BUTTON_STATE = new WritableIntPropertyKey(); static final WritableObjectPropertyKey> RELATED_SEARCHES = new WritableObjectPropertyKey<>(); static final WritableObjectPropertyKey TITLE = new WritableObjectPropertyKey<>(); @@ -56,5 +58,5 @@ class HistoryClustersItemProperties { static final PropertyKey[] ALL_KEYS = {ACCESSIBILITY_STATE, CHIP_CLICK_HANDLER, CLICK_HANDLER, CLUSTER_VISIT, DIVIDER_VISIBLE, END_BUTTON_CLICK_HANDLER, END_BUTTON_DRAWABLE, - ICON_DRAWABLE, LABEL, RELATED_SEARCHES, TITLE, URL, VISIBILITY}; + ICON_DRAWABLE, LABEL, PROGRESS_BUTTON_STATE, RELATED_SEARCHES, TITLE, URL, VISIBILITY}; } \ No newline at end of file diff --git a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediator.java b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediator.java index 785e273014e2c5..f607bee1a4d4be 100644 --- a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediator.java +++ b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediator.java @@ -31,6 +31,7 @@ import org.chromium.chrome.browser.tab.TabLaunchType; import org.chromium.chrome.browser.tabmodel.TabCreator; import org.chromium.chrome.browser.ui.favicon.FaviconUtils; +import org.chromium.components.browser_ui.widget.MoreProgressButton.State; import org.chromium.components.browser_ui.widget.RoundedIconGenerator; import org.chromium.components.browser_ui.widget.selectable_list.SelectableItemView; import org.chromium.components.browser_ui.widget.selectable_list.SelectableListToolbar.SearchDelegate; @@ -44,6 +45,7 @@ import org.chromium.ui.modelutil.MVCListAdapter.ListItem; import org.chromium.ui.modelutil.MVCListAdapter.ModelList; import org.chromium.ui.modelutil.PropertyModel; +import org.chromium.ui.util.AccessibilityUtil; import org.chromium.url.GURL; import java.util.ArrayList; @@ -98,9 +100,12 @@ private VisitMetadata(ListItem visitListItem, ListItem clusterListItem, private ListItem mPrivacyDisclaimerItem; private ListItem mClearBrowsingDataItem; private QueryState mQueryState; + private final ListItem mMoreProgressItem; private final HistoryClustersMetricsLogger mMetricsLogger; private Map mLabelToModelMap = new LinkedHashMap<>(); private Map mVisitMetadataMap = new HashMap<>(); + private final AccessibilityUtil mAccessibilityUtil; + private final boolean mIsScrollToLoadDisabled; /** * Create a new HistoryClustersMediator. @@ -118,13 +123,14 @@ private VisitMetadata(ListItem visitListItem, ListItem clusterListItem, * @param selectionDelegate Delegate that gives us information about the currently selected * items in the list we're displaying. * @param metricsLogger Object that records metrics about user interactions. + * @param accessibilityUtil Utility object that tells us about the current accessibility state. */ HistoryClustersMediator(@NonNull HistoryClustersBridge historyClustersBridge, LargeIconBridge largeIconBridge, @NonNull Context context, @NonNull Resources resources, @NonNull ModelList modelList, @NonNull PropertyModel toolbarModel, HistoryClustersDelegate historyClustersDelegate, Clock clock, TemplateUrlService templateUrlService, SelectionDelegate selectionDelegate, - HistoryClustersMetricsLogger metricsLogger) { + HistoryClustersMetricsLogger metricsLogger, AccessibilityUtil accessibilityUtil) { mHistoryClustersBridge = historyClustersBridge; mLargeIconBridge = largeIconBridge; mModelList = modelList; @@ -138,6 +144,7 @@ private VisitMetadata(ListItem visitListItem, ListItem clusterListItem, mTemplateUrlService = templateUrlService; mSelectionDelegate = selectionDelegate; mMetricsLogger = metricsLogger; + mAccessibilityUtil = accessibilityUtil; PropertyModel toggleModel = new PropertyModel(HistoryClustersItemProperties.ALL_KEYS); mToggleItem = new ListItem(ItemType.TOGGLE, toggleModel); @@ -151,6 +158,18 @@ private VisitMetadata(ListItem visitListItem, ListItem clusterListItem, new PropertyModel(HistoryClustersItemProperties.ALL_KEYS); mClearBrowsingDataItem = new ListItem(ItemType.CLEAR_BROWSING_DATA, clearBrowsingDataModel); mDelegate.shouldShowClearBrowsingDataSupplier().addObserver(show -> ensureHeaders()); + + mIsScrollToLoadDisabled = mAccessibilityUtil.isAccessibilityEnabled() + || AccessibilityUtil.isHardwareKeyboardAttached(mResources.getConfiguration()); + @State + int buttonState = mIsScrollToLoadDisabled ? State.BUTTON : State.LOADING; + PropertyModel moreProgressModel = + new PropertyModel.Builder(HistoryClustersItemProperties.ALL_KEYS) + .with(HistoryClustersItemProperties.PROGRESS_BUTTON_STATE, buttonState) + .with(HistoryClustersItemProperties.CLICK_HANDLER, + (v) -> mPromise.then(this::continueQuery)) + .build(); + mMoreProgressItem = new ListItem(ItemType.MORE_PROGRESS, moreProgressModel); } // SearchDelegate implementation. @@ -168,14 +187,11 @@ public void onEndSearch() { // OnScrollListener implementation @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + if (mIsScrollToLoadDisabled) return; LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); if (layoutManager.findLastVisibleItemPosition() > (mModelList.size() - REMAINING_ITEM_BUFFER_SIZE)) { - mPromise.then(result -> { - if (result.canLoadMore()) { - continueQuery(result.getQuery()); - } - }); + mPromise.then(this::continueQuery); } } @@ -205,11 +221,14 @@ void startQuery(String query) { mPromise = mHistoryClustersBridge.queryClusters(query); mPromise.then(mCallbackController.makeCancelable(this::queryComplete)); + ensureFooter(State.LOADING, true); } - void continueQuery(String query) { - mPromise = mHistoryClustersBridge.loadMoreClusters(query); + void continueQuery(HistoryClustersResult previousResult) { + if (!previousResult.canLoadMore()) return; + mPromise = mHistoryClustersBridge.loadMoreClusters(previousResult.getQuery()); mPromise.then(mCallbackController.makeCancelable(this::queryComplete)); + ensureFooter(State.LOADING, true); } void openHistoryClustersUi(String query) { @@ -349,10 +368,12 @@ private void queryComplete(HistoryClustersResult result) { entry.getValue(), entry.getValue())); } - if (result.canLoadMore() && !result.isContinuation()) { - continueQuery(""); + if (!mIsScrollToLoadDisabled && result.canLoadMore() && !result.isContinuation()) { + continueQuery(result); } + ensureFooter(State.BUTTON, result.canLoadMore()); + return; } @@ -435,6 +456,8 @@ private void queryComplete(HistoryClustersResult result) { clusterModel.set( HistoryClustersItemProperties.LABEL, getTimeString(cluster.getTimestamp())); } + + ensureFooter(State.BUTTON, result.canLoadMore()); } private void resetModel() { @@ -485,6 +508,22 @@ private void ensureHeaders() { } } + private void ensureFooter(@State int buttonState, boolean canLoadMore) { + mMoreProgressItem.model.set( + HistoryClustersItemProperties.PROGRESS_BUTTON_STATE, buttonState); + boolean shouldShow = (buttonState == State.BUTTON && canLoadMore && mIsScrollToLoadDisabled) + || buttonState == State.LOADING; + int currentIndex = mModelList.indexOf(mMoreProgressItem); + boolean showing = currentIndex != -1; + if (showing) { + mModelList.remove(mMoreProgressItem); + } + + if (shouldShow) { + mModelList.add(mMoreProgressItem); + } + } + @VisibleForTesting void onClusterVisitClicked(SelectableItemView view, ClusterVisit clusterVisit) { if (mSelectionDelegate.isSelectionEnabled()) { diff --git a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersViewBinder.java b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersViewBinder.java index fc0e7bcdacea02..edb4da43477afb 100644 --- a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersViewBinder.java +++ b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersViewBinder.java @@ -7,6 +7,7 @@ import android.view.View; import android.view.View.OnClickListener; +import org.chromium.components.browser_ui.widget.MoreProgressButton; import org.chromium.components.browser_ui.widget.selectable_list.SelectableListLayout; import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyModel; @@ -105,4 +106,17 @@ public static void noopBindView( // This view's appearance and behavior are dictated by our parent component, so we // don't manipulate it here. } + + public static void bindMoreProgressView( + PropertyModel propertyModel, View view, PropertyKey key) { + MoreProgressButton button = (MoreProgressButton) view; + if (key == HistoryClustersItemProperties.CLICK_HANDLER) { + button.setOnClickRunnable( + () + -> propertyModel.get(HistoryClustersItemProperties.CLICK_HANDLER) + .onClick(null)); + } else if (key == HistoryClustersItemProperties.PROGRESS_BUTTON_STATE) { + button.setState(propertyModel.get(HistoryClustersItemProperties.PROGRESS_BUTTON_STATE)); + } + } } \ No newline at end of file