Bug 1046485 - Move search bar out of SearchFragment. r=eedens

This commit is contained in:
Margaret Leibovic 2014-08-05 14:09:27 -07:00
parent 9c065af1ff
commit fe0fcb8e82
12 changed files with 165 additions and 201 deletions

View File

@ -10,6 +10,13 @@ import android.graphics.Rect;
* Allows fragments to pass a search event to the main activity.
*/
public interface AcceptsSearchQuery {
/**
* Shows search suggestions.
* @param query
*/
void onSuggest(String query);
/**
* Starts a search.
*
@ -30,6 +37,5 @@ public interface AcceptsSearchQuery {
*/
public interface SuggestionAnimation {
public Rect getStartBounds();
public void onAnimationEnd();
}
}

View File

@ -7,9 +7,9 @@ package org.mozilla.search;
import android.content.AsyncQueryHandler;
import android.content.ContentValues;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.text.TextUtils;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
@ -22,27 +22,40 @@ import com.nineoldandroids.animation.ObjectAnimator;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
import org.mozilla.search.autocomplete.SearchFragment;
import org.mozilla.search.autocomplete.ClearableEditText;
import org.mozilla.search.autocomplete.SuggestionsFragment;
/**
* The main entrance for the Android search intent.
* <p/>
* State management is delegated to child fragments. Fragments communicate
* with each other by passing messages through this activity. The only message passing right
* now, the only message passing occurs when a user wants to submit a search query. That
* passes through the onSearch method here.
* with each other by passing messages through this activity.
*/
public class MainActivity extends FragmentActivity implements AcceptsSearchQuery {
enum State {
START,
static enum SearchState {
PRESEARCH,
POSTSEARCH
}
private State state = State.START;
static enum EditState {
WAITING,
EDITING
}
private SearchState searchState;
private EditState editState;
private AsyncQueryHandler queryHandler;
// Main views in layout.
private ClearableEditText editText;
private View preSearch;
private View postSearch;
private View suggestionsContainer;
private SuggestionsFragment suggestionsFragment;
private static final int SUGGESTION_TRANSITION_DURATION = 300;
private static final Interpolator SUGGESTION_TRANSITION_INTERPOLATOR =
new AccelerateDecelerateInterpolator();
@ -65,6 +78,47 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
queryHandler = new AsyncQueryHandler(getContentResolver()) {};
editText = (ClearableEditText) findViewById(R.id.search_edit_text);
editText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setEditState(EditState.EDITING);
}
});
editText.setTextListener(new ClearableEditText.TextListener() {
@Override
public void onChange(String text) {
// Only load suggestions if we're in edit mode.
if (editState == EditState.EDITING) {
suggestionsFragment.loadSuggestions(text);
}
}
@Override
public void onSubmit(String text) {
// Don't submit an empty query.
final String trimmedQuery = text.trim();
if (!TextUtils.isEmpty(trimmedQuery)) {
onSearch(trimmedQuery);
}
}
});
preSearch = findViewById(R.id.presearch);
postSearch = findViewById(R.id.postsearch);
suggestionsContainer = findViewById(R.id.suggestions_container);
suggestionsFragment = (SuggestionsFragment) getSupportFragmentManager().findFragmentById(R.id.suggestions);
// Dismiss edit mode when the user taps outside of the suggestions.
findViewById(R.id.suggestions_container).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setEditState(EditState.WAITING);
}
});
animationText = (TextView) findViewById(R.id.animation_text);
animationCard = findViewById(R.id.animation_card);
@ -77,7 +131,11 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
protected void onDestroy() {
super.onDestroy();
queryHandler = null;
editText = null;
preSearch = null;
postSearch = null;
suggestionsFragment = null;
suggestionsContainer = null;
animationText = null;
animationCard = null;
}
@ -97,8 +155,14 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
@Override
protected void onResume() {
super.onResume();
// When the app launches, make sure we're in presearch *always*
startPresearch();
setSearchState(SearchState.PRESEARCH);
}
@Override
public void onSuggest(String query) {
editText.setText(query);
}
@Override
@ -118,7 +182,8 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
animateSuggestion(query, suggestionAnimation);
} else {
// Otherwise immediately switch to the results view.
startPostsearch();
setEditState(EditState.WAITING);
setSearchState(SearchState.POSTSEARCH);
}
}
@ -164,15 +229,10 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
@Override
public void onAnimationEnd(Animator animation) {
// This is crappy, but right now we need to make sure we call this before
// setSearchTerm so that we don't kick off a suggestion request.
suggestionAnimation.onAnimationEnd();
setEditState(EditState.WAITING);
setSearchState(SearchState.POSTSEARCH);
// TODO: Find a way to do this without needing to access SearchFragment.
final SearchFragment searchFragment = ((SearchFragment) getSupportFragmentManager().findFragmentById(R.id.search_fragment));
searchFragment.setSearchTerm(query);
startPostsearch();
editText.setText(query);
// We need to manually clear the animation for the views to be hidden on gingerbread.
animationText.clearAnimation();
@ -197,26 +257,32 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
set.start();
}
private void startPresearch() {
if (state != State.PRESEARCH) {
state = State.PRESEARCH;
findViewById(R.id.postsearch).setVisibility(View.INVISIBLE);
findViewById(R.id.presearch).setVisibility(View.VISIBLE);
private void setEditState(EditState editState) {
if (this.editState == editState) {
return;
}
this.editState = editState;
editText.setActive(editState == EditState.EDITING);
suggestionsContainer.setVisibility(editState == EditState.EDITING ? View.VISIBLE : View.INVISIBLE);
}
private void startPostsearch() {
if (state != State.POSTSEARCH) {
state = State.POSTSEARCH;
findViewById(R.id.presearch).setVisibility(View.INVISIBLE);
findViewById(R.id.postsearch).setVisibility(View.VISIBLE);
private void setSearchState(SearchState searchState) {
if (this.searchState == searchState) {
return;
}
this.searchState = searchState;
preSearch.setVisibility(searchState == SearchState.PRESEARCH ? View.VISIBLE : View.INVISIBLE);
postSearch.setVisibility(searchState == SearchState.POSTSEARCH ? View.VISIBLE : View.INVISIBLE);
}
@Override
public void onBackPressed() {
if (state == State.POSTSEARCH) {
startPresearch();
if (editState == EditState.EDITING) {
setEditState(EditState.WAITING);
} else if (searchState == SearchState.POSTSEARCH) {
setSearchState(SearchState.PRESEARCH);
} else {
super.onBackPressed();
}

View File

@ -110,10 +110,6 @@ public class PreSearchFragment extends Fragment {
public Rect getStartBounds() {
return startBounds;
}
@Override
public void onAnimationEnd() {
}
});
}
}

View File

@ -1,15 +0,0 @@
/* 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/. */
package org.mozilla.search.autocomplete;
/**
* Allows rows to pass a "jump" event to the parent fragment.
* <p/>
* A jump event is when a user selects a suggestion, but they'd like to continue
* searching. Right now, the UI uses an arrow that points up and to the left.
*/
interface AcceptsJumpTaps {
public void onJumpTap(String suggestion);
}

View File

@ -11,8 +11,9 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import org.mozilla.search.AcceptsSearchQuery;
import org.mozilla.search.R;
import org.mozilla.search.autocomplete.SearchFragment.Suggestion;
import org.mozilla.search.autocomplete.SuggestionsFragment.Suggestion;
import java.util.List;
@ -21,15 +22,20 @@ import java.util.List;
*/
class AutoCompleteAdapter extends ArrayAdapter<Suggestion> {
private final AcceptsJumpTaps acceptsJumpTaps;
private final AcceptsSearchQuery searchListener;
private final LayoutInflater inflater;
public AutoCompleteAdapter(Context context, AcceptsJumpTaps acceptsJumpTaps) {
public AutoCompleteAdapter(Context context) {
// Uses '0' for the template id since we are overriding getView
// and supplying our own view.
super(context, 0);
this.acceptsJumpTaps = acceptsJumpTaps;
if (context instanceof AcceptsSearchQuery) {
searchListener = (AcceptsSearchQuery) context;
} else {
throw new ClassCastException(context.toString() + " must implement AcceptsSearchQuery.");
}
// Disable notifying on change. We will notify ourselves in update.
setNotifyOnChange(false);
@ -40,7 +46,7 @@ class AutoCompleteAdapter extends ArrayAdapter<Suggestion> {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.search_auto_complete_row, null);
convertView = inflater.inflate(R.layout.search_suggestions_row, null);
}
final Suggestion suggestion = getItem(position);
@ -52,7 +58,7 @@ class AutoCompleteAdapter extends ArrayAdapter<Suggestion> {
jumpButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
acceptsJumpTaps.onJumpTap(suggestion.value);
searchListener.onSuggest(suggestion.value);
}
});

View File

@ -13,7 +13,6 @@ import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.View;
@ -32,12 +31,11 @@ import java.util.ArrayList;
import java.util.List;
/**
* A fragment to handle autocomplete. Its interface with the outside
* world should be very very limited.
* A fragment to show search suggestions.
* <p/>
* TODO: Add more search providers (other than the dictionary)
*/
public class SearchFragment extends Fragment implements AcceptsJumpTaps {
public class SuggestionsFragment extends Fragment {
private static final int LOADER_ID_SUGGESTION = 0;
private static final String KEY_SEARCH_TERM = "search_term";
@ -54,18 +52,9 @@ public class SearchFragment extends Fragment implements AcceptsJumpTaps {
private AutoCompleteAdapter autoCompleteAdapter;
private View mainView;
private ClearableEditText editText;
private ListView suggestionDropdown;
private State state = State.WAITING;
private static enum State {
WAITING, // The user is doing something else in the app.
RUNNING // The user is in search mode.
}
public SearchFragment() {
public SuggestionsFragment() {
// Required empty public constructor
}
@ -86,7 +75,7 @@ public class SearchFragment extends Fragment implements AcceptsJumpTaps {
suggestClient = new SuggestClient(activity, template, SUGGESTION_TIMEOUT, Constants.SUGGESTION_MAX);
suggestionLoaderCallbacks = new SuggestionLoaderCallbacks();
autoCompleteAdapter = new AutoCompleteAdapter(activity, this);
autoCompleteAdapter = new AutoCompleteAdapter(activity);
}
@Override
@ -102,51 +91,7 @@ public class SearchFragment extends Fragment implements AcceptsJumpTaps {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mainView = inflater.inflate(R.layout.search_auto_complete, container, false);
// Intercept clicks on the main view to deactivate the search bar.
mainView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
transitionToWaiting();
}
});
// Disable the click listener while the fragment is State.WAITING.
// We can't do this in the layout file because the setOnClickListener
// implementation calls setClickable(true).
mainView.setClickable(false);
editText = (ClearableEditText) mainView.findViewById(R.id.auto_complete_edit_text);
editText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
transitionToRunning();
}
});
editText.setTextListener(new ClearableEditText.TextListener() {
@Override
public void onChange(String text) {
if (state == State.RUNNING) {
final Bundle args = new Bundle();
args.putString(KEY_SEARCH_TERM, text);
getLoaderManager().restartLoader(LOADER_ID_SUGGESTION, args, suggestionLoaderCallbacks);
}
}
@Override
public void onSubmit(String text) {
// Don't submit an empty query.
final String trimmedQuery = text.trim();
if (!TextUtils.isEmpty(trimmedQuery)) {
transitionToWaiting();
searchListener.onSearch(trimmedQuery);
}
}
});
suggestionDropdown = (ListView) mainView.findViewById(R.id.auto_complete_dropdown);
suggestionDropdown = (ListView) inflater.inflate(R.layout.search_sugestions, container, false);
suggestionDropdown.setAdapter(autoCompleteAdapter);
// Attach listener for tapping on a suggestion.
@ -166,24 +111,17 @@ public class SearchFragment extends Fragment implements AcceptsJumpTaps {
public Rect getStartBounds() {
return startBounds;
}
@Override
public void onAnimationEnd() {
transitionToWaiting();
}
});
}
});
return mainView;
return suggestionDropdown;
}
@Override
public void onDestroyView() {
super.onDestroyView();
editText = null;
if (null != suggestionDropdown) {
suggestionDropdown.setOnItemClickListener(null);
suggestionDropdown.setAdapter(null);
@ -191,41 +129,10 @@ public class SearchFragment extends Fragment implements AcceptsJumpTaps {
}
}
@Override
public void onJumpTap(String suggestion) {
setSearchTerm(suggestion);
}
/**
* Sets the search term in the search bar. If the SearchFragment is
* in State.RUNNING, this will also update the search suggestions.
*
* @param searchTerm
*/
public void setSearchTerm(String searchTerm) {
editText.setText(searchTerm);
}
private void transitionToWaiting() {
if (state == State.WAITING) {
return;
}
state = State.WAITING;
mainView.setClickable(false);
editText.setActive(false);
suggestionDropdown.setVisibility(View.GONE);
}
private void transitionToRunning() {
if (state == State.RUNNING) {
return;
}
state = State.RUNNING;
mainView.setClickable(true);
editText.setActive(true);
suggestionDropdown.setVisibility(View.VISIBLE);
public void loadSuggestions(String query) {
final Bundle args = new Bundle();
args.putString(KEY_SEARCH_TERM, query);
getLoaderManager().restartLoader(LOADER_ID_SUGGESTION, args, suggestionLoaderCallbacks);
}
public static class Suggestion {

View File

@ -5,11 +5,18 @@
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<org.mozilla.search.autocomplete.ClearableEditText
android:id="@+id/search_edit_text"
android:layout_width="match_parent"
android:layout_height="@dimen/search_bar_height"
android:paddingTop="@dimen/search_bar_padding_y"
android:paddingBottom="@dimen/search_bar_padding_y"
android:paddingLeft="@dimen/card_background_padding_x"
android:paddingRight="@dimen/card_background_padding_x"
android:layout_gravity="top"/>
<fragment
android:id="@+id/postsearch"
android:name="org.mozilla.search.PostSearchFragment"
@ -26,12 +33,22 @@
android:layout_marginTop="@dimen/search_bar_height"
android:layout_gravity="top"/>
<fragment
android:id="@+id/search_fragment"
android:name="org.mozilla.search.autocomplete.SearchFragment"
<LinearLayout
android:id="@+id/suggestions_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top"/>
android:orientation="vertical"
android:layout_marginTop="@dimen/search_bar_height"
android:visibility="invisible"
android:layout_gravity="top">
<fragment
android:id="@+id/suggestions"
android:name="org.mozilla.search.autocomplete.SuggestionsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<View
android:id="@+id/animation_card"

View File

@ -1,32 +0,0 @@
<!-- 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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".autocomplete.AutoCompleteFragment">
<org.mozilla.search.autocomplete.ClearableEditText
android:id="@+id/auto_complete_edit_text"
android:layout_width="match_parent"
android:layout_height="@dimen/search_bar_height"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="@dimen/card_background_padding_x"
android:paddingRight="@dimen/card_background_padding_x"/>
<ListView
android:id="@+id/auto_complete_dropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/global_background_color"
android:divider="@null"
android:dividerHeight="0dp"
android:listSelector="@android:color/transparent"
android:visibility="gone"/>
</LinearLayout>

View File

@ -0,0 +1,12 @@
<!-- 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/. -->
<ListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/global_background_color"
android:divider="@null"
android:dividerHeight="0dp"
android:listSelector="@android:color/transparent"/>

View File

@ -23,6 +23,8 @@
<dimen name="card_padding_x">38dp</dimen>
<dimen name="card_padding_y">23dp</dimen>
<dimen name="search_bar_padding_y">10dp</dimen>
<!-- Widget Buttons -->
<dimen name="widget_header_height">70dp</dimen>
<dimen name="widget_button_offset">-50dp</dimen>

View File

@ -6,11 +6,10 @@
search_activity_sources = [
'java/org/mozilla/search/AcceptsSearchQuery.java',
'java/org/mozilla/search/autocomplete/AcceptsJumpTaps.java',
'java/org/mozilla/search/autocomplete/AutoCompleteAdapter.java',
'java/org/mozilla/search/autocomplete/ClearableEditText.java',
'java/org/mozilla/search/autocomplete/SearchFragment.java',
'java/org/mozilla/search/autocomplete/SuggestClient.java',
'java/org/mozilla/search/autocomplete/SuggestionsFragment.java',
'java/org/mozilla/search/Constants.java',
'java/org/mozilla/search/MainActivity.java',
'java/org/mozilla/search/PostSearchFragment.java',