From 64b76923917e6b69d1038e85515c79efb2175400 Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Wed, 13 May 2015 23:40:56 +0800 Subject: [PATCH] quote is also available for users not using official keys! fixed black background issue --- twidere.component.common/build.gradle | 1 + .../api/twitter/api/TweetResources.java | 4 ++ .../api/twitter/model/impl/StatusImpl.java | 14 +++-- .../twidere/util/TwitterContentUtils.java | 59 +++++++++++++++++-- .../util/collection/CompactHashSet.java | 0 .../util/collection/LongSparseMap.java | 22 +++++-- .../support/BrowserSignInActivity.java | 6 +- .../support/ThemedAppCompatActivity.java | 2 +- .../support/RetweetQuoteDialogFragment.java | 14 +++++ .../support/TwitterAPIStatusesLoader.java | 5 ++ .../org/mariotaku/twidere/util/Utils.java | 1 - .../twidere/view/StatusComposeEditText.java | 5 +- .../layout/fragment_messages_conversation.xml | 5 +- 13 files changed, 114 insertions(+), 24 deletions(-) rename {twidere => twidere.component.common}/src/main/java/org/mariotaku/twidere/util/collection/CompactHashSet.java (100%) rename {twidere => twidere.component.common}/src/main/java/org/mariotaku/twidere/util/collection/LongSparseMap.java (77%) diff --git a/twidere.component.common/build.gradle b/twidere.component.common/build.gradle index 902b761e2..dae6ab470 100644 --- a/twidere.component.common/build.gradle +++ b/twidere.component.common/build.gradle @@ -39,6 +39,7 @@ android { dependencies { apt 'com.bluelinelabs:logansquare-compiler:1.0.6' compile 'com.android.support:support-annotations:22.1.1' + compile 'com.android.support:support-v4:22.1.1' compile 'com.bluelinelabs:logansquare:1.0.6' compile 'org.apache.commons:commons-lang3:3.4' compile project(':twidere.component.querybuilder') diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/api/TweetResources.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/api/TweetResources.java index d4cc44280..dd3c2b8fd 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/api/TweetResources.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/api/TweetResources.java @@ -56,4 +56,8 @@ public interface TweetResources { @Body(BodyType.FORM) Status updateStatus(@Form StatusUpdate latestStatus) throws TwitterException; + @POST("/statuses/lookup.json") + @Body(BodyType.FORM) + ResponseList lookupStatuses(@Form("id") long[] ids) throws TwitterException; + } diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/model/impl/StatusImpl.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/model/impl/StatusImpl.java index 3093d90e5..89e4bbf9a 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/model/impl/StatusImpl.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/model/impl/StatusImpl.java @@ -24,11 +24,6 @@ import android.support.annotation.NonNull; import com.bluelinelabs.logansquare.annotation.JsonField; import com.bluelinelabs.logansquare.annotation.JsonObject; -import org.mariotaku.twidere.api.twitter.util.TwitterDateConverter; - -import java.util.Arrays; -import java.util.Date; - import org.mariotaku.twidere.api.twitter.model.CardEntity; import org.mariotaku.twidere.api.twitter.model.GeoLocation; import org.mariotaku.twidere.api.twitter.model.HashtagEntity; @@ -38,6 +33,10 @@ import org.mariotaku.twidere.api.twitter.model.Status; import org.mariotaku.twidere.api.twitter.model.UrlEntity; import org.mariotaku.twidere.api.twitter.model.User; import org.mariotaku.twidere.api.twitter.model.UserMentionEntity; +import org.mariotaku.twidere.api.twitter.util.TwitterDateConverter; + +import java.util.Arrays; +import java.util.Date; /** * Created by mariotaku on 15/5/5. @@ -326,6 +325,11 @@ public class StatusImpl extends TwitterResponseImpl implements Status { '}'; } + public static void setQuotedStatus(Status status, Status quoted) { + if (!(status instanceof StatusImpl)) return; + ((StatusImpl) status).quotedStatus = quoted; + } + @JsonObject static class CurrentUserRetweet { @JsonField(name = "id") diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/util/TwitterContentUtils.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/util/TwitterContentUtils.java index 53c63f286..eb1542813 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/util/TwitterContentUtils.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/util/TwitterContentUtils.java @@ -22,12 +22,8 @@ package org.mariotaku.twidere.util; import android.content.Context; import android.support.annotation.NonNull; -import org.mariotaku.twidere.common.R; -import org.mariotaku.twidere.model.ConsumerKeyType; - -import java.nio.charset.Charset; -import java.util.zip.CRC32; - +import org.mariotaku.twidere.api.twitter.Twitter; +import org.mariotaku.twidere.api.twitter.TwitterException; import org.mariotaku.twidere.api.twitter.model.DirectMessage; import org.mariotaku.twidere.api.twitter.model.EntitySupport; import org.mariotaku.twidere.api.twitter.model.MediaEntity; @@ -35,6 +31,17 @@ import org.mariotaku.twidere.api.twitter.model.Status; import org.mariotaku.twidere.api.twitter.model.UrlEntity; import org.mariotaku.twidere.api.twitter.model.User; import org.mariotaku.twidere.api.twitter.model.UserMentionEntity; +import org.mariotaku.twidere.api.twitter.model.impl.StatusImpl; +import org.mariotaku.twidere.common.R; +import org.mariotaku.twidere.model.ConsumerKeyType; +import org.mariotaku.twidere.util.collection.LongSparseMap; + +import java.nio.charset.Charset; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.CRC32; import static org.mariotaku.twidere.util.HtmlEscapeHelper.toPlainText; @@ -42,6 +49,9 @@ import static org.mariotaku.twidere.util.HtmlEscapeHelper.toPlainText; * Created by mariotaku on 15/1/11. */ public class TwitterContentUtils { + + public static final int TWITTER_BULK_QUERY_COUNT = 100; + public static String formatDirectMessageText(final DirectMessage message) { if (message == null) return null; final HtmlBuilder builder = new HtmlBuilder(message.getText(), false, true, true); @@ -159,6 +169,43 @@ public class TwitterContentUtils { return str.replace("&", "&").replace("<", "<").replace(">", ">"); } + private static final Pattern PATTERN_TWITTER_STATUS_LINK = Pattern.compile("https?://twitter\\.com/(?:#!/)?(\\w+)/status(es)?/(\\d+)"); + + public static > T getStatusesWithQuoteData(Twitter twitter, @NonNull T list) throws TwitterException { + LongSparseMap quotes = new LongSparseMap<>(); + // Phase 1: collect all statuses contains a status link, and put it in the map + for (Status status : list) { + if (status.isQuote()) continue; + final UrlEntity[] entities = status.getUrlEntities(); + if (entities == null || entities.length <= 0) continue; + // Seems Twitter will find last status link for quote target, so we search backward + for (int i = entities.length - 1; i >= 0; i--) { + final Matcher m = PATTERN_TWITTER_STATUS_LINK.matcher(entities[i].getExpandedUrl()); + if (!m.matches()) continue; + quotes.put(Long.parseLong(m.group(3)), status); + break; + } + } + // Phase 2: look up quoted tweets. Each lookup can fetch up to 100 tweets, so we split quote + // ids into batches + final long[] quoteIds = quotes.keys(); + for (int currentBulkIdx = 0, totalLength = quoteIds.length; currentBulkIdx < totalLength; currentBulkIdx += TWITTER_BULK_QUERY_COUNT) { + final int currentBulkCount = Math.min(totalLength, currentBulkIdx + TWITTER_BULK_QUERY_COUNT); + final long[] ids = new long[currentBulkCount]; + System.arraycopy(quoteIds, currentBulkIdx, ids, 0, currentBulkCount); + // Lookup quoted statuses, then set each status into original status + for (Status quoted : twitter.lookupStatuses(ids)) { + final Set orig = quotes.get(quoted.getId()); + // This set shouldn't be null here, add null check to make inspector happy. + if (orig == null) continue; + for (Status status : orig) { + StatusImpl.setQuotedStatus(status, quoted); + } + } + } + return list; + } + private static void parseEntities(final HtmlBuilder builder, final EntitySupport entities) { // Format media. final MediaEntity[] mediaEntities = entities.getMediaEntities(); diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/collection/CompactHashSet.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/util/collection/CompactHashSet.java similarity index 100% rename from twidere/src/main/java/org/mariotaku/twidere/util/collection/CompactHashSet.java rename to twidere.component.common/src/main/java/org/mariotaku/twidere/util/collection/CompactHashSet.java diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/collection/LongSparseMap.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/util/collection/LongSparseMap.java similarity index 77% rename from twidere/src/main/java/org/mariotaku/twidere/util/collection/LongSparseMap.java rename to twidere.component.common/src/main/java/org/mariotaku/twidere/util/collection/LongSparseMap.java index 7d3f032d1..aa5754b13 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/collection/LongSparseMap.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/util/collection/LongSparseMap.java @@ -19,16 +19,17 @@ package org.mariotaku.twidere.util.collection; +import android.support.annotation.Nullable; import android.support.v4.util.LongSparseArray; -import java.util.HashSet; +import java.util.Set; /** * Created by mariotaku on 14/12/12. */ public class LongSparseMap { - private final LongSparseArray> internalArray; + private final LongSparseArray> internalArray; public LongSparseMap() { internalArray = new LongSparseArray<>(); @@ -36,9 +37,9 @@ public class LongSparseMap { public boolean put(long key, T value) { final int idx = internalArray.indexOfKey(key); - final HashSet set; + final CompactHashSet set; if (idx < 0) { - set = new HashSet<>(); + set = new CompactHashSet<>(); internalArray.put(key, set); } else { set = internalArray.valueAt(idx); @@ -46,6 +47,11 @@ public class LongSparseMap { return set.add(value); } + @Nullable + public Set get(long key) { + return internalArray.get(key); + } + public boolean clear(long key) { final int idx = internalArray.indexOfKey(key); if (idx < 0) return false; @@ -63,4 +69,12 @@ public class LongSparseMap { return idx >= 0 && internalArray.valueAt(idx).contains(value); } + public long[] keys() { + final long[] keys = new long[internalArray.size()]; + for (int i = 0, j = internalArray.size(); i < j; i++) { + keys[i] = internalArray.keyAt(i); + } + return keys; + } + } diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/BrowserSignInActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/BrowserSignInActivity.java index d9b4221be..b27d3e653 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/BrowserSignInActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/BrowserSignInActivity.java @@ -42,6 +42,7 @@ import android.widget.Toast; import org.mariotaku.simplerestapi.http.Authorization; import org.mariotaku.simplerestapi.http.Endpoint; import org.mariotaku.twidere.R; +import org.mariotaku.twidere.api.twitter.TwitterOAuth; import org.mariotaku.twidere.api.twitter.auth.OAuthAuthorization; import org.mariotaku.twidere.api.twitter.auth.OAuthToken; import org.mariotaku.twidere.app.TwidereApplication; @@ -55,14 +56,11 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.StringReader; -import org.mariotaku.twidere.api.twitter.TwitterConstants; -import org.mariotaku.twidere.api.twitter.TwitterOAuth; - import static android.text.TextUtils.isEmpty; import static org.mariotaku.twidere.util.Utils.getNonEmptyString; @SuppressLint("SetJavaScriptEnabled") -public class BrowserSignInActivity extends BaseSupportDialogActivity { +public class BrowserSignInActivity extends BaseSupportDialogActivity { private static final String INJECT_CONTENT = "javascript:window.injector.processHTML(''+document.getElementsByTagName('html')[0].innerHTML+'');"; diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/ThemedAppCompatActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/ThemedAppCompatActivity.java index 0bb7ff8cc..391f03626 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/ThemedAppCompatActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/ThemedAppCompatActivity.java @@ -124,7 +124,7 @@ public abstract class ThemedAppCompatActivity extends AppCompatActivity implemen public void setTheme(int resid) { super.setTheme(mCurrentThemeResource = getThemeResourceId()); if (shouldApplyWindowBackground()) { - ThemeUtils.applyWindowBackground(this, getWindow(), resid, mCurrentThemeBackgroundOption, + ThemeUtils.applyWindowBackground(this, getWindow(), mCurrentThemeResource, mCurrentThemeBackgroundOption, mCurrentThemeBackgroundAlpha); } } 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 2ccc6fd01..e9e9c50a9 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 @@ -37,6 +37,7 @@ import android.view.MenuItem; import android.view.View; import com.rengwuxian.materialedittext.MaterialEditText; +import com.rengwuxian.materialedittext.validation.METLengthChecker; import org.mariotaku.twidere.R; import org.mariotaku.twidere.model.ParcelableStatus; @@ -44,6 +45,7 @@ import org.mariotaku.twidere.util.AsyncTwitterWrapper; import org.mariotaku.twidere.util.LinkCreator; import org.mariotaku.twidere.util.MenuUtils; import org.mariotaku.twidere.util.ThemeUtils; +import org.mariotaku.twidere.util.TwidereValidator; import org.mariotaku.twidere.view.holder.StatusViewHolder; import org.mariotaku.twidere.view.holder.StatusViewHolder.DummyStatusHolderAdapter; @@ -56,6 +58,7 @@ public class RetweetQuoteDialogFragment extends BaseSupportDialogFragment implem private MaterialEditText mEditComment; private PopupMenu mPopupMenu; private View mCommentMenu; + private TwidereValidator mValidator; @Override public void onClick(final DialogInterface dialog, final int which) { @@ -108,6 +111,7 @@ public class RetweetQuoteDialogFragment extends BaseSupportDialogFragment implem final Context wrapped = ThemeUtils.getDialogThemedContext(getActivity()); final AlertDialog.Builder builder = new AlertDialog.Builder(wrapped); final Context context = builder.getContext(); + mValidator = new TwidereValidator(context); final LayoutInflater inflater = LayoutInflater.from(context); @SuppressLint("InflateParams") final View view = inflater.inflate(R.layout.dialog_status_quote_retweet, null); final StatusViewHolder holder = new StatusViewHolder(new DummyStatusHolderAdapter(context), view.findViewById(R.id.item_content)); @@ -127,6 +131,16 @@ public class RetweetQuoteDialogFragment extends BaseSupportDialogFragment implem view.findViewById(R.id.action_buttons).setVisibility(View.GONE); view.findViewById(R.id.item_content).setFocusable(false); mEditComment = (MaterialEditText) view.findViewById(R.id.edit_comment); + mEditComment.setLengthChecker(new METLengthChecker() { + + final String statusLink = LinkCreator.getTwitterStatusLink(status.user_screen_name, status.quote_id).toString(); + + @Override + public int getLength(CharSequence text) { + return mValidator.getTweetLength(text + " " + statusLink); + } + }); + mEditComment.setMaxCharacters(mValidator.getMaxTweetLength()); mCommentMenu = view.findViewById(R.id.comment_menu); mPopupMenu = new PopupMenu(context, mCommentMenu, Gravity.NO_GRAVITY, diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/TwitterAPIStatusesLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/TwitterAPIStatusesLoader.java index efc38fd0a..338dcaf26 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/TwitterAPIStatusesLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/TwitterAPIStatusesLoader.java @@ -37,6 +37,8 @@ import org.mariotaku.twidere.app.TwidereApplication; import org.mariotaku.twidere.model.ParcelableStatus; import org.mariotaku.twidere.util.LoganSquareWrapper; import org.mariotaku.twidere.util.TwitterAPIUtils; +import org.mariotaku.twidere.util.TwitterContentUtils; +import org.mariotaku.twidere.util.Utils; import java.io.File; import java.io.FileOutputStream; @@ -108,6 +110,9 @@ public abstract class TwitterAPIStatusesLoader extends ParcelableStatusesLoader } statuses = new ArrayList<>(); truncated = truncateStatuses(getStatuses(twitter, paging), statuses, mSinceId); + if (!Utils.isOfficialTwitterInstance(context, twitter)) { + TwitterContentUtils.getStatusesWithQuoteData(twitter, statuses); + } } catch (final TwitterException e) { // mHandler.post(new ShowErrorRunnable(e)); Log.w(LOGTAG, e); 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 d663864fe..413c592f0 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java @@ -141,7 +141,6 @@ import org.mariotaku.twidere.activity.support.MediaViewerActivity; import org.mariotaku.twidere.adapter.iface.IBaseAdapter; import org.mariotaku.twidere.adapter.iface.IBaseCardAdapter; import org.mariotaku.twidere.api.twitter.Twitter; -import org.mariotaku.twidere.api.twitter.TwitterConstants; import org.mariotaku.twidere.api.twitter.TwitterException; import org.mariotaku.twidere.api.twitter.auth.OAuthSupport; import org.mariotaku.twidere.api.twitter.model.DirectMessage; diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/StatusComposeEditText.java b/twidere/src/main/java/org/mariotaku/twidere/view/StatusComposeEditText.java index b081c57ef..485d5fc4d 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/StatusComposeEditText.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/StatusComposeEditText.java @@ -20,7 +20,6 @@ package org.mariotaku.twidere.view; import android.content.Context; -import android.support.v7.widget.AppCompatMultiAutoCompleteTextView; import android.text.InputType; import android.text.SpannableString; import android.text.Spanned; @@ -28,10 +27,12 @@ import android.text.TextUtils; import android.text.method.ArrowKeyMovementMethod; import android.util.AttributeSet; +import com.rengwuxian.materialedittext.MaterialMultiAutoCompleteTextView; + import org.mariotaku.twidere.R; import org.mariotaku.twidere.adapter.UserHashtagAutoCompleteAdapter; -public class StatusComposeEditText extends AppCompatMultiAutoCompleteTextView { +public class StatusComposeEditText extends MaterialMultiAutoCompleteTextView { private UserHashtagAutoCompleteAdapter mAdapter; private long mAccountId; diff --git a/twidere/src/main/res/layout/fragment_messages_conversation.xml b/twidere/src/main/res/layout/fragment_messages_conversation.xml index ac06aa70e..520292a75 100644 --- a/twidere/src/main/res/layout/fragment_messages_conversation.xml +++ b/twidere/src/main/res/layout/fragment_messages_conversation.xml @@ -126,7 +126,10 @@ android:maxHeight="140dp" android:singleLine="false" android:textAppearance="?android:textAppearanceMedium" - android:textColor="?android:textColorPrimary"> + app:met_baseColor="?android:textColorSecondary" + app:met_helperTextColor="?android:textColorSecondary" + app:met_textColor="?android:textColorPrimary" + app:met_textColorHint="?android:textColorSecondary">