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..9fe245baec5dfd7d03d2e4d82fdc105ed897f957 |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkSearchView.java |
@@ -0,0 +1,406 @@ |
+// 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.chrome.browser.enhancedbookmarks.EnhancedBookmarkSearchRow.SearchHistoryDelegate; |
+import org.chromium.components.bookmarks.BookmarkId; |
+import org.chromium.ui.UiUtils; |
+import org.json.JSONArray; |
+import org.json.JSONException; |
+ |
+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 OnItemClickListener, |
+ OnEditorActionListener, EnhancedBookmarkUIObserver, SearchHistoryDelegate { |
+ /** |
+ * 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 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); |
+ mHistoryResultSwitcher = (HistoryResultSwitcher) findViewById(R.id.history_result_switcher); |
+ |
+ Toolbar searchBar = (Toolbar) findViewById(R.id.search_bar); |
+ searchBar.setNavigationIcon(R.drawable.eb_back_normal); |
+ searchBar.setNavigationContentDescription(R.string.accessibility_toolbar_btn_back); |
+ searchBar.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 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. |
+ saveSearchHistory(); |
+ } |
+ 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 (JSONException 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; |
+ } |
+ |
+ // SearchHistoryDelegate implementation |
+ |
+ @Override |
+ public void saveSearchHistory() { |
+ saveHistoryList((addCurrentTextToHistoryList(readHistoryList()))); |
+ } |
+ |
+ // 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.setSearchHistoryDelegate(EnhancedBookmarkSearchView.this); |
+ return convertView; |
+ } |
+ } |
+} |