Bug 1041026 - Move search bar logic into custom view. r=eedens

This commit is contained in:
Margaret Leibovic 2014-07-22 17:02:20 -07:00
parent 131ef1ed4e
commit c8e8a04188
6 changed files with 203 additions and 127 deletions

View File

@ -13,6 +13,7 @@ import android.view.View;
import org.mozilla.gecko.db.BrowserContract.SearchHistory; import org.mozilla.gecko.db.BrowserContract.SearchHistory;
import org.mozilla.search.autocomplete.AcceptsSearchQuery; import org.mozilla.search.autocomplete.AcceptsSearchQuery;
import org.mozilla.search.autocomplete.SearchFragment;
/** /**
* The main entrance for the Android search intent. * The main entrance for the Android search intent.
@ -58,8 +59,12 @@ public class MainActivity extends FragmentActivity implements AcceptsSearchQuery
public void onSearch(String query) { public void onSearch(String query) {
startPostsearch(); startPostsearch();
storeQuery(query); storeQuery(query);
((PostSearchFragment) getSupportFragmentManager().findFragmentById(R.id.postsearch)) ((PostSearchFragment) getSupportFragmentManager().findFragmentById(R.id.postsearch))
.setUrl("https://search.yahoo.com/search?p=" + Uri.encode(query)); .setUrl("https://search.yahoo.com/search?p=" + Uri.encode(query));
((SearchFragment) getSupportFragmentManager().findFragmentById(R.id.search_fragment))
.setSearchTerm(query);
} }
private void startPresearch() { private void startPresearch() {

View File

@ -0,0 +1,123 @@
/* 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;
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;
import org.mozilla.search.R;
public class ClearableEditText extends FrameLayout {
private EditText editText;
private Button clearButton;
private InputMethodManager inputMethodManager;
private TextListener listener;
private boolean active;
public interface TextListener {
public void onChange(String text);
public void onSubmit(String text);
}
public ClearableEditText(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.clearable_edit_text, this);
editText = (EditText) findViewById(R.id.edit_text);
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (listener != null) {
listener.onChange(s.toString());
}
}
});
// Attach a listener for the "search" key on the keyboard.
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (listener != null && actionId == EditorInfo.IME_ACTION_SEARCH) {
listener.onSubmit(v.getText().toString());
return true;
}
return false;
}
});
clearButton = (Button) findViewById(R.id.clear_button);
clearButton.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
editText.setText("");
}
});
inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
}
public void setText(String text) {
editText.setText(text);
// Move cursor to end of search input.
editText.setSelection(text.length());
}
public void setActive(boolean active) {
if (this.active == active) {
return;
}
this.active = active;
clearButton.setVisibility(active ? View.VISIBLE : View.GONE);
editText.setFocusable(active);
editText.setFocusableInTouchMode(active);
if (active) {
editText.requestFocus();
inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
} else {
editText.clearFocus();
inputMethodManager.hideSoftInputFromWindow(editText.getWindowToken(), 0);
}
}
public void setTextListener(TextListener listener) {
this.listener = listener;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
// When the view is active, pass touch events to child views.
// Otherwise, intercept touch events to allow click listeners on the view to
// fire no matter where the user clicks.
return !active;
}
}

View File

@ -4,7 +4,6 @@
package org.mozilla.search.autocomplete; package org.mozilla.search.autocomplete;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
@ -12,21 +11,13 @@ import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.text.Editable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView;
import org.mozilla.search.R; import org.mozilla.search.R;
@ -39,8 +30,7 @@ import java.util.List;
* <p/> * <p/>
* TODO: Add more search providers (other than the dictionary) * TODO: Add more search providers (other than the dictionary)
*/ */
public class SearchFragment extends Fragment public class SearchFragment extends Fragment implements AcceptsJumpTaps {
implements TextView.OnEditorActionListener, AcceptsJumpTaps {
private static final int LOADER_ID_SUGGESTION = 0; private static final int LOADER_ID_SUGGESTION = 0;
private static final String KEY_SEARCH_TERM = "search_term"; private static final String KEY_SEARCH_TERM = "search_term";
@ -54,16 +44,14 @@ public class SearchFragment extends Fragment
// Color of search term match in search suggestion // Color of search term match in search suggestion
private static final int SUGGESTION_HIGHLIGHT_COLOR = 0xFF999999; private static final int SUGGESTION_HIGHLIGHT_COLOR = 0xFF999999;
private AcceptsSearchQuery searchListener;
private SuggestClient suggestClient; private SuggestClient suggestClient;
private SuggestionLoaderCallbacks suggestionLoaderCallbacks; private SuggestionLoaderCallbacks suggestionLoaderCallbacks;
private InputMethodManager inputMethodManager;
private AutoCompleteAdapter autoCompleteAdapter; private AutoCompleteAdapter autoCompleteAdapter;
private View mainView; private View mainView;
private View searchBar; private ClearableEditText editText;
private EditText editText;
private Button clearButton;
private ListView suggestionDropdown; private ListView suggestionDropdown;
private State state = State.WAITING; private State state = State.WAITING;
@ -81,6 +69,12 @@ public class SearchFragment extends Fragment
public void onAttach(Activity activity) { public void onAttach(Activity activity) {
super.onAttach(activity); super.onAttach(activity);
if (activity instanceof AcceptsSearchQuery) {
searchListener = (AcceptsSearchQuery) activity;
} else {
throw new ClassCastException(activity.toString() + " must implement AcceptsSearchQuery.");
}
// TODO: Don't hard-code this template string (bug 1039758) // TODO: Don't hard-code this template string (bug 1039758)
final String template = "https://search.yahoo.com/sugg/ff?" + final String template = "https://search.yahoo.com/sugg/ff?" +
"output=fxjson&appid=ffm&command=__searchTerms__&nresults=" + SUGGESTION_MAX; "output=fxjson&appid=ffm&command=__searchTerms__&nresults=" + SUGGESTION_MAX;
@ -88,7 +82,6 @@ public class SearchFragment extends Fragment
suggestClient = new SuggestClient(activity, template, SUGGESTION_TIMEOUT, SUGGESTION_MAX); suggestClient = new SuggestClient(activity, template, SUGGESTION_TIMEOUT, SUGGESTION_MAX);
suggestionLoaderCallbacks = new SuggestionLoaderCallbacks(); suggestionLoaderCallbacks = new SuggestionLoaderCallbacks();
inputMethodManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
autoCompleteAdapter = new AutoCompleteAdapter(activity, this); autoCompleteAdapter = new AutoCompleteAdapter(activity, this);
} }
@ -96,9 +89,9 @@ public class SearchFragment extends Fragment
public void onDetach() { public void onDetach() {
super.onDetach(); super.onDetach();
searchListener = null;
suggestClient = null; suggestClient = null;
suggestionLoaderCallbacks = null; suggestionLoaderCallbacks = null;
inputMethodManager = null;
autoCompleteAdapter = null; autoCompleteAdapter = null;
} }
@ -115,42 +108,28 @@ public class SearchFragment extends Fragment
} }
}); });
searchBar = mainView.findViewById(R.id.search_bar); editText = (ClearableEditText) mainView.findViewById(R.id.auto_complete_edit_text);
editText = (EditText) mainView.findViewById(R.id.search_bar_edit_text); editText.setOnClickListener(new View.OnClickListener() {
final View.OnClickListener transitionToRunningListener = new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
transitionToRunning(); transitionToRunning();
} }
}; });
searchBar.setOnClickListener(transitionToRunningListener);
editText.setOnClickListener(transitionToRunningListener);
// Attach a listener for the "search" key on the keyboard. editText.setTextListener(new ClearableEditText.TextListener() {
editText.addTextChangedListener(new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { public void onChange(String text) {
} if (state == State.RUNNING) {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
final Bundle args = new Bundle(); final Bundle args = new Bundle();
args.putString(KEY_SEARCH_TERM, s.toString()); args.putString(KEY_SEARCH_TERM, text);
getLoaderManager().restartLoader(LOADER_ID_SUGGESTION, args, suggestionLoaderCallbacks); getLoaderManager().restartLoader(LOADER_ID_SUGGESTION, args, suggestionLoaderCallbacks);
} }
}); }
editText.setOnEditorActionListener(this);
clearButton = (Button) mainView.findViewById(R.id.search_bar_clear_button);
clearButton.setOnClickListener(new View.OnClickListener(){
@Override @Override
public void onClick(View v) { public void onSubmit(String text) {
editText.setText(""); transitionToWaiting();
searchListener.onSearch(text);
} }
}); });
@ -162,7 +141,9 @@ public class SearchFragment extends Fragment
@Override @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Suggestion suggestion = (Suggestion) suggestionDropdown.getItemAtPosition(position); final Suggestion suggestion = (Suggestion) suggestionDropdown.getItemAtPosition(position);
startSearch(suggestion.value);
transitionToWaiting();
searchListener.onSearch(suggestion.value);
} }
}); });
@ -173,9 +154,7 @@ public class SearchFragment extends Fragment
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
searchBar = null;
editText = null; editText = null;
clearButton = null;
if (null != suggestionDropdown) { if (null != suggestionDropdown) {
suggestionDropdown.setOnItemClickListener(null); suggestionDropdown.setOnItemClickListener(null);
@ -184,78 +163,41 @@ public class SearchFragment extends Fragment
} }
} }
/**
* Handler for the "search" button on the keyboard.
*/
@Override @Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public void onJumpTap(String suggestion) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) { setSearchTerm(suggestion);
startSearch(v.getText().toString());
return true;
}
return false;
} }
/** /**
* Send a search intent and put the widget into waiting. * Sets the search term in the search bar. If the SearchFragment is
* in State.RUNNING, this will also update the search suggestions.
*
* @param searchTerm
*/ */
private void startSearch(String queryString) { public void setSearchTerm(String searchTerm) {
if (getActivity() instanceof AcceptsSearchQuery) { editText.setText(searchTerm);
editText.setText(queryString);
editText.setSelection(queryString.length());
transitionToWaiting();
((AcceptsSearchQuery) getActivity()).onSearch(queryString);
} else {
throw new RuntimeException("Parent activity does not implement AcceptsSearchQuery.");
}
} }
private void transitionToWaiting() { private void transitionToWaiting() {
if (state == State.WAITING) { if (state == State.WAITING) {
return; return;
} }
setEditTextFocusable(false);
mainView.setClickable(false);
suggestionDropdown.setVisibility(View.GONE);
clearButton.setVisibility(View.GONE);
state = State.WAITING; state = State.WAITING;
mainView.setClickable(false);
editText.setActive(false);
suggestionDropdown.setVisibility(View.GONE);
} }
private void transitionToRunning() { private void transitionToRunning() {
if (state == State.RUNNING) { if (state == State.RUNNING) {
return; return;
} }
setEditTextFocusable(true);
mainView.setClickable(true);
suggestionDropdown.setVisibility(View.VISIBLE);
clearButton.setVisibility(View.VISIBLE);
state = State.RUNNING; state = State.RUNNING;
}
private void setEditTextFocusable(boolean focusable) { mainView.setClickable(true);
editText.setFocusable(focusable); editText.setActive(true);
editText.setFocusableInTouchMode(focusable); suggestionDropdown.setVisibility(View.VISIBLE);
if (focusable) {
editText.requestFocus();
inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
} else {
editText.clearFocus();
inputMethodManager.hideSoftInputFromWindow(editText.getWindowToken(), 0);
}
}
@Override
public void onJumpTap(String suggestion) {
editText.setText(suggestion);
// Move cursor to end of search input.
editText.setSelection(suggestion.length());
} }
public static class Suggestion { public static class Suggestion {

View File

@ -0,0 +1,28 @@
<!-- 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/. -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionSearch"
android:inputType="textNoSuggestions|textVisiblePassword"
android:drawableLeft="@drawable/search_icon"
android:drawablePadding="10dp"
android:focusable="false"
android:focusableInTouchMode="false"
android:hint="@string/search_bar_hint"/>
<Button
android:id="@+id/clear_button"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_gravity="right|center_vertical"
android:layout_marginRight="10dp"
android:background="@drawable/search_clear"
android:visibility="gone"/>
</merge>

View File

@ -11,34 +11,11 @@
android:clickable="false" android:clickable="false"
tools:context=".autocomplete.AutoCompleteFragment"> tools:context=".autocomplete.AutoCompleteFragment">
<FrameLayout <org.mozilla.search.autocomplete.ClearableEditText
android:id="@+id/search_bar" android:id="@+id/auto_complete_edit_text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="10dp"> android:padding="10dp"/>
<EditText
android:id="@+id/search_bar_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionSearch"
android:inputType="textNoSuggestions|textVisiblePassword"
android:drawableLeft="@drawable/search_icon"
android:drawablePadding="10dp"
android:focusable="false"
android:focusableInTouchMode="false"
android:hint="@string/search_bar_hint"/>
<Button
android:id="@+id/search_bar_clear_button"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_gravity="right|center_vertical"
android:layout_marginRight="10dp"
android:background="@drawable/search_clear"
android:visibility="gone"/>
</FrameLayout>
<ListView <ListView
android:id="@+id/auto_complete_dropdown" android:id="@+id/auto_complete_dropdown"

View File

@ -8,6 +8,7 @@ search_activity_sources = [
'java/org/mozilla/search/autocomplete/AcceptsJumpTaps.java', 'java/org/mozilla/search/autocomplete/AcceptsJumpTaps.java',
'java/org/mozilla/search/autocomplete/AcceptsSearchQuery.java', 'java/org/mozilla/search/autocomplete/AcceptsSearchQuery.java',
'java/org/mozilla/search/autocomplete/AutoCompleteAdapter.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/SearchFragment.java',
'java/org/mozilla/search/autocomplete/SuggestClient.java', 'java/org/mozilla/search/autocomplete/SuggestClient.java',
'java/org/mozilla/search/Constants.java', 'java/org/mozilla/search/Constants.java',