improved auto complete

This commit is contained in:
Mariotaku Lee 2015-11-10 16:03:03 +08:00
parent 8f19921243
commit d1c26006c4
41 changed files with 1024 additions and 105 deletions

View File

@ -1,5 +1,4 @@
apply plugin: 'com.github.ben-manes.versions'
apply plugin: 'com.github.hierynomus.license'
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
@ -15,7 +14,6 @@ buildscript {
// should be excluded to avoid conflict
exclude group: 'xerces'
}
classpath 'gradle.plugin.nl.javadude.gradle.plugins:license-gradle-plugin:0.12.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}

Binary file not shown.

View File

@ -1,6 +1,6 @@
#Wed Aug 19 14:17:02 CST 2015
#Sun Nov 08 17:18:47 CST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip

View File

@ -93,11 +93,12 @@ dependencies {
compile 'com.soundcloud.android:android-crop:1.0.1@aar'
compile 'com.hannesdorfmann.parcelableplease:annotation:1.0.1'
compile 'com.github.mariotaku:PickNCrop:1dff3ed574'
compile 'com.github.mariotaku:RestFu:0.9.2'
compile 'com.diogobernardino:williamchart:2.0.1'
compile 'com.lnikkila:extendedtouchview:0.1.0'
compile 'com.google.dagger:dagger:2.0.1'
compile 'org.attoparser:attoparser:1.4.0.RELEASE'
googleCompile 'com.google.android.gms:play-services-maps:8.1.0'
googleCompile 'com.google.android.gms:play-services-maps:8.3.0'
googleCompile 'com.google.maps.android:android-maps-utils:0.4'
fdroidCompile 'org.osmdroid:osmdroid-android:4.3'
fdroidCompile 'org.slf4j:slf4j-simple:1.7.12'

View File

@ -341,6 +341,10 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".activity.CopyLinkActivity"
android:label="@string/copy_link"
android:theme="@style/Theme.Twidere.Dark.NoDisplay" />
<activity
android:name=".activity.support.RequestPermissionsActivity"
android:label="@string/permissions_request"

View File

@ -0,0 +1,291 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Created on Nov 21, 2005
*/
package jopt.csp.util;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Arrays;
import org.apache.commons.collections.primitives.ArrayIntList;
import org.apache.commons.collections.primitives.IntCollection;
import org.apache.commons.collections.primitives.IntList;
import org.apache.commons.collections.primitives.RandomAccessIntList;
/**
* A flexible, sortable list of int primitives. Borrows much
* of its functionality from the ArrayIntList implementation
* given in the Commons Primitives project
* (http://jakarta.apache.org/commons/primitives/index.html)
*
* @author Chris Johnson
*/
public class SortableIntList extends RandomAccessIntList implements IntList, Serializable {
private transient int[] data = null;
private int size = 0;
/**
* Construct an empty list with the default
* initial capacity.
*/
public SortableIntList() {
this(8);
}
/**
* Construct an empty list with the given
* initial capacity.
*
* @throws IllegalArgumentException when <i>initialCapacity</i> is negative
*/
public SortableIntList(int initialCapacity) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("capacity " + initialCapacity + " cannot be negative");
}
data = new int[initialCapacity];
size = 0;
}
/**
* Constructs a list containing the elements of the given collection,
* in the order they are returned by that collection's iterator.
*
* @param that the non-<code>null</code> collection of <code>int</code>s
* to add
* @throws NullPointerException if <i>that</i> is <code>null</code>
* @see ArrayIntList#addAll(org.apache.commons.collections.primitives.IntCollection)
*/
public SortableIntList(IntCollection that) {
this(that.size());
addAll(that);
}
@Override
public int get(int index) {
checkRange(index);
return data[index];
}
@Override
public int size() {
return size;
}
/**
* Removes the element at the specified position in
* (optional operation). Any subsequent elements
* are shifted to the left, subtracting one from their
* indices. Returns the element that was removed.
*
* @param index the index of the element to remove
* @return the value of the element that was removed
* @throws UnsupportedOperationException when this operation is not
* supported
* @throws IndexOutOfBoundsException if the specified index is out of range
*/
@Override
public int removeElementAt(int index) {
checkRange(index);
incrModCount();
int oldval = data[index];
int numtomove = size - index - 1;
if (numtomove > 0) {
System.arraycopy(data, index + 1, data, index, numtomove);
}
size--;
return oldval;
}
/**
* Replaces the element at the specified
* position in me with the specified element
* (optional operation). If specified index is
* beyond the current size, the list grows
* to accommodate it. No IndexOutOfBoundsException
* will occur during the set operation.
*
* @param index the index of the element to change
* @param element the value to be stored at the specified position
* @return the value previously stored at the specified position
* @throws UnsupportedOperationException when this operation is not
* supported
*/
@Override
public int set(int index, int element) {
ensureCapacity(index + 1);
ensureSize(index + 1);
incrModCount();
int oldval = data[index];
data[index] = element;
return oldval;
}
/**
* Inserts the specified element at the specified position
* (optional operation). Shifts the element currently
* at that position (if any) and any subsequent elements to the
* right, increasing their indices. If the specified index is
* beyond the current size, this method behaves like a call
* to {@link #set(int, int)}.
*
* @param index the index at which to insert the element
* @param element the value to insert
* @throws UnsupportedOperationException when this operation is not
* supported
* @throws IllegalArgumentException if some aspect of the specified element
* prevents it from being added to me
*/
@Override
public void add(int index, int element) {
if (index >= size) {
set(index, element);
} else {
incrModCount();
ensureCapacity(size + 1);
int numtomove = size - index;
System.arraycopy(data, index, data, index + 1, numtomove);
data[index] = element;
size++;
}
}
/**
* Increases my capacity, if necessary, to ensure that I can hold at
* least the number of elements specified by the minimum capacity
* argument without growing.
*/
public void ensureCapacity(int mincap) {
incrModCount();
if (mincap > data.length) {
int newcap = (data.length * 3) / 2 + 1;
int[] olddata = data;
data = new int[newcap < mincap ? mincap : newcap];
System.arraycopy(olddata, 0, data, 0, size);
}
}
/**
* Reduce my capacity, if necessary, to match my
* current {@link #size size}.
*/
public void trimToSize() {
incrModCount();
if (size < data.length) {
int[] olddata = data;
data = new int[size];
System.arraycopy(olddata, 0, data, 0, size);
}
}
/**
* Sorts the list into ascending numerical order via {@link java.util.Arrays#sort(int[])}
* <p/>
* Sorts the list of ints into ascending numerical order. The sorting algorithm
* is a tuned quicksort, adapted from Jon L. Bentley and M. Douglas McIlroy's "Engineering
* a Sort Function", Software-Practice and Experience, Vol. 23(11) P. 1249-1265 (November
* 1993). This algorithm offers n*log(n) performance on many data sets that cause other
* quicksorts to degrade to quadratic performance.
*/
public void sort() {
trimToSize();
Arrays.sort(data);
}
/**
* Reverses the order of the elements
*/
public void reverse() {
for (int i = 0, mid = size >> 1, j = size - 1; i < mid; i++, j--)
swap(i, j);
}
/**
* Swaps the two specified elements.
* (If the specified positions are equal, invoking this method leaves
* the list unchanged.)
*/
public void swap(int i, int j) {
int tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
/**
* Searches the list for the specified key via {@link java.util.Arrays#binarySearch(int[], int)}
* <p/>
* The array must be sorted (as by the sort method, above) prior to making this call.
* If it is not sorted, the results are undefined. If the list contains multiple elements
* with the specified value, there is no guarantee which one will be found.
*
* @param key the value to be searched for
* @return index of the search key, if it is contained in the list; otherwise, (-(insertion point) - 1)
*/
public int binarySearch(int key) {
trimToSize();
return Arrays.binarySearch(data, key);
}
/**
* Sorts the specified range of the list into ascending numerical order
* via {@link java.util.Arrays#sort(int[], int, int)}
*
* @param fromIndex the index of the first element (inclusive) to be sorted
* @param toIndex the index of the last element (exclusive) to be sorted
*/
public void sort(int fromIndex, int toIndex) {
trimToSize();
Arrays.sort(data, fromIndex, toIndex);
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(data.length);
for (int i = 0; i < size; i++) {
out.writeInt(data[i]);
}
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
data = new int[in.readInt()];
for (int i = 0; i < size; i++) {
data[i] = in.readInt();
}
}
private void ensureSize(int potentialSize) {
if (potentialSize > size) {
size = potentialSize;
}
}
private void checkRange(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Should be at least 0 and less than " + size + ", found " + index);
}
}
}

View File

@ -0,0 +1,39 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.activity;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.util.ClipboardUtils;
public class CopyLinkActivity extends Activity implements Constants {
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ClipboardUtils.setText(this, getIntent().getDataString());
Toast.makeText(this, R.string.link_copied_to_clipboard, Toast.LENGTH_SHORT).show();
finish();
}
}

View File

@ -19,6 +19,7 @@
package org.mariotaku.twidere.activity.support;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
@ -44,38 +45,42 @@ import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.CursorAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import com.mobeta.android.dslv.DragSortCursorAdapter;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.AccountsSpinnerAdapter;
import org.mariotaku.twidere.model.ParcelableAccount;
import org.mariotaku.twidere.model.ParcelableCredentials;
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.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.List;
import jopt.csp.util.SortableIntList;
/**
* Created by mariotaku on 15/1/6.
*/
public class QuickSearchBarActivity extends ThemedFragmentActivity implements OnClickListener,
LoaderCallbacks<Cursor>, OnItemSelectedListener, OnItemClickListener,
OnFitSystemWindowsListener {
OnFitSystemWindowsListener, SwipeDismissListViewTouchListener.DismissCallbacks {
private Spinner mAccountSpinner;
private EditText mSearchQuery;
@ -86,19 +91,24 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
private Rect mSystemWindowsInsets = new Rect();
private boolean mTextChanged;
@Override
public boolean canDismiss(int position) {
return mUsersSearchAdapter.getItemViewType(position) == SuggestionsAdapter.VIEW_TYPE_SEARCH_HISTORY;
}
@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 SuggestionItem item = mUsersSearchAdapter.getSuggestionItem(position);
ids[i] = item._id;
}
mUsersSearchAdapter.addRemovedPositions(reverseSortedPositions);
final ContentResolver cr = getContentResolver();
ContentResolverUtils.bulkDelete(cr, SearchHistory.CONTENT_URI, SearchHistory._ID, ids,
null, false);
getSupportLoaderManager().restartLoader(0, null, this);
}
@Override
@ -224,6 +234,10 @@ 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() {
@ -296,16 +310,17 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
public final String title, summary;
private final long extra_id;
public final long _id, extra_id;
public SuggestionItem(Cursor cursor, SuggestionsAdapter.Indices indices) {
_id = cursor.getLong(indices._id);
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 {
public static class SuggestionsAdapter extends CursorAdapter implements OnClickListener {
static final int VIEW_TYPE_SEARCH_HISTORY = 0;
static final int VIEW_TYPE_SAVED_SEARCH = 1;
@ -316,24 +331,21 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
private final MediaLoaderWrapper mImageLoader;
private final UserColorNameManager mUserColorNameManager;
private final QuickSearchBarActivity mActivity;
private final SortableIntList mRemovedPositions;
private Indices mIndices;
SuggestionsAdapter(QuickSearchBarActivity activity) {
super(activity, null, 0);
mRemovedPositions = new SortableIntList();
mActivity = activity;
mImageLoader = activity.mImageLoader;
mUserColorNameManager = activity.mUserColorNameManager;
mInflater = LayoutInflater.from(activity);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
switch (getItemViewType(cursor.getPosition())) {
switch (getActualItemViewType(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);
@ -353,13 +365,13 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
}
public SuggestionItem getSuggestionItem(int position) {
final Cursor cursor = (Cursor) super.getItem(position);
final Cursor cursor = (Cursor) getItem(position);
return new SuggestionItem(cursor, mIndices);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
switch (getItemViewType(cursor.getPosition())) {
switch (getActualItemViewType(cursor.getPosition())) {
case VIEW_TYPE_SEARCH_HISTORY: {
final SearchViewHolder holder = (SearchViewHolder) view.getTag();
final String title = cursor.getString(mIndices.title);
@ -400,7 +412,11 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
@Override
public int getItemViewType(int position) {
final Cursor cursor = (Cursor) getItem(position);
return getActualItemViewType(getActualPosition(position));
}
public int getActualItemViewType(int position) {
final Cursor cursor = (Cursor) super.getItem(position);
switch (cursor.getString(mIndices.type)) {
case Suggestions.Search.TYPE_SAVED_SEARCH: {
return VIEW_TYPE_SAVED_SEARCH;
@ -440,9 +456,55 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
} else {
mIndices = null;
}
mRemovedPositions.clear();
return super.swapCursor(newCursor);
}
@Override
public int getCount() {
if (mRemovedPositions == null) return super.getCount();
return super.getCount() - mRemovedPositions.size();
}
@Override
public Object getItem(int position) {
return super.getItem(getActualPosition(position));
}
@Override
public long getItemId(int position) {
return super.getItemId(getActualPosition(position));
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return super.getView(getActualPosition(position), convertView, parent);
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return super.getDropDownView(getActualPosition(position), convertView, parent);
}
private int getActualPosition(int position) {
if (mRemovedPositions == null) return position;
int skipped = 0;
for (int i = 0, j = mRemovedPositions.size(); i < j; i++) {
if (position + skipped >= mRemovedPositions.get(i)) {
skipped++;
}
}
return position + skipped;
}
public void addRemovedPositions(int[] positions) {
for (int position : positions) {
mRemovedPositions.add(getActualPosition(position));
}
mRemovedPositions.sort();
notifyDataSetChanged();
}
static class SearchViewHolder {
private final ImageView icon;
@ -471,6 +533,7 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
}
private static class Indices {
private final int _id;
private final int type;
private final int title;
private final int summary;
@ -478,6 +541,7 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
private final int extra_id;
public Indices(Cursor cursor) {
_id = cursor.getColumnIndex(Suggestions._ID);
type = cursor.getColumnIndex(Suggestions.TYPE);
title = cursor.getColumnIndex(Suggestions.TITLE);
summary = cursor.getColumnIndex(Suggestions.SUMMARY);

View File

@ -58,7 +58,8 @@ import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder;
* Created by mariotaku on 15/1/3.
*/
public abstract class AbsActivitiesAdapter<Data> extends LoadMoreSupportAdapter<ViewHolder> implements Constants,
IActivitiesAdapter<Data>, IStatusViewHolder.StatusClickListener, OnLinkClickListener, ActivityTitleSummaryViewHolder.ActivityClickListener {
IActivitiesAdapter<Data>, IStatusViewHolder.StatusClickListener, OnLinkClickListener,
ActivityTitleSummaryViewHolder.ActivityClickListener {
private static final int ITEM_VIEW_TYPE_STUB = 0;
private static final int ITEM_VIEW_TYPE_GAP = 1;
@ -75,6 +76,7 @@ public abstract class AbsActivitiesAdapter<Data> extends LoadMoreSupportAdapter<
private final boolean mDisplayMediaPreview;
private final boolean mNameFirst;
private final boolean mDisplayProfileImage;
private final boolean mShouldUseStarsForLikes;
private final TwidereLinkify mLinkify;
private final DummyStatusHolderAdapter mStatusAdapterDelegate;
private ActivityAdapterListener mActivityAdapterListener;
@ -87,6 +89,7 @@ public abstract class AbsActivitiesAdapter<Data> extends LoadMoreSupportAdapter<
mInflater = LayoutInflater.from(context);
mLoadingHandler = new MediaLoadingHandler(R.id.media_preview_progress);
mTextSize = mPreferences.getInt(KEY_TEXT_SIZE, context.getResources().getInteger(R.integer.default_text_size));
mShouldUseStarsForLikes = mPreferences.getBoolean(KEY_I_WANT_MY_STARS_BACK);
mCompactCards = compact;
mProfileImageStyle = Utils.getProfileImageStyle(mPreferences.getString(KEY_PROFILE_IMAGE_STYLE, null));
mMediaPreviewStyle = Utils.getMediaPreviewStyle(mPreferences.getString(KEY_MEDIA_PREVIEW_STYLE, null));
@ -175,6 +178,11 @@ public abstract class AbsActivitiesAdapter<Data> extends LoadMoreSupportAdapter<
}
@Override
public boolean shouldUseStarsForLikes() {
return mShouldUseStarsForLikes;
}
@Override
public void onUserProfileClick(IStatusViewHolder holder, int position) {
final Context context = getContext();

View File

@ -19,10 +19,7 @@
package org.mariotaku.twidere.adapter.iface;
import android.support.annotation.NonNull;
import org.mariotaku.twidere.model.ParcelableActivity;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.MediaLoadingHandler;
import org.mariotaku.twidere.view.CardMediaContainer.PreviewStyle;
@ -40,9 +37,7 @@ public interface IActivitiesAdapter<Data> extends IContentCardAdapter, IGapSuppo
@PreviewStyle
int getMediaPreviewStyle();
@NonNull
@Override
MediaLoaderWrapper getMediaLoader();
boolean shouldUseStarsForLikes();
MediaLoadingHandler getMediaLoadingHandler();
}

View File

@ -34,6 +34,7 @@ import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback;
import org.mariotaku.twidere.util.LinkCreator;
import org.mariotaku.twidere.util.RecyclerViewNavigationHelper;
import org.mariotaku.twidere.util.RecyclerViewUtils;
import org.mariotaku.twidere.util.Utils;
@ -121,7 +122,9 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentListRecyclerVi
if (status == null) return false;
if (item.getItemId() == R.id.share) {
final Intent shareIntent = Utils.createStatusShareIntent(getActivity(), status);
startActivity(Intent.createChooser(shareIntent, getString(R.string.share_status)));
final Intent chooser = Intent.createChooser(shareIntent, getString(R.string.share_status));
Utils.addCopyLinkIntent(getContext(), chooser, LinkCreator.getTwitterStatusLink(status));
startActivity(chooser);
return true;
}
return Utils.handleMenuItemClick(getActivity(), AbsStatusesFragment.this,

View File

@ -203,7 +203,7 @@ public class RetweetQuoteDialogFragment extends BaseSupportDialogFragment implem
} else {
positiveButton.setText(isMyRetweet(status) ? R.string.cancel_retweet : R.string.retweet);
}
final String statusLink = LinkCreator.getTwitterStatusLink(status.user_screen_name, status.id).toString();
final String statusLink = LinkCreator.getTwitterStatusLink(status).toString();
final StatusTextCountView textCountView = (StatusTextCountView) alertDialog.findViewById(R.id.comment_text_count);
textCountView.setTextCount(mValidator.getTweetLength(s + " " + statusLink));
}

View File

@ -288,7 +288,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
final ParcelableStatus status = getStatus();
if (status == null) return null;
return new NdefMessage(new NdefRecord[]{
NdefRecord.createUri(LinkCreator.getTwitterStatusLink(status.user_screen_name, status.id)),
NdefRecord.createUri(LinkCreator.getTwitterStatusLink(status)),
});
}
});

View File

@ -62,7 +62,6 @@ import android.support.v7.app.AppCompatActivity;
import android.support.v7.internal.widget.ActionBarContainer;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.Toolbar;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
@ -869,9 +868,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
}
final MenuItem muteItem = menu.findItem(R.id.mute_user);
if (muteItem != null) {
final boolean muting = relationship.isSourceMutingTarget();
ActionIconDrawable.setMenuHighlight(muteItem, new TwidereMenuInfo(muting));
muteItem.setTitle(muting ? R.string.unmute : R.string.mute);
muteItem.setChecked(relationship.isSourceMutingTarget());
}
final MenuItem filterItem = menu.findItem(R.id.add_to_filter);
if (filterItem != null) {
@ -881,7 +878,6 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
}
final MenuItem wantRetweetsItem = menu.findItem(R.id.enable_retweets);
if (wantRetweetsItem != null) {
wantRetweetsItem.setChecked(relationship.isSourceWantRetweetsFromTarget());
}
} else {

View File

@ -400,7 +400,7 @@ public class UserListFragment extends BaseSupportFragment implements OnClickList
private void setupUserPages() {
final Bundle args = getArguments(), tabArgs = new Bundle();
if (args.containsKey(EXTRA_USER)) {
if (args.containsKey(EXTRA_USER_LIST)) {
final ParcelableUserList userList = args.getParcelable(EXTRA_USER_LIST);
tabArgs.putLong(EXTRA_ACCOUNT_ID, userList.account_id);
tabArgs.putLong(EXTRA_USER_ID, userList.user_id);

View File

@ -26,17 +26,15 @@ import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Bundle;
import org.mariotaku.twidere.loader.support.CursorSupportUsersLoader;
import org.mariotaku.twidere.loader.support.UserListMembersLoader;
import org.mariotaku.twidere.model.ParcelableUserList;
import org.mariotaku.twidere.util.AsyncTaskUtils;
import org.mariotaku.twidere.util.TwitterAPIFactory;
import org.mariotaku.twidere.api.twitter.Twitter;
import org.mariotaku.twidere.api.twitter.TwitterException;
import org.mariotaku.twidere.api.twitter.model.UserList;
import static org.mariotaku.twidere.util.TwitterAPIFactory.getTwitterInstance;
import org.mariotaku.twidere.loader.support.CursorSupportUsersLoader;
import org.mariotaku.twidere.loader.support.UserListMembersLoader;
import org.mariotaku.twidere.model.ParcelableUserList;
import org.mariotaku.twidere.model.SingleResponse;
import org.mariotaku.twidere.util.AsyncTaskUtils;
import org.mariotaku.twidere.util.TwitterAPIFactory;
public class UserListMembersFragment extends CursorSupportUsersListFragment {
@ -107,7 +105,7 @@ public class UserListMembersFragment extends CursorSupportUsersListFragment {
super.onStop();
}
private class GetUserListTask extends AsyncTask<Object, Object, ParcelableUserList> {
private class GetUserListTask extends AsyncTask<Object, Object, SingleResponse<ParcelableUserList>> {
private final long accountId, userId;
private final long listId;
@ -123,7 +121,7 @@ public class UserListMembersFragment extends CursorSupportUsersListFragment {
}
@Override
protected ParcelableUserList doInBackground(final Object... params) {
protected SingleResponse<ParcelableUserList> doInBackground(final Object... params) {
final Twitter twitter = TwitterAPIFactory.getTwitterInstance(getActivity(), accountId, true);
if (twitter == null) return null;
try {
@ -136,17 +134,16 @@ public class UserListMembersFragment extends CursorSupportUsersListFragment {
list = twitter.showUserList(listName, screenName);
} else
return null;
return new ParcelableUserList(list, accountId);
return SingleResponse.getInstance(new ParcelableUserList(list, accountId));
} catch (final TwitterException e) {
e.printStackTrace();
return null;
return SingleResponse.getInstance(e);
}
}
@Override
protected void onPostExecute(final ParcelableUserList result) {
protected void onPostExecute(final SingleResponse<ParcelableUserList> result) {
if (mUserList != null) return;
mUserList = result;
mUserList = result.getData();
}
}
}

View File

@ -771,7 +771,6 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
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;
@ -815,12 +814,13 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
new Column(CachedUsers.USER_ID, Suggestions.Search.EXTRA_ID).getSQL(),
new Column(SQLConstants.NULL, Suggestions.Search.EXTRA).getSQL()
};
String queryTrimmed = queryEscaped.startsWith("@") ? queryEscaped.substring(1) : queryEscaped;
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[] selectionArgs = new String[]{queryTrimmed, queryTrimmed};
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);
@ -831,7 +831,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
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");
new String[]{queryTrimmed}, null, null, null, "1");
final boolean hasName = exactUserCursor.moveToPosition(0) && exactUserCursor.getInt(0) > 0;
exactUserCursor.close();
final MatrixCursor screenNameCursor = new MatrixCursor(Suggestions.Search.COLUMNS);
@ -870,9 +870,11 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
new Column(CachedUsers.USER_ID, Suggestions.EXTRA_ID).getSQL(),
new Column(CachedUsers.PROFILE_IMAGE_URL, Suggestions.ICON).getSQL(),
};
final String[] orderBy = {"score", CachedUsers.LAST_SEEN, CachedUsers.SCREEN_NAME,
CachedUsers.NAME};
final boolean[] ascending = {false, false, true, true};
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());
mappedProjection, where.getSQL(), whereArgs, new OrderBy(orderBy, ascending).getSQL());
} else if (Suggestions.AutoComplete.TYPE_HASHTAGS.equals(type)) {
final Expression where = Expression.likeRaw(new Column(CachedHashtags.NAME), "?||'%'", "^");
final String[] whereArgs = new String[]{queryEscaped};

View File

@ -878,22 +878,18 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
final Twitter twitter = TwitterAPIFactory.getTwitterInstance(mContext, account_id, true);
if (twitter == null) return SingleResponse.getInstance();
try {
final org.mariotaku.twidere.api.twitter.model.Status status = twitter.createFavorite(status_id);
Utils.setLastSeen(mContext, status.getUserMentionEntities(), System.currentTimeMillis());
final ParcelableStatus status = new ParcelableStatus(twitter.createFavorite(status_id), account_id, false);
Utils.setLastSeen(mContext, status.mentions, System.currentTimeMillis());
final ContentValues values = new ContentValues();
values.put(Statuses.IS_FAVORITE, true);
if (status.isRetweet()) {
values.put(Statuses.FAVORITE_COUNT, status.getRetweetedStatus().getFavoriteCount());
} else {
values.put(Statuses.FAVORITE_COUNT, status.getFavoriteCount());
}
values.put(Statuses.FAVORITE_COUNT, status.favorite_count);
final Expression where = Expression.and(Expression.equals(Statuses.ACCOUNT_ID, account_id),
Expression.or(Expression.equals(Statuses.STATUS_ID, status_id),
Expression.equals(Statuses.RETWEET_ID, status_id)));
for (final Uri uri : TwidereDataStore.STATUSES_URIS) {
mResolver.update(uri, values, where.getSQL(), null);
}
return SingleResponse.getInstance(new ParcelableStatus(status, account_id, false));
return SingleResponse.getInstance(status);
} catch (final TwitterException e) {
Log.w(LOGTAG, e);
return SingleResponse.getInstance(e);
@ -1516,20 +1512,17 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
final Twitter twitter = TwitterAPIFactory.getTwitterInstance(mContext, account_id, true);
if (twitter != null) {
try {
final org.mariotaku.twidere.api.twitter.model.Status status = twitter.destroyFavorite(status_id);
final ParcelableStatus status = new ParcelableStatus(twitter.destroyFavorite(status_id), account_id, false);
final ContentValues values = new ContentValues();
values.put(Statuses.IS_FAVORITE, false);
if (status.isRetweet()) {
values.put(Statuses.FAVORITE_COUNT, status.getRetweetedStatus().getFavoriteCount());
} else {
values.put(Statuses.FAVORITE_COUNT, status.getFavoriteCount());
}
values.put(Statuses.FAVORITE_COUNT, status.favorite_count - 1);
final Expression where = Expression.and(Expression.equals(Statuses.ACCOUNT_ID, account_id),
Expression.or(Expression.equals(Statuses.STATUS_ID, status_id), Expression.equals(Statuses.RETWEET_ID, status_id)));
Expression.or(Expression.equals(Statuses.STATUS_ID, status_id),
Expression.equals(Statuses.RETWEET_ID, status_id)));
for (final Uri uri : TwidereDataStore.STATUSES_URIS) {
mResolver.update(uri, values, where.getSQL(), null);
}
return SingleResponse.getInstance(new ParcelableStatus(status, account_id, false));
return SingleResponse.getInstance(status);
} catch (final TwitterException e) {
return SingleResponse.getInstance(e);
}
@ -1730,6 +1723,9 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
if (status != null || exception.getErrorCode() == HttpResponseCode.NOT_FOUND) {
final ContentValues values = new ContentValues();
values.put(Statuses.MY_RETWEET_ID, -1);
if (status != null) {
values.put(Statuses.RETWEET_COUNT, status.retweet_count - 1);
}
for (final Uri uri : TwidereDataStore.STATUSES_URIS) {
mResolver.delete(uri, Statuses.STATUS_ID + " = " + status_id, null);
mResolver.update(uri, values, Statuses.MY_RETWEET_ID + " = " + status_id, null);
@ -2585,9 +2581,9 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return SingleResponse.getInstance();
}
try {
final org.mariotaku.twidere.api.twitter.model.Status status = twitter.retweetStatus(status_id);
Utils.setLastSeen(mContext, status.getUserMentionEntities(), System.currentTimeMillis());
return SingleResponse.getInstance(new ParcelableStatus(status, account_id, false));
final ParcelableStatus status = new ParcelableStatus(twitter.retweetStatus(status_id), account_id, false);
Utils.setLastSeen(mContext, status.mentions, System.currentTimeMillis());
return SingleResponse.getInstance(status);
} catch (final TwitterException e) {
return SingleResponse.getInstance(e);
}
@ -2606,9 +2602,10 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
protected void onPostExecute(final SingleResponse<ParcelableStatus> result) {
mCreatingRetweetIds.remove(account_id, status_id);
if (result.hasData()) {
final ContentValues values = new ContentValues();
final ParcelableStatus status = result.getData();
final ContentValues values = new ContentValues();
values.put(Statuses.MY_RETWEET_ID, status.id);
values.put(Statuses.RETWEET_COUNT, status.retweet_count);
final Expression where = Expression.or(
Expression.equals(Statuses.STATUS_ID, status_id),
Expression.equals(Statuses.RETWEET_ID, status_id)
@ -2625,7 +2622,6 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
// END HotMobi
bus.post(new StatusRetweetedEvent(status));
Utils.showOkMessage(mContext, R.string.status_retweeted, false);
} else {

View File

@ -22,6 +22,7 @@ package org.mariotaku.twidere.util;
import android.net.Uri;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.model.ParcelableStatus;
/**
* Created by mariotaku on 15/3/14.
@ -83,4 +84,8 @@ public class LinkCreator implements Constants {
builder.appendPath(screenName);
return builder.build();
}
public static Uri getTwitterStatusLink(ParcelableStatus status) {
return getTwitterStatusLink(status.user_screen_name, status.id);
}
}

View File

@ -0,0 +1,403 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.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();
}
}

View File

@ -134,6 +134,7 @@ import org.mariotaku.sqliteqb.library.query.SQLSelectQuery;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.CopyLinkActivity;
import org.mariotaku.twidere.activity.support.AccountSelectorActivity;
import org.mariotaku.twidere.activity.support.ColorPickerDialogActivity;
import org.mariotaku.twidere.activity.support.MediaViewerActivity;
@ -197,6 +198,7 @@ import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.ParcelableUser;
import org.mariotaku.twidere.model.ParcelableUserList;
import org.mariotaku.twidere.model.ParcelableUserMention;
import org.mariotaku.twidere.model.PebbleMessage;
import org.mariotaku.twidere.provider.TwidereDataStore;
import org.mariotaku.twidere.provider.TwidereDataStore.Accounts;
@ -1181,7 +1183,7 @@ public final class Utils implements Constants {
}
public static String getStatusShareText(@NonNull final Context context, @NonNull final ParcelableStatus status) {
final Uri link = LinkCreator.getTwitterStatusLink(status.user_screen_name, status.id);
final Uri link = LinkCreator.getTwitterStatusLink(status);
return context.getString(R.string.status_share_text_format_with_link,
status.text_plain, link.toString());
}
@ -1631,6 +1633,15 @@ public final class Utils implements Constants {
return textView;
}
public static boolean setLastSeen(Context context, ParcelableUserMention[] entities, long time) {
if (entities == null) return false;
boolean result = false;
for (ParcelableUserMention entity : entities) {
result |= setLastSeen(context, entity.id, time);
}
return result;
}
public static boolean setLastSeen(Context context, UserMentionEntity[] entities, long time) {
if (entities == null) return false;
boolean result = false;
@ -3379,11 +3390,20 @@ public final class Utils implements Constants {
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)));
final Intent chooserIntent = Intent.createChooser(shareIntent, context.getString(R.string.share_status));
addCopyLinkIntent(context, chooserIntent, LinkCreator.getTwitterStatusLink(status));
shareItem.setIntent(chooserIntent);
}
}
public static void addCopyLinkIntent(Context context, Intent chooserIntent, Uri uri) {
final Intent copyLinkIntent = new Intent(context, CopyLinkActivity.class);
copyLinkIntent.setData(uri);
final Intent[] alternateIntents = {copyLinkIntent};
chooserIntent.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alternateIntents);
}
private static boolean isMyStatus(ParcelableStatus status) {
if (isMyRetweet(status)) return true;
return status.account_id == status.user_id;

View File

@ -102,14 +102,28 @@ public class ActivityTitleSummaryViewHolder extends ViewHolder implements View.O
break;
}
case Activity.ACTION_FAVORITE: {
activityTypeView.setImageResource(R.drawable.ic_activity_action_favorite);
activityTypeView.setColorFilter(ContextCompat.getColor(context, R.color.highlight_like), Mode.SRC_ATOP);
if (byFriends) {
titleView.setText(getTitleStringByFriends(R.string.activity_by_friends_favorite,
R.string.activity_by_friends_favorite_multi, activity.sources, activity.target_statuses));
if (adapter.shouldUseStarsForLikes()) {
activityTypeView.setImageResource(R.drawable.ic_activity_action_favorite);
activityTypeView.setColorFilter(ContextCompat.getColor(context, R.color.highlight_favorite), Mode.SRC_ATOP);
if (byFriends) {
titleView.setText(getTitleStringByFriends(R.string.activity_by_friends_favorite,
R.string.activity_by_friends_favorite_multi, activity.sources, activity.target_statuses));
} else {
titleView.setText(getTitleStringAboutMe(R.string.activity_about_me_favorite,
R.string.activity_about_me_favorite_multi, activity.sources));
}
} else {
titleView.setText(getTitleStringAboutMe(R.string.activity_about_me_favorite,
R.string.activity_about_me_favorite_multi, activity.sources));
activityTypeView.setImageResource(R.drawable.ic_activity_action_like);
activityTypeView.setColorFilter(ContextCompat.getColor(context, R.color.highlight_like), Mode.SRC_ATOP);
if (byFriends) {
titleView.setText(getTitleStringByFriends(R.string.activity_by_friends_like,
R.string.activity_by_friends_like_multi, activity.sources, activity.target_statuses));
} else {
titleView.setText(getTitleStringAboutMe(R.string.activity_about_me_like,
R.string.activity_about_me_like_multi, activity.sources));
}
}
displayUserProfileImages(activity.sources);
summaryView.setText(activity.target_statuses[0].text_unescaped);
@ -135,10 +149,17 @@ public class ActivityTitleSummaryViewHolder extends ViewHolder implements View.O
showNotSupported();
return;
}
activityTypeView.setImageResource(R.drawable.ic_activity_action_favorite);
activityTypeView.setColorFilter(ContextCompat.getColor(context, R.color.highlight_like), Mode.SRC_ATOP);
titleView.setText(getTitleStringAboutMe(R.string.activity_about_me_favorited_retweet,
R.string.activity_about_me_favorited_retweet_multi, activity.sources));
if (adapter.shouldUseStarsForLikes()) {
activityTypeView.setImageResource(R.drawable.ic_activity_action_favorite);
activityTypeView.setColorFilter(ContextCompat.getColor(context, R.color.highlight_favorite), Mode.SRC_ATOP);
titleView.setText(getTitleStringAboutMe(R.string.activity_about_me_favorited_retweet,
R.string.activity_about_me_favorited_retweet_multi, activity.sources));
} else {
activityTypeView.setImageResource(R.drawable.ic_activity_action_like);
activityTypeView.setColorFilter(ContextCompat.getColor(context, R.color.highlight_like), Mode.SRC_ATOP);
titleView.setText(getTitleStringAboutMe(R.string.activity_about_me_liked_retweet,
R.string.activity_about_me_liked_retweet_multi, activity.sources));
}
displayUserProfileImages(activity.sources);
summaryView.setText(activity.target_statuses[0].text_unescaped);
summaryView.setVisibility(View.VISIBLE);
@ -177,10 +198,17 @@ public class ActivityTitleSummaryViewHolder extends ViewHolder implements View.O
showNotSupported();
return;
}
activityTypeView.setImageResource(R.drawable.ic_activity_action_favorite);
activityTypeView.setColorFilter(ContextCompat.getColor(context, R.color.highlight_like), Mode.SRC_ATOP);
titleView.setText(getTitleStringAboutMe(R.string.activity_about_me_liked_mention,
R.string.activity_about_me_liked_mention_multi, activity.sources));
if (adapter.shouldUseStarsForLikes()) {
activityTypeView.setImageResource(R.drawable.ic_activity_action_favorite);
activityTypeView.setColorFilter(ContextCompat.getColor(context, R.color.highlight_favorite), Mode.SRC_ATOP);
titleView.setText(getTitleStringAboutMe(R.string.activity_about_me_favorited_mention,
R.string.activity_about_me_favorited_mention_multi, activity.sources));
} else {
activityTypeView.setImageResource(R.drawable.ic_activity_action_like);
activityTypeView.setColorFilter(ContextCompat.getColor(context, R.color.highlight_like), Mode.SRC_ATOP);
titleView.setText(getTitleStringAboutMe(R.string.activity_about_me_liked_mention,
R.string.activity_about_me_liked_mention_multi, activity.sources));
}
displayUserProfileImages(activity.sources);
summaryView.setText(activity.target_statuses[0].text_unescaped);
summaryView.setVisibility(View.VISIBLE);

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Twidere - Twitter client for Android
~
~ Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tool="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<org.mariotaku.twidere.view.ShapedImageView
android:id="@android:id/icon"
style="?profileImageStyle"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
<TextView
android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="?android:textColorPrimary"
android:textStyle="bold"
tool:text="Name" />
</LinearLayout>

View File

@ -51,6 +51,7 @@
android:title="@string/enable_retweets" />
<item
android:id="@id/mute_user"
android:checkable="true"
android:icon="@drawable/ic_action_mic_muted"
android:title="@string/twitter_mute_user" />
<item

View File

@ -279,6 +279,10 @@
<string name="activity_about_me_favorite_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> favorited your tweet.</string>
<string name="activity_about_me_favorited_retweet"><xliff:g id="user">%s</xliff:g> favorited your retweet.</string>
<string name="activity_about_me_favorited_retweet_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> favorited your retweet.</string>
<string name="activity_about_me_like"><xliff:g id="user">%s</xliff:g> liked your tweet.</string>
<string name="activity_about_me_like_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> liked your tweet.</string>
<string name="activity_about_me_liked_retweet"><xliff:g id="user">%s</xliff:g> liked your retweet.</string>
<string name="activity_about_me_liked_retweet_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> liked your retweet.</string>
<string name="activity_about_me_follow"><xliff:g id="user">%s</xliff:g> is following you.</string>
<string name="activity_about_me_follow_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> are following you.</string>
<string name="activity_about_me_retweet"><xliff:g id="user">%s</xliff:g> retweeted your tweet.</string>
@ -296,6 +300,8 @@
<string name="activity_about_me_list_member_added_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> added you to their lists.</string>
<string name="activity_by_friends_favorite"><xliff:g id="user">%1$s</xliff:g> favorited <xliff:g id="target">%2$s</xliff:g>\'s tweet.</string>
<string name="activity_by_friends_favorite_multi"><xliff:g id="user">%1$s</xliff:g> favorited <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%3$s</xliff:g>\'s tweet.</string>
<string name="activity_by_friends_like"><xliff:g id="user">%1$s</xliff:g> liked <xliff:g id="target">%2$s</xliff:g>\'s tweet.</string>
<string name="activity_by_friends_like_multi"><xliff:g id="user">%1$s</xliff:g> liked <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%3$s</xliff:g>\'s tweet.</string>
<string name="activity_by_friends_follow"><xliff:g id="user">%1$s</xliff:g> is following <xliff:g id="target">%2$s</xliff:g>.</string>
<string name="activity_by_friends_follow_multi"><xliff:g id="user">%1$s</xliff:g> is following <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%3$s</xliff:g>.</string>
<string name="activity_by_friends_retweet"><xliff:g id="user">%1$s</xliff:g> retweeted <xliff:g id="target">%2$s</xliff:g>\'s tweet.</string>
@ -811,4 +817,6 @@
<string name="twitter_optimized_searches_summary">Use special search terms to improve search results like exclude retweets</string>
<string name="i_want_my_stars_back">I want my stars back!</string>
<string name="i_want_my_stars_back_summary">Show use favorite (star) instead of like (heart)</string>
<string name="copy_link">Copy link</string>
<string name="link_copied_to_clipboard">Link copied to clipboard</string>
</resources>

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" fill="#ffffff"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 295 B

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.4.1 (15681) - http://www.bohemiancoding.com/sketch -->
<title>Artboard</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="Artboard" sketch:type="MSArtboardGroup" fill="#FFFFFF">
<path d="M12,20.4325 L10.695,19.2445 C6.06,15.0415 3,12.2695 3,8.8675 C3,6.0955 5.178,3.9175 7.95,3.9175 C9.516,3.9175 11.019,4.6465 12,5.7985 C12.981,4.6465 14.484,3.9175 16.05,3.9175 C18.822,3.9175 21,6.0955 21,8.8675 C21,12.2695 17.94,15.0415 13.305,19.2535 L12,20.4325 L12,20.4325 Z" id="Shape" sketch:type="MSShapeGroup"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 972 B