Commit f5c22bda authored by Thomas's avatar Thomas
Browse files

- Fix custom emoji display in profile bio

parent 7da253ac
Loading
Loading
Loading
Loading
+33 −122
Original line number Diff line number Diff line
@@ -14,10 +14,6 @@ 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;
@@ -50,23 +46,23 @@ public class CustomEmoji extends ReplacementSpan {
    }

    public SpannableStringBuilder makeEmoji(SpannableStringBuilder content, List<Emoji> emojiList, boolean animate, Status.Callback callback) {
        if (emojiList != null && !emojiList.isEmpty()) {
        View view = viewWeakReference.get();
        if (view == null || emojiList == null || emojiList.isEmpty()) {
            return content;
        }
        int count = 1;
        for (Emoji emoji : emojiList) {
            Matcher matcher = Pattern.compile(":" + emoji.shortcode + ":", Pattern.LITERAL)
                    .matcher(content);
            while (matcher.find()) {
                    CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(viewWeakReference.get()));
                CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view));
                content.setSpan(customEmoji, matcher.start(), matcher.end(), 0);
                String emojiUrl = animate ? emoji.url : emoji.static_url;
                    Glide.with(viewWeakReference.get())
                            .asDrawable()
                            .load(emojiUrl)
                            .into(customEmoji.getTarget(animate, count == emojiList.size() && !callbackCalled ? callback : null, emojiUrl));
                boolean isLastEmoji = count == emojiList.size() && !callbackCalled;
                customEmoji.loadEmoji(view, emojiUrl, animate, isLastEmoji ? callback : null);
            }
            count++;
        }
        }
        return content;
    }

@@ -78,7 +74,7 @@ public class CustomEmoji extends ReplacementSpan {
                @Override
                public void invalidateDrawable(@NonNull Drawable drawable) {
                    if (view != null) {
                        view.invalidate();
                        view.postInvalidate();
                    }
                }

@@ -100,8 +96,17 @@ public class CustomEmoji extends ReplacementSpan {
            ((Animatable) resource).start();
        }
        imageDrawable = resource;
        if (view != null) {
        if (view instanceof android.widget.TextView) {
            android.widget.TextView tv = (android.widget.TextView) view;
            tv.post(() -> {
                CharSequence text = tv.getText();
                tv.setText(text, android.widget.TextView.BufferType.SPANNABLE);
            });
        } else if (view != null) {
            view.post(() -> {
                view.invalidate();
                view.requestLayout();
            });
        }
        if (callback != null && !callbackCalled) {
            callbackCalled = true;
@@ -113,15 +118,18 @@ public class CustomEmoji extends ReplacementSpan {
        loadFailed = true;
        View view = viewWeakReference.get();
        if (view != null) {
            view.post(() -> {
                view.invalidate();
                view.requestLayout();
            });
        }
    }

    public void loadEmoji(View view, String url, boolean animate) {
    public void loadEmoji(View view, String url, boolean animate, Status.Callback callback) {
        EmojiLoader.loadEmojiSpan(view, url, animate, new EmojiLoader.DrawableCallback() {
            @Override
            public void onLoaded(Drawable drawable, boolean shouldAnimate) {
                onEmojiLoaded(drawable, shouldAnimate, null);
                onEmojiLoaded(drawable, shouldAnimate, callback);
            }

            @Override
@@ -131,101 +139,6 @@ public class CustomEmoji extends ReplacementSpan {
        });
    }

    public Target<Drawable> getTarget(boolean animate, Status.Callback callback, String url) {
        return new CustomTarget<>() {

            @Override
            public void onStart() {
                if (imageDrawable instanceof Animatable) {
                    ((Animatable) imageDrawable).start();
                }
            }

            @Override
            public void onStop() {
                if (imageDrawable instanceof Animatable) {
                    ((Animatable) imageDrawable).stop();
                }
            }

            @Override
            public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
                View view = viewWeakReference.get();

                if (animate && resource instanceof Animatable) {
                    resource.setCallback(new Drawable.Callback() {
                        @Override
                        public void invalidateDrawable(@NonNull Drawable drawable) {
                            if (view != null) {
                                view.invalidate();
                            }
                        }

                        @Override
                        public void scheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable, long l) {
                            if (view != null) {
                                view.postDelayed(runnable, l);
                            }
                        }

                        @Override
                        public void unscheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable) {
                            if (view != null) {
                                view.removeCallbacks(runnable);
                            }
                        }
                    });
                    ((Animatable) resource).start();
                } else if (animate && view != null) {
                    EmojiLoader.loadEmojiSpan(view, url, true, new EmojiLoader.DrawableCallback() {
                        @Override
                        public void onLoaded(Drawable drawable, boolean shouldAnimate) {
                            if (drawable instanceof Animatable) {
                                onEmojiLoaded(drawable, true, null);
                            }
                        }

                        @Override
                        public void onFailed() {
                        }
                    });
                }
                imageDrawable = resource;
                if (view != null) {
                    view.invalidate();
                }
                if (callback != null && !callbackCalled) {
                    callbackCalled = true;
                    callback.emojiFetched();
                }
            }

            @Override
            public void onLoadFailed(@Nullable Drawable errorDrawable) {
                loadFailed = true;
                View view = viewWeakReference.get();
                if (view != null) {
                    view.invalidate();
                }
            }

            @Override
            public void onLoadCleared(@Nullable Drawable placeholder) {
                View view = viewWeakReference.get();
                if (imageDrawable != null) {
                    if (imageDrawable instanceof Animatable) {
                        ((Animatable) imageDrawable).stop();
                        imageDrawable.setCallback(null);
                    }
                }
                imageDrawable = null;
                if (view != null) {
                    view.invalidate();
                }
            }
        };
    }

    @Override
    public int getSize(@NonNull Paint paint, CharSequence charSequence, int i, int i1, @Nullable Paint.FontMetricsInt fontMetricsInt) {
        return (int) (paint.getTextSize() * scale);
@@ -242,8 +155,6 @@ public class CustomEmoji extends ReplacementSpan {
            canvas.translate(x, (float) transY);
            imageDrawable.draw(canvas);
            canvas.restore();
        } else if (loadFailed) {
            canvas.drawText(charSequence, start, end, x, y, paint);
        }
    }

+39 −21
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import androidx.annotation.Nullable;

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 com.github.penfeizhou.animation.apng.APNGDrawable;
import com.github.penfeizhou.animation.gif.GifDrawable;
@@ -36,6 +37,10 @@ import com.github.penfeizhou.animation.webp.WebPDrawable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import app.fedilab.android.R;

public class EmojiLoader {

@@ -59,14 +64,20 @@ public class EmojiLoader {
                });
    }

    @SuppressWarnings("unchecked")
    public static void loadEmojiSpan(View view, String url, boolean animate, DrawableCallback callback) {
        if (!Helper.isValidContextForGlide(view.getContext())) {
            return;
        }
        Glide.with(view)
                .asFile()
                .load(url)
                .into(new CustomTarget<File>() {

        // Store target in view tag to prevent garbage collection
        List<Target<?>> targets = (List<Target<?>>) view.getTag(R.id.custom_emoji_targets);
        if (targets == null) {
            targets = new ArrayList<>();
            view.setTag(R.id.custom_emoji_targets, targets);
        }

        CustomTarget<File> target = new CustomTarget<File>() {
            @Override
            public void onResourceReady(@NonNull File file, @Nullable Transition<? super File> transition) {
                Drawable drawable = decodeFile(view.getResources(), file);
@@ -85,7 +96,14 @@ public class EmojiLoader {
            @Override
            public void onLoadCleared(@Nullable Drawable placeholder) {
            }
                });
        };

        targets.add(target);

        Glide.with(view.getContext())
                .asFile()
                .load(url)
                .into(target);
    }

    private static Drawable decodeFile(Resources resources, File file) {
+22 −11
Original line number Diff line number Diff line
@@ -393,8 +393,21 @@ public class SpannableHelper {
            }
        }
        boolean animate = !sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false);
        // Process emojis inline (same as convertEmoji)
        if (view != null && emojiList != null && !emojiList.isEmpty()) {
            for (Emoji emoji : emojiList) {
                Matcher matcher = Pattern.compile(":" + emoji.shortcode + ":", Pattern.LITERAL)
                        .matcher(content);
                while (matcher.find()) {
                    CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view));
        content = customEmoji.makeEmoji(content, emojiList, animate, callback);
                    content.setSpan(customEmoji, matcher.start(), matcher.end(), 0);
                    if (Helper.isValidContextForGlide(context)) {
                        String emojiUrl = animate ? emoji.url : emoji.static_url;
                        customEmoji.loadEmoji(view, emojiUrl, animate, null);
                    }
                }
            }
        }

        if (!imagesToReplace.isEmpty()) {
            for (Map.Entry<String, String> entry : imagesToReplace.entrySet()) {
@@ -405,10 +418,7 @@ public class SpannableHelper {
                while (matcher.find()) {
                    CustomEmoji spanEmoji = new CustomEmoji(new WeakReference<>(view));
                    content.setSpan(spanEmoji, matcher.start(), matcher.end(), 0);
                    Glide.with(view)
                            .asDrawable()
                            .load(url)
                            .into(spanEmoji.getTarget(animate, null, url));
                    spanEmoji.loadEmoji(view, url, animate, null);
                }
            }

@@ -1024,6 +1034,10 @@ public class SpannableHelper {
        if (text == null) {
            return null;
        }
        View view = viewWeakReference.get();
        if (view == null) {
            return new SpannableString(text);
        }
        SpannableString initialContent = new SpannableString(text);

        SpannableStringBuilder content = new SpannableStringBuilder(initialContent);
@@ -1035,14 +1049,11 @@ public class SpannableHelper {
                Matcher matcher = Pattern.compile(":" + emoji.shortcode + ":", Pattern.LITERAL)
                        .matcher(content);
                while (matcher.find()) {
                    CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(viewWeakReference.get()));
                    CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view));
                    content.setSpan(customEmoji, matcher.start(), matcher.end(), 0);
                    if (Helper.isValidContextForGlide(activity)) {
                        String emojiUrl = animate ? emoji.url : emoji.static_url;
                        Glide.with(viewWeakReference.get())
                                .asDrawable()
                                .load(emojiUrl)
                                .into(customEmoji.getTarget(animate, null, emojiUrl));
                        customEmoji.loadEmoji(view, emojiUrl, animate, null);
                    }
                }
            }
+4 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="custom_emoji_targets" type="id" />
</resources>