mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-13 05:15:45 +00:00
Bug 882185 - Restore opt-in search suggestions UI. r=lucasr
This commit is contained in:
parent
36985635ac
commit
d6c21b326d
@ -453,6 +453,7 @@ RES_LAYOUT = \
|
||||
res/layout/autocomplete_list_item.xml \
|
||||
res/layout/bookmark_edit.xml \
|
||||
res/layout/bookmark_folder_row.xml \
|
||||
res/layout/browser_search.xml \
|
||||
res/layout/browser_toolbar.xml \
|
||||
res/layout/datetime_picker.xml \
|
||||
res/layout/doorhanger.xml \
|
||||
@ -467,6 +468,7 @@ RES_LAYOUT = \
|
||||
res/layout/home_last_tabs_page.xml \
|
||||
res/layout/home_list_with_title.xml \
|
||||
res/layout/home_search_item_row.xml \
|
||||
res/layout/home_suggestion_prompt.xml \
|
||||
res/layout/home_visited_page.xml \
|
||||
res/layout/web_app.xml \
|
||||
res/layout/launch_app_list.xml \
|
||||
|
@ -8,6 +8,7 @@ package org.mozilla.gecko.home;
|
||||
import org.mozilla.gecko.AutocompleteHandler;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.PrefsHelper;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
@ -37,9 +38,17 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewStub;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -71,17 +80,23 @@ public class BrowserSearch extends HomeFragment
|
||||
// for an autocomplete result
|
||||
private static final int MAX_AUTOCOMPLETE_SEARCH = 20;
|
||||
|
||||
// Duration for fade-in animation
|
||||
private static final int ANIMATION_DURATION = 250;
|
||||
|
||||
// Holds the current search term to use in the query
|
||||
private String mSearchTerm;
|
||||
|
||||
// Adapter for the list of search results
|
||||
private SearchAdapter mAdapter;
|
||||
|
||||
// The view shown by the fragment.
|
||||
// The view shown by the fragment
|
||||
private LinearLayout mView;
|
||||
|
||||
// The list showing search results
|
||||
private ListView mList;
|
||||
|
||||
// Client that performs search suggestion queries
|
||||
private SuggestClient mSuggestClient;
|
||||
private volatile SuggestClient mSuggestClient;
|
||||
|
||||
// List of search engines from gecko
|
||||
private ArrayList<SearchEngine> mSearchEngines;
|
||||
@ -110,6 +125,12 @@ public class BrowserSearch extends HomeFragment
|
||||
// On edit suggestion listener
|
||||
private OnEditSuggestionListener mEditSuggestionListener;
|
||||
|
||||
// Whether the suggestions will fade in when shown
|
||||
private boolean mAnimateSuggestions;
|
||||
|
||||
// Opt-in prompt view for search suggestions
|
||||
private View mSuggestionsOptInPrompt;
|
||||
|
||||
public interface OnSearchListener {
|
||||
public void onSearch(String engineId, String text);
|
||||
}
|
||||
@ -126,26 +147,6 @@ public class BrowserSearch extends HomeFragment
|
||||
mSearchTerm = "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
registerEventListener("SearchEngines:Data");
|
||||
|
||||
// The search engines list is reused beyond the life-cycle of
|
||||
// this fragment.
|
||||
if (mSearchEngines == null) {
|
||||
mSearchEngines = new ArrayList<SearchEngine>();
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:Get", null));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
unregisterEventListener("SearchEngines:Data");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
@ -189,8 +190,23 @@ public class BrowserSearch extends HomeFragment
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
// All list views are styled to look the same with a global activity theme.
|
||||
// If the style of the list changes, inflate it from an XML.
|
||||
mList = new HomeListView(container.getContext());
|
||||
return mList;
|
||||
mView = (LinearLayout) inflater.inflate(R.layout.browser_search, container, false);
|
||||
mList = (ListView) mView.findViewById(R.id.home_list_view);
|
||||
|
||||
return mView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
unregisterEventListener("SearchEngines:Data");
|
||||
|
||||
mView = null;
|
||||
mList = null;
|
||||
mSearchEngines = null;
|
||||
mSuggestionsOptInPrompt = null;
|
||||
mSuggestClient = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -211,6 +227,10 @@ public class BrowserSearch extends HomeFragment
|
||||
});
|
||||
|
||||
registerForContextMenu(mList);
|
||||
registerEventListener("SearchEngines:Data");
|
||||
|
||||
mSearchEngines = new ArrayList<SearchEngine>();
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:Get", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -312,11 +332,20 @@ public class BrowserSearch extends HomeFragment
|
||||
}
|
||||
|
||||
private void setSuggestions(ArrayList<String> suggestions) {
|
||||
mSearchEngines.get(0).suggestions = suggestions;
|
||||
mAdapter.notifyDataSetChanged();
|
||||
if (mSearchEngines != null) {
|
||||
mSearchEngines.get(0).suggestions = suggestions;
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void setSearchEngines(JSONObject data) {
|
||||
// This method is called via a Runnable posted from the Gecko thread, so
|
||||
// it's possible the fragment and/or its view has been destroyed by the
|
||||
// time we get here. If so, just abort.
|
||||
if (mView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final JSONObject suggest = data.getJSONObject("suggest");
|
||||
final String suggestEngine = suggest.optString("engine", null);
|
||||
@ -358,7 +387,11 @@ public class BrowserSearch extends HomeFragment
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// FIXME: restore suggestion opt-in UI
|
||||
// Show suggestions opt-in prompt only if user hasn't been prompted
|
||||
// and we're not on a private browsing tab.
|
||||
if (!suggestionsPrompted && mSuggestClient != null) {
|
||||
showSuggestionsOptIn();
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error getting search engine JSON", e);
|
||||
}
|
||||
@ -366,6 +399,107 @@ public class BrowserSearch extends HomeFragment
|
||||
filterSuggestions();
|
||||
}
|
||||
|
||||
private void showSuggestionsOptIn() {
|
||||
mSuggestionsOptInPrompt = ((ViewStub) mView.findViewById(R.id.suggestions_opt_in_prompt)).inflate();
|
||||
|
||||
TextView promptText = (TextView) mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_title);
|
||||
promptText.setText(getResources().getString(R.string.suggestions_prompt, mSearchEngines.get(0).name));
|
||||
|
||||
final View yesButton = mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_yes);
|
||||
final View noButton = mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_no);
|
||||
|
||||
final OnClickListener listener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Prevent the buttons from being clicked multiple times (bug 816902)
|
||||
yesButton.setOnClickListener(null);
|
||||
noButton.setOnClickListener(null);
|
||||
|
||||
setSuggestionsEnabled(v == yesButton);
|
||||
}
|
||||
};
|
||||
|
||||
yesButton.setOnClickListener(listener);
|
||||
noButton.setOnClickListener(listener);
|
||||
}
|
||||
|
||||
private void setSuggestionsEnabled(final boolean enabled) {
|
||||
// Clicking the yes/no buttons quickly can cause the click events be
|
||||
// queued before the listeners are removed above, so it's possible
|
||||
// setSuggestionsEnabled() can be called twice. mSuggestionsOptInPrompt
|
||||
// can be null if this happens (bug 828480).
|
||||
if (mSuggestionsOptInPrompt == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make suggestions appear immediately after the user opts in
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SuggestClient client = mSuggestClient;
|
||||
if (client != null) {
|
||||
client.query(mSearchTerm);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Pref observer in gecko will also set prompted = true
|
||||
PrefsHelper.setPref("browser.search.suggest.enabled", enabled);
|
||||
|
||||
TranslateAnimation slideAnimation = new TranslateAnimation(0, mSuggestionsOptInPrompt.getWidth(), 0, 0);
|
||||
slideAnimation.setDuration(ANIMATION_DURATION);
|
||||
slideAnimation.setInterpolator(new AccelerateInterpolator());
|
||||
slideAnimation.setFillAfter(true);
|
||||
final View prompt = mSuggestionsOptInPrompt.findViewById(R.id.prompt);
|
||||
|
||||
TranslateAnimation shrinkAnimation = new TranslateAnimation(0, 0, 0, -1 * mSuggestionsOptInPrompt.getHeight());
|
||||
shrinkAnimation.setDuration(ANIMATION_DURATION);
|
||||
shrinkAnimation.setFillAfter(true);
|
||||
shrinkAnimation.setStartOffset(slideAnimation.getDuration());
|
||||
shrinkAnimation.setAnimationListener(new Animation.AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation a) {
|
||||
// Increase the height of the view so a gap isn't shown during animation
|
||||
mView.getLayoutParams().height = mView.getHeight() +
|
||||
mSuggestionsOptInPrompt.getHeight();
|
||||
mView.requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation a) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation a) {
|
||||
// Removing the view immediately results in a NPE in
|
||||
// dispatchDraw(), possibly because this callback executes
|
||||
// before drawing is finished. Posting this as a Runnable fixes
|
||||
// the issue.
|
||||
mView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mView.removeView(mSuggestionsOptInPrompt);
|
||||
mList.clearAnimation();
|
||||
mSuggestionsOptInPrompt = null;
|
||||
|
||||
if (enabled) {
|
||||
// Reset the view height
|
||||
mView.getLayoutParams().height = LayoutParams.MATCH_PARENT;
|
||||
|
||||
mSuggestionsEnabled = enabled;
|
||||
mAnimateSuggestions = true;
|
||||
mAdapter.notifyDataSetChanged();
|
||||
filterSuggestions();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
prompt.startAnimation(slideAnimation);
|
||||
mSuggestionsOptInPrompt.startAnimation(shrinkAnimation);
|
||||
mList.startAnimation(shrinkAnimation);
|
||||
}
|
||||
|
||||
private void registerEventListener(String eventName) {
|
||||
GeckoAppShell.registerEventListener(eventName, this);
|
||||
}
|
||||
@ -528,7 +662,12 @@ public class BrowserSearch extends HomeFragment
|
||||
row.setSearchTerm(mSearchTerm);
|
||||
|
||||
final SearchEngine engine = mSearchEngines.get(getEngineIndex(position));
|
||||
row.updateFromSearchEngine(engine);
|
||||
final boolean animate = (mAnimateSuggestions && engine.suggestions.size() > 0);
|
||||
row.updateFromSearchEngine(engine, animate);
|
||||
if (animate) {
|
||||
// Only animate suggestions the first time they are shown
|
||||
mAnimateSuggestions = false;
|
||||
}
|
||||
|
||||
return row;
|
||||
} else {
|
||||
@ -633,6 +772,9 @@ public class BrowserSearch extends HomeFragment
|
||||
private class SuggestionLoaderCallbacks implements LoaderCallbacks<ArrayList<String>> {
|
||||
@Override
|
||||
public Loader<ArrayList<String>> onCreateLoader(int id, Bundle args) {
|
||||
// mSuggestClient is set to null in onDestroyView(), so using it
|
||||
// safely here relies on the fact that onCreateLoader() is called
|
||||
// synchronously in restartLoader().
|
||||
return new SuggestionAsyncLoader(getActivity(), mSuggestClient, mSearchTerm);
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,6 @@ package org.mozilla.gecko.home;
|
||||
import org.mozilla.gecko.AnimatedHeightLayout;
|
||||
import org.mozilla.gecko.FlowLayout;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.home.BrowserSearch.OnEditSuggestionListener;
|
||||
import org.mozilla.gecko.home.BrowserSearch.OnSearchListener;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
@ -17,17 +15,17 @@ import org.mozilla.gecko.util.StringUtils;
|
||||
import org.mozilla.gecko.widget.FaviconView;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnLongClickListener;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
class SearchEngineRow extends AnimatedHeightLayout {
|
||||
// Duration for fade-in animation
|
||||
private static final int ANIMATION_DURATION = 250;
|
||||
|
||||
// Inner views
|
||||
private final FlowLayout mSuggestionView;
|
||||
@ -135,7 +133,7 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
||||
mEditSuggestionListener = listener;
|
||||
}
|
||||
|
||||
public void updateFromSearchEngine(SearchEngine searchEngine) {
|
||||
public void updateFromSearchEngine(SearchEngine searchEngine, boolean animate) {
|
||||
// Update search engine reference
|
||||
mSearchEngine = searchEngine;
|
||||
|
||||
@ -168,6 +166,13 @@ class SearchEngineRow extends AnimatedHeightLayout {
|
||||
|
||||
final String suggestion = mSearchEngine.suggestions.get(i);
|
||||
setSuggestionOnView(suggestionItem, suggestion);
|
||||
|
||||
if (animate) {
|
||||
AlphaAnimation anim = new AlphaAnimation(0, 1);
|
||||
anim.setDuration(ANIMATION_DURATION);
|
||||
anim.setStartOffset(i * ANIMATION_DURATION);
|
||||
suggestionItem.startAnimation(anim);
|
||||
}
|
||||
}
|
||||
|
||||
// Hide extra suggestions that have been recycled
|
||||
|
22
mobile/android/base/resources/layout/browser_search.xml
Normal file
22
mobile/android/base/resources/layout/browser_search.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ViewStub android:id="@+id/suggestions_opt_in_prompt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout="@layout/home_suggestion_prompt" />
|
||||
|
||||
<org.mozilla.gecko.home.HomeListView
|
||||
android:id="@+id/home_list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/url_bar_bg">
|
||||
|
||||
<LinearLayout android:id="@+id/prompt"
|
||||
android:focusable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:minHeight="@dimen/search_row_height"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="10dip">
|
||||
|
||||
<TextView android:id="@+id/suggestions_prompt_title"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:textColor="@color/url_bar_title"
|
||||
android:layout_marginLeft="6dip"
|
||||
android:textSize="14sp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView android:id="@+id/suggestions_prompt_yes"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginLeft="15dip"
|
||||
android:background="@drawable/suggestion_selector"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:focusable="true"
|
||||
android:text="@string/button_yes" />
|
||||
|
||||
<TextView android:id="@+id/suggestions_prompt_no"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginLeft="6dip"
|
||||
android:background="@drawable/suggestion_selector"
|
||||
android:nextFocusRight="@+id/suggestions_prompt_no"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:focusable="true"
|
||||
android:text="@string/button_no" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
Loading…
Reference in New Issue
Block a user