improved global search
This commit is contained in:
parent
984f7b9317
commit
8f19921243
|
@ -46,7 +46,7 @@ dependencies {
|
|||
compile 'org.apache.commons:commons-lang3:3.4'
|
||||
compile 'com.github.mariotaku:RestFu:0.9.2'
|
||||
compile 'com.hannesdorfmann.parcelableplease:annotation:1.0.1'
|
||||
compile 'com.github.mariotaku:SQLiteQB:901dd5e72f'
|
||||
compile 'com.github.mariotaku:SQLiteQB:88291f3a28'
|
||||
compile 'com.github.mariotaku.LoganSquareExtension:core:b6f53c9a4d'
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
|
|
@ -202,6 +202,8 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
|
|||
int VIRTUAL_TABLE_ID_CACHED_USERS_WITH_SCORE = 122;
|
||||
int VIRTUAL_TABLE_ID_DRAFTS_UNSENT = 131;
|
||||
int VIRTUAL_TABLE_ID_DRAFTS_NOTIFICATIONS = 132;
|
||||
int VIRTUAL_TABLE_ID_SUGGESTIONS_AUTO_COMPLETE = 141;
|
||||
int VIRTUAL_TABLE_ID_SUGGESTIONS_SEARCH = 142;
|
||||
|
||||
int NOTIFICATION_ID_HOME_TIMELINE = 1;
|
||||
int NOTIFICATION_ID_MENTIONS_TIMELINE = 2;
|
||||
|
|
|
@ -48,6 +48,10 @@ public class ProfileUpdate extends SimpleValueMap {
|
|||
put("profile_link_color", String.format(Locale.ROOT, "%06X", 0xFFFFFF & profileLinkColor));
|
||||
}
|
||||
|
||||
public void setBackgroundColor(int profileLinkColor) {
|
||||
put("profile_background_color", String.format(Locale.ROOT, "%06X", 0xFFFFFF & profileLinkColor));
|
||||
}
|
||||
|
||||
public ProfileUpdate name(String name) {
|
||||
setName(name);
|
||||
return this;
|
||||
|
@ -72,4 +76,9 @@ public class ProfileUpdate extends SimpleValueMap {
|
|||
setLinkColor(linkColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProfileUpdate backgroundColor(int linkColor) {
|
||||
setBackgroundColor(linkColor);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import android.support.annotation.NonNull;
|
|||
import com.bluelinelabs.logansquare.annotation.JsonField;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject;
|
||||
|
||||
import org.mariotaku.library.logansquare.extension.LoganSquareWrapper;
|
||||
import org.mariotaku.twidere.api.twitter.model.DirectMessage;
|
||||
import org.mariotaku.twidere.api.twitter.model.User;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages;
|
||||
|
@ -108,25 +109,6 @@ public class ParcelableDirectMessage implements Parcelable, Comparable<Parcelabl
|
|||
public ParcelableDirectMessage() {
|
||||
}
|
||||
|
||||
public ParcelableDirectMessage(final ContentValues values) {
|
||||
text_plain = values.getAsString(DirectMessages.TEXT_PLAIN);
|
||||
text_html = values.getAsString(DirectMessages.TEXT_HTML);
|
||||
text_unescaped = toPlainText(text_html);
|
||||
sender_screen_name = values.getAsString(DirectMessages.SENDER_SCREEN_NAME);
|
||||
sender_profile_image_url = values.getAsString(DirectMessages.SENDER_PROFILE_IMAGE_URL);
|
||||
sender_name = values.getAsString(DirectMessages.SENDER_NAME);
|
||||
sender_id = getAsLong(values, DirectMessages.SENDER_ID, -1);
|
||||
recipient_screen_name = values.getAsString(DirectMessages.RECIPIENT_SCREEN_NAME);
|
||||
recipient_profile_image_url = values.getAsString(DirectMessages.RECIPIENT_PROFILE_IMAGE_URL);
|
||||
recipient_name = values.getAsString(DirectMessages.RECIPIENT_NAME);
|
||||
recipient_id = getAsLong(values, DirectMessages.RECIPIENT_ID, -1);
|
||||
timestamp = getAsLong(values, DirectMessages.MESSAGE_TIMESTAMP, -1);
|
||||
id = getAsLong(values, DirectMessages.MESSAGE_ID, -1);
|
||||
is_outgoing = getAsBoolean(values, DirectMessages.IS_OUTGOING, false);
|
||||
account_id = getAsLong(values, DirectMessages.ACCOUNT_ID, -1);
|
||||
media = ParcelableMedia.fromSerializedJson(values.getAsString(DirectMessages.MEDIA_JSON));
|
||||
}
|
||||
|
||||
public ParcelableDirectMessage(final Cursor c, final CursorIndices idx) {
|
||||
account_id = idx.account_id != -1 ? c.getLong(idx.account_id) : -1;
|
||||
is_outgoing = idx.is_outgoing != -1 && c.getShort(idx.is_outgoing) == 1;
|
||||
|
|
|
@ -204,9 +204,9 @@ public interface TwidereDataStore {
|
|||
|
||||
String CONTENT_PATH = TABLE_NAME;
|
||||
|
||||
String CONTENT_PATH_WITH_RELATIONSHIP = TABLE_NAME + "/with_relationship";
|
||||
String CONTENT_PATH_WITH_RELATIONSHIP = CONTENT_PATH + "/with_relationship";
|
||||
|
||||
String CONTENT_PATH_WITH_SCORE = TABLE_NAME + "/with_score";
|
||||
String CONTENT_PATH_WITH_SCORE = CONTENT_PATH + "/with_score";
|
||||
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
|
||||
|
||||
|
@ -288,11 +288,40 @@ public interface TwidereDataStore {
|
|||
|
||||
interface Suggestions extends BaseColumns {
|
||||
String TYPE = "type";
|
||||
String VALUE1 = "value1";
|
||||
String VALUE2 = "value2";
|
||||
String TITLE = "title";
|
||||
String SUMMARY = "summary";
|
||||
String ICON = "icon";
|
||||
String EXTRA_ID = "extra_id";
|
||||
String EXTRA = "extra";
|
||||
|
||||
interface Compose extends Suggestions {
|
||||
String TABLE_NAME = "suggestions";
|
||||
|
||||
String CONTENT_PATH = TABLE_NAME;
|
||||
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
|
||||
|
||||
String[] COLUMNS = {_ID, TYPE, TITLE, SUMMARY, ICON, EXTRA_ID, EXTRA};
|
||||
String[] TYPES = {TYPE_PRIMARY_KEY, TYPE_TEXT_NOT_NULL, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT,
|
||||
TYPE_INT, TYPE_TEXT};
|
||||
|
||||
interface AutoComplete extends Suggestions {
|
||||
|
||||
String TYPE_USERS = "users";
|
||||
String TYPE_HASHTAGS = "hashtags";
|
||||
|
||||
String CONTENT_PATH = Suggestions.CONTENT_PATH + "/auto_complete";
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
|
||||
}
|
||||
|
||||
interface Search extends Suggestions {
|
||||
|
||||
String CONTENT_PATH = Suggestions.CONTENT_PATH + "/search";
|
||||
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
|
||||
|
||||
String TYPE_SAVED_SEARCH = "saved_search";
|
||||
String TYPE_USER = "user";
|
||||
String TYPE_SEARCH_HISTORY = "search_history";
|
||||
String TYPE_SCREEN_NAME = "screen_name";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ package org.mariotaku.twidere.util;
|
|||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
import org.apache.commons.lang3.text.translate.AggregateTranslator;
|
||||
import org.apache.commons.lang3.text.translate.CodePointTranslator;
|
||||
import org.apache.commons.lang3.text.translate.EntityArrays;
|
||||
import org.apache.commons.lang3.text.translate.LookupTranslator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
@ -30,6 +32,7 @@ public class HtmlEscapeHelper {
|
|||
|
||||
public static final AggregateTranslator ESCAPE_HTML = new AggregateTranslator(StringEscapeUtils.ESCAPE_HTML4,
|
||||
new UnicodeControlCharacterToHtmlTranslator());
|
||||
public static final LookupTranslator ESCAPE_BASIC = new LookupTranslator(EntityArrays.BASIC_ESCAPE());
|
||||
|
||||
public static String escape(final CharSequence text) {
|
||||
if (text == null) return null;
|
||||
|
@ -51,6 +54,10 @@ public class HtmlEscapeHelper {
|
|||
return StringEscapeUtils.unescapeHtml4(string);
|
||||
}
|
||||
|
||||
public static String escapeBasic(CharSequence text) {
|
||||
return ESCAPE_BASIC.translate(text);
|
||||
}
|
||||
|
||||
private static class UnicodeControlCharacterToHtmlTranslator extends CodePointTranslator {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -186,6 +186,14 @@ public final class ParseUtils {
|
|||
return parseString(object, null);
|
||||
}
|
||||
|
||||
public static String parseString(final int object) {
|
||||
return String.valueOf(object);
|
||||
}
|
||||
|
||||
public static String parseString(final long object) {
|
||||
return String.valueOf(object);
|
||||
}
|
||||
|
||||
public static String parseString(final Object object, final String def) {
|
||||
if (object == null) return def;
|
||||
return String.valueOf(object);
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
package org.mariotaku.twidere.activity.support;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
|
@ -29,7 +28,7 @@ import android.net.Uri;
|
|||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
|
@ -45,53 +44,38 @@ 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 org.apache.commons.lang3.ArrayUtils;
|
||||
import org.mariotaku.sqliteqb.library.Columns.Column;
|
||||
import org.mariotaku.sqliteqb.library.Expression;
|
||||
import org.mariotaku.sqliteqb.library.OrderBy;
|
||||
import org.mariotaku.sqliteqb.library.RawItemArray;
|
||||
import com.mobeta.android.dslv.DragSortCursorAdapter;
|
||||
|
||||
import org.mariotaku.twidere.R;
|
||||
import org.mariotaku.twidere.activity.support.QuickSearchBarActivity.SuggestionItem;
|
||||
import org.mariotaku.twidere.adapter.AccountsSpinnerAdapter;
|
||||
import org.mariotaku.twidere.model.ParcelableAccount;
|
||||
import org.mariotaku.twidere.model.ParcelableCredentials;
|
||||
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.provider.TwidereDataStore.Suggestions;
|
||||
import org.mariotaku.twidere.util.EditTextEnterHandler;
|
||||
import org.mariotaku.twidere.util.EditTextEnterHandler.EnterListener;
|
||||
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
|
||||
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.UserColorNameManager;
|
||||
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 java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/1/6.
|
||||
*/
|
||||
public class QuickSearchBarActivity extends ThemedFragmentActivity implements OnClickListener,
|
||||
LoaderCallbacks<List<SuggestionItem>>, OnItemSelectedListener, OnItemClickListener,
|
||||
DismissCallbacks, OnFitSystemWindowsListener {
|
||||
LoaderCallbacks<Cursor>, OnItemSelectedListener, OnItemClickListener,
|
||||
OnFitSystemWindowsListener {
|
||||
|
||||
private Spinner mAccountSpinner;
|
||||
private EditText mSearchQuery;
|
||||
|
@ -102,25 +86,19 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
|
|||
private Rect mSystemWindowsInsets = new Rect();
|
||||
private boolean mTextChanged;
|
||||
|
||||
@Override
|
||||
public boolean canDismiss(int position) {
|
||||
return mUsersSearchAdapter.canDismiss(position);
|
||||
}
|
||||
|
||||
@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);
|
||||
// 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
|
||||
|
@ -154,18 +132,22 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
|
|||
}
|
||||
|
||||
@Override
|
||||
public Loader<List<SuggestionItem>> onCreateLoader(int id, Bundle args) {
|
||||
return new SuggestionsLoader(this, mAccountSpinner.getSelectedItemId(), mSearchQuery.getText().toString());
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
final long accountId = getAccountId();
|
||||
final Uri.Builder builder = Suggestions.Search.CONTENT_URI.buildUpon();
|
||||
builder.appendQueryParameter(QUERY_PARAM_QUERY, ParseUtils.parseString(mSearchQuery.getText()));
|
||||
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, ParseUtils.parseString(accountId));
|
||||
return new CursorLoader(this, builder.build(), Suggestions.Search.COLUMNS, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<List<SuggestionItem>> loader, List<SuggestionItem> data) {
|
||||
mUsersSearchAdapter.setData(data);
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
mUsersSearchAdapter.changeCursor(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<List<SuggestionItem>> loader) {
|
||||
mUsersSearchAdapter.setData(null);
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
mUsersSearchAdapter.changeCursor(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -176,8 +158,25 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
|
|||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final SuggestionItem item = mUsersSearchAdapter.getItem(position);
|
||||
item.onItemClick(this, position);
|
||||
final SuggestionItem item = mUsersSearchAdapter.getSuggestionItem(position);
|
||||
switch (mUsersSearchAdapter.getItemViewType(position)) {
|
||||
case SuggestionsAdapter.VIEW_TYPE_USER_SUGGESTION_ITEM: {
|
||||
Utils.openUserProfile(this, getAccountId(), item.extra_id, item.summary, null);
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
case SuggestionsAdapter.VIEW_TYPE_USER_SCREEN_NAME: {
|
||||
Utils.openUserProfile(this, getAccountId(), -1, item.title, null);
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
case SuggestionsAdapter.VIEW_TYPE_SAVED_SEARCH:
|
||||
case SuggestionsAdapter.VIEW_TYPE_SEARCH_HISTORY: {
|
||||
Utils.openSearch(this, getAccountId(), item.title);
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -225,9 +224,6 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
|
|||
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);
|
||||
|
||||
EditTextEnterHandler.attach(mSearchQuery, new EnterListener() {
|
||||
|
@ -256,11 +252,7 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
|
|||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (Utils.removeLineBreaks(s)) {
|
||||
doSearch();
|
||||
} else {
|
||||
getSupportLoaderManager().restartLoader(0, null, QuickSearchBarActivity.this);
|
||||
}
|
||||
getSupportLoaderManager().restartLoader(0, null, QuickSearchBarActivity.this);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -286,10 +278,6 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
|
|||
return mAccountSpinner.getSelectedItemId();
|
||||
}
|
||||
|
||||
private static int getHistorySize(CharSequence query) {
|
||||
return TextUtils.isEmpty(query) ? 3 : 2;
|
||||
}
|
||||
|
||||
private void updateWindowAttributes() {
|
||||
final Window window = getWindow();
|
||||
final WindowManager.LayoutParams attributes = window.getAttributes();
|
||||
|
@ -298,190 +286,136 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
|
|||
window.setAttributes(attributes);
|
||||
}
|
||||
|
||||
interface SuggestionItem {
|
||||
|
||||
void bindView(SuggestionsAdapter adapter, View view, int position);
|
||||
|
||||
int getItemLayoutResource();
|
||||
|
||||
int getItemViewType();
|
||||
|
||||
boolean isEnabled();
|
||||
|
||||
void onItemClick(QuickSearchBarActivity activity, int position);
|
||||
|
||||
}
|
||||
|
||||
static abstract class BaseClickableItem implements SuggestionItem {
|
||||
@Override
|
||||
public final boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class SavedSearchItem extends BaseClickableItem {
|
||||
|
||||
static final int ITEM_VIEW_TYPE = 1;
|
||||
private final String mQuery;
|
||||
|
||||
public SavedSearchItem(String query) {
|
||||
mQuery = query;
|
||||
}
|
||||
|
||||
@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);
|
||||
final View editQuery = view.findViewById(R.id.edit_query);
|
||||
editQuery.setTag(position);
|
||||
editQuery.setOnClickListener(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getItemLayoutResource() {
|
||||
return R.layout.list_item_suggestion_search;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType() {
|
||||
return ITEM_VIEW_TYPE;
|
||||
}
|
||||
|
||||
public String getQuery() {
|
||||
return mQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(QuickSearchBarActivity activity, int position) {
|
||||
Utils.openSearch(activity, activity.getAccountId(), mQuery);
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
static class SearchHistoryItem extends BaseClickableItem {
|
||||
|
||||
static final int ITEM_VIEW_TYPE = 0;
|
||||
private final long mCursorId;
|
||||
private final String mQuery;
|
||||
|
||||
public SearchHistoryItem(long cursorId, String query) {
|
||||
mCursorId = cursorId;
|
||||
mQuery = query;
|
||||
}
|
||||
|
||||
public long getCursorId() {
|
||||
return mCursorId;
|
||||
}
|
||||
|
||||
public String getQuery() {
|
||||
return mQuery;
|
||||
}
|
||||
|
||||
@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);
|
||||
final View editQuery = view.findViewById(R.id.edit_query);
|
||||
editQuery.setTag(position);
|
||||
editQuery.setOnClickListener(adapter);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void setSearchQuery(String query) {
|
||||
mSearchQuery.setText(query);
|
||||
if (query == null) return;
|
||||
mSearchQuery.setSelection(query.length());
|
||||
}
|
||||
|
||||
public static class SuggestionsAdapter extends BaseAdapter implements OnClickListener {
|
||||
static class SuggestionItem {
|
||||
|
||||
|
||||
public final String title, summary;
|
||||
private final long extra_id;
|
||||
|
||||
public SuggestionItem(Cursor cursor, SuggestionsAdapter.Indices indices) {
|
||||
title = cursor.getString(indices.title);
|
||||
summary = cursor.getString(indices.summary);
|
||||
extra_id = cursor.getLong(indices.extra_id);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SuggestionsAdapter extends DragSortCursorAdapter implements OnClickListener {
|
||||
|
||||
static final int VIEW_TYPE_SEARCH_HISTORY = 0;
|
||||
static final int VIEW_TYPE_SAVED_SEARCH = 1;
|
||||
static final int VIEW_TYPE_USER_SUGGESTION_ITEM = 2;
|
||||
static final int VIEW_TYPE_USER_SCREEN_NAME = 3;
|
||||
|
||||
private final QuickSearchBarActivity mActivity;
|
||||
private final LayoutInflater mInflater;
|
||||
private final MediaLoaderWrapper mImageLoader;
|
||||
private final UserColorNameManager mUserColorNameManager;
|
||||
private List<SuggestionItem> mData;
|
||||
private final QuickSearchBarActivity mActivity;
|
||||
private Indices mIndices;
|
||||
|
||||
SuggestionsAdapter(QuickSearchBarActivity activity) {
|
||||
super(activity, null, 0);
|
||||
mActivity = activity;
|
||||
mImageLoader = activity.mImageLoader;
|
||||
mUserColorNameManager = activity.mUserColorNameManager;
|
||||
mInflater = LayoutInflater.from(activity);
|
||||
}
|
||||
|
||||
public boolean canDismiss(int position) {
|
||||
return getItemViewType(position) == SearchHistoryItem.ITEM_VIEW_TYPE;
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mActivity;
|
||||
}
|
||||
|
||||
@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;
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
switch (getItemViewType(cursor.getPosition())) {
|
||||
case VIEW_TYPE_SEARCH_HISTORY:
|
||||
case VIEW_TYPE_SAVED_SEARCH: {
|
||||
final View view = mInflater.inflate(R.layout.list_item_suggestion_search, parent, false);
|
||||
final SearchViewHolder holder = new SearchViewHolder(view);
|
||||
holder.edit_query.setOnClickListener(this);
|
||||
view.setTag(holder);
|
||||
return view;
|
||||
}
|
||||
case VIEW_TYPE_USER_SUGGESTION_ITEM:
|
||||
case VIEW_TYPE_USER_SCREEN_NAME: {
|
||||
final View view = mInflater.inflate(R.layout.list_item_suggestion_user, parent, false);
|
||||
view.setTag(new UserViewHolder(view));
|
||||
return view;
|
||||
}
|
||||
}
|
||||
item.bindView(this, view, position);
|
||||
return view;
|
||||
throw new UnsupportedOperationException("Unknown viewType");
|
||||
}
|
||||
|
||||
public MediaLoaderWrapper getImageLoader() {
|
||||
return mImageLoader;
|
||||
public SuggestionItem getSuggestionItem(int position) {
|
||||
final Cursor cursor = (Cursor) super.getItem(position);
|
||||
return new SuggestionItem(cursor, mIndices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return getItem(position).isEnabled();
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
switch (getItemViewType(cursor.getPosition())) {
|
||||
case VIEW_TYPE_SEARCH_HISTORY: {
|
||||
final SearchViewHolder holder = (SearchViewHolder) view.getTag();
|
||||
final String title = cursor.getString(mIndices.title);
|
||||
holder.edit_query.setTag(title);
|
||||
holder.text1.setText(title);
|
||||
holder.icon.setImageResource(R.drawable.ic_action_history);
|
||||
break;
|
||||
}
|
||||
case VIEW_TYPE_SAVED_SEARCH: {
|
||||
final SearchViewHolder holder = (SearchViewHolder) view.getTag();
|
||||
final String title = cursor.getString(mIndices.title);
|
||||
holder.edit_query.setTag(title);
|
||||
holder.text1.setText(title);
|
||||
holder.icon.setImageResource(R.drawable.ic_action_save);
|
||||
break;
|
||||
}
|
||||
case VIEW_TYPE_USER_SUGGESTION_ITEM: {
|
||||
final UserViewHolder holder = (UserViewHolder) view.getTag();
|
||||
holder.text1.setText(mUserColorNameManager.getUserNickname(cursor.getLong(mIndices.extra_id),
|
||||
cursor.getString(mIndices.title), false));
|
||||
holder.text2.setVisibility(View.VISIBLE);
|
||||
holder.text2.setText(String.format("@%s", cursor.getString(mIndices.summary)));
|
||||
holder.icon.clearColorFilter();
|
||||
mImageLoader.displayProfileImage(holder.icon, cursor.getString(mIndices.icon));
|
||||
break;
|
||||
}
|
||||
case VIEW_TYPE_USER_SCREEN_NAME: {
|
||||
final UserViewHolder holder = (UserViewHolder) view.getTag();
|
||||
holder.text1.setText(String.format("@%s", cursor.getString(mIndices.title)));
|
||||
holder.text2.setVisibility(View.GONE);
|
||||
holder.icon.setColorFilter(holder.text1.getCurrentTextColor(), Mode.SRC_ATOP);
|
||||
mImageLoader.cancelDisplayTask(holder.icon);
|
||||
holder.icon.setImageResource(R.drawable.ic_action_user);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (mData == null) return IGNORE_ITEM_VIEW_TYPE;
|
||||
return mData.get(position).getItemViewType();
|
||||
final Cursor cursor = (Cursor) getItem(position);
|
||||
switch (cursor.getString(mIndices.type)) {
|
||||
case Suggestions.Search.TYPE_SAVED_SEARCH: {
|
||||
return VIEW_TYPE_SAVED_SEARCH;
|
||||
}
|
||||
case Suggestions.Search.TYPE_SCREEN_NAME: {
|
||||
return VIEW_TYPE_USER_SCREEN_NAME;
|
||||
}
|
||||
case Suggestions.Search.TYPE_SEARCH_HISTORY: {
|
||||
return VIEW_TYPE_SEARCH_HISTORY;
|
||||
}
|
||||
case Suggestions.Search.TYPE_USER: {
|
||||
return VIEW_TYPE_USER_SUGGESTION_ITEM;
|
||||
}
|
||||
}
|
||||
return IGNORE_ITEM_VIEW_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -489,206 +423,67 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
|
|||
return 4;
|
||||
}
|
||||
|
||||
public void removeItemAt(int position) {
|
||||
if (mData == null) return;
|
||||
mData.remove(position);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setData(List<SuggestionItem> data) {
|
||||
mData = data;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public UserColorNameManager getUserColorNameManager() {
|
||||
return mUserColorNameManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.edit_query: {
|
||||
final SuggestionItem item = getItem((Integer) v.getTag());
|
||||
if (item instanceof SearchHistoryItem) {
|
||||
mActivity.setSearchQuery(((SearchHistoryItem) item).getQuery());
|
||||
} else if (item instanceof SavedSearchItem) {
|
||||
mActivity.setSearchQuery(((SavedSearchItem) item).getQuery());
|
||||
}
|
||||
mActivity.setSearchQuery((String) v.getTag());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class SuggestionsLoader extends AsyncTaskLoader<List<SuggestionItem>> {
|
||||
|
||||
private static final Pattern PATTERN_SCREEN_NAME = Pattern.compile("(?i)[@\uFF20]?([a-z0-9_]{1,20})");
|
||||
|
||||
private final UserColorNameManager mUserColorNameManager;
|
||||
private final long mAccountId;
|
||||
private final String mQuery;
|
||||
|
||||
public SuggestionsLoader(QuickSearchBarActivity context, long accountId, String query) {
|
||||
super(context);
|
||||
mUserColorNameManager = context.mUserColorNameManager;
|
||||
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 long[] nicknameIds = Utils.getMatchedNicknameIds(mQuery, mUserColorNameManager);
|
||||
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.getSQL(), 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) {
|
||||
final Matcher m = PATTERN_SCREEN_NAME.matcher(mQuery);
|
||||
if (m.matches()) {
|
||||
result.add(screenNamePos, new UserScreenNameItem(m.group(1), mAccountId));
|
||||
}
|
||||
}
|
||||
usersCursor.close();
|
||||
public Cursor swapCursor(Cursor newCursor) {
|
||||
if (newCursor != null) {
|
||||
mIndices = new Indices(newCursor);
|
||||
} 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();
|
||||
mIndices = null;
|
||||
}
|
||||
return result;
|
||||
return super.swapCursor(newCursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
static class SearchViewHolder {
|
||||
|
||||
static class UserScreenNameItem extends BaseClickableItem {
|
||||
private final ImageView icon;
|
||||
private final TextView text1;
|
||||
private final View edit_query;
|
||||
|
||||
static final int ITEM_VIEW_TYPE = 3;
|
||||
private final String mScreenName;
|
||||
private final long mAccountId;
|
||||
SearchViewHolder(View view) {
|
||||
icon = (ImageView) view.findViewById(android.R.id.icon);
|
||||
text1 = (TextView) view.findViewById(android.R.id.text1);
|
||||
edit_query = view.findViewById(R.id.edit_query);
|
||||
}
|
||||
|
||||
public UserScreenNameItem(String screenName, long accountId) {
|
||||
mScreenName = screenName;
|
||||
mAccountId = accountId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType() {
|
||||
return ITEM_VIEW_TYPE;
|
||||
static class UserViewHolder {
|
||||
|
||||
private final ImageView icon;
|
||||
private final TextView text1;
|
||||
private final TextView text2;
|
||||
|
||||
UserViewHolder(View view) {
|
||||
icon = (ImageView) view.findViewById(android.R.id.icon);
|
||||
text1 = (TextView) view.findViewById(android.R.id.text1);
|
||||
text2 = (TextView) view.findViewById(android.R.id.text2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(QuickSearchBarActivity activity, int position) {
|
||||
Utils.openUserProfile(activity, mAccountId, -1, mScreenName, null);
|
||||
activity.finish();
|
||||
}
|
||||
private static class Indices {
|
||||
private final int type;
|
||||
private final int title;
|
||||
private final int summary;
|
||||
private final int icon;
|
||||
private final int extra_id;
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
static class UserSuggestionItem extends BaseClickableItem {
|
||||
|
||||
static final int ITEM_VIEW_TYPE = 2;
|
||||
private final ParcelableUser mUser;
|
||||
|
||||
public UserSuggestionItem(Cursor c, CachedIndices i, long accountId) {
|
||||
mUser = new ParcelableUser(c, i, accountId);
|
||||
}
|
||||
|
||||
public ParcelableUser getUser() {
|
||||
return mUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType() {
|
||||
return ITEM_VIEW_TYPE;
|
||||
}
|
||||
|
||||
|
||||
@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 MediaLoaderWrapper loader = adapter.getImageLoader();
|
||||
final UserColorNameManager manager = adapter.getUserColorNameManager();
|
||||
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(manager.getUserNickname(user.id, user.name, false));
|
||||
text2.setVisibility(View.VISIBLE);
|
||||
text2.setText("@" + user.screen_name);
|
||||
icon.clearColorFilter();
|
||||
loader.displayProfileImage(icon, user.profile_image_url);
|
||||
public Indices(Cursor cursor) {
|
||||
type = cursor.getColumnIndex(Suggestions.TYPE);
|
||||
title = cursor.getColumnIndex(Suggestions.TITLE);
|
||||
summary = cursor.getColumnIndex(Suggestions.SUMMARY);
|
||||
icon = cursor.getColumnIndex(Suggestions.ICON);
|
||||
extra_id = cursor.getColumnIndex(Suggestions.EXTRA_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import android.widget.TextView;
|
|||
|
||||
import org.mariotaku.twidere.R;
|
||||
import org.mariotaku.twidere.util.HtmlEscapeHelper;
|
||||
import org.mariotaku.twidere.util.HtmlSpanBuilder;
|
||||
import org.mariotaku.twidere.util.PermissionsManager;
|
||||
|
||||
import static android.text.TextUtils.isEmpty;
|
||||
|
@ -144,7 +145,7 @@ public class RequestPermissionsActivity extends BaseSupportDialogActivity implem
|
|||
} else {
|
||||
appendPermission(builder, getString(R.string.permission_description_none), false);
|
||||
}
|
||||
mMessageView.setText(Html.fromHtml(builder.toString()));
|
||||
mMessageView.setText(HtmlSpanBuilder.fromHtml(builder.toString()));
|
||||
} catch (final NameNotFoundException e) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
|
|
|
@ -19,35 +19,22 @@
|
|||
|
||||
package org.mariotaku.twidere.adapter;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.PorterDuff.Mode;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.widget.SimpleCursorAdapter;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FilterQueryProvider;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.mariotaku.sqliteqb.library.Columns.Column;
|
||||
import org.mariotaku.sqliteqb.library.Expression;
|
||||
import org.mariotaku.sqliteqb.library.OrderBy;
|
||||
import org.mariotaku.sqliteqb.library.RawItemArray;
|
||||
import org.mariotaku.twidere.BuildConfig;
|
||||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.R;
|
||||
import org.mariotaku.twidere.app.TwidereApplication;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.CachedHashtags;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.CachedValues;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Suggestions;
|
||||
import org.mariotaku.twidere.util.MediaLoaderWrapper;
|
||||
import org.mariotaku.twidere.util.ParseUtils;
|
||||
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;
|
||||
|
@ -60,10 +47,6 @@ public class UserHashtagAutoCompleteAdapter extends SimpleCursorAdapter implemen
|
|||
private static final String[] FROM = new String[0];
|
||||
private static final int[] TO = new int[0];
|
||||
|
||||
@NonNull
|
||||
private final ContentResolver mResolver;
|
||||
@NonNull
|
||||
private final SQLiteDatabase mDatabase;
|
||||
@Inject
|
||||
MediaLoaderWrapper mProfileImageLoader;
|
||||
@Inject
|
||||
|
@ -71,32 +54,18 @@ public class UserHashtagAutoCompleteAdapter extends SimpleCursorAdapter implemen
|
|||
@Inject
|
||||
UserColorNameManager mUserColorNameManager;
|
||||
|
||||
private final EditText mEditText;
|
||||
|
||||
private final boolean mDisplayProfileImage;
|
||||
|
||||
private int mProfileImageUrlIdx, mNameIdx, mScreenNameIdx, mUserIdIdx;
|
||||
private char mToken = '@';
|
||||
private int mTypeIdx, mIconIdx, mTitleIdx, mSummaryIdx, mExtraIdIdx;
|
||||
private long mAccountId;
|
||||
private char mToken;
|
||||
|
||||
public UserHashtagAutoCompleteAdapter(final Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public UserHashtagAutoCompleteAdapter(final Context context, final EditText view) {
|
||||
super(context, R.layout.list_item_auto_complete, null, FROM, TO, 0);
|
||||
DaggerGeneralComponent.builder().applicationModule(ApplicationModule.get(context)).build().inject(this);
|
||||
mEditText = view;
|
||||
final TwidereApplication app = TwidereApplication.getInstance(context);
|
||||
mResolver = context.getContentResolver();
|
||||
mDatabase = app.getSQLiteDatabase();
|
||||
mDisplayProfileImage = mPreferences.getBoolean(KEY_DISPLAY_PROFILE_IMAGE, true);
|
||||
}
|
||||
|
||||
public UserHashtagAutoCompleteAdapter(final EditText view) {
|
||||
this(view.getContext(), view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(final View view, final Context context, final Cursor cursor) {
|
||||
if (isCursorClosed()) return;
|
||||
|
@ -107,26 +76,25 @@ public class UserHashtagAutoCompleteAdapter extends SimpleCursorAdapter implemen
|
|||
// Clear images in order to prevent images in recycled view shown.
|
||||
icon.setImageDrawable(null);
|
||||
|
||||
if (mScreenNameIdx != -1 && mNameIdx != -1 && mUserIdIdx != -1) {
|
||||
text1.setText(mUserColorNameManager.getUserNickname(cursor.getLong(mUserIdIdx), cursor.getString(mNameIdx)));
|
||||
text2.setText("@" + cursor.getString(mScreenNameIdx));
|
||||
} else {
|
||||
text1.setText("#" + cursor.getString(mNameIdx));
|
||||
text2.setText(R.string.hashtag);
|
||||
}
|
||||
icon.setVisibility(mDisplayProfileImage ? View.VISIBLE : View.GONE);
|
||||
if (mProfileImageUrlIdx != -1) {
|
||||
if (Suggestions.AutoComplete.TYPE_USERS.equals(cursor.getString(mTypeIdx))) {
|
||||
text1.setText(mUserColorNameManager.getUserNickname(cursor.getLong(mExtraIdIdx), cursor.getString(mTitleIdx)));
|
||||
text2.setText("@" + cursor.getString(mSummaryIdx));
|
||||
if (mDisplayProfileImage) {
|
||||
final String profileImageUrl = cursor.getString(mProfileImageUrlIdx);
|
||||
final String profileImageUrl = cursor.getString(mIconIdx);
|
||||
mProfileImageLoader.displayProfileImage(icon, profileImageUrl);
|
||||
} else {
|
||||
mProfileImageLoader.cancelDisplayTask(icon);
|
||||
}
|
||||
|
||||
icon.clearColorFilter();
|
||||
} else {
|
||||
text1.setText("#" + cursor.getString(mTitleIdx));
|
||||
text2.setText(R.string.hashtag);
|
||||
|
||||
icon.setImageResource(R.drawable.ic_action_hashtag);
|
||||
icon.setColorFilter(text1.getCurrentTextColor(), Mode.SRC_ATOP);
|
||||
}
|
||||
icon.setVisibility(mDisplayProfileImage ? View.VISIBLE : View.GONE);
|
||||
super.bindView(view, context, cursor);
|
||||
}
|
||||
|
||||
|
@ -141,7 +109,7 @@ public class UserHashtagAutoCompleteAdapter extends SimpleCursorAdapter implemen
|
|||
@Override
|
||||
public CharSequence convertToString(final Cursor cursor) {
|
||||
if (isCursorClosed()) return null;
|
||||
return cursor.getString(mScreenNameIdx != -1 ? mScreenNameIdx : mNameIdx);
|
||||
return cursor.getString(mSummaryIdx != -1 ? mSummaryIdx : mTitleIdx);
|
||||
}
|
||||
|
||||
public boolean isCursorClosed() {
|
||||
|
@ -151,45 +119,31 @@ public class UserHashtagAutoCompleteAdapter extends SimpleCursorAdapter implemen
|
|||
|
||||
@Override
|
||||
public Cursor runQueryOnBackgroundThread(final CharSequence constraint) {
|
||||
char token = mToken;
|
||||
if (mEditText != null && constraint != null) {
|
||||
final CharSequence text = mEditText.getText();
|
||||
token = text.charAt(mEditText.getSelectionEnd() - constraint.length() - 1);
|
||||
}
|
||||
if (isAtSymbol(token) == isAtSymbol(mToken)) {
|
||||
if (TextUtils.isEmpty(constraint)) return null;
|
||||
char token = constraint.charAt(0);
|
||||
if (getNormalizedSymbol(token) == getNormalizedSymbol(mToken)) {
|
||||
final FilterQueryProvider filter = getFilterQueryProvider();
|
||||
if (filter != null) return filter.runQuery(constraint);
|
||||
}
|
||||
mToken = token;
|
||||
final String constraintEscaped = constraint != null ? constraint.toString().replaceAll("_", "^_") : null;
|
||||
if (isAtSymbol(token)) {
|
||||
final Expression selection;
|
||||
final String[] selectionArgs;
|
||||
if (constraintEscaped != null) {
|
||||
final long[] nicknameIds = Utils.getMatchedNicknameIds(ParseUtils.parseString(constraint), mUserColorNameManager);
|
||||
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)));
|
||||
selectionArgs = new String[]{constraintEscaped, constraintEscaped};
|
||||
} else {
|
||||
selection = null;
|
||||
selectionArgs = null;
|
||||
final Uri.Builder builder = Suggestions.AutoComplete.CONTENT_URI.buildUpon();
|
||||
builder.appendQueryParameter(QUERY_PARAM_QUERY, String.valueOf(constraint.subSequence(1, constraint.length())));
|
||||
switch (getNormalizedSymbol(token)) {
|
||||
case '#': {
|
||||
builder.appendQueryParameter(QUERY_PARAM_TYPE, Suggestions.AutoComplete.TYPE_HASHTAGS);
|
||||
break;
|
||||
}
|
||||
case '@': {
|
||||
builder.appendQueryParameter(QUERY_PARAM_TYPE, Suggestions.AutoComplete.TYPE_USERS);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
final OrderBy orderBy = new OrderBy(new String[]{CachedUsers.LAST_SEEN, "score", CachedUsers.SCREEN_NAME,
|
||||
CachedUsers.NAME}, new boolean[]{false, false, true, true});
|
||||
final Cursor cursor = mResolver.query(Uri.withAppendedPath(CachedUsers.CONTENT_URI_WITH_SCORE, String.valueOf(mAccountId)),
|
||||
CachedUsers.BASIC_COLUMNS, selection != null ? selection.getSQL() : null, selectionArgs, orderBy.getSQL());
|
||||
if (BuildConfig.DEBUG && cursor == null) throw new NullPointerException();
|
||||
return cursor;
|
||||
} else {
|
||||
final String selection = constraintEscaped != null ? CachedHashtags.NAME + " LIKE ?||'%' ESCAPE '^'"
|
||||
: null;
|
||||
final String[] selectionArgs = constraintEscaped != null ? new String[]{constraintEscaped} : null;
|
||||
final Cursor cursor = mDatabase.query(true, CachedHashtags.TABLE_NAME, CachedHashtags.COLUMNS, selection, selectionArgs,
|
||||
null, null, CachedHashtags.NAME, null);
|
||||
if (BuildConfig.DEBUG && cursor == null) throw new NullPointerException();
|
||||
return cursor;
|
||||
}
|
||||
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(mAccountId));
|
||||
return mContext.getContentResolver().query(builder.build(), Suggestions.AutoComplete.COLUMNS,
|
||||
null, null, null);
|
||||
}
|
||||
|
||||
|
||||
|
@ -200,22 +154,26 @@ public class UserHashtagAutoCompleteAdapter extends SimpleCursorAdapter implemen
|
|||
@Override
|
||||
public Cursor swapCursor(final Cursor cursor) {
|
||||
if (cursor != null) {
|
||||
mNameIdx = cursor.getColumnIndex(CachedValues.NAME);
|
||||
mScreenNameIdx = cursor.getColumnIndex(CachedUsers.SCREEN_NAME);
|
||||
mUserIdIdx = cursor.getColumnIndex(CachedUsers.USER_ID);
|
||||
mProfileImageUrlIdx = cursor.getColumnIndex(CachedUsers.PROFILE_IMAGE_URL);
|
||||
mTypeIdx = cursor.getColumnIndex(Suggestions.AutoComplete.TYPE);
|
||||
mTitleIdx = cursor.getColumnIndex(Suggestions.AutoComplete.TITLE);
|
||||
mSummaryIdx = cursor.getColumnIndex(Suggestions.AutoComplete.SUMMARY);
|
||||
mExtraIdIdx = cursor.getColumnIndex(Suggestions.AutoComplete.EXTRA_ID);
|
||||
mIconIdx = cursor.getColumnIndex(Suggestions.AutoComplete.ICON);
|
||||
}
|
||||
return super.swapCursor(cursor);
|
||||
}
|
||||
|
||||
|
||||
private static boolean isAtSymbol(final char character) {
|
||||
private static char getNormalizedSymbol(final char character) {
|
||||
switch (character) {
|
||||
case '\uff20':
|
||||
case '@':
|
||||
return true;
|
||||
return '@';
|
||||
case '\uff03':
|
||||
case '#':
|
||||
return '#';
|
||||
}
|
||||
return false;
|
||||
return '\0';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -690,7 +690,7 @@ public class MessagesConversationFragment extends BaseSupportFragment implements
|
|||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
Utils.removeLineBreaks(s);
|
||||
// Utils.removeLineBreaks(s);
|
||||
mQueryTextChanged = s.length() == 0;
|
||||
}
|
||||
});
|
||||
|
@ -713,7 +713,6 @@ public class MessagesConversationFragment extends BaseSupportFragment implements
|
|||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
Utils.removeLineBreaks(s);
|
||||
mTextChanged = s.length() == 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -195,7 +195,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
|
|||
if (Utils.hasOfficialAPIAccess(loader.getContext(), mPreferences, account)) {
|
||||
mStatusAdapter.setReplyError(null);
|
||||
} else {
|
||||
final SpannableStringBuilder error = SpannableStringBuilder.valueOf(Html.fromHtml(getString(R.string.cant_load_all_replies_message)));
|
||||
final SpannableStringBuilder error = SpannableStringBuilder.valueOf(HtmlSpanBuilder.fromHtml(getString(R.string.cant_load_all_replies_message)));
|
||||
ClickableSpan dialogSpan = null;
|
||||
for (URLSpan span : error.getSpans(0, error.length(), URLSpan.class)) {
|
||||
if ("#dialog".equals(span.getURL())) {
|
||||
|
@ -860,7 +860,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
|
|||
quotedNameView.setText(manager.getUserNickname(status.quoted_user_id, status.quoted_user_name, false));
|
||||
quotedScreenNameView.setText("@" + status.quoted_user_screen_name);
|
||||
|
||||
quotedTextView.setText(Html.fromHtml(status.quoted_text_html));
|
||||
quotedTextView.setText(HtmlSpanBuilder.fromHtml(status.quoted_text_html));
|
||||
|
||||
linkify.applyAllLinks(quotedTextView, status.account_id, layoutPosition, status.is_possibly_sensitive);
|
||||
ThemeUtils.applyParagraphSpacing(quotedTextView, 1.1f);
|
||||
|
@ -903,9 +903,9 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
|
|||
|
||||
final String timeString = Utils.formatToLongTimeString(context, timestamp);
|
||||
if (!TextUtils.isEmpty(timeString) && !TextUtils.isEmpty(status.source)) {
|
||||
timeSourceView.setText(Html.fromHtml(context.getString(R.string.time_source, timeString, status.source)));
|
||||
timeSourceView.setText(HtmlSpanBuilder.fromHtml(context.getString(R.string.time_source, timeString, status.source)));
|
||||
} else if (TextUtils.isEmpty(timeString) && !TextUtils.isEmpty(status.source)) {
|
||||
timeSourceView.setText(Html.fromHtml(context.getString(R.string.source, status.source)));
|
||||
timeSourceView.setText(HtmlSpanBuilder.fromHtml(context.getString(R.string.source, status.source)));
|
||||
} else if (!TextUtils.isEmpty(timeString) && TextUtils.isEmpty(status.source)) {
|
||||
timeSourceView.setText(timeString);
|
||||
}
|
||||
|
|
|
@ -115,6 +115,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers;
|
|||
import org.mariotaku.twidere.provider.TwidereDataStore.Filters;
|
||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
||||
import org.mariotaku.twidere.util.ContentValuesCreator;
|
||||
import org.mariotaku.twidere.util.HtmlSpanBuilder;
|
||||
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
|
||||
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback;
|
||||
import org.mariotaku.twidere.util.LinkCreator;
|
||||
|
@ -506,7 +507,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
|
|||
}
|
||||
mScreenNameView.setText("@" + user.screen_name);
|
||||
mDescriptionContainer.setVisibility(TextUtils.isEmpty(user.description_html) ? View.GONE : View.VISIBLE);
|
||||
mDescriptionView.setText(user.description_html != null ? Html.fromHtml(user.description_html) : user.description_plain);
|
||||
mDescriptionView.setText(user.description_html != null ? HtmlSpanBuilder.fromHtml(user.description_html) : user.description_plain);
|
||||
final TwidereLinkify linkify = new TwidereLinkify(this);
|
||||
linkify.applyAllLinks(mDescriptionView, user.account_id, false);
|
||||
mDescriptionView.setMovementMethod(null);
|
||||
|
|
|
@ -63,6 +63,7 @@ import org.mariotaku.twidere.util.AsyncTaskManager;
|
|||
import org.mariotaku.twidere.util.AsyncTaskUtils;
|
||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper.UpdateProfileBannerImageTask;
|
||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper.UpdateProfileImageTask;
|
||||
import org.mariotaku.twidere.util.HtmlEscapeHelper;
|
||||
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
|
||||
import org.mariotaku.twidere.util.ParseUtils;
|
||||
import org.mariotaku.twidere.util.TwitterAPIFactory;
|
||||
|
@ -237,6 +238,7 @@ public class UserProfileEditorFragment extends BaseSupportFragment implements On
|
|||
|
||||
if (savedInstanceState != null && savedInstanceState.getParcelable(EXTRA_USER) != null) {
|
||||
final ParcelableUser user = savedInstanceState.getParcelable(EXTRA_USER);
|
||||
assert user != null;
|
||||
displayUser(user);
|
||||
mEditName.setText(savedInstanceState.getString(EXTRA_NAME, user.name));
|
||||
mEditLocation.setText(savedInstanceState.getString(EXTRA_LOCATION, user.location));
|
||||
|
@ -431,7 +433,10 @@ public class UserProfileEditorFragment extends BaseSupportFragment implements On
|
|||
|
||||
private static final String DIALOG_FRAGMENT_TAG = "updating_user_profile";
|
||||
private final UserProfileEditorFragment mFragment;
|
||||
private final FragmentActivity mActivity;
|
||||
private final Handler mHandler;
|
||||
|
||||
// Data fields
|
||||
private final long mAccountId;
|
||||
private final ParcelableUser mOriginal;
|
||||
private final String mName;
|
||||
|
@ -439,7 +444,7 @@ public class UserProfileEditorFragment extends BaseSupportFragment implements On
|
|||
private final String mLocation;
|
||||
private final String mDescription;
|
||||
private final int mLinkColor;
|
||||
private final FragmentActivity mActivity;
|
||||
private final int mBackgroundColor;
|
||||
|
||||
public UpdateProfileTaskInternal(final UserProfileEditorFragment fragment,
|
||||
final long accountId, final ParcelableUser original,
|
||||
|
@ -456,6 +461,7 @@ public class UserProfileEditorFragment extends BaseSupportFragment implements On
|
|||
mLocation = location;
|
||||
mDescription = description;
|
||||
mLinkColor = linkColor;
|
||||
mBackgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -465,11 +471,12 @@ public class UserProfileEditorFragment extends BaseSupportFragment implements On
|
|||
User user = null;
|
||||
if (isProfileChanged()) {
|
||||
final ProfileUpdate profileUpdate = new ProfileUpdate();
|
||||
profileUpdate.name(mName);
|
||||
profileUpdate.name(HtmlEscapeHelper.escapeBasic(mName));
|
||||
profileUpdate.location(HtmlEscapeHelper.escapeBasic(mLocation));
|
||||
profileUpdate.description(HtmlEscapeHelper.escapeBasic(mDescription));
|
||||
profileUpdate.url(mUrl);
|
||||
profileUpdate.location(mLocation);
|
||||
profileUpdate.description(mDescription);
|
||||
profileUpdate.linkColor(mLinkColor);
|
||||
profileUpdate.backgroundColor(mBackgroundColor);
|
||||
user = twitter.updateProfile(profileUpdate);
|
||||
}
|
||||
if (user == null) {
|
||||
|
@ -486,6 +493,7 @@ public class UserProfileEditorFragment extends BaseSupportFragment implements On
|
|||
final ParcelableUser orig = mOriginal;
|
||||
if (orig == null) return true;
|
||||
if (mLinkColor != orig.link_color) return true;
|
||||
if (mBackgroundColor != orig.background_color) return true;
|
||||
if (!stringEquals(mName, orig.name)) return true;
|
||||
if (!stringEquals(mDescription, isEmpty(orig.description_expanded) ? orig.description_plain : orig.description_expanded))
|
||||
return true;
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package org.mariotaku.twidere.provider;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
|
@ -33,6 +34,7 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
|||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MergeCursor;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteFullException;
|
||||
|
@ -42,7 +44,6 @@ import android.graphics.BitmapFactory;
|
|||
import android.graphics.Typeface;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
@ -55,6 +56,7 @@ import android.support.v4.app.NotificationCompat.InboxStyle;
|
|||
import android.support.v4.util.LongSparseArray;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -66,8 +68,11 @@ import org.apache.commons.lang3.ArrayUtils;
|
|||
import org.mariotaku.sqliteqb.library.Columns.Column;
|
||||
import org.mariotaku.sqliteqb.library.Expression;
|
||||
import org.mariotaku.sqliteqb.library.OnConflict;
|
||||
import org.mariotaku.sqliteqb.library.OrderBy;
|
||||
import org.mariotaku.sqliteqb.library.RawItemArray;
|
||||
import org.mariotaku.sqliteqb.library.RawSQLLang;
|
||||
import org.mariotaku.sqliteqb.library.SQLConstants;
|
||||
import org.mariotaku.sqliteqb.library.SQLFunctions;
|
||||
import org.mariotaku.sqliteqb.library.SQLQueryBuilder;
|
||||
import org.mariotaku.sqliteqb.library.SetValue;
|
||||
import org.mariotaku.sqliteqb.library.query.SQLInsertQuery;
|
||||
|
@ -93,8 +98,10 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Drafts;
|
|||
import org.mariotaku.twidere.provider.TwidereDataStore.Mentions;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.NetworkUsages;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Preferences;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.SavedSearches;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.SearchHistory;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Suggestions;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts;
|
||||
import org.mariotaku.twidere.receiver.NotificationReceiver;
|
||||
import org.mariotaku.twidere.service.BackgroundOperationService;
|
||||
|
@ -129,6 +136,8 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
|
@ -720,7 +729,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
|||
case VIRTUAL_TABLE_ID_CACHED_USERS_WITH_SCORE: {
|
||||
final long accountId = ParseUtils.parseLong(uri.getLastPathSegment(), -1);
|
||||
final SQLSelectQuery query = CachedUsersQueryBuilder.withScore(projection,
|
||||
selection, sortOrder, accountId);
|
||||
selection, sortOrder, accountId, 0);
|
||||
final Cursor c = mDatabaseWrapper.rawQuery(query.getSQL(), selectionArgs);
|
||||
setNotificationUri(c, CachedUsers.CONTENT_URI);
|
||||
return c;
|
||||
|
@ -740,6 +749,12 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
|||
setNotificationUri(c, getNotificationUri(tableId, uri));
|
||||
return c;
|
||||
}
|
||||
case VIRTUAL_TABLE_ID_SUGGESTIONS_AUTO_COMPLETE: {
|
||||
return getAutoCompleteSuggestionsCursor(uri);
|
||||
}
|
||||
case VIRTUAL_TABLE_ID_SUGGESTIONS_SEARCH: {
|
||||
return getSearchSuggestionCursor(uri);
|
||||
}
|
||||
}
|
||||
if (table == null) return null;
|
||||
final Cursor c = mDatabaseWrapper.query(table, projection, selection, selectionArgs, null, null, sortOrder);
|
||||
|
@ -750,6 +765,131 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
|||
}
|
||||
}
|
||||
|
||||
private static final Pattern PATTERN_SCREEN_NAME = Pattern.compile("(?i)[@\uFF20]?([a-z0-9_]{1,20})");
|
||||
|
||||
private Cursor getSearchSuggestionCursor(Uri uri) {
|
||||
final String query = uri.getQueryParameter(QUERY_PARAM_QUERY);
|
||||
final long accountId = ParseUtils.parseLong(uri.getQueryParameter(QUERY_PARAM_ACCOUNT_ID), -1);
|
||||
if (query == null || accountId <= 0) return null;
|
||||
final ContentResolver resolver = getContentResolver();
|
||||
final boolean emptyQuery = TextUtils.isEmpty(query);
|
||||
final String queryEscaped = query.replace("_", "^_");
|
||||
final Cursor[] cursors;
|
||||
final String[] historyProjection = {
|
||||
new Column(SearchHistory._ID, Suggestions.Search._ID).getSQL(),
|
||||
new Column("'" + Suggestions.Search.TYPE_SEARCH_HISTORY + "'", Suggestions.Search.TYPE).getSQL(),
|
||||
new Column(SearchHistory.QUERY, Suggestions.Search.TITLE).getSQL(),
|
||||
new Column(SQLConstants.NULL, Suggestions.Search.SUMMARY).getSQL(),
|
||||
new Column(SQLConstants.NULL, Suggestions.Search.ICON).getSQL(),
|
||||
new Column("0", Suggestions.Search.EXTRA_ID).getSQL(),
|
||||
new Column(SQLConstants.NULL, Suggestions.Search.EXTRA).getSQL()
|
||||
};
|
||||
final Expression historySelection = Expression.likeRaw(new Column(SearchHistory.QUERY), "?||'%'", "^");
|
||||
@SuppressLint("Recycle") final Cursor historyCursor = mDatabaseWrapper.query(true,
|
||||
SearchHistory.TABLE_NAME, historyProjection, historySelection.getSQL(),
|
||||
new String[]{queryEscaped}, null, null, SearchHistory.DEFAULT_SORT_ORDER,
|
||||
TextUtils.isEmpty(query) ? "3" : "2");
|
||||
if (emptyQuery) {
|
||||
final String[] savedSearchesProjection = {
|
||||
new Column(SavedSearches._ID, Suggestions.Search._ID).getSQL(),
|
||||
new Column("'" + Suggestions.Search.TYPE_SAVED_SEARCH + "'", Suggestions.Search.TYPE).getSQL(),
|
||||
new Column(SavedSearches.QUERY, Suggestions.Search.TITLE).getSQL(),
|
||||
new Column(SQLConstants.NULL, Suggestions.Search.SUMMARY).getSQL(),
|
||||
new Column(SQLConstants.NULL, Suggestions.Search.ICON).getSQL(),
|
||||
new Column("0", Suggestions.Search.EXTRA_ID).getSQL(),
|
||||
new Column(SQLConstants.NULL, Suggestions.Search.EXTRA).getSQL()
|
||||
};
|
||||
final Expression savedSearchesWhere = Expression.equals(SavedSearches.ACCOUNT_ID, accountId);
|
||||
@SuppressLint("Recycle") final Cursor savedSearchesCursor = mDatabaseWrapper.query(true,
|
||||
SavedSearches.TABLE_NAME, savedSearchesProjection, savedSearchesWhere.getSQL(),
|
||||
null, null, null, SavedSearches.DEFAULT_SORT_ORDER, null);
|
||||
cursors = new Cursor[2];
|
||||
cursors[1] = savedSearchesCursor;
|
||||
} else {
|
||||
final String[] usersProjection = {
|
||||
new Column(CachedUsers._ID, Suggestions.Search._ID).getSQL(),
|
||||
new Column("'" + Suggestions.Search.TYPE_USER + "'", Suggestions.Search.TYPE).getSQL(),
|
||||
new Column(CachedUsers.NAME, Suggestions.Search.TITLE).getSQL(),
|
||||
new Column(CachedUsers.SCREEN_NAME, Suggestions.Search.SUMMARY).getSQL(),
|
||||
new Column(CachedUsers.PROFILE_IMAGE_URL, Suggestions.Search.ICON).getSQL(),
|
||||
new Column(CachedUsers.USER_ID, Suggestions.Search.EXTRA_ID).getSQL(),
|
||||
new Column(SQLConstants.NULL, Suggestions.Search.EXTRA).getSQL()
|
||||
};
|
||||
final long[] nicknameIds = Utils.getMatchedNicknameIds(query, mUserColorNameManager);
|
||||
final Expression usersSelection = 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 SQLSelectQuery usersQuery = CachedUsersQueryBuilder.withScore(usersProjection,
|
||||
usersSelection.getSQL(), orderBy.getSQL(), accountId, 0);
|
||||
@SuppressLint("Recycle") final Cursor usersCursor = mDatabaseWrapper.rawQuery(usersQuery.getSQL(), selectionArgs);
|
||||
final Expression exactUserSelection = Expression.or(Expression.likeRaw(new Column(CachedUsers.SCREEN_NAME), "?", "^"));
|
||||
final Cursor exactUserCursor = mDatabaseWrapper.query(CachedUsers.TABLE_NAME,
|
||||
new String[]{SQLFunctions.COUNT()}, exactUserSelection.getSQL(),
|
||||
new String[]{queryEscaped}, null, null, null, "1");
|
||||
final boolean hasName = exactUserCursor.moveToPosition(0) && exactUserCursor.getInt(0) > 0;
|
||||
exactUserCursor.close();
|
||||
final MatrixCursor screenNameCursor = new MatrixCursor(Suggestions.Search.COLUMNS);
|
||||
if (!hasName) {
|
||||
final Matcher m = PATTERN_SCREEN_NAME.matcher(query);
|
||||
if (m.matches()) {
|
||||
screenNameCursor.addRow(new Object[]{0, Suggestions.Search.TYPE_SCREEN_NAME,
|
||||
query, null, null, 0, null});
|
||||
}
|
||||
}
|
||||
cursors = new Cursor[3];
|
||||
cursors[1] = screenNameCursor;
|
||||
cursors[2] = usersCursor;
|
||||
}
|
||||
cursors[0] = historyCursor;
|
||||
return new MergeCursor(cursors);
|
||||
}
|
||||
|
||||
private Cursor getAutoCompleteSuggestionsCursor(@NonNull Uri uri) {
|
||||
final String query = uri.getQueryParameter(QUERY_PARAM_QUERY);
|
||||
final String type = uri.getQueryParameter(QUERY_PARAM_TYPE);
|
||||
final String accountId = uri.getQueryParameter(QUERY_PARAM_ACCOUNT_ID);
|
||||
if (query == null || type == null) return null;
|
||||
final String queryEscaped = query.replace("_", "^_");
|
||||
if (Suggestions.AutoComplete.TYPE_USERS.equals(type)) {
|
||||
final long[] nicknameIds = Utils.getMatchedNicknameIds(query, mUserColorNameManager);
|
||||
final Expression where = 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[] whereArgs = {queryEscaped, queryEscaped};
|
||||
final String[] mappedProjection = {
|
||||
new Column(CachedUsers._ID, Suggestions._ID).getSQL(),
|
||||
new Column("'" + Suggestions.AutoComplete.TYPE_USERS + "'", Suggestions.TYPE).getSQL(),
|
||||
new Column(CachedUsers.NAME, Suggestions.TITLE).getSQL(),
|
||||
new Column(CachedUsers.SCREEN_NAME, Suggestions.SUMMARY).getSQL(),
|
||||
new Column(CachedUsers.USER_ID, Suggestions.EXTRA_ID).getSQL(),
|
||||
new Column(CachedUsers.PROFILE_IMAGE_URL, Suggestions.ICON).getSQL(),
|
||||
};
|
||||
return query(Uri.withAppendedPath(CachedUsers.CONTENT_URI_WITH_SCORE, accountId),
|
||||
mappedProjection, where.getSQL(), whereArgs, new OrderBy(new String[]{"score", CachedUsers.LAST_SEEN},
|
||||
new boolean[]{false, false}).getSQL());
|
||||
} else if (Suggestions.AutoComplete.TYPE_HASHTAGS.equals(type)) {
|
||||
final Expression where = Expression.likeRaw(new Column(CachedHashtags.NAME), "?||'%'", "^");
|
||||
final String[] whereArgs = new String[]{queryEscaped};
|
||||
final String[] mappedProjection = {
|
||||
new Column(CachedHashtags._ID, Suggestions._ID).getSQL(),
|
||||
new Column("'" + Suggestions.AutoComplete.TYPE_HASHTAGS + "'", Suggestions.TYPE).getSQL(),
|
||||
new Column(CachedHashtags.NAME, Suggestions.TITLE).getSQL(),
|
||||
new Column("NULL", Suggestions.SUMMARY).getSQL(),
|
||||
new Column("0", Suggestions.EXTRA_ID).getSQL(),
|
||||
new Column("NULL", Suggestions.ICON).getSQL(),
|
||||
};
|
||||
return query(CachedHashtags.CONTENT_URI, mappedProjection, where.getSQL(),
|
||||
whereArgs, null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(@NonNull final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) {
|
||||
try {
|
||||
|
@ -929,6 +1069,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
|||
private ParcelFileDescriptor getCacheFileFd(final String name) throws FileNotFoundException {
|
||||
if (name == null) return null;
|
||||
final Context context = getContext();
|
||||
assert context != null;
|
||||
final File cacheDir = context.getCacheDir();
|
||||
final File file = new File(cacheDir, name);
|
||||
if (!file.exists()) return null;
|
||||
|
@ -938,6 +1079,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
|||
private ContentResolver getContentResolver() {
|
||||
if (mContentResolver != null) return mContentResolver;
|
||||
final Context context = getContext();
|
||||
assert context != null;
|
||||
return mContentResolver = context.getContentResolver();
|
||||
}
|
||||
|
||||
|
@ -959,6 +1101,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
|||
private NotificationManager getNotificationManager() {
|
||||
if (mNotificationManager != null) return mNotificationManager;
|
||||
final Context context = getContext();
|
||||
assert context != null;
|
||||
return mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
|
@ -974,6 +1117,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
|||
|
||||
private Bitmap getProfileImageForNotification(final String profile_image_url) {
|
||||
final Context context = getContext();
|
||||
assert context != null;
|
||||
final Resources res = context.getResources();
|
||||
final int w = res.getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
|
||||
final int h = res.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
|
||||
|
@ -1025,7 +1169,6 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
|||
}
|
||||
|
||||
private void notifyUnreadCountChanged(final int position) {
|
||||
final Context context = getContext();
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -1409,6 +1552,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
|||
}
|
||||
|
||||
private void showMessagesNotification(AccountPreferences pref, StringLongPair[] pairs, ContentValues[] valuesArray) {
|
||||
final Context context = getContext();
|
||||
assert context != null;
|
||||
final long accountId = pref.getAccountId();
|
||||
final long prevOldestId = mReadStateManager.getPosition(TAG_OLDEST_MESSAGES, String.valueOf(accountId));
|
||||
long oldestId = -1;
|
||||
|
@ -1418,7 +1563,6 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
|||
if (messageId <= prevOldestId) return;
|
||||
}
|
||||
mReadStateManager.setPosition(TAG_OLDEST_MESSAGES, String.valueOf(accountId), oldestId, false);
|
||||
final Context context = getContext();
|
||||
final Resources resources = context.getResources();
|
||||
final NotificationManager nm = getNotificationManager();
|
||||
final ArrayList<Expression> orExpressions = new ArrayList<>();
|
||||
|
@ -1574,31 +1718,4 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
|||
mNameFirst = mPreferences.getBoolean(KEY_NAME_FIRST, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class GetWritableDatabaseTask extends AsyncTask<Object, Object, SQLiteDatabase> {
|
||||
private final Context mContext;
|
||||
private final SQLiteOpenHelper mHelper;
|
||||
private final SQLiteDatabaseWrapper mWrapper;
|
||||
|
||||
GetWritableDatabaseTask(final Context context, final SQLiteOpenHelper helper,
|
||||
final SQLiteDatabaseWrapper wrapper) {
|
||||
mContext = context;
|
||||
mHelper = helper;
|
||||
mWrapper = wrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SQLiteDatabase doInBackground(final Object... params) {
|
||||
return mHelper.getWritableDatabase();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final SQLiteDatabase result) {
|
||||
mWrapper.setSQLiteDatabase(result);
|
||||
if (result != null) {
|
||||
mContext.sendBroadcast(new Intent(BROADCAST_DATABASE_READY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -78,9 +78,13 @@ public class CacheUsersStatusesTask extends AsyncTask<TwitterListResponse<Status
|
|||
values.put(CachedHashtags.NAME, hashtag);
|
||||
hashTagValues.add(values);
|
||||
}
|
||||
usersValues.add(createCachedUser(status.getUser()));
|
||||
final ContentValues cachedUser = createCachedUser(status.getUser());
|
||||
cachedUser.put(CachedUsers.LAST_SEEN, System.currentTimeMillis());
|
||||
usersValues.add(cachedUser);
|
||||
if (status.isRetweet()) {
|
||||
usersValues.add(createCachedUser(status.getRetweetedStatus().getUser()));
|
||||
final ContentValues cachedRetweetedUser = createCachedUser(status.getRetweetedStatus().getUser());
|
||||
cachedRetweetedUser.put(CachedUsers.LAST_SEEN, System.currentTimeMillis());
|
||||
usersValues.add(cachedRetweetedUser);
|
||||
}
|
||||
|
||||
bulkInsert(resolver, CachedStatuses.CONTENT_URI, statusesValues);
|
||||
|
|
|
@ -19,8 +19,10 @@
|
|||
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.text.style.URLSpan;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -33,6 +35,7 @@ import org.attoparser.markup.html.elements.IHtmlElement;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -60,10 +63,20 @@ public class HtmlSpanBuilder {
|
|||
}
|
||||
|
||||
private static Object createSpan(TagInfo info) {
|
||||
switch (info.name) {
|
||||
switch (info.name.toLowerCase(Locale.US)) {
|
||||
case "a": {
|
||||
return new URLSpan(info.getAttribute("href"));
|
||||
}
|
||||
case "b":
|
||||
case "strong": {
|
||||
return new StyleSpan(Typeface.BOLD);
|
||||
}
|
||||
case "em":
|
||||
case "cite":
|
||||
case "dfn":
|
||||
case "i": {
|
||||
return new StyleSpan(Typeface.ITALIC);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,400 +0,0 @@
|
|||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.graphics.Rect;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewPropertyAnimator;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link View.OnTouchListener} that makes the list items in a {@link ListView}
|
||||
* dismissable. {@link ListView} is given special treatment because by default it handles touches
|
||||
* for its list items... i.e. it's in charge of drawing the pressed state (the list selector),
|
||||
* handling list item clicks, etc.
|
||||
* <p/>
|
||||
* <p>After creating the listener, the caller should also call
|
||||
* {@link ListView#setOnScrollListener(AbsListView.OnScrollListener)}, passing
|
||||
* in the scroll listener returned by {@link #makeScrollListener()}. If a scroll listener is
|
||||
* already assigned, the caller should still pass scroll changes through to this listener. This will
|
||||
* ensure that this {@link SwipeDismissListViewTouchListener} is paused during list view
|
||||
* scrolling.</p>
|
||||
* <p/>
|
||||
* <p>Example usage:</p>
|
||||
* <p/>
|
||||
* <pre>
|
||||
* SwipeDismissListViewTouchListener touchListener =
|
||||
* new SwipeDismissListViewTouchListener(
|
||||
* listView,
|
||||
* new SwipeDismissListViewTouchListener.OnDismissCallback() {
|
||||
* public void onDismiss(ListView listView, int[] reverseSortedPositions) {
|
||||
* for (int position : reverseSortedPositions) {
|
||||
* adapter.remove(adapter.getItem(position));
|
||||
* }
|
||||
* adapter.notifyDataSetChanged();
|
||||
* }
|
||||
* });
|
||||
* listView.setOnTouchListener(touchListener);
|
||||
* listView.setOnScrollListener(touchListener.makeScrollListener());
|
||||
* </pre>
|
||||
* <p/>
|
||||
* <p>This class Requires API level 12 or later due to use of {@link
|
||||
* ViewPropertyAnimator}.</p>
|
||||
*/
|
||||
public class SwipeDismissListViewTouchListener implements View.OnTouchListener {
|
||||
// Cached ViewConfiguration and system-wide constant values
|
||||
private int mSlop;
|
||||
private int mMinFlingVelocity;
|
||||
private int mMaxFlingVelocity;
|
||||
private long mAnimationTime;
|
||||
|
||||
// Fixed properties
|
||||
private ListView mListView;
|
||||
private DismissCallbacks mCallbacks;
|
||||
private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
|
||||
|
||||
// Transient properties
|
||||
private List<PendingDismissData> mPendingDismisses = new ArrayList<>();
|
||||
private int mDismissAnimationRefCount = 0;
|
||||
private float mDownX;
|
||||
private float mDownY;
|
||||
private boolean mSwiping;
|
||||
private int mSwipingSlop;
|
||||
private VelocityTracker mVelocityTracker;
|
||||
private int mDownPosition;
|
||||
private View mDownView;
|
||||
private boolean mPaused;
|
||||
|
||||
/**
|
||||
* The callback interface used by {@link SwipeDismissListViewTouchListener} to inform its client
|
||||
* about a successful dismissal of one or more list item positions.
|
||||
*/
|
||||
public interface DismissCallbacks {
|
||||
/**
|
||||
* Called to determine whether the given position can be dismissed.
|
||||
*/
|
||||
boolean canDismiss(int position);
|
||||
|
||||
/**
|
||||
* Called when the user has indicated they she would like to dismiss one or more list item
|
||||
* positions.
|
||||
*
|
||||
* @param listView The originating {@link ListView}.
|
||||
* @param reverseSortedPositions An array of positions to dismiss, sorted in descending
|
||||
* order for convenience.
|
||||
*/
|
||||
void onDismiss(ListView listView, int[] reverseSortedPositions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new swipe-to-dismiss touch listener for the given list view.
|
||||
*
|
||||
* @param listView The list view whose items should be dismissable.
|
||||
* @param callbacks The callback to trigger when the user has indicated that she would like to
|
||||
* dismiss one or more list items.
|
||||
*/
|
||||
public SwipeDismissListViewTouchListener(ListView listView, DismissCallbacks callbacks) {
|
||||
ViewConfiguration vc = ViewConfiguration.get(listView.getContext());
|
||||
mSlop = vc.getScaledTouchSlop();
|
||||
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
|
||||
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
|
||||
mAnimationTime = listView.getContext().getResources().getInteger(
|
||||
android.R.integer.config_shortAnimTime);
|
||||
mListView = listView;
|
||||
mCallbacks = callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
|
||||
*
|
||||
* @param enabled Whether or not to watch for gestures.
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
mPaused = !enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link AbsListView.OnScrollListener} to be added to the {@link
|
||||
* ListView} using {@link ListView#setOnScrollListener(AbsListView.OnScrollListener)}.
|
||||
* If a scroll listener is already assigned, the caller should still pass scroll changes through
|
||||
* to this listener. This will ensure that this {@link SwipeDismissListViewTouchListener} is
|
||||
* paused during list view scrolling.</p>
|
||||
*
|
||||
* @see SwipeDismissListViewTouchListener
|
||||
*/
|
||||
public AbsListView.OnScrollListener makeScrollListener() {
|
||||
return new AbsListView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
|
||||
setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||
if (mViewWidth < 2) {
|
||||
mViewWidth = mListView.getWidth();
|
||||
}
|
||||
|
||||
switch (motionEvent.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
if (mPaused) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: ensure this is a finger, and set a flag
|
||||
|
||||
// Find the child view that was touched (perform a hit test)
|
||||
Rect rect = new Rect();
|
||||
int childCount = mListView.getChildCount();
|
||||
int[] listViewCoords = new int[2];
|
||||
mListView.getLocationOnScreen(listViewCoords);
|
||||
int x = (int) motionEvent.getRawX() - listViewCoords[0];
|
||||
int y = (int) motionEvent.getRawY() - listViewCoords[1];
|
||||
View child;
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
child = mListView.getChildAt(i);
|
||||
child.getHitRect(rect);
|
||||
if (rect.contains(x, y)) {
|
||||
mDownView = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mDownView != null) {
|
||||
mDownX = motionEvent.getRawX();
|
||||
mDownY = motionEvent.getRawY();
|
||||
mDownPosition = mListView.getPositionForView(mDownView);
|
||||
if (mCallbacks.canDismiss(mDownPosition)) {
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
mVelocityTracker.addMovement(motionEvent);
|
||||
} else {
|
||||
mDownView = null;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
if (mVelocityTracker == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (mDownView != null && mSwiping) {
|
||||
// cancel
|
||||
mDownView.animate()
|
||||
.translationX(0)
|
||||
.alpha(1)
|
||||
.setDuration(mAnimationTime)
|
||||
.setListener(null);
|
||||
}
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
mDownX = 0;
|
||||
mDownY = 0;
|
||||
mDownView = null;
|
||||
mDownPosition = ListView.INVALID_POSITION;
|
||||
mSwiping = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_UP: {
|
||||
if (mVelocityTracker == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
float deltaX = motionEvent.getRawX() - mDownX;
|
||||
mVelocityTracker.addMovement(motionEvent);
|
||||
mVelocityTracker.computeCurrentVelocity(1000);
|
||||
float velocityX = mVelocityTracker.getXVelocity();
|
||||
float absVelocityX = Math.abs(velocityX);
|
||||
float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
|
||||
boolean dismiss = false;
|
||||
boolean dismissRight = false;
|
||||
if (Math.abs(deltaX) > mViewWidth / 2 && mSwiping) {
|
||||
dismiss = true;
|
||||
dismissRight = deltaX > 0;
|
||||
} else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
|
||||
&& absVelocityY < absVelocityX && mSwiping) {
|
||||
// dismiss only if flinging in the same direction as dragging
|
||||
dismiss = (velocityX < 0) == (deltaX < 0);
|
||||
dismissRight = mVelocityTracker.getXVelocity() > 0;
|
||||
}
|
||||
if (dismiss && mDownPosition != ListView.INVALID_POSITION) {
|
||||
// dismiss
|
||||
final View downView = mDownView; // mDownView gets null'd before animation ends
|
||||
final int downPosition = mDownPosition;
|
||||
++mDismissAnimationRefCount;
|
||||
mDownView.animate()
|
||||
.translationX(dismissRight ? mViewWidth : -mViewWidth)
|
||||
.alpha(0)
|
||||
.setDuration(mAnimationTime)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
performDismiss(downView, downPosition);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// cancel
|
||||
mDownView.animate()
|
||||
.translationX(0)
|
||||
.alpha(1)
|
||||
.setDuration(mAnimationTime)
|
||||
.setListener(null);
|
||||
}
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
mDownX = 0;
|
||||
mDownY = 0;
|
||||
mDownView = null;
|
||||
mDownPosition = ListView.INVALID_POSITION;
|
||||
mSwiping = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
if (mVelocityTracker == null || mPaused) {
|
||||
break;
|
||||
}
|
||||
|
||||
mVelocityTracker.addMovement(motionEvent);
|
||||
float deltaX = motionEvent.getRawX() - mDownX;
|
||||
float deltaY = motionEvent.getRawY() - mDownY;
|
||||
if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) {
|
||||
mSwiping = true;
|
||||
mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);
|
||||
mListView.requestDisallowInterceptTouchEvent(true);
|
||||
|
||||
// Cancel ListView's touch (un-highlighting the item)
|
||||
MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
|
||||
cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
|
||||
(motionEvent.getActionIndex()
|
||||
<< MotionEvent.ACTION_POINTER_INDEX_SHIFT));
|
||||
mListView.onTouchEvent(cancelEvent);
|
||||
cancelEvent.recycle();
|
||||
}
|
||||
|
||||
if (mSwiping) {
|
||||
mDownView.setTranslationX(deltaX - mSwipingSlop);
|
||||
mDownView.setAlpha(Math.max(0f, Math.min(1f,
|
||||
1f - 2f * Math.abs(deltaX) / mViewWidth)));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class PendingDismissData implements Comparable<PendingDismissData> {
|
||||
public int position;
|
||||
public View view;
|
||||
|
||||
public PendingDismissData(int position, View view) {
|
||||
this.position = position;
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull PendingDismissData other) {
|
||||
// Sort by descending position
|
||||
return other.position - position;
|
||||
}
|
||||
}
|
||||
|
||||
private void performDismiss(final View dismissView, final int dismissPosition) {
|
||||
// Animate the dismissed list item to zero-height and fire the dismiss callback when
|
||||
// all dismissed list item animations have completed. This triggers layout on each animation
|
||||
// frame; in the future we may want to do something smarter and more performant.
|
||||
|
||||
final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
|
||||
final int originalHeight = dismissView.getHeight();
|
||||
|
||||
ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
|
||||
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
--mDismissAnimationRefCount;
|
||||
if (mDismissAnimationRefCount == 0) {
|
||||
// No active animations, process all pending dismisses.
|
||||
// Sort by descending position
|
||||
Collections.sort(mPendingDismisses);
|
||||
|
||||
int[] dismissPositions = new int[mPendingDismisses.size()];
|
||||
for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {
|
||||
dismissPositions[i] = mPendingDismisses.get(i).position;
|
||||
}
|
||||
mCallbacks.onDismiss(mListView, dismissPositions);
|
||||
|
||||
// Reset mDownPosition to avoid MotionEvent.ACTION_UP trying to start a dismiss
|
||||
// animation with a stale position
|
||||
mDownPosition = ListView.INVALID_POSITION;
|
||||
|
||||
ViewGroup.LayoutParams lp;
|
||||
for (PendingDismissData pendingDismiss : mPendingDismisses) {
|
||||
// Reset view presentation
|
||||
pendingDismiss.view.setAlpha(1f);
|
||||
pendingDismiss.view.setTranslationX(0);
|
||||
lp = pendingDismiss.view.getLayoutParams();
|
||||
lp.height = originalHeight;
|
||||
pendingDismiss.view.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
// Send a cancel event
|
||||
long time = SystemClock.uptimeMillis();
|
||||
MotionEvent cancelEvent = MotionEvent.obtain(time, time,
|
||||
MotionEvent.ACTION_CANCEL, 0, 0, 0);
|
||||
mListView.dispatchTouchEvent(cancelEvent);
|
||||
|
||||
mPendingDismisses.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
lp.height = (Integer) valueAnimator.getAnimatedValue();
|
||||
dismissView.setLayoutParams(lp);
|
||||
}
|
||||
});
|
||||
|
||||
mPendingDismisses.add(new PendingDismissData(dismissPosition, dismissView));
|
||||
animator.start();
|
||||
}
|
||||
}
|
|
@ -101,16 +101,6 @@ public class TwidereColorUtils {
|
|||
return getContrastYIQ(color, 128, colorDark, colorLight);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static int getContrastYIQ(int color) {
|
||||
return getContrastYIQ(color, 128);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static int getContrastYIQ(int color, int threshold) {
|
||||
return getContrastYIQ(color, threshold, Color.BLACK, Color.WHITE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get most contrasting color
|
||||
*
|
||||
|
|
|
@ -78,10 +78,8 @@ public class TwidereQueryBuilder {
|
|||
return qb.build();
|
||||
}
|
||||
|
||||
public static SQLSelectQuery withScore(final String[] projection,
|
||||
final String selection,
|
||||
final String sortOrder,
|
||||
final long accountId) {
|
||||
public static SQLSelectQuery withScore(final String[] projection, final String selection,
|
||||
final String sortOrder, final long accountId, final int limit) {
|
||||
final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder();
|
||||
final Selectable select = Utils.getColumnsFromProjection(projection);
|
||||
final Column[] columns = new Column[CachedUsers.COLUMNS.length + 1];
|
||||
|
@ -106,6 +104,9 @@ public class TwidereQueryBuilder {
|
|||
if (sortOrder != null) {
|
||||
qb.orderBy(new OrderBy(sortOrder));
|
||||
}
|
||||
if (limit > 0) {
|
||||
qb.limit(limit);
|
||||
}
|
||||
return qb.build();
|
||||
}
|
||||
|
||||
|
@ -231,7 +232,7 @@ public class TwidereQueryBuilder {
|
|||
}
|
||||
qb.where(where);
|
||||
qb.groupBy(Utils.getColumnsFromProjection(ConversationEntries.CONVERSATION_ID, DirectMessages.ACCOUNT_ID));
|
||||
qb.orderBy(new OrderBy(ConversationEntries.MESSAGE_TIMESTAMP ,false));
|
||||
qb.orderBy(new OrderBy(ConversationEntries.MESSAGE_TIMESTAMP, false));
|
||||
return qb.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ import com.twitter.Validator;
|
|||
* Created by mariotaku on 15/4/29.
|
||||
*/
|
||||
public class TwitterValidatorMETLengthChecker extends METLengthChecker {
|
||||
private Validator mValidator;
|
||||
private final Validator mValidator;
|
||||
|
||||
public TwitterValidatorMETLengthChecker(@NonNull Validator validator) {
|
||||
mValidator = validator;
|
||||
|
|
|
@ -84,7 +84,6 @@ import android.support.v4.view.accessibility.AccessibilityEventCompat;
|
|||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.ShareActionProvider;
|
||||
import android.system.ErrnoException;
|
||||
import android.text.Editable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateFormat;
|
||||
|
@ -223,6 +222,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Preferences;
|
|||
import org.mariotaku.twidere.provider.TwidereDataStore.SavedSearches;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.SearchHistory;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Suggestions;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Tabs;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts;
|
||||
import org.mariotaku.twidere.service.RefreshService;
|
||||
|
@ -363,6 +363,12 @@ public final class Utils implements Constants {
|
|||
VIRTUAL_TABLE_ID_DRAFTS_UNSENT);
|
||||
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Drafts.CONTENT_PATH_NOTIFICATIONS,
|
||||
VIRTUAL_TABLE_ID_DRAFTS_NOTIFICATIONS);
|
||||
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Drafts.CONTENT_PATH_NOTIFICATIONS,
|
||||
VIRTUAL_TABLE_ID_DRAFTS_NOTIFICATIONS);
|
||||
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Suggestions.AutoComplete.CONTENT_PATH,
|
||||
VIRTUAL_TABLE_ID_SUGGESTIONS_AUTO_COMPLETE);
|
||||
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Suggestions.Search.CONTENT_PATH,
|
||||
VIRTUAL_TABLE_ID_SUGGESTIONS_SEARCH);
|
||||
|
||||
LINK_HANDLER_URI_MATCHER.addURI(AUTHORITY_STATUS, null, LINK_ID_STATUS);
|
||||
LINK_HANDLER_URI_MATCHER.addURI(AUTHORITY_USER, null, LINK_ID_USER);
|
||||
|
@ -1625,21 +1631,6 @@ public final class Utils implements Constants {
|
|||
return textView;
|
||||
}
|
||||
|
||||
public static boolean removeLineBreaks(Editable s) {
|
||||
boolean deleted = false;
|
||||
try {
|
||||
for (int i = s.length() - 1; i >= 0; i--) {
|
||||
if (s.charAt(i) == '\n') {
|
||||
s.delete(i, i + 1);
|
||||
deleted |= true;
|
||||
}
|
||||
}
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
throw new IndexOutOfBoundsException("Error processing " + s + ", original message: " + e.getMessage());
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public static boolean setLastSeen(Context context, UserMentionEntity[] entities, long time) {
|
||||
if (entities == null) return false;
|
||||
boolean result = false;
|
||||
|
@ -3386,6 +3377,9 @@ public final class Utils implements Constants {
|
|||
final Intent shareIntent = createStatusShareIntent(context, status);
|
||||
shareSubMenu.removeGroup(MENU_GROUP_STATUS_SHARE);
|
||||
addIntentToMenu(context, shareSubMenu, shareIntent, MENU_GROUP_STATUS_SHARE);
|
||||
} else {
|
||||
final Intent shareIntent = createStatusShareIntent(context, status);
|
||||
shareItem.setIntent(Intent.createChooser(shareIntent, context.getString(R.string.share_status)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,26 +31,29 @@ public class StatusTextTokenizer implements MultiAutoCompleteTextView.Tokenizer
|
|||
|
||||
@Override
|
||||
public int findTokenStart(CharSequence text, int cursor) {
|
||||
int i = cursor;
|
||||
// Search backward to find start symbol
|
||||
while (i > 0 && !isStartSymbol(text.charAt(i - 1))) {
|
||||
int i = cursor - 1;
|
||||
int len = text.length();
|
||||
while (i >= 0 && i < len && !isStartSymbol(text.charAt(i))) {
|
||||
i--;
|
||||
}
|
||||
if (i < 0) return cursor;
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int findTokenEnd(CharSequence text, int cursor) {
|
||||
int i = cursor;
|
||||
int i = cursor - 1;
|
||||
int len = text.length();
|
||||
// Search backward to find start symbol
|
||||
while (i > 0 && !isStartSymbol(text.charAt(i - 1))) {
|
||||
while (i >= 0 && i < len && isStartSymbol(text.charAt(i))) {
|
||||
i--;
|
||||
}
|
||||
// Search forward to find space
|
||||
while (i < len && !isSpace(text.charAt(i))) {
|
||||
i++;
|
||||
}
|
||||
if (i < 0) return cursor;
|
||||
return i;
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ public class ComposeEditText extends AppCompatMultiAutoCompleteTextView {
|
|||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
if (!isInEditMode() && mAdapter == null) {
|
||||
mAdapter = new UserHashtagAutoCompleteAdapter(this);
|
||||
mAdapter = new UserHashtagAutoCompleteAdapter(getContext());
|
||||
}
|
||||
setAdapter(mAdapter);
|
||||
updateAccountId();
|
||||
|
|
|
@ -62,7 +62,7 @@ public class ComposeMaterialEditText extends AppCompatMultiAutoCompleteTextView
|
|||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
if (!isInEditMode() && mAdapter == null) {
|
||||
mAdapter = new UserHashtagAutoCompleteAdapter(this);
|
||||
mAdapter = new UserHashtagAutoCompleteAdapter(getContext());
|
||||
}
|
||||
setAdapter(mAdapter);
|
||||
updateAccountId();
|
||||
|
|
|
@ -35,6 +35,8 @@ import org.mariotaku.twidere.R;
|
|||
import org.mariotaku.twidere.adapter.MessageConversationAdapter;
|
||||
import org.mariotaku.twidere.model.ParcelableDirectMessage.CursorIndices;
|
||||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
import org.mariotaku.twidere.util.HtmlSpanBuilder;
|
||||
import org.mariotaku.twidere.util.JsonSerializer;
|
||||
import org.mariotaku.twidere.util.MediaLoaderWrapper;
|
||||
import org.mariotaku.twidere.util.StatusActionModeCallback;
|
||||
import org.mariotaku.twidere.util.ThemeUtils;
|
||||
|
@ -83,8 +85,8 @@ public class MessageViewHolder extends ViewHolder implements OnMediaClickListene
|
|||
|
||||
final long accountId = cursor.getLong(indices.account_id);
|
||||
final long timestamp = cursor.getLong(indices.message_timestamp);
|
||||
final ParcelableMedia[] media = ParcelableMedia.fromSerializedJson(cursor.getString(indices.media));
|
||||
textView.setText(Html.fromHtml(cursor.getString(indices.text)));
|
||||
final ParcelableMedia[] media = JsonSerializer.parseArray(cursor.getString(indices.media), ParcelableMedia.class);
|
||||
textView.setText(HtmlSpanBuilder.fromHtml(cursor.getString(indices.text)));
|
||||
linkify.applyAllLinks(textView, accountId, false);
|
||||
time.setText(Utils.formatToLongTimeString(context, timestamp));
|
||||
mediaContainer.setVisibility(media != null && media.length > 0 ? View.VISIBLE : View.GONE);
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.mariotaku.twidere.model.ParcelableLocation;
|
|||
import org.mariotaku.twidere.model.ParcelableMedia;
|
||||
import org.mariotaku.twidere.model.ParcelableStatus;
|
||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
||||
import org.mariotaku.twidere.util.HtmlSpanBuilder;
|
||||
import org.mariotaku.twidere.util.MediaLoaderWrapper;
|
||||
import org.mariotaku.twidere.util.MediaLoadingHandler;
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
|
||||
|
@ -118,7 +119,7 @@ public class StatusViewHolder extends ViewHolder implements Constants, OnClickLi
|
|||
nameView.setScreenName("@" + TWIDERE_PREVIEW_SCREEN_NAME);
|
||||
nameView.updateText();
|
||||
if (adapter.getLinkHighlightingStyle() == VALUE_LINK_HIGHLIGHT_OPTION_CODE_NONE) {
|
||||
textView.setText(Html.fromHtml(TWIDERE_PREVIEW_TEXT_HTML));
|
||||
textView.setText(HtmlSpanBuilder.fromHtml(TWIDERE_PREVIEW_TEXT_HTML));
|
||||
adapter.getTwidereLinkify().applyAllLinks(textView, -1, -1, false, adapter.getLinkHighlightingStyle());
|
||||
} else {
|
||||
textView.setText(toPlainText(TWIDERE_PREVIEW_TEXT_HTML));
|
||||
|
@ -180,7 +181,7 @@ public class StatusViewHolder extends ViewHolder implements Constants, OnClickLi
|
|||
|
||||
if (adapter.getLinkHighlightingStyle() != VALUE_LINK_HIGHLIGHT_OPTION_CODE_NONE
|
||||
&& !TextUtils.isEmpty(status.quoted_text_html)) {
|
||||
final Spanned text = Html.fromHtml(status.quoted_text_html);
|
||||
final Spanned text = HtmlSpanBuilder.fromHtml(status.quoted_text_html);
|
||||
quotedTextView.setText(text);
|
||||
linkify.applyAllLinks(quotedTextView, status.account_id, getLayoutPosition(),
|
||||
status.is_possibly_sensitive, adapter.getLinkHighlightingStyle());
|
||||
|
@ -260,7 +261,7 @@ public class StatusViewHolder extends ViewHolder implements Constants, OnClickLi
|
|||
} else if (adapter.getLinkHighlightingStyle() == VALUE_LINK_HIGHLIGHT_OPTION_CODE_NONE) {
|
||||
textView.setText(status.text_unescaped);
|
||||
} else {
|
||||
textView.setText(Html.fromHtml(status.text_html));
|
||||
textView.setText(HtmlSpanBuilder.fromHtml(status.text_html));
|
||||
linkify.applyAllLinks(textView, status.account_id, getLayoutPosition(),
|
||||
status.is_possibly_sensitive,
|
||||
adapter.getLinkHighlightingStyle());
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
android:id="@id/share"
|
||||
android:icon="@drawable/ic_action_share"
|
||||
android:title="@string/share"
|
||||
app:actionProviderClass="org.mariotaku.twidere.menu.SupportStatusShareProvider"
|
||||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@id/copy"
|
||||
|
|
Loading…
Reference in New Issue