pull/281/head
Thomas 2 years ago
parent 941dd21561
commit 0bdee201e7

@ -99,8 +99,7 @@ dependencies {
implementation project(path: ':cropper') implementation project(path: ':cropper')
annotationProcessor "com.github.bumptech.glide:compiler:4.12.0" annotationProcessor "com.github.bumptech.glide:compiler:4.12.0"
implementation 'jp.wasabeef:glide-transformations:4.3.0' implementation 'jp.wasabeef:glide-transformations:4.3.0'
implementation 'com.github.penfeizhou.android.animation:apng:2.22.0' implementation 'com.github.penfeizhou.android.animation:glide-plugin:2.22.0'
implementation 'com.github.penfeizhou.android.animation:gif:2.22.0'
implementation 'com.google.android.exoplayer:exoplayer:2.16.1' implementation 'com.google.android.exoplayer:exoplayer:2.16.1'
implementation "androidx.viewpager2:viewpager2:1.0.0" implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation 'com.github.piasy:rxandroidaudio:1.7.0' implementation 'com.github.piasy:rxandroidaudio:1.7.0'

@ -0,0 +1,130 @@
package app.fedilab.android.helper;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.style.ReplacementSpan;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Emoji;
public class CustomEmoji extends ReplacementSpan {
private final View view;
private final float scale;
private Drawable imageDrawable;
CustomEmoji(WeakReference<View> viewWeakReference) {
Context mContext = viewWeakReference.get().getContext();
view = viewWeakReference.get();
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
scale = sharedpreferences.getFloat(mContext.getString(R.string.SET_FONT_SCALE), 1.0f);
}
public static void displayEmoji(List<Emoji> emojis, Spannable spannableString, WeakReference<View> viewWeakReference) {
View view = viewWeakReference.get();
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(view.getContext());
boolean animate = !sharedpreferences.getBoolean(view.getContext().getString(R.string.SET_DISABLE_GIF), false);
for (Emoji emoji : emojis) {
Matcher matcher = Pattern.compile(":" + emoji.shortcode + ":", Pattern.LITERAL)
.matcher(spannableString);
while (matcher.find()) {
CustomEmoji customEmoji = new CustomEmoji(viewWeakReference);
spannableString.setSpan(customEmoji, matcher.start(), matcher.end(), 0);
Glide.with(view)
.asDrawable()
.load(animate ? emoji.url : emoji.static_url)
.into(customEmoji.getTarget(animate));
}
}
}
@Override
public int getSize(@NonNull Paint paint, CharSequence charSequence, int i, int i1, @Nullable Paint.FontMetricsInt fontMetricsInt) {
Log.v(Helper.TAG, "fontMetricsInt: " + fontMetricsInt);
if (fontMetricsInt != null) {
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
fontMetricsInt.top = (int) fontMetrics.top;
fontMetricsInt.ascent = (int) fontMetrics.ascent;
fontMetricsInt.descent = (int) fontMetrics.descent;
fontMetricsInt.bottom = (int) fontMetrics.bottom;
}
return (int) (paint.getTextSize() * scale);
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence charSequence, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
if (imageDrawable != null) {
canvas.save();
int emojiSize = (int) (paint.getTextSize() * scale);
Drawable drawable = imageDrawable;
drawable.setBounds(0, 0, emojiSize, emojiSize);
int transY = bottom - drawable.getBounds().bottom;
Log.v(Helper.TAG, "transY: " + transY);
transY -= paint.getFontMetrics().descent / 2;
Log.v(Helper.TAG, "transY: " + transY);
canvas.translate(x, (float) transY);
Log.v(Helper.TAG, "x: " + x);
drawable.draw(canvas);
canvas.restore();
}
}
public Target<Drawable> getTarget(boolean animate) {
return new CustomTarget<Drawable>() {
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
if (animate) {
Drawable.Callback callback = resource.getCallback();
resource.setCallback(new Drawable.Callback() {
@Override
public void invalidateDrawable(@NonNull Drawable drawable) {
callback.invalidateDrawable(drawable);
view.invalidate();
}
@Override
public void scheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable, long l) {
callback.scheduleDrawable(drawable, runnable, l);
}
@Override
public void unscheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable) {
callback.unscheduleDrawable(drawable, runnable);
}
});
}
Log.v(Helper.TAG, "imageDrawable2: " + imageDrawable);
imageDrawable = resource;
view.invalidate();
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
};
}
}

@ -17,7 +17,6 @@ package app.fedilab.android.helper;
import static app.fedilab.android.BaseMainActivity.currentAccount; import static app.fedilab.android.BaseMainActivity.currentAccount;
import static app.fedilab.android.helper.Helper.USER_AGENT; import static app.fedilab.android.helper.Helper.USER_AGENT;
import static app.fedilab.android.helper.Helper.convertDpToPixel;
import static app.fedilab.android.helper.Helper.urlPattern; import static app.fedilab.android.helper.Helper.urlPattern;
import static app.fedilab.android.helper.ThemeHelper.linkColor; import static app.fedilab.android.helper.ThemeHelper.linkColor;
@ -25,8 +24,6 @@ import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@ -38,7 +35,6 @@ import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextPaint; import android.text.TextPaint;
import android.text.style.ClickableSpan; import android.text.style.ClickableSpan;
import android.text.style.ImageSpan;
import android.text.style.URLSpan; import android.text.style.URLSpan;
import android.util.Patterns; import android.util.Patterns;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -47,16 +43,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceManager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.FutureTarget;
import com.github.penfeizhou.animation.apng.APNGDrawable;
import com.github.penfeizhou.animation.apng.decode.APNGParser;
import com.github.penfeizhou.animation.gif.GifDrawable;
import com.github.penfeizhou.animation.gif.decode.GifParser;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
@ -65,7 +52,6 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -78,7 +64,6 @@ import app.fedilab.android.activities.ProfileActivity;
import app.fedilab.android.client.entities.api.Account; import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.Announcement; import app.fedilab.android.client.entities.api.Announcement;
import app.fedilab.android.client.entities.api.Attachment; import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.client.entities.api.Emoji;
import app.fedilab.android.client.entities.api.Field; import app.fedilab.android.client.entities.api.Field;
import app.fedilab.android.client.entities.api.Mention; import app.fedilab.android.client.entities.api.Mention;
import app.fedilab.android.client.entities.api.Poll; import app.fedilab.android.client.entities.api.Poll;
@ -95,7 +80,7 @@ public class SpannableHelper {
return convert(context, status, text, true); return convert(context, status, text, true);
} }
/** /**
* Convert HTML content to text. Also, it handles click on link and transform emoji * Convert HTML content to text. Also, it handles click on link
* This needs to be run asynchronously * This needs to be run asynchronously
* *
* @param context {@link Context} * @param context {@link Context}
@ -138,77 +123,6 @@ public class SpannableHelper {
content.removeSpan(span); content.removeSpan(span);
} }
//--- EMOJI ----
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean disableGif = sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_GIF), false);
List<Emoji> emojiList = status.reblog != null ? status.reblog.emojis : status.emojis;
//Will convert emoji if asked
if (emojiList != null && emojiList.size() > 0) {
for (Emoji emoji : emojiList) {
if (Helper.isValidContextForGlide(context)) {
FutureTarget<File> futureTarget = Glide.with(context)
.asFile()
.load(disableGif ? emoji.static_url : emoji.url)
.submit();
try {
File file = futureTarget.get();
final String targetedEmoji = ":" + emoji.shortcode + ":";
if (content.toString().contains(targetedEmoji)) {
//emojis can be used several times so we have to loop
for (int startPosition = -1; (startPosition = content.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) {
final int endPosition = startPosition + targetedEmoji.length();
if (endPosition <= content.toString().length() && endPosition >= startPosition) {
ImageSpan imageSpan;
if (APNGParser.isAPNG(file.getAbsolutePath())) {
APNGDrawable apngDrawable = APNGDrawable.fromFile(file.getAbsolutePath());
try {
apngDrawable.setBounds(0, 0, (int) convertDpToPixel(20, context), (int) convertDpToPixel(20, context));
apngDrawable.setVisible(true, true);
imageSpan = new ImageSpan(apngDrawable);
if (endPosition <= content.length()) {
content.setSpan(
imageSpan, startPosition,
endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
} catch (Exception ignored) {
}
} else if (GifParser.isGif(file.getAbsolutePath())) {
GifDrawable gifDrawable = GifDrawable.fromFile(file.getAbsolutePath());
try {
gifDrawable.setBounds(0, 0, (int) convertDpToPixel(20, context), (int) convertDpToPixel(20, context));
gifDrawable.setVisible(true, true);
imageSpan = new ImageSpan(gifDrawable);
if (endPosition <= content.length()) {
content.setSpan(
imageSpan, startPosition,
endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
} catch (Exception ignored) {
}
} else {
Drawable drawable = Drawable.createFromPath(file.getAbsolutePath());
try {
drawable.setBounds(0, 0, (int) convertDpToPixel(20, context), (int) convertDpToPixel(20, context));
drawable.setVisible(true, true);
imageSpan = new ImageSpan(drawable);
if (endPosition <= content.length()) {
content.setSpan(
imageSpan, startPosition,
endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
} catch (Exception ignored) {
}
}
}
}
}
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
}
}
//--- URLs ---- //--- URLs ----
Matcher matcherLink = Patterns.WEB_URL.matcher(content); Matcher matcherLink = Patterns.WEB_URL.matcher(content);
int offSetTruncate = 0; int offSetTruncate = 0;
@ -565,7 +479,7 @@ public class SpannableHelper {
/** /**
* Convert HTML content to text. Also, it handles click on link and transform emoji * Convert HTML content to text. Also, it handles click on link
* This needs to be run asynchronously * This needs to be run asynchronously
* *
* @param context {@link Context} * @param context {@link Context}
@ -603,77 +517,6 @@ public class SpannableHelper {
content.removeSpan(span); content.removeSpan(span);
} }
//--- EMOJI ----
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean disableGif = sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_GIF), false);
List<Emoji> emojiList = announcement.emojis;
//Will convert emoji if asked
if (emojiList != null && emojiList.size() > 0) {
for (Emoji emoji : emojiList) {
if (Helper.isValidContextForGlide(context)) {
FutureTarget<File> futureTarget = Glide.with(context)
.asFile()
.load(disableGif ? emoji.static_url : emoji.url)
.submit();
try {
File file = futureTarget.get();
final String targetedEmoji = ":" + emoji.shortcode + ":";
if (content.toString().contains(targetedEmoji)) {
//emojis can be used several times so we have to loop
for (int startPosition = -1; (startPosition = content.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) {
final int endPosition = startPosition + targetedEmoji.length();
if (endPosition <= content.toString().length() && endPosition >= startPosition) {
ImageSpan imageSpan;
if (APNGParser.isAPNG(file.getAbsolutePath())) {
APNGDrawable apngDrawable = APNGDrawable.fromFile(file.getAbsolutePath());
try {
apngDrawable.setBounds(0, 0, (int) convertDpToPixel(20, context), (int) convertDpToPixel(20, context));
apngDrawable.setVisible(true, true);
imageSpan = new ImageSpan(apngDrawable);
if (endPosition <= content.length()) {
content.setSpan(
imageSpan, startPosition,
endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
} catch (Exception ignored) {
}
} else if (GifParser.isGif(file.getAbsolutePath())) {
GifDrawable gifDrawable = GifDrawable.fromFile(file.getAbsolutePath());
try {
gifDrawable.setBounds(0, 0, (int) convertDpToPixel(20, context), (int) convertDpToPixel(20, context));
gifDrawable.setVisible(true, true);
imageSpan = new ImageSpan(gifDrawable);
if (endPosition <= content.length()) {
content.setSpan(
imageSpan, startPosition,
endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
} catch (Exception ignored) {
}
} else {
Drawable drawable = Drawable.createFromPath(file.getAbsolutePath());
try {
drawable.setBounds(0, 0, (int) convertDpToPixel(20, context), (int) convertDpToPixel(20, context));
drawable.setVisible(true, true);
imageSpan = new ImageSpan(drawable);
if (endPosition <= content.length()) {
content.setSpan(
imageSpan, startPosition,
endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
} catch (Exception ignored) {
}
}
}
}
}
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
}
}
//--- URLs ---- //--- URLs ----
Matcher matcherLink = Patterns.WEB_URL.matcher(content); Matcher matcherLink = Patterns.WEB_URL.matcher(content);
int offSetTruncate = 0; int offSetTruncate = 0;
@ -1035,7 +878,7 @@ public class SpannableHelper {
} }
/** /**
* Convert HTML content to text. Also, it handles click on link and transform emoji * Convert HTML content to text. Also, it handles click on link
* This needs to be run asynchronously * This needs to be run asynchronously
* *
* @param context {@link Context} * @param context {@link Context}
@ -1132,7 +975,7 @@ public class SpannableHelper {
/** /**
* Convert HTML content to text. Also, it handles click on link and transform emoji * Convert HTML content to text. Also, it handles click on link
* This needs to be run asynchronously * This needs to be run asynchronously
* *
* @param context {@link Context} * @param context {@link Context}
@ -1157,75 +1000,6 @@ public class SpannableHelper {
URLSpan[] urls = content.getSpans(0, (content.length() - 1), URLSpan.class); URLSpan[] urls = content.getSpans(0, (content.length() - 1), URLSpan.class);
for (URLSpan span : urls) for (URLSpan span : urls)
content.removeSpan(span); content.removeSpan(span);
//--- EMOJI ----
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean disableGif = sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_GIF), false);
//Will convert emoji if asked
if (account.emojis != null && account.emojis.size() > 0) {
for (Emoji emoji : account.emojis) {
if (Helper.isValidContextForGlide(context)) {
FutureTarget<File> futureTarget = Glide.with(context)
.asFile()
.load(disableGif ? emoji.static_url : emoji.url)
.submit();
try {
File file = futureTarget.get();
final String targetedEmoji = ":" + emoji.shortcode + ":";
if (content.toString().contains(targetedEmoji)) {
//emojis can be used several times so we have to loop
for (int startPosition = -1; (startPosition = content.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) {
final int endPosition = startPosition + targetedEmoji.length();
if (endPosition <= content.toString().length() && endPosition >= startPosition) {
ImageSpan imageSpan;
if (APNGParser.isAPNG(file.getAbsolutePath())) {
APNGDrawable apngDrawable = APNGDrawable.fromFile(file.getAbsolutePath());
try {
apngDrawable.setBounds(0, 0, (int) convertDpToPixel(20, context), (int) convertDpToPixel(20, context));
apngDrawable.setVisible(true, true);
imageSpan = new ImageSpan(apngDrawable);
content.setSpan(
imageSpan, startPosition,
endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
} catch (Exception ignored) {
}
} else if (GifParser.isGif(file.getAbsolutePath())) {
GifDrawable gifDrawable = GifDrawable.fromFile(file.getAbsolutePath());
try {
gifDrawable.setBounds(0, 0, (int) convertDpToPixel(20, context), (int) convertDpToPixel(20, context));
gifDrawable.setVisible(true, true);
imageSpan = new ImageSpan(gifDrawable);
content.setSpan(
imageSpan, startPosition,
endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
} catch (Exception ignored) {
}
} else {
try {
Drawable drawable = Drawable.createFromPath(file.getAbsolutePath());
drawable.setBounds(0, 0, (int) convertDpToPixel(20, context), (int) convertDpToPixel(20, context));
drawable.setVisible(true, true);
imageSpan = new ImageSpan(drawable);
if (endPosition <= content.length()) {
content.setSpan(
imageSpan, startPosition,
endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
} catch (Exception ignored) {
}
}
}
}
}
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
}
}
if (limitedToDisplayName) {
return content;
}
//--- URLs ---- //--- URLs ----
Matcher matcherALink = Patterns.WEB_URL.matcher(content); Matcher matcherALink = Patterns.WEB_URL.matcher(content);

@ -82,6 +82,7 @@ import com.github.stom79.mytransl.translate.Params;
import com.github.stom79.mytransl.translate.Translate; import com.github.stom79.mytransl.translate.Translate;
import com.varunest.sparkbutton.SparkButton; import com.varunest.sparkbutton.SparkButton;
import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -117,6 +118,7 @@ import app.fedilab.android.databinding.LayoutMediaBinding;
import app.fedilab.android.databinding.LayoutPollItemBinding; import app.fedilab.android.databinding.LayoutPollItemBinding;
import app.fedilab.android.exception.DBException; import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.CrossActionHelper; import app.fedilab.android.helper.CrossActionHelper;
import app.fedilab.android.helper.CustomEmoji;
import app.fedilab.android.helper.GlideFocus; import app.fedilab.android.helper.GlideFocus;
import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.LongClickLinkMovementMethod; import app.fedilab.android.helper.LongClickLinkMovementMethod;
@ -253,6 +255,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
statusToDeal.bookmarked = statusReturned.bookmarked; statusToDeal.bookmarked = statusReturned.bookmarked;
statusToDeal.reblogs_count = statusReturned.reblogs_count; statusToDeal.reblogs_count = statusReturned.reblogs_count;
statusToDeal.favourites_count = statusReturned.favourites_count; statusToDeal.favourites_count = statusReturned.favourites_count;
//Update status in cache if not a remote instance //Update status in cache if not a remote instance
if (!remote) { if (!remote) {
new Thread(() -> { new Thread(() -> {
@ -370,6 +373,10 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.actionButtonBookmark.setActiveImageTint(R.color.marked_icon); holder.binding.actionButtonBookmark.setActiveImageTint(R.color.marked_icon);
if (statusToDeal.emojis != null && statusToDeal.emojis.size() > 0) {
CustomEmoji.displayEmoji(statusToDeal.emojis, statusToDeal.span_content, new WeakReference<>(holder.binding.statusContent));
}
if (status.pinned) { if (status.pinned) {
holder.binding.statusPinned.setVisibility(View.VISIBLE); holder.binding.statusPinned.setVisibility(View.VISIBLE);
} else { } else {

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<shape />
Loading…
Cancel
Save