From 75c21cdf51f37270e4a4814b0eec19441eeeda09 Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Sat, 21 Nov 2015 13:45:55 +0800 Subject: [PATCH] improved auto complete, A LOT --- .../twidere/provider/TwidereDataStore.java | 7 +- .../support/UserListSelectorActivity.java | 4 +- ...r.java => ComposeAutoCompleteAdapter.java} | 31 ++-- .../adapter/UserAutoCompleteAdapter.java | 147 ++++++++++++++++++ .../twidere/fragment/BaseFiltersFragment.java | 4 +- .../AddUserListMemberDialogFragment.java | 6 +- .../support/RetweetQuoteDialogFragment.java | 4 +- .../twidere/provider/TwidereDataProvider.java | 18 ++- .../twidere/util/dagger/GeneralComponent.java | 7 +- .../twidere/view/ComposeEditText.java | 51 ++++-- .../twidere/view/ComposeMaterialEditText.java | 101 ------------ .../src/main/res/layout/activity_compose.xml | 8 +- .../layout/dialog_status_quote_retweet.xml | 4 +- 13 files changed, 242 insertions(+), 150 deletions(-) rename twidere/src/main/java/org/mariotaku/twidere/adapter/{UserHashtagAutoCompleteAdapter.java => ComposeAutoCompleteAdapter.java} (87%) create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/UserAutoCompleteAdapter.java delete mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ComposeMaterialEditText.java diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/provider/TwidereDataStore.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/provider/TwidereDataStore.java index e3cd62cee..6562351ce 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/provider/TwidereDataStore.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/provider/TwidereDataStore.java @@ -270,6 +270,8 @@ public interface TwidereDataStore { String LAST_SEEN = "last_seen"; + String SCORE = "score"; + String[] COLUMNS = {_ID, USER_ID, CREATED_AT, NAME, SCREEN_NAME, DESCRIPTION_PLAIN, LOCATION, URL, PROFILE_IMAGE_URL, PROFILE_BANNER_URL, IS_PROTECTED, IS_VERIFIED, IS_FOLLOWING, FOLLOWERS_COUNT, FRIENDS_COUNT, STATUSES_COUNT, FAVORITES_COUNT, @@ -293,6 +295,7 @@ public interface TwidereDataStore { String ICON = "icon"; String EXTRA_ID = "extra_id"; String EXTRA = "extra"; + String VALUE = "value"; String TABLE_NAME = "suggestions"; @@ -300,9 +303,9 @@ public interface TwidereDataStore { Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH); - String[] COLUMNS = {_ID, TYPE, TITLE, SUMMARY, ICON, EXTRA_ID, EXTRA}; + String[] COLUMNS = {_ID, TYPE, TITLE, SUMMARY, ICON, EXTRA_ID, EXTRA, VALUE}; String[] TYPES = {TYPE_PRIMARY_KEY, TYPE_TEXT_NOT_NULL, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT, - TYPE_INT, TYPE_TEXT}; + TYPE_INT, TYPE_TEXT, TYPE_TEXT}; interface AutoComplete extends Suggestions { diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/UserListSelectorActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/UserListSelectorActivity.java index ef2942aab..d88efcbd6 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/UserListSelectorActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/UserListSelectorActivity.java @@ -38,7 +38,7 @@ import android.widget.ListView; import org.mariotaku.twidere.R; import org.mariotaku.twidere.adapter.SimpleParcelableUserListsAdapter; import org.mariotaku.twidere.adapter.SimpleParcelableUsersAdapter; -import org.mariotaku.twidere.adapter.UserHashtagAutoCompleteAdapter; +import org.mariotaku.twidere.adapter.UserAutoCompleteAdapter; import org.mariotaku.twidere.api.twitter.Twitter; import org.mariotaku.twidere.api.twitter.TwitterException; import org.mariotaku.twidere.api.twitter.http.HttpResponseCode; @@ -169,7 +169,7 @@ public class UserListSelectorActivity extends BaseSupportDialogActivity implemen getUserLists(mScreenName); } } - final UserHashtagAutoCompleteAdapter adapter = new UserHashtagAutoCompleteAdapter(this); + final UserAutoCompleteAdapter adapter = new UserAutoCompleteAdapter(this); adapter.setAccountId(getAccountId()); mEditScreenName.setAdapter(adapter); mEditScreenName.setText(mScreenName); diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/UserHashtagAutoCompleteAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/ComposeAutoCompleteAdapter.java similarity index 87% rename from twidere/src/main/java/org/mariotaku/twidere/adapter/UserHashtagAutoCompleteAdapter.java rename to twidere/src/main/java/org/mariotaku/twidere/adapter/ComposeAutoCompleteAdapter.java index 919aa8c33..ef7115c19 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/UserHashtagAutoCompleteAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/ComposeAutoCompleteAdapter.java @@ -29,6 +29,7 @@ import android.view.View; import android.widget.FilterQueryProvider; import android.widget.TextView; +import org.apache.commons.lang3.StringUtils; import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.R; import org.mariotaku.twidere.provider.TwidereDataStore.Suggestions; @@ -42,7 +43,7 @@ import org.mariotaku.twidere.view.ProfileImageView; import javax.inject.Inject; -public class UserHashtagAutoCompleteAdapter extends SimpleCursorAdapter implements Constants { +public class ComposeAutoCompleteAdapter extends SimpleCursorAdapter implements Constants { private static final String[] FROM = new String[0]; private static final int[] TO = new int[0]; @@ -56,11 +57,11 @@ public class UserHashtagAutoCompleteAdapter extends SimpleCursorAdapter implemen private final boolean mDisplayProfileImage; - private int mTypeIdx, mIconIdx, mTitleIdx, mSummaryIdx, mExtraIdIdx; + private int mTypeIdx, mIconIdx, mTitleIdx, mSummaryIdx, mExtraIdIdx, mValueIdx; private long mAccountId; private char mToken; - public UserHashtagAutoCompleteAdapter(final Context context) { + public ComposeAutoCompleteAdapter(final Context context) { super(context, R.layout.list_item_auto_complete, null, FROM, TO, 0); DaggerGeneralComponent.builder().applicationModule(ApplicationModule.get(context)).build().inject(this); mDisplayProfileImage = mPreferences.getBoolean(KEY_DISPLAY_PROFILE_IMAGE, true); @@ -68,7 +69,6 @@ public class UserHashtagAutoCompleteAdapter extends SimpleCursorAdapter implemen @Override public void bindView(final View view, final Context context, final Cursor cursor) { - if (isCursorClosed()) return; final TextView text1 = (TextView) view.findViewById(android.R.id.text1); final TextView text2 = (TextView) view.findViewById(android.R.id.text2); final ProfileImageView icon = (ProfileImageView) view.findViewById(android.R.id.icon); @@ -78,7 +78,7 @@ public class UserHashtagAutoCompleteAdapter extends SimpleCursorAdapter implemen if (Suggestions.AutoComplete.TYPE_USERS.equals(cursor.getString(mTypeIdx))) { text1.setText(mUserColorNameManager.getUserNickname(cursor.getLong(mExtraIdIdx), cursor.getString(mTitleIdx))); - text2.setText("@" + cursor.getString(mSummaryIdx)); + text2.setText('@' + cursor.getString(mSummaryIdx)); if (mDisplayProfileImage) { final String profileImageUrl = cursor.getString(mIconIdx); mProfileImageLoader.displayProfileImage(icon, profileImageUrl); @@ -88,7 +88,7 @@ public class UserHashtagAutoCompleteAdapter extends SimpleCursorAdapter implemen icon.clearColorFilter(); } else { - text1.setText("#" + cursor.getString(mTitleIdx)); + text1.setText('#' + cursor.getString(mTitleIdx)); text2.setText(R.string.hashtag); icon.setImageResource(R.drawable.ic_action_hashtag); @@ -99,7 +99,7 @@ public class UserHashtagAutoCompleteAdapter extends SimpleCursorAdapter implemen } public void closeCursor() { - final Cursor cursor = getCursor(); + final Cursor cursor = swapCursor(null); if (cursor == null) return; if (!cursor.isClosed()) { cursor.close(); @@ -108,13 +108,15 @@ public class UserHashtagAutoCompleteAdapter extends SimpleCursorAdapter implemen @Override public CharSequence convertToString(final Cursor cursor) { - if (isCursorClosed()) return null; - return cursor.getString(mSummaryIdx != -1 ? mSummaryIdx : mTitleIdx); - } - - public boolean isCursorClosed() { - final Cursor cursor = getCursor(); - return cursor == null || cursor.isClosed(); + switch (StringUtils.defaultIfEmpty(cursor.getString(mTypeIdx), "")) { + case Suggestions.AutoComplete.TYPE_HASHTAGS: { + return '#' + cursor.getString(mValueIdx); + } + case Suggestions.AutoComplete.TYPE_USERS: { + return '@' + cursor.getString(mValueIdx); + } + } + return cursor.getString(mValueIdx); } @Override @@ -159,6 +161,7 @@ public class UserHashtagAutoCompleteAdapter extends SimpleCursorAdapter implemen mSummaryIdx = cursor.getColumnIndex(Suggestions.AutoComplete.SUMMARY); mExtraIdIdx = cursor.getColumnIndex(Suggestions.AutoComplete.EXTRA_ID); mIconIdx = cursor.getColumnIndex(Suggestions.AutoComplete.ICON); + mValueIdx = cursor.getColumnIndex(Suggestions.AutoComplete.VALUE); } return super.swapCursor(cursor); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/UserAutoCompleteAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/UserAutoCompleteAdapter.java new file mode 100644 index 000000000..5608038bb --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/UserAutoCompleteAdapter.java @@ -0,0 +1,147 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.adapter; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.v4.widget.SimpleCursorAdapter; +import android.text.TextUtils; +import android.view.View; +import android.widget.FilterQueryProvider; +import android.widget.TextView; + +import org.mariotaku.sqliteqb.library.Columns; +import org.mariotaku.sqliteqb.library.Expression; +import org.mariotaku.sqliteqb.library.OrderBy; +import org.mariotaku.sqliteqb.library.RawItemArray; +import org.mariotaku.twidere.Constants; +import org.mariotaku.twidere.R; +import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers; +import org.mariotaku.twidere.util.MediaLoaderWrapper; +import org.mariotaku.twidere.util.SharedPreferencesWrapper; +import org.mariotaku.twidere.util.UserColorNameManager; +import org.mariotaku.twidere.util.Utils; +import org.mariotaku.twidere.util.dagger.ApplicationModule; +import org.mariotaku.twidere.util.dagger.DaggerGeneralComponent; +import org.mariotaku.twidere.view.ProfileImageView; + +import javax.inject.Inject; + + +public class UserAutoCompleteAdapter extends SimpleCursorAdapter implements Constants { + + private static final String[] FROM = new String[0]; + private static final int[] TO = new int[0]; + + @Inject + MediaLoaderWrapper mProfileImageLoader; + @Inject + SharedPreferencesWrapper mPreferences; + @Inject + UserColorNameManager mUserColorNameManager; + + private final boolean mDisplayProfileImage; + + private int mIdIdx, mNameIdx, mScreenNameIdx, mProfileImageIdx; + private long mAccountId; + private char mToken; + + public UserAutoCompleteAdapter(final Context context) { + super(context, R.layout.list_item_auto_complete, null, FROM, TO, 0); + DaggerGeneralComponent.builder().applicationModule(ApplicationModule.get(context)).build().inject(this); + mDisplayProfileImage = mPreferences.getBoolean(KEY_DISPLAY_PROFILE_IMAGE, true); + } + + @Override + public void bindView(final View view, final Context context, final Cursor cursor) { + final TextView text1 = (TextView) view.findViewById(android.R.id.text1); + final TextView text2 = (TextView) view.findViewById(android.R.id.text2); + final ProfileImageView icon = (ProfileImageView) view.findViewById(android.R.id.icon); + + // Clear images in order to prevent images in recycled view shown. + icon.setImageDrawable(null); + + text1.setText(mUserColorNameManager.getUserNickname(cursor.getLong(mIdIdx), cursor.getString(mNameIdx))); + text2.setText('@' + cursor.getString(mScreenNameIdx)); + if (mDisplayProfileImage) { + final String profileImageUrl = cursor.getString(mProfileImageIdx); + mProfileImageLoader.displayProfileImage(icon, profileImageUrl); + } else { + mProfileImageLoader.cancelDisplayTask(icon); + } + + icon.setVisibility(mDisplayProfileImage ? View.VISIBLE : View.GONE); + super.bindView(view, context, cursor); + } + + public void closeCursor() { + final Cursor cursor = swapCursor(null); + if (cursor == null) return; + if (!cursor.isClosed()) { + cursor.close(); + } + } + + @Override + public CharSequence convertToString(final Cursor cursor) { + return cursor.getString(mScreenNameIdx); + } + + @Override + public Cursor runQueryOnBackgroundThread(final CharSequence constraint) { + if (TextUtils.isEmpty(constraint)) return null; + final FilterQueryProvider filter = getFilterQueryProvider(); + if (filter != null) return filter.runQuery(constraint); + final String query = constraint.toString(); + final String queryEscaped = query.replace("_", "^_"); + final long[] nicknameIds = Utils.getMatchedNicknameIds(query, mUserColorNameManager); + final Expression usersSelection = Expression.or( + Expression.likeRaw(new Columns.Column(CachedUsers.SCREEN_NAME), "?||'%'", "^"), + Expression.likeRaw(new Columns.Column(CachedUsers.NAME), "?||'%'", "^"), + Expression.in(new Columns.Column(CachedUsers.USER_ID), new RawItemArray(nicknameIds))); + final String[] selectionArgs = new String[]{queryEscaped, queryEscaped}; + final String[] order = {CachedUsers.LAST_SEEN, CachedUsers.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)); + return mContext.getContentResolver().query(uri, CachedUsers.COLUMNS, usersSelection.getSQL(), + selectionArgs, orderBy.getSQL()); + } + + + public void setAccountId(long accountId) { + mAccountId = accountId; + } + + @Override + public Cursor swapCursor(final Cursor cursor) { + if (cursor != null) { + mIdIdx = cursor.getColumnIndex(CachedUsers.USER_ID); + mNameIdx = cursor.getColumnIndex(CachedUsers.NAME); + mScreenNameIdx = cursor.getColumnIndex(CachedUsers.SCREEN_NAME); + mProfileImageIdx = cursor.getColumnIndex(CachedUsers.PROFILE_IMAGE_URL); + } + return super.swapCursor(cursor); + } + + +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/BaseFiltersFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/BaseFiltersFragment.java index 45208bf9b..8a8a9b30a 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/BaseFiltersFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/BaseFiltersFragment.java @@ -59,8 +59,8 @@ import org.mariotaku.sqliteqb.library.Expression; import org.mariotaku.sqliteqb.library.RawItemArray; import org.mariotaku.twidere.R; import org.mariotaku.twidere.activity.support.UserListSelectorActivity; +import org.mariotaku.twidere.adapter.ComposeAutoCompleteAdapter; import org.mariotaku.twidere.adapter.SourceAutoCompleteAdapter; -import org.mariotaku.twidere.adapter.UserHashtagAutoCompleteAdapter; import org.mariotaku.twidere.fragment.support.AbsContentListViewFragment; import org.mariotaku.twidere.fragment.support.BaseSupportDialogFragment; import org.mariotaku.twidere.model.ParcelableUser; @@ -292,7 +292,7 @@ public abstract class BaseFiltersFragment extends AbsContentListViewFragment parent, View view, int position, long id) { + removeIMESuggestions(); + } + }); + // HACK: remove AUTO_COMPLETE flag to force IME show auto completion + setRawInputType(getInputType() & ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE); + } + + @Override + public void setBackgroundTintColor(@NonNull ColorStateList color) { + setSupportBackgroundTintList(color); + } + + public void setAccountId(long accountId) { + mAccountId = accountId; + updateAccountId(); + } + + @Override + protected MovementMethod getDefaultMovementMethod() { + return ArrowKeyMovementMethod.getInstance(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (!isInEditMode() && mAdapter == null) { - mAdapter = new UserHashtagAutoCompleteAdapter(getContext()); + mAdapter = new ComposeAutoCompleteAdapter(getContext()); } setAdapter(mAdapter); updateAccountId(); @@ -66,13 +96,14 @@ public class ComposeEditText extends AppCompatMultiAutoCompleteTextView { } } - public void setAccountId(long accountId) { - mAccountId = accountId; - updateAccountId(); - } - private void updateAccountId() { if (mAdapter == null) return; mAdapter.setAccountId(mAccountId); } + + private void removeIMESuggestions() { + final int selectionEnd = getSelectionEnd(), selectionStart = getSelectionStart(); + Selection.removeSelection(getText()); + setSelection(selectionStart, selectionEnd); + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/ComposeMaterialEditText.java b/twidere/src/main/java/org/mariotaku/twidere/view/ComposeMaterialEditText.java deleted file mode 100644 index 5e3266e17..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/view/ComposeMaterialEditText.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2014 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.view; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.support.annotation.NonNull; -import android.support.v7.widget.AppCompatMultiAutoCompleteTextView; -import android.text.InputType; -import android.text.method.ArrowKeyMovementMethod; -import android.util.AttributeSet; - -import org.mariotaku.twidere.R; -import org.mariotaku.twidere.adapter.UserHashtagAutoCompleteAdapter; -import org.mariotaku.twidere.util.widget.StatusTextTokenizer; -import org.mariotaku.twidere.view.iface.IThemeBackgroundTintView; - -public class ComposeMaterialEditText extends AppCompatMultiAutoCompleteTextView implements IThemeBackgroundTintView { - - private UserHashtagAutoCompleteAdapter mAdapter; - private long mAccountId; - - public ComposeMaterialEditText(final Context context) { - this(context, null); - } - - public ComposeMaterialEditText(final Context context, final AttributeSet attrs) { - this(context, attrs, R.attr.autoCompleteTextViewStyle); - } - - public ComposeMaterialEditText(final Context context, final AttributeSet attrs, final int defStyle) { - super(context, attrs, defStyle); - setTokenizer(new StatusTextTokenizer()); - setMovementMethod(ArrowKeyMovementMethod.getInstance()); - setupComposeInputType(); - } - - private void setupComposeInputType() { - int rawInputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; - rawInputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE; - setRawInputType(rawInputType); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (!isInEditMode() && mAdapter == null) { - mAdapter = new UserHashtagAutoCompleteAdapter(getContext()); - } - setAdapter(mAdapter); - updateAccountId(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (mAdapter != null) { - mAdapter.closeCursor(); - mAdapter = null; - } - } - - @Override - public void setBackgroundTintColor(@NonNull ColorStateList color) { - setSupportBackgroundTintList(color); - } - - public void setAccountId(long accountId) { - mAccountId = accountId; - updateAccountId(); - } - - private void updateAccountId() { - if (mAdapter == null) return; - mAdapter.setAccountId(mAccountId); - } - - @Override - protected void replaceText(final CharSequence text) { - super.replaceText(text); - append(" "); - } - -} diff --git a/twidere/src/main/res/layout/activity_compose.xml b/twidere/src/main/res/layout/activity_compose.xml index 64f4efefd..10ee903c6 100644 --- a/twidere/src/main/res/layout/activity_compose.xml +++ b/twidere/src/main/res/layout/activity_compose.xml @@ -44,7 +44,7 @@ android:alpha="0.2" android:numColumns="@integer/grid_column_image_preview" android:stretchMode="columnWidth" - tools:listitem="@layout/grid_item_image_preview" /> + tools:listitem="@layout/gallery_item_image_preview" /> + android:scrollbars="vertical" + android:singleLine="false" /> - - +