fixing leaks

This commit is contained in:
Mariotaku Lee 2015-12-25 17:54:13 +08:00
parent 1c5dfb86a9
commit 6f6dec7bb6
25 changed files with 594 additions and 445 deletions

View File

@ -0,0 +1,42 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.api.twitter.model;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
/**
* Created by mariotaku on 15/12/25.
*/
@JsonObject
public class Contributor {
@JsonField(name = "id")
long id;
@JsonField(name = "screen_name")
String screenName;
public long getId() {
return id;
}
public String getScreenName() {
return screenName;
}
}

View File

@ -20,12 +20,12 @@
package org.mariotaku.twidere.api.twitter.model;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import com.bluelinelabs.logansquare.annotation.OnJsonParseComplete;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.mariotaku.twidere.api.twitter.util.TwitterDateConverter;
import java.io.IOException;
@ -81,8 +81,9 @@ public class Status extends TwitterResponseObject implements Comparable<Status>,
@JsonField(name = "current_user_retweet")
CurrentUserRetweet currentUserRetweet;
@Nullable
@JsonField(name = "contributors")
long[] contributors;
Contributor[] contributors;
@JsonField(name = "retweet_count")
long retweetCount;
@ -127,25 +128,21 @@ public class Status extends TwitterResponseObject implements Comparable<Status>,
public String getInReplyToScreenName() {
return inReplyToScreenName;
}
public long getInReplyToUserId() {
return inReplyToUserId;
}
public long getInReplyToStatusId() {
return inReplyToStatusId;
}
public boolean isTruncated() {
return truncated;
}
@ -157,11 +154,12 @@ public class Status extends TwitterResponseObject implements Comparable<Status>,
public String getSource() {
return source;
}
/**
* UTC time when this Tweet was created.
*/
public Date getCreatedAt() {
return createdAt;
}
@ -201,6 +199,10 @@ public class Status extends TwitterResponseObject implements Comparable<Status>,
return currentUserRetweet != null;
}
public boolean wasRetweeted() {
return retweeted;
}
public long getFavoriteCount() {
return favoriteCount;
@ -213,6 +215,10 @@ public class Status extends TwitterResponseObject implements Comparable<Status>,
}
/**
* <i>Perspectival</i>. Only surfaces on methods supporting the <code>include_my_retweet</code> parameter,
* when set to true. Details the Tweet ID of the users own retweet (if existent) of this Tweet.
*/
public long getCurrentUserRetweet() {
if (currentUserRetweet == null) return -1;
return currentUserRetweet.id;
@ -279,7 +285,12 @@ public class Status extends TwitterResponseObject implements Comparable<Status>,
}
public long[] getContributors() {
/**
* An collection of brief user objects (usually only one) indicating users who contributed to
* the authorship of the tweet, on behalf of the official tweet author.
*/
@Nullable
public Contributor[] getContributors() {
return contributors;
}

View File

@ -62,6 +62,244 @@ import java.util.Map.Entry;
@JsonObject
@ParcelablePlease
public class ParcelableStatus implements Parcelable, Comparable<ParcelableStatus>, Cloneable {
@ParcelableThisPlease
@JsonField(name = "id")
@CursorField(Statuses.STATUS_ID)
public long id;
public static final Comparator<ParcelableStatus> REVERSE_ID_COMPARATOR = new Comparator<ParcelableStatus>() {
@Override
public int compare(final ParcelableStatus object1, final ParcelableStatus object2) {
final long diff = object1.id - object2.id;
if (diff > Integer.MAX_VALUE) return Integer.MAX_VALUE;
if (diff < Integer.MIN_VALUE) return Integer.MIN_VALUE;
return (int) diff;
}
};
@ParcelableThisPlease
@JsonField(name = "account_id")
@CursorField(Statuses.ACCOUNT_ID)
public long account_id;
@ParcelableThisPlease
@JsonField(name = "timestamp")
@CursorField(Statuses.STATUS_TIMESTAMP)
public long timestamp;
public static final Comparator<ParcelableStatus> TIMESTAMP_COMPARATOR = new Comparator<ParcelableStatus>() {
@Override
public int compare(final ParcelableStatus object1, final ParcelableStatus object2) {
final long diff = object2.timestamp - object1.timestamp;
if (diff > Integer.MAX_VALUE) return Integer.MAX_VALUE;
if (diff < Integer.MIN_VALUE) return Integer.MIN_VALUE;
return (int) diff;
}
};
@ParcelableThisPlease
@JsonField(name = "user_id")
@CursorField(Statuses.USER_ID)
public long user_id;
@ParcelableThisPlease
@JsonField(name = "retweet_id")
@CursorField(Statuses.RETWEET_ID)
public long retweet_id;
@ParcelableThisPlease
@JsonField(name = "retweeted_by_user_id")
@CursorField(Statuses.RETWEETED_BY_USER_ID)
public long retweeted_by_user_id;
@ParcelableThisPlease
@JsonField(name = "retweet_timestamp")
@CursorField(Statuses.RETWEET_TIMESTAMP)
public long retweet_timestamp;
@ParcelableThisPlease
@JsonField(name = "retweet_count")
@CursorField(Statuses.RETWEET_COUNT)
public long retweet_count;
@ParcelableThisPlease
@JsonField(name = "favorite_count")
@CursorField(Statuses.FAVORITE_COUNT)
public long favorite_count;
@ParcelableThisPlease
@JsonField(name = "reply_count")
@CursorField(Statuses.REPLY_COUNT)
public long reply_count;
@ParcelableThisPlease
@JsonField(name = "in_reply_to_status_id")
@CursorField(Statuses.IN_REPLY_TO_STATUS_ID)
public long in_reply_to_status_id;
@ParcelableThisPlease
@JsonField(name = "in_reply_to_user_id")
@CursorField(Statuses.IN_REPLY_TO_USER_ID)
public long in_reply_to_user_id;
@ParcelableThisPlease
@JsonField(name = "my_retweet_id")
@CursorField(Statuses.MY_RETWEET_ID)
public long my_retweet_id;
@ParcelableThisPlease
@JsonField(name = "quoted_id")
@CursorField(Statuses.QUOTED_ID)
public long quoted_id;
@ParcelableThisPlease
@JsonField(name = "quoted_timestamp")
@CursorField(Statuses.QUOTED_TIMESTAMP)
public long quoted_timestamp;
@ParcelableThisPlease
@JsonField(name = "quoted_user_id")
@CursorField(Statuses.QUOTED_USER_ID)
public long quoted_user_id;
@ParcelableThisPlease
@JsonField(name = "is_gap")
@CursorField(Statuses.IS_GAP)
public boolean is_gap;
@ParcelableThisPlease
@JsonField(name = "is_retweet")
@CursorField(Statuses.IS_RETWEET)
public boolean is_retweet;
@ParcelableThisPlease
@JsonField(name = "retweeted")
@CursorField(Statuses.RETWEETED)
public boolean retweeted;
@ParcelableThisPlease
@JsonField(name = "is_favorite")
@CursorField(Statuses.IS_FAVORITE)
public boolean is_favorite;
@ParcelableThisPlease
@JsonField(name = "is_possibly_sensitive")
@CursorField(Statuses.IS_POSSIBLY_SENSITIVE)
public boolean is_possibly_sensitive;
@ParcelableThisPlease
@JsonField(name = "user_is_following")
@CursorField(Statuses.IS_FOLLOWING)
public boolean user_is_following;
@ParcelableThisPlease
@JsonField(name = "user_is_protected")
@CursorField(Statuses.IS_PROTECTED)
public boolean user_is_protected;
@ParcelableThisPlease
@JsonField(name = "user_is_verified")
@CursorField(Statuses.IS_VERIFIED)
public boolean user_is_verified;
@ParcelableThisPlease
@JsonField(name = "is_quote")
@CursorField(Statuses.IS_QUOTE)
public boolean is_quote;
@ParcelableThisPlease
@JsonField(name = "quoted_user_is_protected")
@CursorField(Statuses.QUOTED_USER_IS_PROTECTED)
public boolean quoted_user_is_protected;
@ParcelableThisPlease
@JsonField(name = "quoted_user_is_verified")
@CursorField(Statuses.QUOTED_USER_IS_VERIFIED)
public boolean quoted_user_is_verified;
@ParcelableThisPlease
@JsonField(name = "retweeted_by_user_name")
@CursorField(Statuses.RETWEETED_BY_USER_NAME)
public String retweeted_by_user_name;
@ParcelableThisPlease
@JsonField(name = "retweeted_by_user_screen_name")
@CursorField(Statuses.RETWEETED_BY_USER_SCREEN_NAME)
public String retweeted_by_user_screen_name;
@ParcelableThisPlease
@JsonField(name = "retweeted_by_user_profile_image")
@CursorField(Statuses.RETWEETED_BY_USER_PROFILE_IMAGE)
public String retweeted_by_user_profile_image;
@ParcelableThisPlease
@JsonField(name = "text_html")
@CursorField(Statuses.TEXT_HTML)
public String text_html;
@ParcelableThisPlease
@JsonField(name = "text_plain")
@CursorField(Statuses.TEXT_PLAIN)
public String text_plain;
@ParcelableThisPlease
@JsonField(name = "lang")
@CursorField(Statuses.LANG)
public String lang;
@ParcelableThisPlease
@JsonField(name = "user_name")
@CursorField(Statuses.USER_NAME)
public String user_name;
@ParcelableThisPlease
@JsonField(name = "user_screen_name")
@CursorField(Statuses.USER_SCREEN_NAME)
public String user_screen_name;
@ParcelableThisPlease
@JsonField(name = "in_reply_to_name")
@CursorField(Statuses.IN_REPLY_TO_USER_NAME)
public String in_reply_to_name;
@ParcelableThisPlease
@JsonField(name = "in_reply_to_screen_name")
@CursorField(Statuses.IN_REPLY_TO_USER_SCREEN_NAME)
public String in_reply_to_screen_name;
@ParcelableThisPlease
@JsonField(name = "source")
@CursorField(Statuses.SOURCE)
public String source;
@ParcelableThisPlease
@JsonField(name = "user_profile_image_url")
@CursorField(Statuses.USER_PROFILE_IMAGE_URL)
public String user_profile_image_url;
@ParcelableThisPlease
@JsonField(name = "text_unescaped")
@CursorField(Statuses.TEXT_UNESCAPED)
public String text_unescaped;
@ParcelableThisPlease
@JsonField(name = "card_name")
@CursorField(Statuses.CARD_NAME)
public String card_name;
@ParcelableThisPlease
@JsonField(name = "quoted_text_html")
@CursorField(Statuses.QUOTED_TEXT_HTML)
public String quoted_text_html;
@ParcelableThisPlease
@JsonField(name = "quoted_text_plain")
@CursorField(Statuses.QUOTED_TEXT_PLAIN)
public String quoted_text_plain;
@ParcelableThisPlease
@JsonField(name = "quoted_text_unescaped")
@CursorField(Statuses.QUOTED_TEXT_UNESCAPED)
public String quoted_text_unescaped;
@ParcelableThisPlease
@JsonField(name = "quoted_source")
@CursorField(Statuses.QUOTED_SOURCE)
public String quoted_source;
@ParcelableThisPlease
@JsonField(name = "quoted_user_name")
@CursorField(Statuses.QUOTED_USER_NAME)
public String quoted_user_name;
@ParcelableThisPlease
@JsonField(name = "quoted_user_screen_name")
@CursorField(Statuses.QUOTED_USER_SCREEN_NAME)
public String quoted_user_screen_name;
@ParcelableThisPlease
@JsonField(name = "quoted_user_profile_image")
@CursorField(Statuses.QUOTED_USER_PROFILE_IMAGE)
public String quoted_user_profile_image;
@ParcelableThisPlease
@JsonField(name = "location")
@CursorField(value = Statuses.LOCATION, converter = ParcelableLocation.Converter.class)
public ParcelableLocation location;
@ParcelableThisPlease
@JsonField(name = "place_full_name")
@CursorField(value = Statuses.PLACE_FULL_NAME, converter = LoganSquareCursorFieldConverter.class)
public String place_full_name;
@ParcelableThisPlease
@JsonField(name = "mentions")
@CursorField(value = Statuses.MENTIONS_JSON, converter = LoganSquareCursorFieldConverter.class)
public ParcelableUserMention[] mentions;
@ParcelableThisPlease
@JsonField(name = "media")
@CursorField(value = Statuses.MEDIA_JSON, converter = LoganSquareCursorFieldConverter.class)
public ParcelableMedia[] media;
@ParcelableThisPlease
@JsonField(name = "quoted_media")
@CursorField(value = Statuses.QUOTED_MEDIA_JSON, converter = LoganSquareCursorFieldConverter.class)
public ParcelableMedia[] quoted_media;
@ParcelableThisPlease
@JsonField(name = "card")
@CursorField(value = Statuses.CARD, converter = LoganSquareCursorFieldConverter.class)
public ParcelableCardEntity card;
@CursorField(value = Statuses._ID, excludeWrite = true)
long _id;
public static final Creator<ParcelableStatus> CREATOR = new Creator<ParcelableStatus>() {
public ParcelableStatus createFromParcel(Parcel source) {
ParcelableStatus target = new ParcelableStatus();
@ -74,296 +312,6 @@ public class ParcelableStatus implements Parcelable, Comparable<ParcelableStatus
}
};
public static final Comparator<ParcelableStatus> REVERSE_ID_COMPARATOR = new Comparator<ParcelableStatus>() {
@Override
public int compare(final ParcelableStatus object1, final ParcelableStatus object2) {
final long diff = object1.id - object2.id;
if (diff > Integer.MAX_VALUE) return Integer.MAX_VALUE;
if (diff < Integer.MIN_VALUE) return Integer.MIN_VALUE;
return (int) diff;
}
};
public static final Comparator<ParcelableStatus> TIMESTAMP_COMPARATOR = new Comparator<ParcelableStatus>() {
@Override
public int compare(final ParcelableStatus object1, final ParcelableStatus object2) {
final long diff = object2.timestamp - object1.timestamp;
if (diff > Integer.MAX_VALUE) return Integer.MAX_VALUE;
if (diff < Integer.MIN_VALUE) return Integer.MIN_VALUE;
return (int) diff;
}
};
@CursorField(value = Statuses._ID, excludeWrite = true)
long _id;
@ParcelableThisPlease
@JsonField(name = "id")
@CursorField(Statuses.STATUS_ID)
public long id;
@ParcelableThisPlease
@JsonField(name = "account_id")
@CursorField(Statuses.ACCOUNT_ID)
public long account_id;
@ParcelableThisPlease
@JsonField(name = "timestamp")
@CursorField(Statuses.STATUS_TIMESTAMP)
public long timestamp;
@ParcelableThisPlease
@JsonField(name = "user_id")
@CursorField(Statuses.USER_ID)
public long user_id;
@ParcelableThisPlease
@JsonField(name = "retweet_id")
@CursorField(Statuses.RETWEET_ID)
public long retweet_id;
@ParcelableThisPlease
@JsonField(name = "retweeted_by_user_id")
@CursorField(Statuses.RETWEETED_BY_USER_ID)
public long retweeted_by_user_id;
@ParcelableThisPlease
@JsonField(name = "retweet_timestamp")
@CursorField(Statuses.RETWEET_TIMESTAMP)
public long retweet_timestamp;
@ParcelableThisPlease
@JsonField(name = "retweet_count")
@CursorField(Statuses.RETWEET_COUNT)
public long retweet_count;
@ParcelableThisPlease
@JsonField(name = "favorite_count")
@CursorField(Statuses.FAVORITE_COUNT)
public long favorite_count;
@ParcelableThisPlease
@JsonField(name = "reply_count")
@CursorField(Statuses.REPLY_COUNT)
public long reply_count;
@ParcelableThisPlease
@JsonField(name = "in_reply_to_status_id")
@CursorField(Statuses.IN_REPLY_TO_STATUS_ID)
public long in_reply_to_status_id;
@ParcelableThisPlease
@JsonField(name = "in_reply_to_user_id")
@CursorField(Statuses.IN_REPLY_TO_USER_ID)
public long in_reply_to_user_id;
@ParcelableThisPlease
@JsonField(name = "my_retweet_id")
@CursorField(Statuses.MY_RETWEET_ID)
public long my_retweet_id;
@ParcelableThisPlease
@JsonField(name = "quoted_id")
@CursorField(Statuses.QUOTED_ID)
public long quoted_id;
@ParcelableThisPlease
@JsonField(name = "quoted_timestamp")
@CursorField(Statuses.QUOTED_TIMESTAMP)
public long quoted_timestamp;
@ParcelableThisPlease
@JsonField(name = "quoted_user_id")
@CursorField(Statuses.QUOTED_USER_ID)
public long quoted_user_id;
@ParcelableThisPlease
@JsonField(name = "is_gap")
@CursorField(Statuses.IS_GAP)
public boolean is_gap;
@ParcelableThisPlease
@JsonField(name = "is_retweet")
@CursorField(Statuses.IS_RETWEET)
public boolean is_retweet;
@ParcelableThisPlease
@JsonField(name = "is_favorite")
@CursorField(Statuses.IS_FAVORITE)
public boolean is_favorite;
@ParcelableThisPlease
@JsonField(name = "is_possibly_sensitive")
@CursorField(Statuses.IS_POSSIBLY_SENSITIVE)
public boolean is_possibly_sensitive;
@ParcelableThisPlease
@JsonField(name = "user_is_following")
@CursorField(Statuses.IS_FOLLOWING)
public boolean user_is_following;
@ParcelableThisPlease
@JsonField(name = "user_is_protected")
@CursorField(Statuses.IS_PROTECTED)
public boolean user_is_protected;
@ParcelableThisPlease
@JsonField(name = "user_is_verified")
@CursorField(Statuses.IS_VERIFIED)
public boolean user_is_verified;
@ParcelableThisPlease
@JsonField(name = "is_quote")
@CursorField(Statuses.IS_QUOTE)
public boolean is_quote;
@ParcelableThisPlease
@JsonField(name = "quoted_user_is_protected")
@CursorField(Statuses.QUOTED_USER_IS_PROTECTED)
public boolean quoted_user_is_protected;
@ParcelableThisPlease
@JsonField(name = "quoted_user_is_verified")
@CursorField(Statuses.QUOTED_USER_IS_VERIFIED)
public boolean quoted_user_is_verified;
@ParcelableThisPlease
@JsonField(name = "retweeted_by_user_name")
@CursorField(Statuses.RETWEETED_BY_USER_NAME)
public String retweeted_by_user_name;
@ParcelableThisPlease
@JsonField(name = "retweeted_by_user_screen_name")
@CursorField(Statuses.RETWEETED_BY_USER_SCREEN_NAME)
public String retweeted_by_user_screen_name;
@ParcelableThisPlease
@JsonField(name = "retweeted_by_user_profile_image")
@CursorField(Statuses.RETWEETED_BY_USER_PROFILE_IMAGE)
public String retweeted_by_user_profile_image;
@ParcelableThisPlease
@JsonField(name = "text_html")
@CursorField(Statuses.TEXT_HTML)
public String text_html;
@ParcelableThisPlease
@JsonField(name = "text_plain")
@CursorField(Statuses.TEXT_PLAIN)
public String text_plain;
@ParcelableThisPlease
@JsonField(name = "lang")
@CursorField(Statuses.LANG)
public String lang;
@ParcelableThisPlease
@JsonField(name = "user_name")
@CursorField(Statuses.USER_NAME)
public String user_name;
@ParcelableThisPlease
@JsonField(name = "user_screen_name")
@CursorField(Statuses.USER_SCREEN_NAME)
public String user_screen_name;
@ParcelableThisPlease
@JsonField(name = "in_reply_to_name")
@CursorField(Statuses.IN_REPLY_TO_USER_NAME)
public String in_reply_to_name;
@ParcelableThisPlease
@JsonField(name = "in_reply_to_screen_name")
@CursorField(Statuses.IN_REPLY_TO_USER_SCREEN_NAME)
public String in_reply_to_screen_name;
@ParcelableThisPlease
@JsonField(name = "source")
@CursorField(Statuses.SOURCE)
public String source;
@ParcelableThisPlease
@JsonField(name = "user_profile_image_url")
@CursorField(Statuses.USER_PROFILE_IMAGE_URL)
public String user_profile_image_url;
@ParcelableThisPlease
@JsonField(name = "text_unescaped")
@CursorField(Statuses.TEXT_UNESCAPED)
public String text_unescaped;
@ParcelableThisPlease
@JsonField(name = "card_name")
@CursorField(Statuses.CARD_NAME)
public String card_name;
@ParcelableThisPlease
@JsonField(name = "quoted_text_html")
@CursorField(Statuses.QUOTED_TEXT_HTML)
public String quoted_text_html;
@ParcelableThisPlease
@JsonField(name = "quoted_text_plain")
@CursorField(Statuses.QUOTED_TEXT_PLAIN)
public String quoted_text_plain;
@ParcelableThisPlease
@JsonField(name = "quoted_text_unescaped")
@CursorField(Statuses.QUOTED_TEXT_UNESCAPED)
public String quoted_text_unescaped;
@ParcelableThisPlease
@JsonField(name = "quoted_source")
@CursorField(Statuses.QUOTED_SOURCE)
public String quoted_source;
@ParcelableThisPlease
@JsonField(name = "quoted_user_name")
@CursorField(Statuses.QUOTED_USER_NAME)
public String quoted_user_name;
@ParcelableThisPlease
@JsonField(name = "quoted_user_screen_name")
@CursorField(Statuses.QUOTED_USER_SCREEN_NAME)
public String quoted_user_screen_name;
@ParcelableThisPlease
@JsonField(name = "quoted_user_profile_image")
@CursorField(Statuses.QUOTED_USER_PROFILE_IMAGE)
public String quoted_user_profile_image;
@ParcelableThisPlease
@JsonField(name = "location")
@CursorField(value = Statuses.LOCATION, converter = ParcelableLocation.Converter.class)
public ParcelableLocation location;
@ParcelableThisPlease
@JsonField(name = "place_full_name")
@CursorField(value = Statuses.PLACE_FULL_NAME, converter = LoganSquareCursorFieldConverter.class)
public String place_full_name;
@ParcelableThisPlease
@JsonField(name = "mentions")
@CursorField(value = Statuses.MENTIONS_JSON, converter = LoganSquareCursorFieldConverter.class)
public ParcelableUserMention[] mentions;
@ParcelableThisPlease
@JsonField(name = "media")
@CursorField(value = Statuses.MEDIA_JSON, converter = LoganSquareCursorFieldConverter.class)
public ParcelableMedia[] media;
@ParcelableThisPlease
@JsonField(name = "quoted_media")
@CursorField(value = Statuses.QUOTED_MEDIA_JSON, converter = LoganSquareCursorFieldConverter.class)
public ParcelableMedia[] quoted_media;
@ParcelableThisPlease
@JsonField(name = "card")
@CursorField(value = Statuses.CARD, converter = LoganSquareCursorFieldConverter.class)
public ParcelableCardEntity card;
ParcelableStatus() {
}
@ -374,15 +322,18 @@ public class ParcelableStatus implements Parcelable, Comparable<ParcelableStatus
id = orig.getId();
timestamp = getTime(orig.getCreatedAt());
final Status retweeted = orig.getRetweetedStatus();
final User retweet_user = retweeted != null ? orig.getUser() : null;
final Status retweetedStatus = orig.getRetweetedStatus();
final User retweetUser = retweetedStatus != null ? orig.getUser() : null;
is_retweet = orig.isRetweet();
retweet_id = retweeted != null ? retweeted.getId() : -1;
retweet_timestamp = retweeted != null ? getTime(retweeted.getCreatedAt()) : -1;
retweeted_by_user_id = retweet_user != null ? retweet_user.getId() : -1;
retweeted_by_user_name = retweet_user != null ? retweet_user.getName() : null;
retweeted_by_user_screen_name = retweet_user != null ? retweet_user.getScreenName() : null;
retweeted_by_user_profile_image = TwitterContentUtils.getProfileImageUrl(retweet_user);
retweeted = orig.wasRetweeted();
if (retweetedStatus != null) {
retweet_id = retweetedStatus.getId();
retweet_timestamp = getTime(retweetedStatus.getCreatedAt());
retweeted_by_user_id = retweetUser.getId();
retweeted_by_user_name = retweetUser.getName();
retweeted_by_user_screen_name = retweetUser.getScreenName();
retweeted_by_user_profile_image = TwitterContentUtils.getProfileImageUrl(retweetUser);
}
final Status quoted = orig.getQuotedStatus();
is_quote = orig.isQuote();
@ -405,15 +356,28 @@ public class ParcelableStatus implements Parcelable, Comparable<ParcelableStatus
}
final Status status;
if (retweeted != null) {
status = retweeted;
if (retweetedStatus != null) {
status = retweetedStatus;
reply_count = retweetedStatus.getReplyCount();
retweet_count = retweetedStatus.getRetweetCount();
favorite_count = retweetedStatus.getFavoriteCount();
in_reply_to_name = TwitterContentUtils.getInReplyToName(retweetedStatus);
in_reply_to_screen_name = retweetedStatus.getInReplyToScreenName();
in_reply_to_status_id = retweetedStatus.getInReplyToStatusId();
in_reply_to_user_id = retweetedStatus.getInReplyToUserId();
} else {
status = orig;
}
reply_count = orig.getReplyCount();
retweet_count = orig.getRetweetCount();
favorite_count = orig.getFavoriteCount();
retweet_count = (retweeted != null ? retweeted : orig).getRetweetCount();
favorite_count = (retweeted != null ? retweeted : orig).getFavoriteCount();
reply_count = (retweeted != null ? retweeted : orig).getReplyCount();
in_reply_to_name = TwitterContentUtils.getInReplyToName(orig);
in_reply_to_screen_name = orig.getInReplyToScreenName();
in_reply_to_status_id = orig.getInReplyToStatusId();
in_reply_to_user_id = orig.getInReplyToUserId();
}
final User user = status.getUser();
user_id = user.getId();
@ -426,10 +390,6 @@ public class ParcelableStatus implements Parcelable, Comparable<ParcelableStatus
text_html = TwitterContentUtils.formatStatusText(status);
media = ParcelableMedia.fromStatus(status);
text_plain = TwitterContentUtils.unescapeTwitterStatusText(status.getText());
in_reply_to_name = TwitterContentUtils.getInReplyToName(retweeted != null ? retweeted : orig);
in_reply_to_screen_name = (retweeted != null ? retweeted : orig).getInReplyToScreenName();
in_reply_to_status_id = (retweeted != null ? retweeted : orig).getInReplyToStatusId();
in_reply_to_user_id = (retweeted != null ? retweeted : orig).getInReplyToUserId();
source = status.getSource();
location = ParcelableLocation.fromGeoLocation(status.getGeoLocation());
is_favorite = status.isFavorited();
@ -632,18 +592,18 @@ public class ParcelableStatus implements Parcelable, Comparable<ParcelableStatus
}
}
@Nullable
public ParcelableBindingValue getValue(@Nullable String key) {
if (key == null || values == null) return null;
return values.get(key);
}
@Nullable
public static ParcelableBindingValue getValue(@Nullable ParcelableCardEntity entity, @Nullable String key) {
if (entity == null) return null;
return entity.getValue(key);
}
@Nullable
public ParcelableBindingValue getValue(@Nullable String key) {
if (key == null || values == null) return null;
return values.get(key);
}
@Override
public String toString() {
return "ParcelableCardEntity{" +

View File

@ -855,6 +855,8 @@ public interface TwidereDataStore {
String QUOTED_USER_IS_VERIFIED = "quoted_user_is_verified";
String QUOTED_USER_IS_PROTECTED = "quoted_user_is_protected";
String RETWEETED = "retweeted";
String[] COLUMNS = {_ID, ACCOUNT_ID, STATUS_ID, USER_ID,
STATUS_TIMESTAMP, TEXT_HTML, TEXT_PLAIN, TEXT_UNESCAPED, USER_NAME, USER_SCREEN_NAME,
USER_PROFILE_IMAGE_URL, IN_REPLY_TO_STATUS_ID, IN_REPLY_TO_USER_ID, IN_REPLY_TO_USER_NAME,
@ -866,7 +868,7 @@ public interface TwidereDataStore {
QUOTED_USER_IS_VERIFIED, QUOTED_USER_IS_PROTECTED, MY_RETWEET_ID, IS_RETWEET,
IS_QUOTE, IS_FAVORITE, IS_PROTECTED, IS_VERIFIED, IS_FOLLOWING, IS_GAP,
IS_POSSIBLY_SENSITIVE, MEDIA_JSON, MENTIONS_JSON, QUOTED_MEDIA_JSON, CARD_NAME, CARD,
PLACE_FULL_NAME, LANG};
PLACE_FULL_NAME, LANG, RETWEETED};
String[] TYPES = {TYPE_PRIMARY_KEY, TYPE_INT, TYPE_INT,
TYPE_INT, TYPE_INT, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT,
@ -876,7 +878,7 @@ public interface TwidereDataStore {
TYPE_TEXT, TYPE_TEXT, TYPE_TEXT, TYPE_BOOLEAN, TYPE_BOOLEAN, TYPE_INT, TYPE_BOOLEAN,
TYPE_BOOLEAN, TYPE_BOOLEAN, TYPE_BOOLEAN, TYPE_BOOLEAN, TYPE_BOOLEAN, TYPE_BOOLEAN,
TYPE_BOOLEAN, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT,
TYPE_TEXT, TYPE_TEXT, TYPE_TEXT};
TYPE_TEXT, TYPE_TEXT, TYPE_TEXT, TYPE_BOOLEAN};
}

View File

@ -181,22 +181,35 @@ public class HotMobiLogger {
record.setState(intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1));
record.setTimestamp(System.currentTimeMillis());
record.setTimeOffset(TimeZone.getDefault().getRawOffset());
getInstance(context).log(record);
getInstance(context).log(record, null);
}
public void log(long accountId, final Object event) {
mExecutor.execute(new WriteLogTask(mApplication, accountId, event));
public <T> void log(long accountId, final T event, final PreProcessing<T> preProcessing) {
mExecutor.execute(new WriteLogTask<>(mApplication, accountId, event, preProcessing));
}
public void log(Object event) {
log(ACCOUNT_ID_NOT_NEEDED, event);
public <T> void log(long accountId, final T event) {
log(accountId, event, null);
}
public void logList(List<?> events, long accountId, String type) {
mExecutor.execute(new WriteLogTask(mApplication, accountId, type, events));
public <T> void log(final T event) {
log(event, null);
}
public void log(final Object event, final PreProcessing preProcessing) {
log(ACCOUNT_ID_NOT_NEEDED, event, preProcessing);
}
public <T> void logList(List<T> events, long accountId, String type) {
logList(events, accountId, type, null);
}
public <T> void logList(List<T> events, long accountId, String type, final PreProcessing<T> preProcessing) {
mExecutor.execute(new WriteLogTask<>(mApplication, accountId, type, events, preProcessing));
}
public static void logScreenEvent(Context context, ScreenEvent.Action action, long presentDuration) {
getInstance(context).log(ScreenEvent.create(context, action, presentDuration));
getInstance(context).log(ScreenEvent.create(context, action, presentDuration), null);
}
}

View File

@ -0,0 +1,29 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package edu.tsinghua.hotmobi;
import android.content.Context;
/**
* Created by mariotaku on 15/12/24.
*/
public interface PreProcessing<T> {
void process(T event, Context appContext);
}

View File

@ -110,7 +110,6 @@ public class UploadLogsTask implements Runnable {
if (response.isSuccessful()) {
succeeded &= logFile.delete();
}
response.close();
} catch (IOException e) {
Log.w(HotMobiLogger.LOGTAG, e);
succeeded = false;

View File

@ -21,6 +21,7 @@ package edu.tsinghua.hotmobi;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.Nullable;
import android.util.Log;
import com.bluelinelabs.logansquare.LoganSquare;
@ -40,24 +41,28 @@ import java.util.List;
/**
* Created by mariotaku on 15/8/23.
*/
public class WriteLogTask implements Runnable, Constants {
public class WriteLogTask<T> implements Runnable, Constants {
private static final byte[] LF = {'\n'};
private final Context context;
private final long accountId;
private final String type;
private final List<?> events;
private final List<T> events;
@Nullable
private final PreProcessing<T> preProcessing;
public WriteLogTask(Context context, long accountId, Object event) {
this(context, accountId, HotMobiLogger.getLogFilename(event), Collections.singletonList(event));
public WriteLogTask(Context context, long accountId, T event, @Nullable PreProcessing<T> preProcessing) {
this(context, accountId, HotMobiLogger.getLogFilename(event), Collections.singletonList(event), preProcessing);
}
public WriteLogTask(Context context, long accountId, String type, List<?> events) {
public WriteLogTask(Context context, long accountId, String type, List<T> events,
@Nullable PreProcessing<T> preProcessing) {
this.context = context;
this.accountId = accountId;
this.type = type;
this.events = events;
this.preProcessing = preProcessing;
}
@Override
@ -71,7 +76,10 @@ public class WriteLogTask implements Runnable, Constants {
raf = new RandomAccessFile(HotMobiLogger.getLogFile(context, accountId, type), "rw");
fc = raf.getChannel();
final FileLock lock = fc.lock();
for (Object event : events) {
for (T event : events) {
if (preProcessing != null) {
preProcessing.process(event, context);
}
if (BuildConfig.DEBUG) {
if (accountId > 0) {
Log.v(HotMobiLogger.LOGTAG, "Log " + type + " for account " + accountId + ": " + event);

View File

@ -33,7 +33,7 @@ import static org.mariotaku.twidere.annotation.Preference.Type.STRING;
public interface Constants extends TwidereConstants {
String DATABASES_NAME = "twidere.sqlite";
int DATABASES_VERSION = 114;
int DATABASES_VERSION = 115;
int MENU_GROUP_STATUS_EXTENSION = 10;
int MENU_GROUP_COMPOSE_EXTENSION = 11;

View File

@ -85,6 +85,7 @@ import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.VideoLoader.VideoLoadingListener;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import pl.droidsonroids.gif.GifSupportChecker;
@ -402,7 +403,8 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
final boolean isLoading = getLoaderManager().hasRunningLoaders();
final TaskRunnable<File, Pair<Boolean, Intent>, Pair<Fragment, Menu>> checkState = new TaskRunnable<File, Pair<Boolean, Intent>, Pair<Fragment, Menu>>() {
final TaskRunnable<File, Pair<Boolean, Intent>, Pair<Fragment, Menu>> checkState
= new TaskRunnable<File, Pair<Boolean, Intent>, Pair<Fragment, Menu>>() {
@Override
public Pair<Boolean, Intent> doLongOperation(File file) throws InterruptedException {
final boolean hasImage = file != null && file.exists();
@ -411,7 +413,8 @@ public final class MediaViewerActivity extends BaseAppCompatActivity implements
}
final Intent intent = new Intent(Intent.ACTION_SEND);
final Uri fileUri = Uri.fromFile(file);
intent.setDataAndType(fileUri, Utils.getImageMimeType(file));
final String imageMimeType = Utils.getImageMimeType(file);
intent.setDataAndType(fileUri, imageMimeType);
intent.putExtra(Intent.EXTRA_STREAM, fileUri);
final MediaViewerActivity activity = (MediaViewerActivity) getActivity();
if (activity.hasStatus()) {

View File

@ -102,6 +102,8 @@ import org.mariotaku.twidere.util.view.ConsumerKeySecretValidator;
import org.mariotaku.twidere.view.TintedStatusNativeActionModeAwareLayout;
import org.mariotaku.twidere.view.iface.TintedStatusLayout;
import java.lang.ref.WeakReference;
import static android.text.TextUtils.isEmpty;
import static org.mariotaku.twidere.util.ContentValuesCreator.createAccount;
import static org.mariotaku.twidere.util.DataStoreUtils.getActivatedAccountIds;
@ -577,14 +579,15 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
public static abstract class AbstractSignInTask extends AsyncTask<Object, Runnable, SignInResponse> {
protected final SignInActivity activity;
protected final WeakReference<SignInActivity> activityRef;
public AbstractSignInTask(final SignInActivity activity) {
this.activity = activity;
this.activityRef = new WeakReference<>(activity);
}
@Override
protected void onPostExecute(final SignInResponse result) {
final SignInActivity activity = activityRef.get();
if (activity != null) {
activity.onSignInResult(result);
}
@ -592,6 +595,7 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
@Override
protected void onPreExecute() {
final SignInActivity activity = activityRef.get();
if (activity != null) {
activity.onSignInStart();
}
@ -730,6 +734,8 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
}
private SignInResponse authBasic() throws TwitterException {
final SignInActivity activity = activityRef.get();
if (activity == null) return new SignInResponse(false, false, null);
final String versionSuffix = noVersionSuffix ? null : "1.1";
final Endpoint endpoint = new Endpoint(TwitterAPIFactory.getApiUrl(apiUrlFormat, "api", versionSuffix));
final Authorization auth = new BasicAuthorization(username, password);
@ -744,6 +750,8 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
}
private SignInResponse authOAuth() throws AuthenticationException, TwitterException {
final SignInActivity activity = activityRef.get();
if (activity == null) return new SignInResponse(false, false, null);
Endpoint endpoint = TwitterAPIFactory.getOAuthEndpoint(apiUrlFormat, "api", null, sameOAuthSigningUrl);
OAuthAuthorization auth = new OAuthAuthorization(consumerKey.getOauthToken(), consumerKey.getOauthTokenSecret());
final TwitterOAuth oauth = TwitterAPIFactory.getInstance(activity, endpoint, auth, TwitterOAuth.class);
@ -762,6 +770,8 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
}
private SignInResponse authTwipOMode() throws TwitterException {
final SignInActivity activity = activityRef.get();
if (activity == null) return new SignInResponse(false, false, null);
final String versionSuffix = noVersionSuffix ? null : "1.1";
final Endpoint endpoint = new Endpoint(TwitterAPIFactory.getApiUrl(apiUrlFormat, "api", versionSuffix));
final Authorization auth = new EmptyAuthorization();
@ -774,6 +784,8 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
}
private SignInResponse authxAuth() throws TwitterException {
final SignInActivity activity = activityRef.get();
if (activity == null) return new SignInResponse(false, false, null);
Endpoint endpoint = TwitterAPIFactory.getOAuthEndpoint(apiUrlFormat, "api", null, sameOAuthSigningUrl);
OAuthAuthorization auth = new OAuthAuthorization(consumerKey.getOauthToken(), consumerKey.getOauthTokenSecret());
final TwitterOAuth oauth = TwitterAPIFactory.getInstance(activity, endpoint, auth, TwitterOAuth.class);
@ -800,6 +812,8 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
publishProgress(new Runnable() {
@Override
public void run() {
final SignInActivity activity = activityRef.get();
if (activity == null) return;
activity.dismissDialogFragment(FRAGMENT_TAG_SIGN_IN_PROGRESS);
}
});
@ -807,6 +821,8 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
publishProgress(new Runnable() {
@Override
public void run() {
final SignInActivity activity = activityRef.get();
if (activity == null) return;
activity.postAfterFragmentResumed(new Runnable() {
@Override
public void run() {
@ -830,6 +846,8 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
publishProgress(new Runnable() {
@Override
public void run() {
final SignInActivity activity = activityRef.get();
if (activity == null) return;
activity.showSignInProgressDialog();
}
});

View File

@ -72,7 +72,6 @@ public abstract class AbsActivitiesAdapter<Data> extends LoadMoreSupportAdapter<
private final MediaLoadingHandler mLoadingHandler;
private final int mCardBackgroundColor;
private final boolean mCompactCards;
private final TwidereLinkify mLinkify;
private final DummyStatusHolderAdapter mStatusAdapterDelegate;
private ActivityAdapterListener mActivityAdapterListener;
@ -80,14 +79,13 @@ public abstract class AbsActivitiesAdapter<Data> extends LoadMoreSupportAdapter<
protected AbsActivitiesAdapter(final Context context, boolean compact) {
super(context);
mStatusAdapterDelegate = new DummyStatusHolderAdapter(context);
mStatusAdapterDelegate = new DummyStatusHolderAdapter(context, new TwidereLinkify(new OnLinkClickHandler(context, null)));
mCardBackgroundColor = ThemeUtils.getCardBackgroundColor(context,
ThemeUtils.getThemeBackgroundOption(context),
ThemeUtils.getUserThemeBackgroundAlpha(context));
mInflater = LayoutInflater.from(context);
mLoadingHandler = new MediaLoadingHandler(R.id.media_preview_progress);
mCompactCards = compact;
mLinkify = new TwidereLinkify(new OnLinkClickHandler(context, null));
mStatusAdapterDelegate.updateOptions();
}
@ -133,10 +131,6 @@ public abstract class AbsActivitiesAdapter<Data> extends LoadMoreSupportAdapter<
return mStatusAdapterDelegate.getLinkHighlightingStyle();
}
public TwidereLinkify getLinkify() {
return mLinkify;
}
public boolean isNameFirst() {
return mStatusAdapterDelegate.isNameFirst();
}

View File

@ -1263,7 +1263,8 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
private static final int VIEW_TYPE_CONVERSATION_LOAD_INDICATOR = 2;
private static final int VIEW_TYPE_REPLIES_LOAD_INDICATOR = 3;
private static final int VIEW_TYPE_REPLY_ERROR = 4;
private static final int VIEW_TYPE_SPACE = 5;
private static final int VIEW_TYPE_CONVERSATION_ERROR = 5;
private static final int VIEW_TYPE_SPACE = 6;
private static final int ITEM_IDX_CONVERSATION_ERROR = 0;
private static final int ITEM_IDX_CONVERSATION_LOAD_MORE = 1;
private static final int ITEM_IDX_CONVERSATION = 2;
@ -1300,7 +1301,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
private List<ParcelableStatus> mConversation, mReplies;
private StatusAdapterListener mStatusAdapterListener;
private RecyclerView mRecyclerView;
private CharSequence mReplyError;
private CharSequence mReplyError, mConversationError;
private boolean mRepliesLoading, mConversationsLoading;
private TranslationResult mTranslationResult;
@ -1615,6 +1616,11 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
errorHolder.showError(mReplyError);
break;
}
case VIEW_TYPE_CONVERSATION_ERROR: {
final StatusErrorItemViewHolder errorHolder = (StatusErrorItemViewHolder) holder;
errorHolder.showError(mReplyError);
break;
}
case VIEW_TYPE_CONVERSATION_LOAD_INDICATOR: {
LoadIndicatorViewHolder indicatorHolder = ((LoadIndicatorViewHolder) holder);
indicatorHolder.setLoadProgressVisible(mConversationsLoading);
@ -1660,6 +1666,8 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
return VIEW_TYPE_SPACE;
case ITEM_IDX_REPLY_ERROR:
return VIEW_TYPE_REPLY_ERROR;
case ITEM_IDX_CONVERSATION_ERROR:
return VIEW_TYPE_CONVERSATION_ERROR;
}
throw new IllegalStateException();
}
@ -1781,6 +1789,12 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
updateItemDecoration();
}
public void setConversationError(CharSequence error) {
mConversationError = error;
setCount(ITEM_IDX_CONVERSATION_ERROR, error != null ? 1 : 0);
updateItemDecoration();
}
public void setReplies(List<ParcelableStatus> replies) {
mReplies = replies;
setCount(ITEM_IDX_REPLY, replies != null ? replies.size() : 0);
@ -1857,7 +1871,6 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
@Override
public int getDecoratedMeasuredHeight(View child) {
final int height = super.getDecoratedMeasuredHeight(child);
int heightBeforeSpace = 0;
if (getItemViewType(child) == StatusAdapter.VIEW_TYPE_SPACE) {
for (int i = 0, j = getChildCount(); i < j; i++) {
@ -1877,7 +1890,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
return Math.max(0, spaceHeight);
}
}
return height;
return super.getDecoratedMeasuredHeight(child);
}
@Override

View File

@ -22,12 +22,14 @@ package org.mariotaku.twidere.util;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import org.apache.commons.collections.primitives.ArrayIntList;
import org.apache.commons.collections.primitives.IntList;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.hotmobi.PreProcessing;
import edu.tsinghua.hotmobi.model.SessionEvent;
/**
@ -56,7 +58,7 @@ public class ActivityTracker implements Application.ActivityLifecycleCallbacks {
}
@Override
public void onActivityStarted(Activity activity) {
public void onActivityStarted(final Activity activity) {
mInternalStack.add(System.identityHashCode(activity));
// BEGIN HotMobi
if (mSessionEvent == null) {
@ -82,9 +84,13 @@ public class ActivityTracker implements Application.ActivityLifecycleCallbacks {
// BEGIN HotMobi
final SessionEvent event = mSessionEvent;
if (event != null && !isSwitchingInSameTask(hashCode)) {
event.dumpPreferences(activity);
event.markEnd();
HotMobiLogger.getInstance(activity).log(event);
HotMobiLogger.getInstance(activity).log(event, new PreProcessing<SessionEvent>() {
@Override
public void process(SessionEvent event, Context appContext) {
event.dumpPreferences(appContext);
}
});
mSessionEvent = null;
}
// END HotMobi

View File

@ -42,8 +42,9 @@ public class EmojiSupportUtils {
for (int i = array.length() - 1; i >= 0; i--) {
final int codePoint = array.get(i);
if (isEmoji(codePoint)) {
int arrayIdx = i, arrayEnd = i + 1;
int textIdx = array.indexOfText(codePoint, i);
int arrayIdx = i, arrayEnd = i + 1, arrayIdxOffset = 0;
int textIdx = array.indexOfText(codePoint, i), textIdxOffset = 0;
int indexOffset = 0;
if (textIdx == -1 || textIdx < textStart) {
continue;
}
@ -52,31 +53,60 @@ public class EmojiSupportUtils {
if (i > 0) {
int prev = array.get(i - 1);
if (isRegionalIndicatorSymbol(prev)) {
textIdx -= Character.charCount(prev);
arrayIdx--;
i--;
arrayIdxOffset = -1;
textIdxOffset = -Character.charCount(prev);
indexOffset = -1;
}
}
} else if (isModifier(codePoint)) {
if (i > 0) {
int prev = array.get(i - 1);
if (isEmoji(prev)) {
textIdx -= Character.charCount(prev);
arrayIdx--;
i--;
arrayIdxOffset = -1;
textIdxOffset = -Character.charCount(prev);
indexOffset = -1;
}
}
} else if (isKeyCap(codePoint)) {
if (i > 0) {
int prev = array.get(i - 1);
if (isPhoneNumberSymbol(prev)) {
arrayIdxOffset = -1;
textIdxOffset = -Character.charCount(prev);
indexOffset = -1;
}
}
}
if (textEnd > textStart + textLength) continue;
final EmojiSpan[] spans = text.getSpans(textIdx, textEnd, EmojiSpan.class);
if (spans.length > 0) continue;
final Drawable drawable = emoji.getEmojiDrawableFor(array.subarray(arrayIdx, arrayEnd));
if (drawable == null) continue;
text.setSpan(new EmojiSpan(drawable), textIdx, textEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
EmojiSpan[] spans = text.getSpans(textIdx + textIdxOffset, textEnd, EmojiSpan.class);
if (spans.length == 0) {
Drawable drawable = emoji.getEmojiDrawableFor(array.subarray(arrayIdx + arrayIdxOffset,
arrayEnd));
if (drawable == null) {
// Not emoji combination, just use fallback
textIdxOffset = 0;
arrayIdxOffset = 0;
indexOffset = 0;
spans = text.getSpans(textIdx + textIdxOffset, textEnd, EmojiSpan.class);
if (spans.length == 0) {
drawable = emoji.getEmojiDrawableFor(array.subarray(arrayIdx + arrayIdxOffset,
arrayEnd));
}
}
if (drawable != null) {
text.setSpan(new EmojiSpan(drawable), textIdx + textIdxOffset, textEnd,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
i += indexOffset;
}
}
}
private static boolean isPhoneNumberSymbol(int codePoint) {
return codePoint == 0x0023 || codePoint == 0x002a || inRange(codePoint, 0x0030, 0x0039);
}
private static boolean isModifier(int codePoint) {
return inRange(codePoint, 0x1f3fb, 0x1f3ff);
}
@ -93,4 +123,8 @@ public class EmojiSupportUtils {
return inRange(codePoint, 0x1f1e6, 0x1f1ff);
}
private static boolean isKeyCap(int codePoint) {
return codePoint == 0x20e3;
}
}

View File

@ -11,8 +11,11 @@ import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.webkit.URLUtil;
import com.squareup.okhttp.Authenticator;
import com.squareup.okhttp.Dns;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import org.apache.commons.lang3.math.NumberUtils;
import org.mariotaku.restfu.ExceptionFactory;
@ -51,6 +54,7 @@ import org.mariotaku.twidere.model.RequestType;
import org.mariotaku.twidere.util.dagger.ApplicationModule;
import org.mariotaku.twidere.util.net.NetworkUsageUtils;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
@ -118,6 +122,7 @@ public class TwitterAPIFactory implements TwidereConstants {
final OkHttpClient client = new OkHttpClient();
updateHttpClientConfiguration(prefs, client);
client.setDns(dns);
DebugModeUtils.initForHttpClient(client);
NetworkUsageUtils.initForHttpClient(context, client);
return new OkHttpRestClient(client);
}
@ -386,8 +391,8 @@ public class TwitterAPIFactory implements TwidereConstants {
sExtraParams.put("include_cards", "true");
sExtraParams.put("cards_platform", "Android-12");
sExtraParams.put("include_entities", "true");
sExtraParams.put("include_my_retweet", "1");
sExtraParams.put("include_rts", "1");
sExtraParams.put("include_my_retweet", "true");
sExtraParams.put("include_rts", "true");
sExtraParams.put("include_reply_count", "true");
sExtraParams.put("include_descendent_reply_count", "true");
sExtraParams.put("full_text", "true");

View File

@ -38,11 +38,16 @@ import org.mariotaku.twidere.util.ConnectivityUtils;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* Created by mariotaku on 15/6/24.
*/
public class NetworkUsageUtils implements Constants {
private static final Executor sNetworkUsageExecutor = Executors.newSingleThreadExecutor();
public static void initForHttpClient(Context context, OkHttpClient client) {
final List<Interceptor> interceptors = client.networkInterceptors();
interceptors.add(new NetworkUsageInterceptor(context));
@ -74,12 +79,17 @@ public class NetworkUsageUtils implements Constants {
values.put(NetworkUsages.REQUEST_NETWORK, sNetworkType);
final Response response = chain.proceed(request);
values.put(NetworkUsages.KILOBYTES_RECEIVED, getBodyLength(response.body()) / 1024.0);
final ContentResolver cr = context.getContentResolver();
try {
cr.insert(NetworkUsages.CONTENT_URI, values);
} catch (IllegalStateException e) {
Log.e(LOGTAG, "Unable to log network usage", e);
}
sNetworkUsageExecutor.execute(new Runnable() {
@Override
public void run() {
final ContentResolver cr = context.getContentResolver();
try {
cr.insert(NetworkUsages.CONTENT_URI, values);
} catch (IllegalStateException e) {
Log.e(LOGTAG, "Unable to log network usage", e);
}
}
});
return response;
}

View File

@ -87,8 +87,8 @@ public class TwidereDns implements Constants, Dns {
// First, I'll try to load address cached.
final InetAddress[] cachedHostAddr = mHostCache.get(host);
if (cachedHostAddr != null) {
if (BuildConfig.DEBUG) {
Log.d(RESOLVER_LOGTAG, "Got cached " + Arrays.toString(cachedHostAddr));
if (BuildConfig.DEBUG && Log.isLoggable(RESOLVER_LOGTAG, Log.VERBOSE)) {
Log.v(RESOLVER_LOGTAG, "Got cached " + Arrays.toString(cachedHostAddr));
return cachedHostAddr;
}
}
@ -100,7 +100,7 @@ public class TwidereDns implements Constants, Dns {
final InetAddress[] hostAddr = fromAddressString(originalHost, mappedAddr);
putCache(originalHost, hostAddr);
if (BuildConfig.DEBUG) {
Log.d(RESOLVER_LOGTAG, "Got mapped " + Arrays.toString(hostAddr));
Log.v(RESOLVER_LOGTAG, "Got mapped " + Arrays.toString(hostAddr));
}
if (hostAddr != null) {
return hostAddr;
@ -110,8 +110,8 @@ public class TwidereDns implements Constants, Dns {
try {
final InetAddress[] hostAddr = mResolver.resolve(host);
putCache(originalHost, hostAddr);
if (BuildConfig.DEBUG) {
Log.d(RESOLVER_LOGTAG, "Got hosts " + Arrays.toString(hostAddr));
if (BuildConfig.DEBUG && Log.isLoggable(RESOLVER_LOGTAG, Log.VERBOSE)) {
Log.v(RESOLVER_LOGTAG, "Got hosts " + Arrays.toString(hostAddr));
}
return hostAddr;
} catch (UnknownHostException e) {
@ -121,8 +121,8 @@ public class TwidereDns implements Constants, Dns {
if (customMappedHost != null) {
final InetAddress[] hostAddr = fromAddressString(originalHost, customMappedHost);
putCache(originalHost, hostAddr);
if (BuildConfig.DEBUG) {
Log.d(RESOLVER_LOGTAG, "Got mapped address " + customMappedHost + " for host " + host);
if (BuildConfig.DEBUG && Log.isLoggable(RESOLVER_LOGTAG, Log.VERBOSE)) {
Log.v(RESOLVER_LOGTAG, "Got mapped address " + customMappedHost + " for host " + host);
}
if (hostAddr != null) {
return hostAddr;
@ -154,8 +154,8 @@ public class TwidereDns implements Constants, Dns {
if (!resolvedAddresses.isEmpty()) {
final InetAddress[] hostAddr = resolvedAddresses.toArray(new InetAddress[resolvedAddresses.size()]);
putCache(originalHost, hostAddr);
if (BuildConfig.DEBUG) {
Log.d(RESOLVER_LOGTAG, "Resolved " + Arrays.toString(hostAddr));
if (BuildConfig.DEBUG && Log.isLoggable(RESOLVER_LOGTAG, Log.VERBOSE)) {
Log.v(RESOLVER_LOGTAG, "Resolved " + Arrays.toString(hostAddr));
}
return hostAddr;
}
@ -166,8 +166,8 @@ public class TwidereDns implements Constants, Dns {
return resolveInternal(originalHost, ((CNAMERecord) record).getTarget().toString());
}
}
if (BuildConfig.DEBUG) {
Log.w(RESOLVER_LOGTAG, "Resolve address " + host + " failed, using original host");
if (BuildConfig.DEBUG && Log.isLoggable(RESOLVER_LOGTAG, Log.VERBOSE)) {
Log.v(RESOLVER_LOGTAG, "Resolve address " + host + " failed, using original host");
}
final InetAddress[] defaultAddresses = InetAddress.getAllByName(host);
putCache(host, defaultAddresses);

View File

@ -31,6 +31,8 @@ import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.view.themed.ThemedTextView;
import java.lang.ref.WeakReference;
import static android.text.format.DateUtils.getRelativeTimeSpanString;
import static org.mariotaku.twidere.util.Utils.formatSameDayTime;
@ -111,17 +113,19 @@ public class ShortTimeView extends ThemedTextView implements Constants, OnShared
private static class TickerRunnable implements Runnable {
private final ShortTimeView mTextView;
private final WeakReference<ShortTimeView> mViewRef;
private TickerRunnable(final ShortTimeView view) {
mTextView = view;
mViewRef = new WeakReference<>(view);
}
@Override
public void run() {
final Handler handler = mTextView.getHandler();
final ShortTimeView view = mViewRef.get();
if (view == null) return;
final Handler handler = view.getHandler();
if (handler == null) return;
mTextView.invalidateTime();
view.invalidateTime();
final long now = SystemClock.uptimeMillis();
final long next = now + TICKER_DURATION - now % TICKER_DURATION;
handler.postAtTime(this, next);

View File

@ -26,7 +26,6 @@ import android.database.Cursor;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.text.Spanned;
import android.text.method.ArrowKeyMovementMethod;
import android.view.View;
import android.widget.TextView;
@ -38,7 +37,6 @@ import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.util.HtmlSpanBuilder;
import org.mariotaku.twidere.util.JsonSerializer;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.StatusActionModeCallback;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.TwidereColorUtils;
import org.mariotaku.twidere.util.TwidereLinkify;
@ -55,7 +53,6 @@ public class MessageViewHolder extends ViewHolder implements OnMediaClickListene
protected final MessageConversationAdapter adapter;
private final int textColorPrimary, textColorPrimaryInverse, textColorSecondary, textColorSecondaryInverse;
private final StatusActionModeCallback callback;
public MessageViewHolder(final MessageConversationAdapter adapter, final View itemView) {
@ -75,7 +72,6 @@ public class MessageViewHolder extends ViewHolder implements OnMediaClickListene
time = (TextView) itemView.findViewById(R.id.time);
mediaContainer = (CardMediaContainer) itemView.findViewById(R.id.media_preview_container);
mediaContainer.setStyle(adapter.getMediaPreviewStyle());
callback = new StatusActionModeCallback(textView, adapter.getContext());
}
public void displayMessage(Cursor cursor, ParcelableDirectMessageCursorIndices indices) {
@ -92,10 +88,6 @@ public class MessageViewHolder extends ViewHolder implements OnMediaClickListene
time.setText(Utils.formatToLongTimeString(context, timestamp));
mediaContainer.setVisibility(media != null && media.length > 0 ? View.VISIBLE : View.GONE);
mediaContainer.displayMedia(media, loader, accountId, true, this, adapter.getMediaLoadingHandler());
textView.setTextIsSelectable(true);
textView.setMovementMethod(ArrowKeyMovementMethod.getInstance());
textView.setCustomSelectionActionModeCallback(callback);
}
@Override

View File

@ -471,11 +471,15 @@ public class StatusViewHolder extends ViewHolder implements Constants, OnClickLi
private boolean useStarsForLikes;
public DummyStatusHolderAdapter(Context context) {
this(context, new TwidereLinkify(null));
}
public DummyStatusHolderAdapter(Context context, TwidereLinkify linkify) {
DaggerGeneralComponent.builder().applicationModule(ApplicationModule.get(context)).build().inject(this);
this.context = context;
preferences = SharedPreferencesWrapper.getInstance(context, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
handler = new MediaLoadingHandler(R.id.media_preview_progress);
linkify = new TwidereLinkify(null);
this.linkify = linkify;
updateOptions();
}

View File

@ -17,10 +17,11 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<org.mariotaku.twidere.view.ExtendedRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<org.mariotaku.twidere.view.ExtendedRelativeLayout
android:id="@+id/main_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/element_spacing_normal">
@ -53,7 +54,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="0"
tools:listitem="@layout/spinner_item_account_icon" />
tools:listitem="@layout/spinner_item_account_icon"/>
<EditText
android:id="@+id/search_query"
@ -65,7 +66,7 @@
android:hint="@string/search_hint"
android:inputType="text|textMultiLine">
<requestFocus />
<requestFocus/>
</EditText>
<org.mariotaku.twidere.view.ActionIconButton
@ -75,14 +76,14 @@
android:layout_weight="0"
android:background="?actionBarItemBackground"
android:color="?menuIconColor"
android:src="@drawable/ic_action_search" />
android:src="@drawable/ic_action_search"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="#20808080" />
android:background="#20808080"/>
<ListView
android:id="@+id/suggestions_list"
@ -90,7 +91,7 @@
android:layout_height="0dp"
android:layout_weight="1"
android:listSelector="?selectableItemBackground"
tools:listitem="@layout/list_item_user" />
tools:listitem="@layout/list_item_user"/>
</LinearLayout>
</android.support.v7.widget.CardView>

View File

@ -18,17 +18,17 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:showIn="@layout/card_item_message_conversation_incoming">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:showIn="@layout/card_item_message_conversation_incoming">
<org.mariotaku.twidere.view.CardMediaContainer
android:id="@+id/media_preview_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/layout_card_media_preview" />
<include layout="@layout/layout_card_media_preview"/>
</org.mariotaku.twidere.view.CardMediaContainer>
@ -45,8 +45,7 @@
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
android:textIsSelectable="true"
tools:text="@string/sample_status_text" />
tools:text="@string/sample_status_text"/>
<org.mariotaku.twidere.view.themed.ThemedTextView
android:id="@+id/time"
@ -54,6 +53,6 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/element_spacing_small"
android:textColor="?android:attr/textColorSecondary"
tools:text="12:00" />
tools:text="12:00"/>
</LinearLayout>
</RelativeLayout>

View File

@ -16,13 +16,13 @@
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<org.mariotaku.twidere.view.ColorLabelFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:background="?cardItemBackgroundColor">
<org.mariotaku.twidere.view.ColorLabelFrameLayout android:id="@+id/item_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:background="?cardItemBackgroundColor">
<RelativeLayout
android:layout_width="match_parent"
@ -47,7 +47,7 @@
android:scaleType="centerInside"
android:visibility="gone"
tools:src="@drawable/ic_activity_action_retweet"
tools:visibility="visible" />
tools:visibility="visible"/>
<org.mariotaku.twidere.view.ActionIconThemedTextView
android:id="@+id/reply_retweet_status"
@ -66,7 +66,7 @@
android:visibility="gone"
tools:text="Retweeted by Mariotaku"
tools:textSize="@dimen/text_size_extra_small"
tools:visibility="visible" />
tools:visibility="visible"/>
<org.mariotaku.twidere.view.ProfileImageView
android:id="@+id/profile_image"
@ -83,7 +83,7 @@
android:layout_marginRight="@dimen/element_spacing_small"
android:contentDescription="@string/profile_image"
android:scaleType="centerCrop"
tools:visibility="visible" />
tools:visibility="visible"/>
<org.mariotaku.twidere.view.BoundsImageView
android:id="@+id/profile_type"
@ -96,7 +96,7 @@
android:layout_marginEnd="@dimen/element_spacing_minus_small"
android:layout_marginRight="@dimen/element_spacing_minus_small"
android:scaleType="fitCenter"
tools:visibility="gone" />
tools:visibility="gone"/>
<RelativeLayout
android:id="@+id/status_content"
@ -123,7 +123,7 @@
android:layout_weight="1"
app:nv_primaryTextColor="?android:textColorPrimary"
app:nv_primaryTextStyle="bold"
app:nv_secondaryTextColor="?android:textColorSecondary" />
app:nv_secondaryTextColor="?android:textColorSecondary"/>
<org.mariotaku.twidere.view.ShortTimeView
android:id="@+id/time"
@ -132,7 +132,7 @@
android:layout_weight="0"
android:textAppearance="?android:textAppearanceSmall"
tools:text="42 mins ago"
tools:textSize="@dimen/text_size_extra_small" />
tools:textSize="@dimen/text_size_extra_small"/>
<org.mariotaku.twidere.view.ActionIconView
android:id="@+id/extra_type"
@ -140,7 +140,7 @@
android:layout_height="@dimen/element_size_small"
android:layout_weight="0"
android:color="?android:textColorSecondary"
tools:src="@drawable/ic_action_gallery" />
tools:src="@drawable/ic_action_gallery"/>
</LinearLayout>
@ -156,9 +156,8 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary"
android:visibility="visible"
app:nv_primaryTextStyle="bold"
tools:text="@string/sample_status_text"
tools:visibility="visible" />
tools:visibility="visible"/>
<org.mariotaku.twidere.view.ForegroundColorView
android:id="@+id/quote_indicator"
@ -171,7 +170,7 @@
android:layout_marginRight="@dimen/element_spacing_normal"
android:background="?quoteIndicatorBackgroundColor"
android:visibility="gone"
tools:visibility="visible" />
tools:visibility="visible"/>
<org.mariotaku.twidere.view.NameView
android:id="@+id/quoted_name"
@ -185,7 +184,7 @@
android:visibility="gone"
app:nv_primaryTextColor="?android:textColorPrimary"
app:nv_secondaryTextColor="?android:textColorSecondary"
tools:visibility="visible" />
tools:visibility="visible"/>
<org.mariotaku.twidere.view.TimelineContentTextView
android:id="@+id/quoted_text"
@ -200,7 +199,7 @@
android:textColor="?android:attr/textColorPrimary"
android:visibility="gone"
tools:text="@string/sample_status_text"
tools:visibility="visible" />
tools:visibility="visible"/>
<org.mariotaku.twidere.view.CardMediaContainer
android:id="@+id/media_preview"
@ -248,7 +247,7 @@
android:paddingRight="@dimen/element_spacing_normal"
android:textAppearance="?android:textAppearanceSmall"
app:iabActivatedColor="@color/highlight_reply"
app:iabColor="?android:textColorTertiary" />
app:iabColor="?android:textColorTertiary"/>
<org.mariotaku.twidere.view.ActionIconThemedTextView
android:id="@+id/retweet_count"
@ -264,7 +263,7 @@
android:paddingRight="@dimen/element_spacing_normal"
android:textAppearance="?android:textAppearanceSmall"
app:iabActivatedColor="@color/highlight_retweet"
app:iabColor="?android:textColorTertiary" />
app:iabColor="?android:textColorTertiary"/>
<org.mariotaku.twidere.view.ActionIconThemedTextView
android:id="@+id/favorite_count"
@ -280,7 +279,7 @@
android:paddingRight="@dimen/element_spacing_normal"
android:textAppearance="?android:textAppearanceSmall"
app:iabActivatedColor="@color/highlight_like"
app:iabColor="?android:textColorTertiary" />
app:iabColor="?android:textColorTertiary"/>
</LinearLayout>
@ -296,7 +295,7 @@
android:color="?android:textColorTertiary"
android:focusable="false"
android:src="@drawable/ic_action_more_horizontal"
tools:visibility="visible" />
tools:visibility="visible"/>
</RelativeLayout>
</org.mariotaku.twidere.view.ColorLabelFrameLayout>

View File

@ -17,10 +17,13 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<org.mariotaku.twidere.view.SquareFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<org.mariotaku.twidere.view.SquareFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="@dimen/element_spacing_small">
android:padding="@dimen/element_spacing_small"
tools:layout_width="48dp">
<org.mariotaku.twidere.view.ProfileImageView
android:id="@android:id/icon"
@ -29,6 +32,6 @@
android:layout_height="match_parent"
android:layout_gravity="center"
android:contentDescription="@string/profile_image"
android:scaleType="centerCrop" />
android:scaleType="centerCrop"/>
</org.mariotaku.twidere.view.SquareFrameLayout>