Chromium Code Reviews| Index: chrome/android/java/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkSearchView.java |
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkSearchView.java b/chrome/android/java/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkSearchView.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..7cff0ae46c33040cbbd089f4292db080ec8ac4ba |
| --- /dev/null |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkSearchView.java |
| @@ -0,0 +1,404 @@ |
| +// Copyright 2015 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.enhancedbookmarks; |
| + |
| +import android.content.Context; |
| +import android.preference.PreferenceManager; |
| +import android.support.v7.widget.Toolbar; |
| +import android.text.Editable; |
| +import android.text.TextUtils; |
| +import android.text.TextWatcher; |
| +import android.util.AttributeSet; |
| +import android.view.KeyEvent; |
| +import android.view.LayoutInflater; |
| +import android.view.View; |
| +import android.view.View.OnClickListener; |
| +import android.view.ViewGroup; |
| +import android.view.inputmethod.EditorInfo; |
| +import android.widget.AdapterView; |
| +import android.widget.AdapterView.OnItemClickListener; |
| +import android.widget.ArrayAdapter; |
| +import android.widget.BaseAdapter; |
| +import android.widget.EditText; |
| +import android.widget.LinearLayout; |
| +import android.widget.ListView; |
| +import android.widget.TextView; |
| +import android.widget.TextView.OnEditorActionListener; |
| +import android.widget.ViewSwitcher; |
| + |
| +import org.chromium.base.ApiCompatibilityUtils; |
| +import org.chromium.chrome.R; |
| +import org.chromium.chrome.browser.BookmarksBridge.BookmarkItem; |
| +import org.chromium.chrome.browser.BookmarksBridge.BookmarkModelObserver; |
| +import org.chromium.components.bookmarks.BookmarkId; |
| +import org.chromium.ui.UiUtils; |
| +import org.json.JSONArray; |
| + |
| +import java.util.ArrayList; |
| +import java.util.List; |
| + |
| +/** |
| + * Activity for searching bookmarks. Search results will be updated when user is typing. Before |
| + * typing, a list of search history is shown. |
| + */ |
| +public class EnhancedBookmarkSearchView extends LinearLayout |
| + implements OnClickListener, OnItemClickListener, OnEditorActionListener, |
| + EnhancedBookmarkUIObserver { |
| + /** |
| + * A custom {@link ViewSwitcher} that wraps another {@link ViewSwitcher} inside. |
| + */ |
| + public static class HistoryResultSwitcher extends ViewSwitcher { |
| + ViewSwitcher mResultEmptySwitcher; |
| + |
| + /** |
| + * Constructor for xml inflation. |
| + */ |
| + public HistoryResultSwitcher(Context context, AttributeSet attrs) { |
| + super(context, attrs); |
| + } |
| + |
| + @Override |
| + protected void onFinishInflate() { |
| + super.onFinishInflate(); |
| + mResultEmptySwitcher = (ViewSwitcher) findViewById(R.id.result_empty_switcher); |
| + } |
| + |
| + void showHistory() { |
| + if (getCurrentView().getId() == R.id.eb_history_list) return; |
| + showNext(); |
| + } |
| + |
| + void showResult() { |
| + if (getCurrentView().getId() == R.id.eb_history_list) showNext(); |
| + if (mResultEmptySwitcher.getCurrentView().getId() == R.id.eb_search_empty_view) { |
| + mResultEmptySwitcher.showNext(); |
| + } |
| + } |
| + |
| + void showEmpty() { |
| + if (getCurrentView().getId() == R.id.eb_history_list) showNext(); |
| + if (mResultEmptySwitcher.getCurrentView().getId() == R.id.eb_result_list) { |
| + mResultEmptySwitcher.showNext(); |
| + } |
| + } |
| + } |
| + |
| + private static enum UIState {HISTORY, RESULT, EMPTY} |
| + |
| + private static final String PREF_SEARCH_HISTORY = "bookmark_search_history"; |
| + private static final int SEARCH_HISTORY_MAX_ENTRIES = 10; |
| + private static final int HISTORY_ITEM_PADDING_START_DP = 72; |
| + private static final int MAXIMUM_NUMBER_OF_RESULTS = 500; |
| + |
| + private EnhancedBookmarksModel mEnhancedBookmarksModel; |
| + private EnhancedBookmarkDelegate mDelegate; |
| + private Toolbar mSearchBar; |
|
Kibeom Kim (inactive)
2015/08/12 04:26:30
Since this is only used in onFinishInflate(), does
Ian Wen
2015/08/12 18:32:41
Done.
|
| + private EditText mSearchText; |
| + private ListView mResultList; |
| + private ListView mHistoryList; |
| + private HistoryResultSwitcher mHistoryResultSwitcher; |
| + private UIState mCurrentUIState; |
| + |
| + private BookmarkModelObserver mModelObserver = new BookmarkModelObserver() { |
| + @Override |
| + public void bookmarkModelChanged() { |
| + if (mCurrentUIState == UIState.RESULT || mCurrentUIState == UIState.EMPTY) { |
| + sendSearchQuery(); |
| + } |
| + } |
| + |
| + @Override |
| + public void bookmarkNodeRemoved(BookmarkItem parent, int oldIndex, BookmarkItem node, |
| + boolean isDoingExtensiveChanges) { |
| + // If isDoingExtensiveChanges is false, it will fall back to bookmarkModelChange() |
| + if (isDoingExtensiveChanges && mCurrentUIState == UIState.RESULT) { |
| + sendSearchQuery(); |
| + } |
| + } |
| + }; |
| + |
| + /** |
| + * Constructor for inflating from XML. |
| + */ |
| + public EnhancedBookmarkSearchView(Context context, AttributeSet attrs) { |
| + super(context, attrs); |
| + } |
| + |
| + @Override |
| + protected void onFinishInflate() { |
| + super.onFinishInflate(); |
| + mSearchText = (EditText) findViewById(R.id.eb_search_text); |
| + mResultList = (ListView) findViewById(R.id.eb_result_list); |
| + mHistoryList = (ListView) findViewById(R.id.eb_history_list); |
| + mSearchBar = (Toolbar) findViewById(R.id.search_bar); |
| + mHistoryResultSwitcher = (HistoryResultSwitcher) findViewById(R.id.history_result_switcher); |
| + |
| + mSearchBar.setNavigationIcon(R.drawable.eb_back_normal); |
| + mSearchBar.setNavigationContentDescription(R.string.accessibility_toolbar_btn_back); |
| + mSearchBar.setNavigationOnClickListener(new OnClickListener() { |
| + @Override |
| + public void onClick(View v) { |
| + onBackPressed(); |
| + } |
| + }); |
| + mHistoryList.setOnItemClickListener(this); |
| + mSearchText.setOnEditorActionListener(this); |
| + mSearchText.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 (TextUtils.isEmpty(s.toString().trim())) { |
| + resetUI(); |
| + } else { |
| + sendSearchQuery(); |
| + } |
| + } |
| + }); |
| + mCurrentUIState = UIState.HISTORY; |
| + } |
| + |
| + private void updateHistoryList() { |
| + mHistoryList.setAdapter(new ArrayAdapter<String>(getContext(), |
| + android.R.layout.simple_list_item_1, android.R.id.text1, |
| + readHistoryList()) { |
| + @Override |
| + public View getView(int position, View convertView, ViewGroup parent) { |
| + View textView = super.getView(position, convertView, parent); |
| + // Set padding start to specific size. |
| + int paddingStart = (int) (HISTORY_ITEM_PADDING_START_DP |
| + * getResources().getDisplayMetrics().density); |
| + ApiCompatibilityUtils.setPaddingRelative(textView, paddingStart, |
| + textView.getPaddingTop(), textView.getPaddingRight(), |
| + textView.getPaddingBottom()); |
| + return textView; |
| + } |
| + }); |
| + } |
| + |
| + private void resetUI() { |
| + setUIState(UIState.HISTORY); |
| + mResultList.setAdapter(null); |
| + if (!TextUtils.isEmpty(mSearchText.getText())) mSearchText.setText(""); |
| + } |
| + |
| + private void sendSearchQuery() { |
| + String currentText = mSearchText.getText().toString().trim(); |
| + if (TextUtils.isEmpty(currentText)) return; |
| + |
| + List<BookmarkMatch> results = mEnhancedBookmarksModel.searchBookmarks(currentText, |
| + MAXIMUM_NUMBER_OF_RESULTS); |
| + populateResultListView(results); |
| + } |
| + |
| + @Override |
| + public boolean dispatchKeyEvent(KeyEvent event) { |
| + // To intercept hardware key, a view must have focus. |
| + if (mDelegate == null) return super.dispatchKeyEvent(event); |
| + |
| + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { |
| + KeyEvent.DispatcherState state = getKeyDispatcherState(); |
| + if (state != null) { |
| + if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { |
| + state.startTracking(event, this); |
| + return true; |
| + } else if (event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled() |
| + && state.isTracking(event)) { |
| + onBackPressed(); |
| + return true; |
| + } |
| + } |
| + } |
| + |
| + return super.dispatchKeyEvent(event); |
| + } |
| + |
| + /** |
| + * Make result list visible and popuplate the list with given list of bookmarks. |
| + */ |
| + private void populateResultListView(List<BookmarkMatch> ids) { |
| + if (ids.isEmpty()) { |
| + setUIState(UIState.EMPTY); |
| + } else { |
| + setUIState(UIState.RESULT); |
| + mResultList.setAdapter(new ResultListAdapter(ids, mDelegate)); |
| + } |
| + } |
| + |
| + private void setUIState(UIState state) { |
| + if (mCurrentUIState == state) return; |
| + mCurrentUIState = state; |
| + if (state == UIState.HISTORY) { |
| + mHistoryResultSwitcher.showHistory(); |
| + updateHistoryList(); |
| + } else if (state == UIState.RESULT) { |
| + mHistoryResultSwitcher.showResult(); |
| + } else if (state == UIState.EMPTY) { |
| + mHistoryResultSwitcher.showEmpty(); |
| + } |
| + } |
| + |
| + @Override |
| + protected void onVisibilityChanged(View changedView, int visibility) { |
| + super.onVisibilityChanged(changedView, visibility); |
| + // This method might be called very early. Null check on bookmark model here. |
| + if (mEnhancedBookmarksModel == null) return; |
| + |
| + if (visibility == View.VISIBLE) { |
| + mEnhancedBookmarksModel.addObserver(mModelObserver); |
| + updateHistoryList(); |
| + mSearchText.requestFocus(); |
| + UiUtils.showKeyboard(mSearchText); |
| + } else { |
| + UiUtils.hideKeyboard(mSearchText); |
| + mEnhancedBookmarksModel.removeObserver(mModelObserver); |
| + resetUI(); |
| + clearFocus(); |
| + } |
| + } |
| + |
| + private void onBackPressed() { |
| + if (mCurrentUIState == UIState.HISTORY) { |
| + mDelegate.closeSearchUI(); |
| + } else { |
| + resetUI(); |
| + } |
| + } |
| + |
| + @Override |
| + public void onClick(View v) { |
| + assert v instanceof EnhancedBookmarkSearchRow; |
| + saveHistoryList((addCurrentTextToHistoryList(readHistoryList()))); |
| + } |
| + |
| + @Override |
| + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
| + assert parent == mHistoryList : "Only history list should have onItemClickListener."; |
| + mSearchText.setText((String) parent.getAdapter().getItem(position)); |
| + } |
| + |
| + @Override |
| + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { |
| + if (actionId == EditorInfo.IME_ACTION_SEARCH) { |
| + // History is saved either when the user clicks search button or a search result is |
| + // clicked. |
| + saveHistoryList(addCurrentTextToHistoryList(readHistoryList())); |
| + } |
| + return false; |
| + } |
| + |
| + private void saveHistoryList(List<String> history) { |
| + JSONArray jsonArray = new JSONArray(history); |
| + PreferenceManager.getDefaultSharedPreferences(getContext()).edit() |
| + .putString(PREF_SEARCH_HISTORY, jsonArray.toString()).commit(); |
| + } |
| + |
| + private List<String> readHistoryList() { |
| + try { |
| + String unformatted = PreferenceManager.getDefaultSharedPreferences(getContext()) |
| + .getString(PREF_SEARCH_HISTORY, "[]"); |
| + JSONArray jsonArray = new JSONArray(unformatted); |
| + ArrayList<String> result = new ArrayList<String>(); |
| + for (int i = 0; i < jsonArray.length(); i++) { |
| + result.add(jsonArray.getString(i)); |
| + } |
| + return result; |
| + } catch (Exception e) { |
| + return new ArrayList<String>(); |
| + } |
| + } |
| + |
| + /** |
| + * Adds the current search text as top entry of the list. |
| + */ |
| + private List<String> addCurrentTextToHistoryList(List<String> history) { |
| + String text = mSearchText.getText().toString().trim(); |
| + if (TextUtils.isEmpty(text)) return history; |
| + |
| + history.remove(text); |
| + history.add(0, text); |
| + if (history.size() > SEARCH_HISTORY_MAX_ENTRIES) { |
| + history.remove(history.size() - 1); |
| + } |
| + return history; |
| + } |
| + |
| + // EnhancedBookmarkUIObserver implementation |
| + |
| + @Override |
| + public void onEnhancedBookmarkDelegateInitialized(EnhancedBookmarkDelegate delegate) { |
| + mDelegate = delegate; |
| + mDelegate.addUIObserver(this); |
| + mEnhancedBookmarksModel = mDelegate.getModel(); |
| + } |
| + |
| + @Override |
| + public void onDestroy() { |
| + mEnhancedBookmarksModel.removeObserver(mModelObserver); |
| + mDelegate.removeUIObserver(this); |
| + } |
| + |
| + @Override |
| + public void onAllBookmarksStateSet() { |
| + } |
| + |
| + @Override |
| + public void onFolderStateSet(BookmarkId folder) { |
| + } |
| + |
| + @Override |
| + public void onFilterStateSet(EnhancedBookmarkFilter filter) { |
| + } |
| + |
| + @Override |
| + public void onSelectionStateChange(List<BookmarkId> selectedBookmarks) { |
| + } |
| + |
| + private class ResultListAdapter extends BaseAdapter { |
| + private EnhancedBookmarkDelegate mDelegate; |
| + private List<BookmarkMatch> mBookmarktList; |
| + |
| + public ResultListAdapter(List<BookmarkMatch> bookmarkMatches, |
| + EnhancedBookmarkDelegate delegate) { |
| + mDelegate = delegate; |
| + mBookmarktList = bookmarkMatches; |
| + } |
| + |
| + @Override |
| + public int getCount() { |
| + return mBookmarktList.size(); |
| + } |
| + |
| + @Override |
| + public BookmarkMatch getItem(int position) { |
| + return mBookmarktList.get(position); |
| + } |
| + |
| + @Override |
| + public long getItemId(int position) { |
| + return position; |
| + } |
| + |
| + @Override |
| + public View getView(int position, View convertView, ViewGroup parent) { |
| + final BookmarkMatch bookmarkMatch = getItem(position); |
| + if (convertView == null) { |
| + convertView = LayoutInflater.from(parent.getContext()).inflate( |
| + R.layout.eb_search_row, parent, false); |
| + } |
| + final EnhancedBookmarkSearchRow row = (EnhancedBookmarkSearchRow) convertView; |
| + row.onEnhancedBookmarkDelegateInitialized(mDelegate); |
| + row.setBookmarkId(bookmarkMatch.getBookmarkId()); |
| + row.setOnBookmarkClickListener(EnhancedBookmarkSearchView.this); |
| + return convertView; |
| + } |
| + } |
| +} |