fixed #233
changed 'favorite' to 'like' to comply twitter's new design spec
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/11/4.
|
||||
*/
|
||||
public final class CodePointArray {
|
||||
|
||||
private final int[] codePoints;
|
||||
|
||||
public CodePointArray(@NonNull final CharSequence cs) {
|
||||
final int inputLength = cs.length();
|
||||
final int[] temp = new int[inputLength];
|
||||
int codePointsLength = 0;
|
||||
for (int offset = 0; offset < inputLength; ) {
|
||||
final int codePoint = Character.codePointAt(cs, offset);
|
||||
temp[codePointsLength++] = codePoint;
|
||||
offset += Character.charCount(codePoint);
|
||||
}
|
||||
codePoints = new int[codePointsLength];
|
||||
System.arraycopy(temp, 0, codePoints, 0, codePointsLength);
|
||||
}
|
||||
|
||||
public int get(int pos) {
|
||||
return codePoints[pos];
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return codePoints.length;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String substring(int start, int end) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (int i = start; i < end; i++) {
|
||||
sb.appendCodePoint(codePoints[i]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -22,7 +22,6 @@ package org.mariotaku.twidere.util;
|
|||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
|
||||
|
@ -34,7 +33,7 @@ import static org.mariotaku.twidere.util.HtmlEscapeHelper.unescape;
|
|||
public class HtmlBuilder {
|
||||
|
||||
private final String source;
|
||||
private final int[] codePoints;
|
||||
private final CodePointArray codePoints;
|
||||
private final int codePointsLength;
|
||||
private final boolean throwExceptions, sourceIsEscaped, shouldReEscape;
|
||||
|
||||
|
@ -44,20 +43,11 @@ public class HtmlBuilder {
|
|||
final boolean shouldReEscape) {
|
||||
if (source == null) throw new NullPointerException();
|
||||
this.source = source;
|
||||
final int length = source.length();
|
||||
final int[] codepointsTemp = new int[length];
|
||||
int codePointsLength = 0;
|
||||
for (int offset = 0; offset < length; ) {
|
||||
final int codepoint = source.codePointAt(offset);
|
||||
codepointsTemp[codePointsLength++] = codepoint;
|
||||
offset += Character.charCount(codepoint);
|
||||
}
|
||||
codePoints = new int[codePointsLength];
|
||||
System.arraycopy(codepointsTemp, 0, codePoints, 0, codePointsLength);
|
||||
throwExceptions = strict;
|
||||
this.codePoints = new CodePointArray(source);
|
||||
this.throwExceptions = strict;
|
||||
this.sourceIsEscaped = sourceIsEscaped;
|
||||
this.shouldReEscape = shouldReEscape;
|
||||
this.codePointsLength = codePointsLength;
|
||||
this.codePointsLength = codePoints.length();
|
||||
}
|
||||
|
||||
public boolean addLink(final String link, final String display, final int start, final int end) {
|
||||
|
@ -107,7 +97,7 @@ public class HtmlBuilder {
|
|||
builder.append(spec.link);
|
||||
builder.append("\">");
|
||||
if (start >= 0 && start <= end && end <= codePointsLength) {
|
||||
builder.append(!isEmpty(spec.display) ? spec.display_is_html ? spec.display : toHtml(spec.display)
|
||||
builder.append(!isEmpty(spec.display) ? spec.displayIsHtml ? spec.display : toHtml(spec.display)
|
||||
: spec.link);
|
||||
}
|
||||
builder.append("</a>");
|
||||
|
@ -128,20 +118,26 @@ public class HtmlBuilder {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HtmlBuilder{orig=" + source + ", codePoints=" + Arrays.toString(codePoints) + ", string_length="
|
||||
+ codePointsLength + ", throw_exceptions=" + throwExceptions + ", source_is_escaped=" + sourceIsEscaped
|
||||
+ ", should_re_escape=" + shouldReEscape + ", links=" + links + "}";
|
||||
return "HtmlBuilder{" +
|
||||
"source='" + source + '\'' +
|
||||
", codePoints=" + codePoints +
|
||||
", codePointsLength=" + codePointsLength +
|
||||
", throwExceptions=" + throwExceptions +
|
||||
", sourceIsEscaped=" + sourceIsEscaped +
|
||||
", shouldReEscape=" + shouldReEscape +
|
||||
", links=" + links +
|
||||
'}';
|
||||
}
|
||||
|
||||
private void appendSource(final StringBuilder builder, final int start, final int end) {
|
||||
if (sourceIsEscaped == shouldReEscape) {
|
||||
for (int i = start; i < end; i++) {
|
||||
builder.appendCodePoint(codePoints[i]);
|
||||
builder.appendCodePoint(codePoints.get(i));
|
||||
}
|
||||
} else if (shouldReEscape) {
|
||||
builder.append(escape(subString(start, end)));
|
||||
builder.append(escape(codePoints.substring(start, end)));
|
||||
} else {
|
||||
builder.append(unescape(subString(start, end)));
|
||||
builder.append(unescape(codePoints.substring(start, end)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,26 +146,18 @@ public class HtmlBuilder {
|
|||
return shouldReEscape ? escape(source) : unescape(source);
|
||||
}
|
||||
|
||||
private String subString(final int start, final int end) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (int i = start; i < end; i++) {
|
||||
sb.appendCodePoint(codePoints[i]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
static final class LinkSpec implements Comparable<LinkSpec> {
|
||||
|
||||
final String link, display;
|
||||
final int start, end;
|
||||
final boolean display_is_html;
|
||||
final boolean displayIsHtml;
|
||||
|
||||
LinkSpec(final String link, final String display, final int start, final int end, final boolean display_is_html) {
|
||||
LinkSpec(final String link, final String display, final int start, final int end, final boolean displayIsHtml) {
|
||||
this.link = link;
|
||||
this.display = display;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.display_is_html = display_is_html;
|
||||
this.displayIsHtml = displayIsHtml;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -186,7 +174,7 @@ public class HtmlBuilder {
|
|||
if (display == null) {
|
||||
if (other.display != null) return false;
|
||||
} else if (!display.equals(other.display)) return false;
|
||||
if (display_is_html != other.display_is_html) return false;
|
||||
if (displayIsHtml != other.displayIsHtml) return false;
|
||||
if (end != other.end) return false;
|
||||
if (link == null) {
|
||||
if (other.link != null) return false;
|
||||
|
@ -200,7 +188,7 @@ public class HtmlBuilder {
|
|||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (display == null ? 0 : display.hashCode());
|
||||
result = prime * result + (display_is_html ? 1231 : 1237);
|
||||
result = prime * result + (displayIsHtml ? 1231 : 1237);
|
||||
result = prime * result + end;
|
||||
result = prime * result + (link == null ? 0 : link.hashCode());
|
||||
result = prime * result + start;
|
||||
|
@ -209,8 +197,13 @@ public class HtmlBuilder {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LinkSpec{link=" + link + ", display=" + display + ", start=" + start + ", end=" + end
|
||||
+ ", display_is_html=" + display_is_html + "}";
|
||||
return "LinkSpec{" +
|
||||
"link='" + link + '\'' +
|
||||
", display='" + display + '\'' +
|
||||
", start=" + start +
|
||||
", end=" + end +
|
||||
", displayIsHtml=" + displayIsHtml +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,27 +20,51 @@
|
|||
package org.mariotaku.twidere.util;
|
||||
|
||||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
import org.apache.commons.lang3.text.translate.AggregateTranslator;
|
||||
import org.apache.commons.lang3.text.translate.CodePointTranslator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
public class HtmlEscapeHelper {
|
||||
|
||||
public static String escape(final String string) {
|
||||
if (string == null) return null;
|
||||
return StringEscapeUtils.escapeHtml4(string);
|
||||
}
|
||||
public static final AggregateTranslator ESCAPE_HTML = new AggregateTranslator(StringEscapeUtils.ESCAPE_HTML4,
|
||||
new UnicodeControlCharacterToHtmlTranslator());
|
||||
|
||||
public static String toHtml(final String string) {
|
||||
if (string == null) return null;
|
||||
return escape(string).replace("\n", "<br/>");
|
||||
}
|
||||
public static String escape(final CharSequence text) {
|
||||
if (text == null) return null;
|
||||
return ESCAPE_HTML.translate(text);
|
||||
}
|
||||
|
||||
public static String toPlainText(final String string) {
|
||||
if (string == null) return null;
|
||||
return unescape(string.replace("<br/>", "\n").replaceAll("<!--.*?-->|<[^>]+>", ""));
|
||||
}
|
||||
public static String toHtml(final String string) {
|
||||
if (string == null) return null;
|
||||
return escape(string).replace("\n", "<br/>");
|
||||
}
|
||||
|
||||
public static String unescape(final String string) {
|
||||
if (string == null) return null;
|
||||
return StringEscapeUtils.unescapeHtml4(string);
|
||||
}
|
||||
public static String toPlainText(final String string) {
|
||||
if (string == null) return null;
|
||||
return unescape(string.replace("<br/>", "\n").replaceAll("<!--.*?-->|<[^>]+>", ""));
|
||||
}
|
||||
|
||||
public static String unescape(final String string) {
|
||||
if (string == null) return null;
|
||||
return StringEscapeUtils.unescapeHtml4(string);
|
||||
}
|
||||
|
||||
private static class UnicodeControlCharacterToHtmlTranslator extends CodePointTranslator {
|
||||
|
||||
@Override
|
||||
public boolean translate(int codePoint, Writer out) throws IOException {
|
||||
if (Character.isISOControl(codePoint)) {
|
||||
out.append("&#x");
|
||||
final char[] chars = Character.toChars(codePoint);
|
||||
for (char c : chars) {
|
||||
out.append(Integer.toHexString(c));
|
||||
}
|
||||
out.append(';');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@ import android.support.annotation.NonNull;
|
|||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.apache.commons.lang3.text.translate.CharSequenceTranslator;
|
||||
import org.apache.commons.lang3.text.translate.EntityArrays;
|
||||
import org.apache.commons.lang3.text.translate.LookupTranslator;
|
||||
import org.mariotaku.twidere.api.twitter.Twitter;
|
||||
import org.mariotaku.twidere.api.twitter.TwitterException;
|
||||
import org.mariotaku.twidere.api.twitter.model.DirectMessage;
|
||||
|
@ -59,7 +62,7 @@ public class TwitterContentUtils {
|
|||
if (message == null) return null;
|
||||
final HtmlBuilder builder = new HtmlBuilder(message.getText(), false, true, true);
|
||||
TwitterContentUtils.parseEntities(builder, message);
|
||||
return builder.build().replace("\n", "<br/>");
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static String formatExpandedUserDescription(final User user) {
|
||||
|
@ -76,14 +79,14 @@ public class TwitterContentUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
return toPlainText(builder.build().replace("\n", "<br/>"));
|
||||
return toPlainText(builder.build());
|
||||
}
|
||||
|
||||
public static String formatStatusText(final Status status) {
|
||||
if (status == null) return null;
|
||||
final HtmlBuilder builder = new HtmlBuilder(status.getText(), false, true, true);
|
||||
TwitterContentUtils.parseEntities(builder, status);
|
||||
return builder.build().replace("\n", "<br/>");
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static String formatUserDescription(final User user) {
|
||||
|
@ -100,7 +103,7 @@ public class TwitterContentUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
return builder.build().replace("\n", "<br/>");
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -167,9 +170,11 @@ public class TwitterContentUtils {
|
|||
return ConsumerKeyType.UNKNOWN;
|
||||
}
|
||||
|
||||
public static String unescapeTwitterStatusText(final String str) {
|
||||
if (str == null) return null;
|
||||
return str.replace("&", "&").replace("<", "<").replace(">", ">");
|
||||
private static final CharSequenceTranslator UNESCAPE_TWITTER_RAW_TEXT = new LookupTranslator(EntityArrays.BASIC_UNESCAPE());
|
||||
|
||||
public static String unescapeTwitterStatusText(final CharSequence text) {
|
||||
if (text == null) return null;
|
||||
return UNESCAPE_TWITTER_RAW_TEXT.translate(text);
|
||||
}
|
||||
|
||||
public static <T extends List<Status>> T getStatusesWithQuoteData(Twitter twitter, @NonNull T list) throws TwitterException {
|
||||
|
|
|
@ -96,6 +96,7 @@ dependencies {
|
|||
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.maps.android:android-maps-utils:0.4'
|
||||
fdroidCompile 'org.osmdroid:osmdroid-android:4.3'
|
||||
|
|
|
@ -158,6 +158,7 @@
|
|||
android:excludeFromRecents="true"
|
||||
android:label="@string/compose"
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName=".activity.support.HomeActivity"
|
||||
android:theme="@style/Theme.Twidere.Dark.Dialog"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter android:label="@string/compose">
|
||||
|
@ -222,6 +223,7 @@
|
|||
<activity
|
||||
android:name=".activity.SettingsActivity"
|
||||
android:label="@string/settings"
|
||||
android:parentActivityName=".activity.support.HomeActivity"
|
||||
android:theme="@style/Theme.Twidere.Dark"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
|
@ -256,6 +258,7 @@
|
|||
android:theme="@style/Theme.Twidere.Dark.NoDisplay" />
|
||||
<activity
|
||||
android:name=".activity.support.LinkHandlerActivity"
|
||||
android:parentActivityName=".activity.support.HomeActivity"
|
||||
android:theme="@style/Theme.Twidere.Dark.DialogWhenLarge.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<meta-data
|
||||
|
|
|
@ -19,8 +19,10 @@
|
|||
|
||||
package edu.tsinghua.hotmobi.model;
|
||||
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
|
@ -59,6 +61,9 @@ public class SessionEvent extends BaseEvent implements Parcelable {
|
|||
@ParcelableThisPlease
|
||||
@JsonField(name = "preferences")
|
||||
HashMap<String, String> preferences;
|
||||
@ParcelableThisPlease
|
||||
@JsonField(name = "device_preferences")
|
||||
HashMap<String, String> devicePreferences;
|
||||
|
||||
protected SessionEvent(Parcel in) {
|
||||
super(in);
|
||||
|
@ -90,6 +95,14 @@ public class SessionEvent extends BaseEvent implements Parcelable {
|
|||
this.preferences = preferences;
|
||||
}
|
||||
|
||||
public HashMap<String, String> getDevicePreferences() {
|
||||
return devicePreferences;
|
||||
}
|
||||
|
||||
public void setDevicePreferences(HashMap<String, String> devicePreferences) {
|
||||
this.devicePreferences = devicePreferences;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
|
@ -104,6 +117,20 @@ public class SessionEvent extends BaseEvent implements Parcelable {
|
|||
preferences.put("notification_" + accountId + "_interactions", String.valueOf(pref.isMentionsNotificationEnabled()));
|
||||
}
|
||||
setPreferences(preferences);
|
||||
final HashMap<String, String> devicePreferences = new HashMap<>();
|
||||
devicePreferences.put("device_secure", isDeviceSecure(context));
|
||||
setDevicePreferences(devicePreferences);
|
||||
}
|
||||
|
||||
private static String isDeviceSecure(Context context) {
|
||||
KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
||||
return String.valueOf(false);
|
||||
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return String.valueOf(km.isKeyguardSecure());
|
||||
} else {
|
||||
return String.valueOf(km.isDeviceSecure());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -430,7 +430,7 @@ public class LinkHandlerActivity extends BaseAppCompatActivity implements System
|
|||
break;
|
||||
}
|
||||
case LINK_ID_USER_FAVORITES: {
|
||||
setTitle(R.string.favorites);
|
||||
setTitle(R.string.likes);
|
||||
break;
|
||||
}
|
||||
case LINK_ID_USER_FOLLOWERS: {
|
||||
|
|
|
@ -516,7 +516,7 @@ public class AccountsDashboardFragment extends BaseSupportFragment implements Lo
|
|||
// if (accounts.length > 1) {
|
||||
// mAccountOptionsAdapter.add(new OptionItem(R.string.compose, R.drawable.ic_action_status_compose, R.id.compose));
|
||||
// }
|
||||
mAccountOptionsAdapter.add(new OptionItem(R.string.favorites, R.drawable.ic_action_star, R.id.favorites));
|
||||
mAccountOptionsAdapter.add(new OptionItem(R.string.likes, R.drawable.ic_action_heart, R.id.favorites));
|
||||
mAccountOptionsAdapter.add(new OptionItem(R.string.lists, R.drawable.ic_action_list, R.id.lists));
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ import org.mariotaku.twidere.model.SingleResponse;
|
|||
import org.mariotaku.twidere.util.AsyncTaskUtils;
|
||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
||||
import org.mariotaku.twidere.util.CompareUtils;
|
||||
import org.mariotaku.twidere.util.HtmlSpanBuilder;
|
||||
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
|
||||
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback;
|
||||
import org.mariotaku.twidere.util.LinkCreator;
|
||||
|
@ -906,7 +907,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
|
|||
}
|
||||
timeSourceView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
textView.setText(Html.fromHtml(status.text_html));
|
||||
textView.setText(HtmlSpanBuilder.fromHtml(status.text_html));
|
||||
linkify.applyAllLinks(textView, status.account_id, layoutPosition, status.is_possibly_sensitive);
|
||||
ThemeUtils.applyParagraphSpacing(textView, 1.1f);
|
||||
|
||||
|
|
|
@ -1449,7 +1449,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
|
|||
if (Utils.isOfficialKeyAccount(context, accountId)) {
|
||||
mPagerAdapter.addTab(UserMediaTimelineFragment.class, tabArgs, getString(R.string.media), R.drawable.ic_action_gallery, TAB_TYPE_MEDIA, TAB_POSITION_MEDIA, null);
|
||||
}
|
||||
mPagerAdapter.addTab(UserFavoritesFragment.class, tabArgs, getString(R.string.favorites), R.drawable.ic_action_star, TAB_TYPE_FAVORITES, TAB_POSITION_FAVORITES, null);
|
||||
mPagerAdapter.addTab(UserFavoritesFragment.class, tabArgs, getString(R.string.likes), R.drawable.ic_action_heart, TAB_TYPE_FAVORITES, TAB_POSITION_FAVORITES, null);
|
||||
}
|
||||
|
||||
private void updateFollowProgressState() {
|
||||
|
|
|
@ -81,7 +81,7 @@ public class CustomTabUtils implements Constants {
|
|||
TrendsSuggestionsFragment.class, R.string.trends, R.drawable.ic_action_hashtag,
|
||||
CustomTabConfiguration.ACCOUNT_NONE, CustomTabConfiguration.FIELD_TYPE_NONE, 3, true));
|
||||
CUSTOM_TABS_CONFIGURATION_MAP.put(TAB_TYPE_FAVORITES, new CustomTabConfiguration(UserFavoritesFragment.class,
|
||||
R.string.favorites, R.drawable.ic_action_star, CustomTabConfiguration.ACCOUNT_REQUIRED,
|
||||
R.string.likes, R.drawable.ic_action_heart, CustomTabConfiguration.ACCOUNT_REQUIRED,
|
||||
CustomTabConfiguration.FIELD_TYPE_USER, 4));
|
||||
CUSTOM_TABS_CONFIGURATION_MAP.put(TAB_TYPE_USER_TIMELINE, new CustomTabConfiguration(
|
||||
UserTimelineFragment.class, R.string.users_statuses, R.drawable.ic_action_quote,
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.URLSpan;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.attoparser.AttoParseException;
|
||||
import org.attoparser.IAttoParser;
|
||||
import org.attoparser.markup.MarkupAttoParser;
|
||||
import org.attoparser.markup.html.AbstractStandardNonValidatingHtmlAttoHandler;
|
||||
import org.attoparser.markup.html.HtmlParsingConfiguration;
|
||||
import org.attoparser.markup.html.elements.IHtmlElement;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/11/4.
|
||||
*/
|
||||
public class HtmlSpanBuilder {
|
||||
|
||||
private static final IAttoParser PARSER = new MarkupAttoParser();
|
||||
|
||||
public static Spanned fromHtml(String html) {
|
||||
final HtmlParsingConfiguration conf = new HtmlParsingConfiguration();
|
||||
final HtmlSpanHandler handler = new HtmlSpanHandler(conf);
|
||||
try {
|
||||
PARSER.parse(html, handler);
|
||||
} catch (AttoParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return handler.getText();
|
||||
}
|
||||
|
||||
private static void applyTag(SpannableStringBuilder sb, int start, int end, TagInfo info) {
|
||||
final Object span = createSpan(info);
|
||||
if (span == null) return;
|
||||
sb.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
private static Object createSpan(TagInfo info) {
|
||||
switch (info.name) {
|
||||
case "a": {
|
||||
return new URLSpan(info.getAttribute("href"));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int lastIndexOfTag(List<TagInfo> info, String name) {
|
||||
for (int i = info.size() - 1; i >= 0; i--) {
|
||||
if (StringUtils.equals(info.get(i).name, name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static class TagInfo {
|
||||
final int start;
|
||||
final String name;
|
||||
final Map<String, String> attributes;
|
||||
|
||||
public TagInfo(int start, String name, Map<String, String> attributes) {
|
||||
this.start = start;
|
||||
this.name = name;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public String getAttribute(String attr) {
|
||||
return attributes.get(attr);
|
||||
}
|
||||
}
|
||||
|
||||
private static class HtmlSpanHandler extends AbstractStandardNonValidatingHtmlAttoHandler {
|
||||
private final SpannableStringBuilder sb;
|
||||
List<TagInfo> tagInfo;
|
||||
|
||||
public HtmlSpanHandler(HtmlParsingConfiguration conf) {
|
||||
super(conf);
|
||||
this.sb = new SpannableStringBuilder();
|
||||
tagInfo = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleText(char[] buffer, int offset, int len, int line, int col) throws AttoParseException {
|
||||
sb.append(HtmlEscapeHelper.unescape(new String(buffer, offset, len)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHtmlCloseElement(IHtmlElement element, String elementName, int line, int col) throws AttoParseException {
|
||||
final int lastIndex = lastIndexOfTag(tagInfo, elementName);
|
||||
if (lastIndex != -1) {
|
||||
TagInfo info = tagInfo.get(lastIndex);
|
||||
applyTag(sb, info.start, sb.length(), info);
|
||||
tagInfo.remove(lastIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHtmlOpenElement(IHtmlElement element, String elementName, Map<String, String> attributes, int line, int col) throws AttoParseException {
|
||||
tagInfo.add(new TagInfo(sb.length(), elementName, attributes));
|
||||
}
|
||||
|
||||
public Spanned getText() {
|
||||
return sb;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ public class KeyboardShortcutsHandler implements Constants, KeyboardShortcutCons
|
|||
sActionLabelMap.put(ACTION_HOME_ACCOUNTS_DASHBOARD, R.string.open_accounts_dashboard);
|
||||
sActionLabelMap.put(ACTION_STATUS_REPLY, R.string.reply);
|
||||
sActionLabelMap.put(ACTION_STATUS_RETWEET, R.string.retweet);
|
||||
sActionLabelMap.put(ACTION_STATUS_FAVORITE, R.string.favorite);
|
||||
sActionLabelMap.put(ACTION_STATUS_FAVORITE, R.string.like);
|
||||
sActionLabelMap.put(ACTION_NAVIGATION_PREVIOUS, R.string.previous_item);
|
||||
sActionLabelMap.put(ACTION_NAVIGATION_NEXT, R.string.next_item);
|
||||
sActionLabelMap.put(ACTION_NAVIGATION_PAGE_DOWN, R.string.page_down);
|
||||
|
|
|
@ -24,7 +24,7 @@ import android.support.annotation.NonNull;
|
|||
/**
|
||||
* Created by mariotaku on 14/12/23.
|
||||
*/
|
||||
public class StringUtils {
|
||||
public class TwidereStringUtils {
|
||||
public static boolean regionMatchesIgnoreCase(@NonNull final String string, final int thisStart,
|
||||
@NonNull final String match, final int start,
|
||||
final int length) {
|
|
@ -74,6 +74,7 @@ import android.support.v4.app.Fragment;
|
|||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
import android.support.v4.util.Pair;
|
||||
import android.support.v4.view.ActionProvider;
|
||||
|
@ -1727,13 +1728,13 @@ public final class Utils implements Constants {
|
|||
return def;
|
||||
}
|
||||
|
||||
public static int getCardHighlightColor(final Resources res, final boolean isMention, final boolean isFavorite,
|
||||
final boolean isRetweet) {
|
||||
public static int getCardHighlightColor(final Context context, final boolean isMention,
|
||||
final boolean isFavorite, final boolean isRetweet) {
|
||||
if (isMention)
|
||||
return res.getColor(R.color.highlight_reply);
|
||||
return ContextCompat.getColor(context, R.color.highlight_reply);
|
||||
else if (isFavorite)
|
||||
return res.getColor(R.color.highlight_favorite);
|
||||
else if (isRetweet) res.getColor(R.color.highlight_retweet);
|
||||
return ContextCompat.getColor(context, R.color.highlight_like);
|
||||
else if (isRetweet) ContextCompat.getColor(context, R.color.highlight_retweet);
|
||||
return Color.TRANSPARENT;
|
||||
}
|
||||
|
||||
|
@ -1955,7 +1956,7 @@ public final class Utils implements Constants {
|
|||
if (key == -1 || isEmpty(value)) {
|
||||
continue;
|
||||
}
|
||||
if (StringUtils.startsWithIgnoreCase(value, str)) {
|
||||
if (TwidereStringUtils.startsWithIgnoreCase(value, str)) {
|
||||
list.add(key);
|
||||
}
|
||||
}
|
||||
|
@ -3332,8 +3333,8 @@ public final class Utils implements Constants {
|
|||
final ParcelableCredentials account) {
|
||||
if (context == null || menu == null || status == null || account == null) return;
|
||||
final Resources resources = context.getResources();
|
||||
final int retweetHighlight = resources.getColor(R.color.highlight_retweet);
|
||||
final int favoriteHighlight = resources.getColor(R.color.highlight_favorite);
|
||||
final int retweetHighlight = ContextCompat.getColor(context, R.color.highlight_retweet);
|
||||
final int likeHighlight = ContextCompat.getColor(context, R.color.highlight_like);
|
||||
final boolean isMyRetweet = isMyRetweet(status);
|
||||
final MenuItem delete = menu.findItem(R.id.delete);
|
||||
if (delete != null) {
|
||||
|
@ -3346,8 +3347,8 @@ public final class Utils implements Constants {
|
|||
}
|
||||
final MenuItem favorite = menu.findItem(R.id.favorite);
|
||||
if (favorite != null) {
|
||||
ActionIconDrawable.setMenuHighlight(favorite, new TwidereMenuInfo(status.is_favorite, favoriteHighlight));
|
||||
favorite.setTitle(status.is_favorite ? R.string.unfavorite : R.string.favorite);
|
||||
ActionIconDrawable.setMenuHighlight(favorite, new TwidereMenuInfo(status.is_favorite, likeHighlight));
|
||||
favorite.setTitle(status.is_favorite ? R.string.undo_like : R.string.like);
|
||||
}
|
||||
final MenuItem translate = menu.findItem(R.id.translate);
|
||||
if (translate != null) {
|
||||
|
|
|
@ -103,7 +103,7 @@ public class ActivityTitleSummaryViewHolder extends ViewHolder implements View.O
|
|||
}
|
||||
case Activity.ACTION_FAVORITE: {
|
||||
activityTypeView.setImageResource(R.drawable.ic_activity_action_favorite);
|
||||
activityTypeView.setColorFilter(ContextCompat.getColor(context, R.color.highlight_favorite), Mode.SRC_ATOP);
|
||||
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));
|
||||
|
@ -136,7 +136,7 @@ public class ActivityTitleSummaryViewHolder extends ViewHolder implements View.O
|
|||
return;
|
||||
}
|
||||
activityTypeView.setImageResource(R.drawable.ic_activity_action_favorite);
|
||||
activityTypeView.setColorFilter(ContextCompat.getColor(context, R.color.highlight_favorite), Mode.SRC_ATOP);
|
||||
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));
|
||||
displayUserProfileImages(activity.sources);
|
||||
|
@ -178,9 +178,9 @@ public class ActivityTitleSummaryViewHolder extends ViewHolder implements View.O
|
|||
return;
|
||||
}
|
||||
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));
|
||||
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);
|
||||
|
|
|
@ -780,4 +780,6 @@
|
|||
<string name="bug_reports">错误报告</string>
|
||||
<string name="invalid_consumer_key">无效的Consumer key</string>
|
||||
<string name="invalid_consumer_secret">无效的Consumer secret</string>
|
||||
<string name="like">喜欢</string>
|
||||
<string name="likes">喜欢</string>
|
||||
</resources>
|
||||
|
|
After Width: | Height: | Size: 686 B |
After Width: | Height: | Size: 510 B |
After Width: | Height: | Size: 865 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 329 B |
Before Width: | Height: | Size: 240 B |
Before Width: | Height: | Size: 459 B |
Before Width: | Height: | Size: 478 B |
|
@ -288,14 +288,14 @@
|
|||
style="?cardActionButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/button_size_content_card"
|
||||
android:drawableLeft="@drawable/ic_action_star"
|
||||
android:drawableStart="@drawable/ic_action_star"
|
||||
android:drawableLeft="@drawable/ic_action_heart"
|
||||
android:drawableStart="@drawable/ic_action_heart"
|
||||
android:focusable="false"
|
||||
android:gravity="center"
|
||||
android:paddingLeft="@dimen/element_spacing_normal"
|
||||
android:paddingRight="@dimen/element_spacing_normal"
|
||||
android:textAppearance="?android:textAppearanceSmall"
|
||||
app:iabActivatedColor="@color/highlight_favorite"
|
||||
app:iabActivatedColor="@color/highlight_like"
|
||||
app:iabColor="?android:textColorTertiary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -272,14 +272,14 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/button_size_content_card"
|
||||
android:layout_weight="0"
|
||||
android:drawableLeft="@drawable/ic_action_star"
|
||||
android:drawableStart="@drawable/ic_action_star"
|
||||
android:drawableLeft="@drawable/ic_action_heart"
|
||||
android:drawableStart="@drawable/ic_action_heart"
|
||||
android:focusable="false"
|
||||
android:gravity="center"
|
||||
android:paddingLeft="@dimen/element_spacing_normal"
|
||||
android:paddingRight="@dimen/element_spacing_normal"
|
||||
android:textAppearance="?android:textAppearanceSmall"
|
||||
app:iabActivatedColor="@color/highlight_favorite"
|
||||
app:iabActivatedColor="@color/highlight_like"
|
||||
app:iabColor="?android:textColorTertiary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -434,7 +434,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:text="@string/favorites"
|
||||
android:text="@string/likes"
|
||||
android:textAppearance="?android:textAppearanceSmall"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
</LinearLayout>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="AlwaysShowAction">
|
||||
|
||||
<item
|
||||
android:id="@id/reply"
|
||||
|
@ -14,8 +16,8 @@
|
|||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@id/favorite"
|
||||
android:icon="@drawable/ic_action_star"
|
||||
android:title="@string/favorite"
|
||||
android:icon="@drawable/ic_action_heart"
|
||||
android:title="@string/like"
|
||||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@id/share"
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
<color name="action_icon_light_disabled">#4DFFFFFF</color>
|
||||
<color name="highlight_retweet">@color/material_light_green</color>
|
||||
<color name="highlight_favorite">@color/material_amber</color>
|
||||
<color name="highlight_like">@color/material_pink</color>
|
||||
<color name="highlight_reply">@color/material_light_blue</color>
|
||||
<color name="highlight_follow">@color/material_light_blue</color>
|
||||
<color name="unread_color">@color/material_red</color>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generated by crowdin.net -->
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Generated by crowdin.net -->
|
||||
<resources>
|
||||
|
||||
<plurals name="Nitems_selected">
|
||||
|
@ -58,5 +57,9 @@
|
|||
<item quantity="one">@string/N_favorites_quantity_one</item>
|
||||
<item quantity="other">@string/N_favorites_quantity_other</item>
|
||||
</plurals>
|
||||
<plurals name="N_likes">
|
||||
<item quantity="one">@string/N_likes_quantity_one</item>
|
||||
<item quantity="other">@string/N_likes_quantity_other</item>
|
||||
</plurals>
|
||||
|
||||
</resources>
|
|
@ -55,6 +55,9 @@
|
|||
<string name="cancel_retweet">Cancel retweet</string>
|
||||
<string name="favorite">Favorite</string>
|
||||
<string name="unfavorite">Unfavorite</string>
|
||||
<!-- v. e.g. An action label on a tweet to like this tweet. Formerly Twitter's favorite. -->
|
||||
<string name="like">Like</string>
|
||||
<string name="undo_like">Undo like</string>
|
||||
<string name="reply">Reply</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="view_image">Image</string>
|
||||
|
@ -98,6 +101,7 @@
|
|||
<string name="retweeted_by_count">Retweeted by <xliff:g id="retweet_count">%d</xliff:g> users</string>
|
||||
<string name="users_retweeted_this">Users retweeted this</string>
|
||||
<string name="users_favorited_this">Users favorited this</string>
|
||||
<string name="users_liked_this">Users liked this</string>
|
||||
<string name="reply_to">Reply to <xliff:g id="user_name">%s</xliff:g></string>
|
||||
<string name="quote_user">Quote <xliff:g id="user_name">%s</xliff:g></string>
|
||||
<string name="time_source"><xliff:g id="time">%1$s</xliff:g> · <xliff:g id="source">%2$s</xliff:g></string>
|
||||
|
@ -131,6 +135,8 @@
|
|||
<string name="location">Location</string>
|
||||
<string name="url">URL</string>
|
||||
<string name="favorites">Favorites</string>
|
||||
<!-- n. formerly Twitter's favorite, in the plural -->
|
||||
<string name="likes">Likes</string>
|
||||
<string name="name">Name</string>
|
||||
<string name="refresh">Refresh</string>
|
||||
<string name="retry">Retry</string>
|
||||
|
@ -283,6 +289,8 @@
|
|||
<string name="activity_about_me_retweeted_mention_multi"><xliff:g id="user">%s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> retweeted a tweet mentioned you.</string>
|
||||
<string name="activity_about_me_favorited_mention"><xliff:g id="user">%s</xliff:g> favorited a tweet mentioned you.</string>
|
||||
<string name="activity_about_me_favorited_mention_multi"><xliff:g id="user">%s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> favorited a tweet mentioned you.</string>
|
||||
<string name="activity_about_me_liked_mention"><xliff:g id="user">%s</xliff:g> liked a tweet mentioned you.</string>
|
||||
<string name="activity_about_me_liked_mention_multi"><xliff:g id="user">%s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> liked a tweet mentioned you.</string>
|
||||
<string name="activity_about_me_list_member_added"><xliff:g id="user">%s</xliff:g> added you to list.</string>
|
||||
<string name="activity_about_me_list_member_added_with_name"><xliff:g id="user">%1$s</xliff:g> added you to list <xliff:g id="list">%2$s</xliff:g>".</string>
|
||||
<string name="activity_about_me_list_member_added_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> added you to their lists.</string>
|
||||
|
@ -731,8 +739,10 @@
|
|||
<string name="hide_card_actions">Hide card actions</string>
|
||||
<string name="N_statuses_quantity_one" tools:ignore="PluralsCandidate"><xliff:g id="count">%d</xliff:g> tweet</string>
|
||||
<string name="N_statuses_quantity_other" tools:ignore="PluralsCandidate"><xliff:g id="count">%d</xliff:g> tweets</string>
|
||||
<string name="N_favorites_quantity_one" tools:ignore="PluralsCandidate"><xliff:g id="count">%d</xliff:g> favorites</string>
|
||||
<string name="N_favorites_quantity_one" tools:ignore="PluralsCandidate"><xliff:g id="count">%d</xliff:g> favorite</string>
|
||||
<string name="N_favorites_quantity_other" tools:ignore="PluralsCandidate"><xliff:g id="count">%d</xliff:g> favorites</string>
|
||||
<string name="N_likes_quantity_one" tools:ignore="PluralsCandidate"><xliff:g id="count">%d</xliff:g> like</string>
|
||||
<string name="N_likes_quantity_other" tools:ignore="PluralsCandidate"><xliff:g id="count">%d</xliff:g> likes</string>
|
||||
<string name="drafts_hint_messages">Your unsent tweets will save here</string>
|
||||
<string name="keyboard_shortcuts">Keyboard shortcuts</string>
|
||||
<string name="keyboard_shortcut_hint">Press keys</string>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.4 (15588) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Artboard</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="Artboard" sketch:type="MSArtboardGroup" fill="#FFFFFF">
|
||||
<path d="M16,24.5708333 L14.6708333,23.3608333 C9.95,19.08 6.83333333,16.2566667 6.83333333,12.7916667 C6.83333333,9.96833333 9.05166667,7.75 11.875,7.75 C13.47,7.75 15.0008333,8.4925 16,9.66583333 C16.9991667,8.4925 18.53,7.75 20.125,7.75 C22.9483333,7.75 25.1666667,9.96833333 25.1666667,12.7916667 C25.1666667,16.2566667 22.05,19.08 17.3291667,23.37 L16,24.5708333 L16,24.5708333 Z" id="Shape" sketch:type="MSShapeGroup"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |