diff --git a/build.gradle b/build.gradle index 301f12951..888109126 100644 --- a/build.gradle +++ b/build.gradle @@ -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 } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 30d399d8d..fd7e590e5 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 50c99ee33..5d18332f7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/twidere/build.gradle b/twidere/build.gradle index 5a6574f82..e8e5eb6a8 100644 --- a/twidere/build.gradle +++ b/twidere/build.gradle @@ -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' diff --git a/twidere/src/main/AndroidManifest.xml b/twidere/src/main/AndroidManifest.xml index 2b75df2a3..135c5eba2 100644 --- a/twidere/src/main/AndroidManifest.xml +++ b/twidere/src/main/AndroidManifest.xml @@ -341,6 +341,10 @@ + + * + * 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 . + */ + +/* + * 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 initialCapacity 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-null collection of ints + * to add + * @throws NullPointerException if that is null + * @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[])} + *

+ * 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)} + *

+ * 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); + } + } + +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/CopyLinkActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/CopyLinkActivity.java new file mode 100644 index 000000000..8575c2ff9 --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/CopyLinkActivity.java @@ -0,0 +1,39 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 Mariotaku Lee + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.mariotaku.twidere.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(); + } +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/QuickSearchBarActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/QuickSearchBarActivity.java index b15418e10..484e4b765 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/QuickSearchBarActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/QuickSearchBarActivity.java @@ -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, 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); diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsActivitiesAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsActivitiesAdapter.java index caf8c0948..80d77dafa 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsActivitiesAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsActivitiesAdapter.java @@ -58,7 +58,8 @@ import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder; * Created by mariotaku on 15/1/3. */ public abstract class AbsActivitiesAdapter extends LoadMoreSupportAdapter implements Constants, - IActivitiesAdapter, IStatusViewHolder.StatusClickListener, OnLinkClickListener, ActivityTitleSummaryViewHolder.ActivityClickListener { + IActivitiesAdapter, 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 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 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 extends LoadMoreSupportAdapter< } + @Override + public boolean shouldUseStarsForLikes() { + return mShouldUseStarsForLikes; + } + @Override public void onUserProfileClick(IStatusViewHolder holder, int position) { final Context context = getContext(); diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IActivitiesAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IActivitiesAdapter.java index 690785bfc..868781fea 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IActivitiesAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IActivitiesAdapter.java @@ -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 extends IContentCardAdapter, IGapSuppo @PreviewStyle int getMediaPreviewStyle(); - @NonNull - @Override - MediaLoaderWrapper getMediaLoader(); + boolean shouldUseStarsForLikes(); MediaLoadingHandler getMediaLoadingHandler(); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java index 41a50cd76..e759fa460 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java @@ -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 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, diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/RetweetQuoteDialogFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/RetweetQuoteDialogFragment.java index 7d5eded23..8fb147175 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/RetweetQuoteDialogFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/RetweetQuoteDialogFragment.java @@ -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)); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java index 3c02a14be..b003c8e59 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java @@ -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)), }); } }); diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java index 38b2b6353..8832c8e24 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java @@ -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 { diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListFragment.java index fb3c17bd9..9ebe31621 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListFragment.java @@ -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); diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListMembersFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListMembersFragment.java index ac8eeaec4..af9b81664 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListMembersFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListMembersFragment.java @@ -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 { + private class GetUserListTask extends AsyncTask> { 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 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 result) { if (mUserList != null) return; - mUserList = result; + mUserList = result.getData(); } } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java b/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java index 7dd78621b..02f9046d1 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java +++ b/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java @@ -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}; diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/AsyncTwitterWrapper.java b/twidere/src/main/java/org/mariotaku/twidere/util/AsyncTwitterWrapper.java index 5efa20e87..cf44d8e93 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/AsyncTwitterWrapper.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/AsyncTwitterWrapper.java @@ -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 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 { diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/LinkCreator.java b/twidere/src/main/java/org/mariotaku/twidere/util/LinkCreator.java index c95a6d340..242b61b7a 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/LinkCreator.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/LinkCreator.java @@ -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); + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/SwipeDismissListViewTouchListener.java b/twidere/src/main/java/org/mariotaku/twidere/util/SwipeDismissListViewTouchListener.java new file mode 100644 index 000000000..34745a47d --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/util/SwipeDismissListViewTouchListener.java @@ -0,0 +1,403 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 Mariotaku Lee + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.mariotaku.twidere.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. + *

+ *

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.

+ *

+ *

Example usage:

+ *

+ *

+ * 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());
+ * 
+ *

+ *

This class Requires API level 12 or later due to use of {@link + * ViewPropertyAnimator}.

+ */ +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 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.

+ * + * @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 { + 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(); + } +} \ No newline at end of file diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java index 2c38dddc2..082c97a0b 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java @@ -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; diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/ActivityTitleSummaryViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/ActivityTitleSummaryViewHolder.java index 0d6c700d3..7e37dcc35 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/ActivityTitleSummaryViewHolder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/holder/ActivityTitleSummaryViewHolder.java @@ -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); diff --git a/twidere/src/main/res-svg2png/drawable-hdpi/ic_activity_action_favorite.png b/twidere/src/main/res-svg2png/drawable-hdpi/ic_activity_action_favorite.png new file mode 100644 index 000000000..c4b71586c Binary files /dev/null and b/twidere/src/main/res-svg2png/drawable-hdpi/ic_activity_action_favorite.png differ diff --git a/twidere/src/main/res-svg2png/drawable-hdpi/ic_activity_action_like.png b/twidere/src/main/res-svg2png/drawable-hdpi/ic_activity_action_like.png new file mode 100644 index 000000000..76bf2a7ed Binary files /dev/null and b/twidere/src/main/res-svg2png/drawable-hdpi/ic_activity_action_like.png differ diff --git a/twidere/src/main/res-svg2png/drawable-mdpi/ic_activity_action_favorite.png b/twidere/src/main/res-svg2png/drawable-mdpi/ic_activity_action_favorite.png new file mode 100644 index 000000000..24d927383 Binary files /dev/null and b/twidere/src/main/res-svg2png/drawable-mdpi/ic_activity_action_favorite.png differ diff --git a/twidere/src/main/res-svg2png/drawable-mdpi/ic_activity_action_like.png b/twidere/src/main/res-svg2png/drawable-mdpi/ic_activity_action_like.png new file mode 100644 index 000000000..616d6198b Binary files /dev/null and b/twidere/src/main/res-svg2png/drawable-mdpi/ic_activity_action_like.png differ diff --git a/twidere/src/main/res-svg2png/drawable-xhdpi/ic_activity_action_favorite.png b/twidere/src/main/res-svg2png/drawable-xhdpi/ic_activity_action_favorite.png new file mode 100644 index 000000000..dd5d17e18 Binary files /dev/null and b/twidere/src/main/res-svg2png/drawable-xhdpi/ic_activity_action_favorite.png differ diff --git a/twidere/src/main/res-svg2png/drawable-xhdpi/ic_activity_action_like.png b/twidere/src/main/res-svg2png/drawable-xhdpi/ic_activity_action_like.png new file mode 100644 index 000000000..db9f1d6d4 Binary files /dev/null and b/twidere/src/main/res-svg2png/drawable-xhdpi/ic_activity_action_like.png differ diff --git a/twidere/src/main/res-svg2png/drawable-xxhdpi/ic_activity_action_favorite.png b/twidere/src/main/res-svg2png/drawable-xxhdpi/ic_activity_action_favorite.png new file mode 100644 index 000000000..d1aaf4f68 Binary files /dev/null and b/twidere/src/main/res-svg2png/drawable-xxhdpi/ic_activity_action_favorite.png differ diff --git a/twidere/src/main/res-svg2png/drawable-xxhdpi/ic_activity_action_like.png b/twidere/src/main/res-svg2png/drawable-xxhdpi/ic_activity_action_like.png new file mode 100644 index 000000000..152adbf0e Binary files /dev/null and b/twidere/src/main/res-svg2png/drawable-xxhdpi/ic_activity_action_like.png differ diff --git a/twidere/src/main/res-svg2png/drawable-xxxhdpi/ic_activity_action_favorite.png b/twidere/src/main/res-svg2png/drawable-xxxhdpi/ic_activity_action_favorite.png new file mode 100644 index 000000000..a2e8fb55e Binary files /dev/null and b/twidere/src/main/res-svg2png/drawable-xxxhdpi/ic_activity_action_favorite.png differ diff --git a/twidere/src/main/res-svg2png/drawable-xxxhdpi/ic_activity_action_like.png b/twidere/src/main/res-svg2png/drawable-xxxhdpi/ic_activity_action_like.png new file mode 100644 index 000000000..e6ab25af5 Binary files /dev/null and b/twidere/src/main/res-svg2png/drawable-xxxhdpi/ic_activity_action_like.png differ diff --git a/twidere/src/main/res/drawable-hdpi/ic_activity_action_favorite.png b/twidere/src/main/res/drawable-hdpi/ic_activity_action_favorite.png deleted file mode 100755 index 6d5a9b300..000000000 Binary files a/twidere/src/main/res/drawable-hdpi/ic_activity_action_favorite.png and /dev/null differ diff --git a/twidere/src/main/res/drawable-mdpi/ic_activity_action_favorite.png b/twidere/src/main/res/drawable-mdpi/ic_activity_action_favorite.png deleted file mode 100755 index 09d662b7f..000000000 Binary files a/twidere/src/main/res/drawable-mdpi/ic_activity_action_favorite.png and /dev/null differ diff --git a/twidere/src/main/res/drawable-xhdpi/ic_activity_action_favorite.png b/twidere/src/main/res/drawable-xhdpi/ic_activity_action_favorite.png deleted file mode 100755 index b2ef3e478..000000000 Binary files a/twidere/src/main/res/drawable-xhdpi/ic_activity_action_favorite.png and /dev/null differ diff --git a/twidere/src/main/res/drawable-xxhdpi/ic_activity_action_favorite.png b/twidere/src/main/res/drawable-xxhdpi/ic_activity_action_favorite.png deleted file mode 100755 index fe555ff11..000000000 Binary files a/twidere/src/main/res/drawable-xxhdpi/ic_activity_action_favorite.png and /dev/null differ diff --git a/twidere/src/main/res/layout/list_item_compose_account.xml b/twidere/src/main/res/layout/list_item_compose_account.xml new file mode 100644 index 000000000..acc025d68 --- /dev/null +++ b/twidere/src/main/res/layout/list_item_compose_account.xml @@ -0,0 +1,43 @@ + + + + + + + + + \ No newline at end of file diff --git a/twidere/src/main/res/menu/menu_user_profile.xml b/twidere/src/main/res/menu/menu_user_profile.xml index fb80c3467..f371d91f4 100644 --- a/twidere/src/main/res/menu/menu_user_profile.xml +++ b/twidere/src/main/res/menu/menu_user_profile.xml @@ -51,6 +51,7 @@ android:title="@string/enable_retweets" /> %1$s and %2$s favorited your tweet. %s favorited your retweet. %1$s and %2$s favorited your retweet. + %s liked your tweet. + %1$s and %2$s liked your tweet. + %s liked your retweet. + %1$s and %2$s liked your retweet. %s is following you. %1$s and %2$s are following you. %s retweeted your tweet. @@ -296,6 +300,8 @@ %1$s and %2$s added you to their lists. %1$s favorited %2$s\'s tweet. %1$s favorited %2$s and %3$s\'s tweet. + %1$s liked %2$s\'s tweet. + %1$s liked %2$s and %3$s\'s tweet. %1$s is following %2$s. %1$s is following %2$s and %3$s. %1$s retweeted %2$s\'s tweet. @@ -811,4 +817,6 @@ Use special search terms to improve search results like exclude retweets I want my stars back! Show use favorite (star) instead of like (heart) + Copy link + Link copied to clipboard \ No newline at end of file diff --git a/twidere/src/main/svg/drawable/ic_activity_action_favorite-mdpi.svg b/twidere/src/main/svg/drawable/ic_activity_action_favorite-mdpi.svg new file mode 100644 index 000000000..fa3a669f6 --- /dev/null +++ b/twidere/src/main/svg/drawable/ic_activity_action_favorite-mdpi.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/twidere/src/main/svg/drawable/ic_activity_action_like-mdpi.svg b/twidere/src/main/svg/drawable/ic_activity_action_like-mdpi.svg new file mode 100644 index 000000000..97d0679f0 --- /dev/null +++ b/twidere/src/main/svg/drawable/ic_activity_action_like-mdpi.svg @@ -0,0 +1,12 @@ + + + + Artboard + Created with Sketch. + + + + + + + \ No newline at end of file