parent
0077388c65
commit
41233a837b
54 changed files with 1266 additions and 1042 deletions
|
@ -0,0 +1,137 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Tusky 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 Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.SpannedString;
|
||||
import android.text.style.ReplacementSpan;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.squareup.picasso.Target;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class CustomEmojiHelper {
|
||||
|
||||
/**
|
||||
* replaces emoji shortcodes in a text with EmojiSpans
|
||||
* @param text the text containing custom emojis
|
||||
* @param emojis a list of the custom emojis
|
||||
* @param textView a reference to the textView the emojis will be shown in
|
||||
* @return the text with the shortcodes replaced by EmojiSpans
|
||||
*/
|
||||
public static Spanned emojifyText(Spanned text, List<Status.Emoji> emojis, final TextView textView) {
|
||||
|
||||
if (!emojis.isEmpty()) {
|
||||
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder(text);
|
||||
for (Status.Emoji emoji : emojis) {
|
||||
CharSequence pattern = new StringBuilder(":").append(emoji.getShortcode()).append(':');
|
||||
Matcher matcher = Pattern.compile(pattern.toString()).matcher(text);
|
||||
while (matcher.find()) {
|
||||
// We keep a span as a Picasso target, because Picasso keeps weak reference to
|
||||
// the target so an anonymous class would likely be garbage collected.
|
||||
EmojiSpan span = new EmojiSpan(textView);
|
||||
builder.setSpan(span, matcher.start(), matcher.end(), 0);
|
||||
Picasso.with(textView.getContext())
|
||||
.load(emoji.getUrl())
|
||||
.into(span);
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
public static Spanned emojifyString(String string, List<Status.Emoji> emojis, final TextView textView) {
|
||||
return emojifyText(new SpannedString(string), emojis, textView);
|
||||
}
|
||||
|
||||
|
||||
public static class EmojiSpan extends ReplacementSpan implements Target {
|
||||
|
||||
private @Nullable Drawable imageDrawable;
|
||||
private WeakReference<TextView> textViewWeakReference;
|
||||
|
||||
EmojiSpan(TextView textView) {
|
||||
this.textViewWeakReference = new WeakReference<>(textView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
|
||||
@Nullable Paint.FontMetricsInt fm) {
|
||||
|
||||
/* update FontMetricsInt or otherwise span does not get drawn when
|
||||
it covers the whole text */
|
||||
Paint.FontMetricsInt metrics = paint.getFontMetricsInt();
|
||||
if (fm != null) {
|
||||
fm.top = metrics.top;
|
||||
fm.ascent = metrics.ascent;
|
||||
fm.descent = metrics.descent;
|
||||
fm.bottom = metrics.bottom;
|
||||
}
|
||||
|
||||
return (int) (paint.getTextSize()*1.2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x,
|
||||
int top, int y, int bottom, @NonNull Paint paint) {
|
||||
if (imageDrawable == null) return;
|
||||
canvas.save();
|
||||
|
||||
int emojiSize = (int) (paint.getTextSize() * 1.1);
|
||||
imageDrawable.setBounds(0, 0, emojiSize, emojiSize);
|
||||
|
||||
int transY = bottom - imageDrawable.getBounds().bottom;
|
||||
transY -= paint.getFontMetricsInt().descent/2;
|
||||
canvas.translate(x, transY);
|
||||
imageDrawable.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
|
||||
TextView textView = textViewWeakReference.get();
|
||||
if(textView != null) {
|
||||
imageDrawable = new BitmapDrawable(textView.getContext().getResources(), bitmap);
|
||||
textView.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBitmapFailed(Drawable errorDrawable) {}
|
||||
|
||||
@Override
|
||||
public void onPrepareLoad(Drawable placeHolderDrawable) {}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.style.URLSpan;
|
||||
import android.view.View;
|
||||
|
||||
public class CustomTabURLSpan extends URLSpan {
|
||||
public CustomTabURLSpan(String url) {
|
||||
super(url);
|
||||
}
|
||||
|
||||
private CustomTabURLSpan(Parcel src) {
|
||||
super(src);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<CustomTabURLSpan> CREATOR = new Parcelable.Creator<CustomTabURLSpan>() {
|
||||
|
||||
@Override
|
||||
public CustomTabURLSpan createFromParcel(Parcel source) {
|
||||
return new CustomTabURLSpan(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomTabURLSpan[] newArray(int size) {
|
||||
return new CustomTabURLSpan[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Uri uri = Uri.parse(getURL());
|
||||
LinkHelper.openLinkInCustomTab(uri, view.getContext());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.URLSpan;
|
||||
import android.view.View;
|
||||
|
||||
public class CustomURLSpan extends URLSpan {
|
||||
public CustomURLSpan(String url) {
|
||||
super(url);
|
||||
}
|
||||
|
||||
private CustomURLSpan(Parcel src) {
|
||||
super(src);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<CustomURLSpan> CREATOR = new Parcelable.Creator<CustomURLSpan>() {
|
||||
|
||||
@Override
|
||||
public CustomURLSpan createFromParcel(Parcel source) {
|
||||
return new CustomURLSpan(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomURLSpan[] newArray(int size) {
|
||||
return new CustomURLSpan[size];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
LinkHelper.openLink(getURL(), view.getContext());
|
||||
}
|
||||
|
||||
@Override public void updateDrawState(TextPaint ds) {
|
||||
super.updateDrawState(ds);
|
||||
ds.setUnderlineText(false);
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import android.support.customtabs.CustomTabsIntent;
|
|||
import android.support.v4.content.ContextCompat;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextPaint;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.text.style.URLSpan;
|
||||
|
@ -63,12 +64,11 @@ public class LinkHelper {
|
|||
* @param view the returned text will be put in
|
||||
* @param content containing text with mentions, links, or hashtags
|
||||
* @param mentions any '@' mentions which are known to be in the content
|
||||
* @param useCustomTabs whether to use custom tabs when opening web links
|
||||
* @param listener to notify about particular spans that are clicked
|
||||
*/
|
||||
public static void setClickableText(TextView view, Spanned content,
|
||||
@Nullable Status.Mention[] mentions, boolean useCustomTabs,
|
||||
final LinkListener listener) {
|
||||
@Nullable Status.Mention[] mentions, final LinkListener listener) {
|
||||
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder(content);
|
||||
URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class);
|
||||
for (URLSpan span : urlSpans) {
|
||||
|
@ -83,17 +83,21 @@ public class LinkHelper {
|
|||
public void onClick(View widget) {
|
||||
listener.onViewTag(tag);
|
||||
}
|
||||
@Override public void updateDrawState(TextPaint ds) {
|
||||
super.updateDrawState(ds);
|
||||
ds.setUnderlineText(false);
|
||||
}
|
||||
};
|
||||
builder.removeSpan(span);
|
||||
builder.setSpan(newSpan, start, end, flags);
|
||||
} else if (text.charAt(0) == '@' && mentions != null) {
|
||||
} else if (text.charAt(0) == '@' && mentions != null && mentions.length > 0) {
|
||||
String accountUsername = text.subSequence(1, text.length()).toString();
|
||||
/* There may be multiple matches for users on different instances with the same
|
||||
* username. If a match has the same domain we know it's for sure the same, but if
|
||||
* that can't be found then just go with whichever one matched last. */
|
||||
String id = null;
|
||||
for (Status.Mention mention : mentions) {
|
||||
if (mention.localUsername.equals(accountUsername)) {
|
||||
if (mention.localUsername.equalsIgnoreCase(accountUsername)) {
|
||||
id = mention.id;
|
||||
if (mention.url.contains(getDomain(span.getURL()))) {
|
||||
break;
|
||||
|
@ -107,12 +111,16 @@ public class LinkHelper {
|
|||
public void onClick(View widget) {
|
||||
listener.onViewAccount(accountId);
|
||||
}
|
||||
@Override public void updateDrawState(TextPaint ds) {
|
||||
super.updateDrawState(ds);
|
||||
ds.setUnderlineText(false);
|
||||
}
|
||||
};
|
||||
builder.removeSpan(span);
|
||||
builder.setSpan(newSpan, start, end, flags);
|
||||
}
|
||||
} else if (useCustomTabs) {
|
||||
ClickableSpan newSpan = new CustomTabURLSpan(span.getURL());
|
||||
} else {
|
||||
ClickableSpan newSpan = new CustomURLSpan(span.getURL());
|
||||
builder.removeSpan(span);
|
||||
builder.setSpan(newSpan, start, end, flags);
|
||||
}
|
||||
|
|
|
@ -85,9 +85,7 @@ public class OkHttpUtils {
|
|||
|
||||
@NonNull
|
||||
public static OkHttpClient getCompatibleClient() {
|
||||
OkHttpClient client = getCompatibleClientBuilder().build();
|
||||
Log.d(TAG, client.connectTimeoutMillis()+" "+client.readTimeoutMillis()+" "+client.writeTimeoutMillis());
|
||||
return client;
|
||||
return getCompatibleClientBuilder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.arch.core.util.Function;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.keylesspalace.tusky.entity.Notification;
|
||||
|
@ -23,16 +22,14 @@ import com.keylesspalace.tusky.entity.Status;
|
|||
import com.keylesspalace.tusky.viewdata.NotificationViewData;
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by charlag on 12/07/2017.
|
||||
*/
|
||||
|
||||
public final class ViewDataUtils {
|
||||
@Nullable
|
||||
public static StatusViewData.Concrete statusToViewData(@Nullable Status status) {
|
||||
public static StatusViewData.Concrete statusToViewData(@Nullable Status status,
|
||||
boolean alwaysShowSensitiveMedia) {
|
||||
if (status == null) return null;
|
||||
Status visibleStatus = status.reblog == null ? status : status.reblog;
|
||||
return new StatusViewData.Builder().setId(status.id)
|
||||
|
@ -51,6 +48,7 @@ public final class ViewDataUtils {
|
|||
.setNickname(visibleStatus.account.username)
|
||||
.setRebloggedAvatar(status.reblog == null ? null : status.account.avatar)
|
||||
.setSensitive(visibleStatus.sensitive)
|
||||
.setIsShowingSensitiveContent(alwaysShowSensitiveMedia || !visibleStatus.sensitive)
|
||||
.setSpoilerText(visibleStatus.spoilerText)
|
||||
.setRebloggedByUsername(status.reblog == null ? null : status.account.username)
|
||||
.setUserFullName(visibleStatus.account.getDisplayName())
|
||||
|
@ -62,37 +60,9 @@ public final class ViewDataUtils {
|
|||
.createStatusViewData();
|
||||
}
|
||||
|
||||
public static List<StatusViewData> statusListToViewDataList(List<Status> statuses) {
|
||||
List<StatusViewData> viewDatas = new ArrayList<>(statuses.size());
|
||||
for (Status s : statuses) {
|
||||
viewDatas.add(statusToViewData(s));
|
||||
}
|
||||
return viewDatas;
|
||||
}
|
||||
|
||||
public static Function<Status, StatusViewData.Concrete> statusMapper() {
|
||||
return statusMapper;
|
||||
}
|
||||
|
||||
public static NotificationViewData.Concrete notificationToViewData(Notification notification) {
|
||||
public static NotificationViewData.Concrete notificationToViewData(Notification notification, boolean alwaysShowSensitiveData) {
|
||||
return new NotificationViewData.Concrete(notification.type, notification.id, notification.account,
|
||||
statusToViewData(notification.status));
|
||||
statusToViewData(notification.status, alwaysShowSensitiveData), false);
|
||||
}
|
||||
|
||||
public static List<NotificationViewData> notificationListToViewDataList(
|
||||
List<Notification> notifications) {
|
||||
List<NotificationViewData> viewDatas = new ArrayList<>(notifications.size());
|
||||
for (Notification n : notifications) {
|
||||
viewDatas.add(notificationToViewData(n));
|
||||
}
|
||||
return viewDatas;
|
||||
}
|
||||
|
||||
private static final Function<Status, StatusViewData.Concrete> statusMapper =
|
||||
new Function<Status, StatusViewData.Concrete>() {
|
||||
@Override
|
||||
public StatusViewData.Concrete apply(Status input) {
|
||||
return ViewDataUtils.statusToViewData(input);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue