Twidere-App-Android-Twitter.../twidere/src/main/java/org/mariotaku/twidere/activity/support/QuickSearchBarActivity.java

667 lines
24 KiB
Java

/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.activity.support;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import org.apache.commons.lang3.ArrayUtils;
import org.mariotaku.querybuilder.Columns.Column;
import org.mariotaku.querybuilder.Expression;
import org.mariotaku.querybuilder.OrderBy;
import org.mariotaku.querybuilder.RawItemArray;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.support.QuickSearchBarActivity.SuggestionItem;
import org.mariotaku.twidere.adapter.AccountsSpinnerAdapter;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.model.ParcelableAccount;
import org.mariotaku.twidere.model.ParcelableUser;
import org.mariotaku.twidere.model.ParcelableUser.CachedIndices;
import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers;
import org.mariotaku.twidere.provider.TwidereDataStore.SavedSearches;
import org.mariotaku.twidere.provider.TwidereDataStore.SearchHistory;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.ParseUtils;
import org.mariotaku.twidere.util.SwipeDismissListViewTouchListener;
import org.mariotaku.twidere.util.SwipeDismissListViewTouchListener.DismissCallbacks;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.content.ContentResolverUtils;
import org.mariotaku.twidere.view.ExtendedRelativeLayout;
import org.mariotaku.twidere.view.iface.IExtendedView.OnFitSystemWindowsListener;
import java.util.ArrayList;
import java.util.List;
import static org.mariotaku.twidere.util.UserColorNameUtils.getUserNickname;
/**
* Created by mariotaku on 15/1/6.
*/
public class QuickSearchBarActivity extends ThemedFragmentActivity implements OnClickListener,
OnEditorActionListener, LoaderCallbacks<List<SuggestionItem>>, TextWatcher,
OnItemSelectedListener, OnItemClickListener, DismissCallbacks, OnFitSystemWindowsListener {
private Spinner mAccountSpinner;
private EditText mSearchQuery;
private View mSearchSubmit;
private ListView mSuggestionsList;
private SuggestionsAdapter mUsersSearchAdapter;
private ExtendedRelativeLayout mMainContent;
private Rect mSystemWindowsInsets = new Rect();
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public boolean canDismiss(int position) {
return position < getHistorySize(mSearchQuery.getText());
}
@Override
public void onDismiss(ListView listView, int[] reverseSortedPositions) {
final long[] ids = new long[reverseSortedPositions.length];
for (int i = 0, j = reverseSortedPositions.length; i < j; i++) {
final int position = reverseSortedPositions[i];
final SearchHistoryItem item = (SearchHistoryItem) mUsersSearchAdapter.getItem(position);
mUsersSearchAdapter.removeItemAt(position);
ids[i] = item.getCursorId();
}
final ContentResolver cr = getContentResolver();
final Long[] idsObject = ArrayUtils.toObject(ids);
ContentResolverUtils.bulkDelete(cr, SearchHistory.CONTENT_URI, SearchHistory._ID, idsObject,
null, false);
getSupportLoaderManager().restartLoader(0, null, this);
}
@Override
public void onFitSystemWindows(Rect insets) {
mSystemWindowsInsets.set(insets);
updateWindowAttributes();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final SuggestionItem item = mUsersSearchAdapter.getItem(position);
item.onItemClick(this, position);
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
getSupportLoaderManager().restartLoader(0, null, this);
}
@Override
public void afterTextChanged(Editable s) {
}
@Override
public int getThemeColor() {
return ThemeUtils.getUserAccentColor(this, getThemeResourceId());
}
@Override
public int getThemeResourceId() {
return ThemeUtils.getQuickSearchBarThemeResource(this);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quick_search_bar);
final List<ParcelableAccount> accounts = ParcelableAccount.getAccountsList(this, false);
final AccountsSpinnerAdapter accountsSpinnerAdapter = new AccountsSpinnerAdapter(this, R.layout.spinner_item_account_icon);
accountsSpinnerAdapter.setDropDownViewResource(R.layout.list_item_user);
accountsSpinnerAdapter.addAll(accounts);
mAccountSpinner.setAdapter(accountsSpinnerAdapter);
mAccountSpinner.setOnItemSelectedListener(this);
if (savedInstanceState == null) {
final Intent intent = getIntent();
final int index = accountsSpinnerAdapter.findItemPosition(intent.getLongExtra(EXTRA_ACCOUNT_ID, -1));
if (index != -1) {
mAccountSpinner.setSelection(index);
}
}
mMainContent.setOnFitSystemWindowsListener(this);
mUsersSearchAdapter = new SuggestionsAdapter(this);
mSuggestionsList.setAdapter(mUsersSearchAdapter);
mSuggestionsList.setOnItemClickListener(this);
final SwipeDismissListViewTouchListener listener = new SwipeDismissListViewTouchListener(mSuggestionsList, this);
mSuggestionsList.setOnTouchListener(listener);
mSuggestionsList.setOnScrollListener(listener.makeScrollListener());
mSearchSubmit.setOnClickListener(this);
mSearchQuery.setOnEditorActionListener(this);
mSearchQuery.addTextChangedListener(this);
getSupportLoaderManager().initLoader(0, null, this);
}
@Override
protected void onResume() {
super.onResume();
updateWindowAttributes();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.search_submit: {
doSearch();
break;
}
}
}
@Override
public void onContentChanged() {
super.onContentChanged();
mMainContent = (ExtendedRelativeLayout) findViewById(R.id.main_content);
mAccountSpinner = (Spinner) findViewById(R.id.account_spinner);
mSearchQuery = (EditText) findViewById(R.id.search_query);
mSearchSubmit = findViewById(R.id.search_submit);
mSuggestionsList = (ListView) findViewById(R.id.suggestions_list);
}
@Override
public Loader<List<SuggestionItem>> onCreateLoader(int id, Bundle args) {
return new SuggestionsLoader(this, mAccountSpinner.getSelectedItemId(), mSearchQuery.getText().toString());
}
@Override
public void onLoadFinished(Loader<List<SuggestionItem>> loader, List<SuggestionItem> data) {
mUsersSearchAdapter.setData(data);
}
@Override
public void onLoaderReset(Loader<List<SuggestionItem>> loader) {
mUsersSearchAdapter.setData(null);
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (event == null) return false;
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_ENTER: {
doSearch();
return true;
}
}
return false;
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
getSupportLoaderManager().restartLoader(0, null, this);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
private void doSearch() {
if (isFinishing()) return;
final String query = ParseUtils.parseString(mSearchQuery.getText());
if (TextUtils.isEmpty(query)) return;
final long accountId = mAccountSpinner.getSelectedItemId();
Utils.openSearch(this, accountId, query);
finish();
}
private long getAccountId() {
return mAccountSpinner.getSelectedItemId();
}
private void updateWindowAttributes() {
final Window window = getWindow();
final WindowManager.LayoutParams attributes = window.getAttributes();
attributes.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
attributes.y = mSystemWindowsInsets.top;
window.setAttributes(attributes);
}
static interface SuggestionItem {
void bindView(SuggestionsAdapter adapter, View view, int position);
int getItemLayoutResource();
int getItemViewType();
boolean isEnabled();
void onItemClick(QuickSearchBarActivity activity, int position);
}
static class HeaderItem implements SuggestionItem {
static final int ITEM_VIEW_TYPE = 1;
@Override
public void bindView(SuggestionsAdapter adapter, View view, int position) {
}
@Override
public int getItemViewType() {
return ITEM_VIEW_TYPE;
}
@Override
public boolean isEnabled() {
return false;
}
@Override
public void onItemClick(QuickSearchBarActivity activity, int position) {
}
@Override
public int getItemLayoutResource() {
return 0;
}
}
static class SearchHistoryItem extends BaseClickableItem {
static final int ITEM_VIEW_TYPE = 1;
private final long mCursorId;
private final String mQuery;
public long getCursorId() {
return mCursorId;
}
public SearchHistoryItem(long cursorId, String query) {
mCursorId = cursorId;
mQuery = query;
}
@Override
public final int getItemLayoutResource() {
return R.layout.list_item_suggestion_search;
}
@Override
public int getItemViewType() {
return ITEM_VIEW_TYPE;
}
@Override
public void onItemClick(QuickSearchBarActivity activity, int position) {
Utils.openSearch(activity, activity.getAccountId(), mQuery);
activity.finish();
}
@Override
public void bindView(SuggestionsAdapter adapter, View view, int position) {
final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
final TextView text1 = (TextView) view.findViewById(android.R.id.text1);
text1.setText(mQuery);
icon.setImageResource(R.drawable.ic_action_history);
icon.setColorFilter(text1.getCurrentTextColor(), Mode.SRC_ATOP);
}
}
static abstract class BaseClickableItem implements SuggestionItem {
@Override
public final boolean isEnabled() {
return true;
}
}
static class SavedSearchItem extends BaseClickableItem {
static final int ITEM_VIEW_TYPE = 2;
private final String mQuery;
public SavedSearchItem(String query) {
mQuery = query;
}
@Override
public final int getItemLayoutResource() {
return R.layout.list_item_suggestion_search;
}
@Override
public int getItemViewType() {
return ITEM_VIEW_TYPE;
}
@Override
public void bindView(SuggestionsAdapter adapter, View view, int position) {
final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
final TextView text1 = (TextView) view.findViewById(android.R.id.text1);
text1.setText(mQuery);
icon.setImageResource(R.drawable.ic_action_save);
icon.setColorFilter(text1.getCurrentTextColor(), Mode.SRC_ATOP);
}
@Override
public void onItemClick(QuickSearchBarActivity activity, int position) {
Utils.openSearch(activity, activity.getAccountId(), mQuery);
activity.finish();
}
}
static class UserSuggestionItem extends BaseClickableItem {
static final int ITEM_VIEW_TYPE = 3;
private final ParcelableUser mUser;
public UserSuggestionItem(Cursor c, CachedIndices i, long accountId) {
mUser = new ParcelableUser(c, i, accountId);
}
@Override
public int getItemViewType() {
return ITEM_VIEW_TYPE;
}
public ParcelableUser getUser() {
return mUser;
}
@Override
public void onItemClick(QuickSearchBarActivity activity, int position) {
Utils.openUserProfile(activity, mUser, null);
activity.finish();
}
@Override
public final int getItemLayoutResource() {
return R.layout.list_item_suggestion_user;
}
@Override
public void bindView(SuggestionsAdapter adapter, View view, int position) {
final ParcelableUser user = mUser;
final Context context = adapter.getContext();
final MediaLoaderWrapper loader = adapter.getImageLoader();
final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
final TextView text1 = (TextView) view.findViewById(android.R.id.text1);
final TextView text2 = (TextView) view.findViewById(android.R.id.text2);
final String nick = getUserNickname(context, user.id);
text1.setText(TextUtils.isEmpty(nick) ? user.name : adapter.isNicknameOnly() ? nick :
context.getString(R.string.name_with_nickname, user.name, nick));
text2.setVisibility(View.VISIBLE);
text2.setText("@" + user.screen_name);
icon.clearColorFilter();
loader.displayProfileImage(icon, user.profile_image_url);
}
}
static class UserScreenNameItem extends BaseClickableItem {
static final int ITEM_VIEW_TYPE = 3;
private final String mScreenName;
private final long mAccountId;
public UserScreenNameItem(String screenName, long accountId) {
mScreenName = screenName;
mAccountId = accountId;
}
@Override
public int getItemViewType() {
return ITEM_VIEW_TYPE;
}
@Override
public void onItemClick(QuickSearchBarActivity activity, int position) {
Utils.openUserProfile(activity, mAccountId, -1, mScreenName, null);
activity.finish();
}
@Override
public final int getItemLayoutResource() {
return R.layout.list_item_suggestion_user;
}
@Override
public void bindView(SuggestionsAdapter adapter, View view, int position) {
final MediaLoaderWrapper loader = adapter.getImageLoader();
final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
final TextView text1 = (TextView) view.findViewById(android.R.id.text1);
final TextView text2 = (TextView) view.findViewById(android.R.id.text2);
text1.setText('@' + mScreenName);
text2.setVisibility(View.GONE);
icon.setColorFilter(text1.getCurrentTextColor(), Mode.SRC_ATOP);
loader.cancelDisplayTask(icon);
icon.setImageResource(R.drawable.ic_action_user);
}
}
public static class SuggestionsAdapter extends BaseAdapter {
private final Context mContext;
private final LayoutInflater mInflater;
private final MediaLoaderWrapper mImageLoader;
private final boolean mNicknameOnly;
private List<SuggestionItem> mData;
SuggestionsAdapter(Context context) {
mContext = context;
mInflater = LayoutInflater.from(context);
mImageLoader = TwidereApplication.getInstance(context).getImageLoaderWrapper();
final SharedPreferences pref = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
mNicknameOnly = pref.getBoolean(KEY_NICKNAME_ONLY, false);
}
public Context getContext() {
return mContext;
}
@Override
public int getCount() {
if (mData == null) return 0;
return mData.size();
}
@Override
public SuggestionItem getItem(int position) {
if (mData == null) return null;
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final View view;
final SuggestionItem item = getItem(position);
if (convertView == null) {
view = mInflater.inflate(item.getItemLayoutResource(), parent, false);
} else {
view = convertView;
}
item.bindView(this, view, position);
return view;
}
public MediaLoaderWrapper getImageLoader() {
return mImageLoader;
}
@Override
public boolean isEnabled(int position) {
return getItem(position).isEnabled();
}
@Override
public int getItemViewType(int position) {
if (mData == null) return IGNORE_ITEM_VIEW_TYPE;
return mData.get(position).getItemViewType();
}
@Override
public int getViewTypeCount() {
return 4;
}
public boolean isNicknameOnly() {
return mNicknameOnly;
}
public void removeItemAt(int position) {
if (mData == null) return;
mData.remove(position);
notifyDataSetChanged();
}
public void setData(List<SuggestionItem> data) {
mData = data;
notifyDataSetChanged();
}
}
private static int getHistorySize(CharSequence query) {
return TextUtils.isEmpty(query) ? 3 : 2;
}
public static class SuggestionsLoader extends AsyncTaskLoader<List<SuggestionItem>> {
private final long mAccountId;
private final String mQuery;
public SuggestionsLoader(Context context, long accountId, String query) {
super(context);
mAccountId = accountId;
mQuery = query;
}
@Override
public List<SuggestionItem> loadInBackground() {
final boolean emptyQuery = TextUtils.isEmpty(mQuery);
final Context context = getContext();
final ContentResolver resolver = context.getContentResolver();
final List<SuggestionItem> result = new ArrayList<>();
final String[] historyProjection = {SearchHistory._ID, SearchHistory.QUERY};
final Cursor historyCursor = resolver.query(SearchHistory.CONTENT_URI,
historyProjection, null, null, SearchHistory.DEFAULT_SORT_ORDER);
for (int i = 0, j = Math.min(getHistorySize(mQuery), historyCursor.getCount()); i < j; i++) {
historyCursor.moveToPosition(i);
result.add(new SearchHistoryItem(historyCursor.getLong(0), historyCursor.getString(1)));
}
historyCursor.close();
if (!emptyQuery) {
final String queryEscaped = mQuery.replace("_", "^_");
final SharedPreferences nicknamePrefs = context.getSharedPreferences(
USER_NICKNAME_PREFERENCES_NAME, Context.MODE_PRIVATE);
final long[] nicknameIds = Utils.getMatchedNicknameIds(ParseUtils.parseString(mQuery),
nicknamePrefs);
final Expression selection = Expression.or(
Expression.likeRaw(new Column(CachedUsers.SCREEN_NAME), "?||'%'", "^"),
Expression.likeRaw(new Column(CachedUsers.NAME), "?||'%'", "^"),
Expression.in(new Column(CachedUsers.USER_ID), new RawItemArray(nicknameIds)));
final String[] selectionArgs = new String[]{queryEscaped, queryEscaped};
final String[] order = {CachedUsers.LAST_SEEN, "score", CachedUsers.SCREEN_NAME, CachedUsers.NAME};
final boolean[] ascending = {false, false, true, true};
final OrderBy orderBy = new OrderBy(order, ascending);
final Uri uri = Uri.withAppendedPath(CachedUsers.CONTENT_URI_WITH_SCORE, String.valueOf(mAccountId));
final Cursor usersCursor = context.getContentResolver().query(uri,
CachedUsers.COLUMNS, selection != null ? selection.getSQL() : null,
selectionArgs, orderBy.getSQL());
final CachedIndices usersIndices = new CachedIndices(usersCursor);
final int screenNamePos = result.size();
boolean hasName = false;
for (int i = 0, j = Math.min(5, usersCursor.getCount()); i < j; i++) {
usersCursor.moveToPosition(i);
final UserSuggestionItem userSuggestionItem = new UserSuggestionItem(usersCursor, usersIndices, mAccountId);
final ParcelableUser user = userSuggestionItem.getUser();
result.add(userSuggestionItem);
if (user.screen_name.equalsIgnoreCase(mQuery)) {
hasName = true;
}
}
if (!hasName && mQuery.matches("(?i)[a-z0-9_]{1,20}")) {
result.add(screenNamePos, new UserScreenNameItem(mQuery, mAccountId));
}
usersCursor.close();
} else {
final String[] savedSearchesProjection = {SavedSearches.QUERY};
final Expression savedSearchesWhere = Expression.equals(SavedSearches.ACCOUNT_ID, mAccountId);
final Cursor savedSearchesCursor = resolver.query(SavedSearches.CONTENT_URI,
savedSearchesProjection, savedSearchesWhere.getSQL(), null,
SavedSearches.DEFAULT_SORT_ORDER);
savedSearchesCursor.moveToFirst();
while (!savedSearchesCursor.isAfterLast()) {
result.add(new SavedSearchItem(savedSearchesCursor.getString(0)));
savedSearchesCursor.moveToNext();
}
savedSearchesCursor.close();
}
return result;
}
@Override
protected void onStartLoading() {
forceLoad();
}
}
}