Commit ac4d5d15 authored by Shakti Sahu's avatar Shakti Sahu Committed by Commit Bot

Query Tiles : Added MVC

This CL creates MVC structure for the query tiles coordinator. The layout and
styling will be added in later CLs.

Bug: 1060910, 1060913
Change-Id: Icb90403dd6786b269172415bb54b1e79694ed9a0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2091947
Commit-Queue: Shakti Sahu <shaktisahu@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Cr-Commit-Position: refs/heads/master@{#749552}
parent 2880bfa1
......@@ -808,6 +808,7 @@ android_library("chrome_test_java") {
"//chrome/browser/ui/android/appmenu:java",
"//chrome/browser/ui/android/appmenu:test_support_java",
"//chrome/browser/ui/messages/android:java",
"//chrome/browser/upboarding/query_tiles:query_tiles_javatests",
"//chrome/browser/util:java",
"//chrome/test/android:chrome_java_test_support",
"//chrome/test/android/test_trusted_web_activity:test_trusted_web_activity_java",
......
......@@ -37,4 +37,4 @@ public class QueryTileSection {
private void onQueryTilesChanged(boolean hasTiles) {
mQueryTileSectionView.setVisibility(hasTiles ? View.VISIBLE : View.GONE);
}
}
\ No newline at end of file
}
......@@ -26,6 +26,11 @@ public class TileProviderFactory {
return sTileProvider;
}
/** For testing only. */
public static void setTileProviderForTesting(TileProvider provider) {
sTileProvider = provider;
}
@NativeMethods
interface Natives {
TileProvider getForProfile(Profile profile);
......
......@@ -40,11 +40,19 @@ if (is_android) {
"android/java/src/org/chromium/chrome/browser/query_tiles/TileProvider.java",
"android/java/src/org/chromium/chrome/browser/query_tiles/bridges/TileProviderBridge.java",
"android/java/src/org/chromium/chrome/browser/query_tiles/list/QueryTileCoordinator.java",
"android/java/src/org/chromium/chrome/browser/query_tiles/list/TileListModel.java",
"android/java/src/org/chromium/chrome/browser/query_tiles/list/TileListProperties.java",
"android/java/src/org/chromium/chrome/browser/query_tiles/list/TileListPropertyViewBinder.java",
"android/java/src/org/chromium/chrome/browser/query_tiles/list/TileListView.java",
"android/java/src/org/chromium/chrome/browser/query_tiles/list/TileViewHolder.java",
]
deps = [
":chrome_upboarding_query_tiles_java_resources",
"//base:base_java",
"//base:jni_java",
"//third_party/android_deps:androidx_recyclerview_recyclerview_java",
"//ui/android:ui_java",
]
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
......@@ -55,4 +63,28 @@ if (is_android) {
sources = [ "android/java/src/org/chromium/chrome/browser/query_tiles/bridges/TileProviderBridge.java" ]
}
android_resources("chrome_upboarding_query_tiles_java_resources") {
resource_dirs = [ "android/java/res" ]
custom_package = "org.chromium.chrome.query_tiles"
}
android_library("query_tiles_javatests") {
testonly = true
sources = [ "android/javatests/src/org/chromium/chrome/browser/query_tiles/QueryTileSectionTest.java" ]
deps = [
":public_java",
"//base:base_java",
"//base:base_java_test_support",
"//chrome/android:chrome_java",
"//chrome/android:chrome_test_util_java",
"//chrome/test/android:chrome_java_test_support",
"//third_party/espresso:espresso_all_java",
"//third_party/hamcrest:hamcrest_java",
"//third_party/junit",
"//third_party/mockito:mockito_java",
]
}
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="86dp"
android:layout_height="86dp"
android:orientation="vertical">
<org.chromium.ui.widget.ChromeImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:scaleType="centerCrop"
android:importantForAccessibility="no"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
......@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.query_tiles;
import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
......@@ -28,7 +30,8 @@ public class Tile {
public final List<Tile> children;
/** Constructor. */
private Tile(String id, String displayTitle, String accessibilityText, String queryText,
@VisibleForTesting
Tile(String id, String displayTitle, String accessibilityText, String queryText,
List<Tile> children) {
this.id = id;
this.displayTitle = displayTitle;
......
......@@ -5,19 +5,51 @@
package org.chromium.chrome.browser.query_tiles.list;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.View;
import org.chromium.base.Callback;
import org.chromium.chrome.browser.query_tiles.Tile;
import org.chromium.chrome.browser.query_tiles.TileProvider;
/**
* The top level coordinator for the query tiles UI.
*/
public class QueryTileCoordinator {
private final TileListModel mModel;
private final TileListView mView;
private final TileProvider mTileProvider;
private final Callback<Boolean> mVisibilityCallback;
public QueryTileCoordinator(
Context context, TileProvider tileProvider, Callback<Boolean> visibilityCallback) {}
Context context, TileProvider tileProvider, Callback<Boolean> visibilityCallback) {
mTileProvider = tileProvider;
mVisibilityCallback = visibilityCallback;
mModel = new TileListModel();
mView = new TileListView(context, mModel);
mModel.getProperties().set(TileListProperties.CLICK_CALLBACK, this::onQueryTileClicked);
mModel.getProperties().set(TileListProperties.VISUALS_CALLBACK, this::getVisuals);
onQueryTileClicked(null);
}
/** @return The {@link View} that represents this coordinator. */
public View getView() {
return null;
return mView.getView();
}
private void onQueryTileClicked(Tile tile) {
if (tile != null) {
mModel.set(tile.children);
} else {
mTileProvider.getQueryTiles(tiles -> {
mModel.set(tiles);
mVisibilityCallback.onResult(!tiles.isEmpty());
});
}
}
private void getVisuals(Tile tile, Callback<Bitmap> callback) {
mTileProvider.getThumbnail(tile.id, callback);
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.query_tiles.list;
import org.chromium.chrome.browser.query_tiles.Tile;
import org.chromium.ui.modelutil.ListModel;
import org.chromium.ui.modelutil.PropertyModel;
/**
* This model represents the data required to build a list UI around a set of {@link Tile}s.
* This includes (1) a {@link ListModel} implementation and (2) exposing a
* {@link PropertyModel} for shared item properties and general list information.
*/
class TileListModel extends ListModel<Tile> {
private final PropertyModel mListProperties = new PropertyModel(TileListProperties.ALL_KEYS);
/**
* @return A {@link PropertyModel} instance, which is a set of shared properties for the
* list.
*/
public PropertyModel getProperties() {
return mListProperties;
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.query_tiles.list;
import android.graphics.Bitmap;
import org.chromium.base.Callback;
import org.chromium.chrome.browser.query_tiles.Tile;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
/**
* The properties required to build a {@link Tile} which contain two types of properties:
* (1) A set of properties that act directly on the list view itself. (2) A set of
* properties that are effectively shared across all list items like callbacks.
*/
interface TileListProperties {
/** A helper interface to support retrieving {@link Bitmap} asynchronously. */
@FunctionalInterface
interface VisualsProvider {
/**
* @param tile The {@link Tile} to get the {@link Bitmap} for.
* @param callback A {@link Callback} that will be notified on completion.
*/
void getVisuals(Tile tile, Callback<Bitmap> callback);
}
/** The callback to run when a {@link Tile} is clicked on the UI. */
WritableObjectPropertyKey<Callback<Tile>> CLICK_CALLBACK = new WritableObjectPropertyKey<>();
/** The provider to retrieve expensive visuals for a {@link Tile}. */
WritableObjectPropertyKey<VisualsProvider> VISUALS_CALLBACK = new WritableObjectPropertyKey<>();
PropertyKey[] ALL_KEYS = new PropertyKey[] {CLICK_CALLBACK, VISUALS_CALLBACK};
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.query_tiles.list;
import androidx.recyclerview.widget.RecyclerView;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor.ViewBinder;
/**
* Binds the {@link TileListModel} with the {@link TileListView}.
*/
class TileListPropertyViewBinder implements ViewBinder<PropertyModel, RecyclerView, PropertyKey> {
@Override
public void bind(PropertyModel model, RecyclerView view, PropertyKey propertyKey) {}
}
\ No newline at end of file
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.query_tiles.list;
import android.content.Context;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.chromium.ui.modelutil.ForwardingListObservable;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import org.chromium.ui.modelutil.RecyclerViewAdapter;
/**
* The View component of a query tiles. This takes the {@link TileListModel} and creates the
* glue to display it on the screen.
*/
class TileListView {
private final TileListModel mModel;
private final RecyclerView mView;
private final LinearLayoutManager mLayoutManager;
/** Constructor. */
public TileListView(Context context, TileListModel model) {
mModel = model;
mView = new RecyclerView(context);
mView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
mView.setLayoutManager(mLayoutManager);
PropertyModelChangeProcessor.create(
mModel.getProperties(), mView, new TileListPropertyViewBinder());
RecyclerViewAdapter<TileViewHolder, Void> adapter =
new RecyclerViewAdapter<TileViewHolder, Void>(
new ModelChangeProcessor(mModel), TileViewHolder::create);
mView.setAdapter(adapter);
mView.post(adapter::notifyDataSetChanged);
}
/** @return The Android {@link View} representing this widget. */
public View getView() {
return mView;
}
private static class ModelChangeProcessor extends ForwardingListObservable<Void>
implements RecyclerViewAdapter.Delegate<TileViewHolder, Void> {
private final TileListModel mModel;
public ModelChangeProcessor(TileListModel model) {
mModel = model;
model.addObserver(this);
}
@Override
public int getItemCount() {
return mModel.size();
}
@Override
public int getItemViewType(int position) {
return 0;
}
@Override
public void onBindViewHolder(
TileViewHolder viewHolder, int position, @Nullable Void payload) {
viewHolder.bind(mModel.getProperties(), mModel.get(position));
}
@Override
public void onViewRecycled(TileViewHolder viewHolder) {
viewHolder.recycle();
}
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.query_tiles.list;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import org.chromium.chrome.browser.query_tiles.Tile;
import org.chromium.chrome.query_tiles.R;
import org.chromium.ui.modelutil.PropertyModel;
/**
* A {@link ViewHolder} responsible for building and setting properties on the query tile views.
*/
class TileViewHolder extends ViewHolder {
/** Creates an instance of a {@link TileViewHolder}. */
protected TileViewHolder(View itemView) {
super(itemView);
}
/**
* Used as a method reference for ViewHolderFactory.
* @see RecyclerViewAdapter.ViewHolderFactory#createViewHolder
*/
public static TileViewHolder create(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.query_tile_view, parent, false);
return new TileViewHolder(view);
}
/**
* Binds the currently held {@link View} to {@code item}.
* @param properties The shared {@link PropertyModel} all items can access.
* @param tile The {@link ListItem} to visually represent in this {@link ViewHolder}.
*/
public void bind(PropertyModel properties, Tile tile) {
TextView title = itemView.findViewById(R.id.title);
title.setText(tile.displayTitle);
itemView.setOnClickListener(
v -> { properties.get(TileListProperties.CLICK_CALLBACK).onResult(tile); });
final ImageView thumbnail = itemView.findViewById(R.id.thumbnail);
properties.get(TileListProperties.VISUALS_CALLBACK)
.getVisuals(tile, thumbnail::setImageBitmap);
}
/**
* Gives subclasses a chance to free up expensive resources when this {@link ViewHolder} is no
* longer attached to the parent {@link RecyclerView}.
*/
public void recycle() {}
}
specific_include_rules = {
'QueryTileSectionTest\.java': [
"+chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java",
],
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.query_tiles;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import android.graphics.Bitmap;
import android.support.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.Callback;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.util.NewTabPageTestUtils;
import org.chromium.chrome.test.util.browser.Features;
import java.util.ArrayList;
import java.util.List;
/**
* Tests for the query tiles section on new tab page.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class QueryTileSectionTest {
@Rule
public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
@Before
public void setUp() {
TileProviderFactory.setTileProviderForTesting(new TestTileProvider());
mActivityTestRule.startMainActivityOnBlankPage();
mActivityTestRule.loadUrl("chrome-native://newtab/");
Tab tab = mActivityTestRule.getActivity().getActivityTab();
NewTabPageTestUtils.waitForNtpLoaded(tab);
}
@After
public void tearDown() {}
@Test
@SmallTest
@Features.EnableFeatures(ChromeFeatureList.QUERY_TILES)
public void testSimpleTest() throws Exception {
onView(withId(R.id.query_tiles)).check(matches(isDisplayed()));
}
private static class TestTileProvider implements TileProvider {
private List<Tile> mTiles = new ArrayList<>();
private TestTileProvider() {
Tile tile = new Tile("1", "Tile 1", "Tile 1", "Tile 1", null);
mTiles.add(tile);
}
@Override
public void getQueryTiles(Callback<List<Tile>> callback) {
callback.onResult(mTiles);
}
@Override
public void getThumbnail(String id, Callback<Bitmap> callback) {
callback.onResult(null);
}
}
}
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