From 455e5666a9d8267b00105ea51ecac1768bee1dc3 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 13 Jul 2022 09:27:10 +0200 Subject: [PATCH] Some tries --- app/build.gradle | 6 +- .../android/client/entities/api/Account.java | 1 + .../android/client/entities/api/Status.java | 1 + .../fedilab/android/helper/CustomEmoji.java | 185 ++++++++---------- .../android/ui/drawer/StatusAdapter.java | 51 +++-- .../viewmodel/mastodon/StatusesVM.java | 4 +- 6 files changed, 133 insertions(+), 115 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 255d6f7c9..b41919ee1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -85,11 +85,9 @@ dependencies { implementation 'com.github.GrenderG:Toasty:1.5.2' implementation 'org.framagit.tom79:SparkButton:1.0.13' implementation "com.github.bumptech.glide:glide:4.12.0" + implementation "com.github.bumptech.glide:okhttp3-integration:4.12.0" + implementation 'com.github.mergehez:ArgPlayer:v3.1' - implementation ("com.github.bumptech.glide:recyclerview-integration:4.12.0") { - // Excludes the support library because it's already included by Glide. - transitive = false - } implementation project(path: ':mytransl') implementation project(path: ':ratethisapp') diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Account.java b/app/src/main/java/app/fedilab/android/client/entities/api/Account.java index 1905ad42f..0f253477a 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Account.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Account.java @@ -78,6 +78,7 @@ public class Account implements Serializable { public transient Spannable span_display_name; public transient Spannable span_note; public transient RelationShip relationShip; + public boolean emojiFetched = false; public static class AccountParams implements Serializable { @SerializedName("discoverable") diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Status.java b/app/src/main/java/app/fedilab/android/client/entities/api/Status.java index 967364848..5224dca53 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Status.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Status.java @@ -105,6 +105,7 @@ public class Status implements Serializable, Cloneable { public transient boolean setCursorToEnd = false; public transient int cursorPosition = 0; public transient boolean submitted = false; + public transient boolean emojiFetched = false; @NonNull public Object clone() throws CloneNotSupportedException { diff --git a/app/src/main/java/app/fedilab/android/helper/CustomEmoji.java b/app/src/main/java/app/fedilab/android/helper/CustomEmoji.java index c45605d22..801625f45 100644 --- a/app/src/main/java/app/fedilab/android/helper/CustomEmoji.java +++ b/app/src/main/java/app/fedilab/android/helper/CustomEmoji.java @@ -1,13 +1,24 @@ package app.fedilab.android.helper; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ import android.content.Context; import android.content.SharedPreferences; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.text.Spannable; -import android.text.style.ReplacementSpan; +import android.text.style.ImageSpan; import android.view.View; import androidx.annotation.NonNull; @@ -16,10 +27,9 @@ 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 com.github.penfeizhou.animation.apng.APNGDrawable; -import java.lang.ref.WeakReference; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -27,107 +37,86 @@ import java.util.regex.Pattern; import app.fedilab.android.R; import app.fedilab.android.client.entities.api.Emoji; -public class CustomEmoji extends ReplacementSpan { +public class CustomEmoji { - private final float scale; - private Drawable imageDrawable; - private final WeakReference viewWeakReference; - - - CustomEmoji(WeakReference viewWeakReference) { - Context mContext = viewWeakReference.get().getContext(); - this.viewWeakReference = viewWeakReference; - SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(mContext); - scale = sharedpreferences.getFloat(mContext.getString(R.string.SET_FONT_SCALE), 1.0f); - } - - public static void displayEmoji(List emojis, Spannable spannableString, View view) { + public static void displayEmoji(Context context, List emojis, Spannable content, View view, String id, Callback listener) { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(view.getContext()); boolean animate = !sharedpreferences.getBoolean(view.getContext().getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false); + int count = 1; for (Emoji emoji : emojis) { - Matcher matcher = Pattern.compile(":" + emoji.shortcode + ":", Pattern.LITERAL) - .matcher(spannableString); - while (matcher.find()) { - CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view)); - spannableString.setSpan(customEmoji, matcher.start(), matcher.end(), 0); - Glide.with(view.getContext()) - .asDrawable() - .load(animate ? emoji.url : emoji.static_url) - .into(customEmoji.getTarget(animate)); - } + int finalCount = count; + Glide.with(view.getContext()) + .asDrawable() + .load(animate ? emoji.url : emoji.static_url) + .into( + new CustomTarget() { + @Override + public void onLoadFailed(@Nullable Drawable errorDrawable) { + super.onLoadFailed(errorDrawable); + if (finalCount == emojis.size()) { + listener.allEmojisfound(id); + } + } + @Override + public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { + Matcher matcher = Pattern.compile(":" + emoji.shortcode + ":", Pattern.LITERAL) + .matcher(content); + while (matcher.find()) { + ImageSpan imageSpan; + resource.setBounds(0, 0, (int) Helper.convertDpToPixel(20, context), (int) Helper.convertDpToPixel(20, context)); + resource.setVisible(true, true); + imageSpan = new ImageSpan(resource); + content.setSpan( + imageSpan, matcher.start(), + matcher.end(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } + if (animate && resource instanceof APNGDrawable) { + Drawable.Callback callback = resource.getCallback(); + resource.setCallback(new Drawable.Callback() { + @Override + public void invalidateDrawable(@NonNull Drawable drawable) { + if (callback != null) { + callback.invalidateDrawable(drawable); + } + view.invalidate(); + } + + @Override + public void scheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable, long l) { + if (callback != null) { + callback.scheduleDrawable(drawable, runnable, l); + } + } + + @Override + public void unscheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable) { + if (callback != null) { + callback.unscheduleDrawable(drawable, runnable); + } + } + }); + ((APNGDrawable) resource).start(); + + } + if (finalCount == emojis.size()) { + listener.allEmojisfound(id); + } + } + + @Override + public void onLoadCleared(@Nullable Drawable placeholder) { + + } + } + ); + count++; } } - @Override - public int getSize(@NonNull Paint paint, CharSequence charSequence, int i, int i1, @Nullable Paint.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); + public interface Callback { + void allEmojisfound(String id); } - @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; - transY -= paint.getFontMetrics().descent / 2; - canvas.translate(x, (float) transY); - drawable.draw(canvas); - canvas.restore(); - } - } - - public Target getTarget(boolean animate) { - return new CustomTarget() { - @Override - public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { - View view = viewWeakReference.get(); - if (animate && resource instanceof Animatable) { - Drawable.Callback callback = resource.getCallback(); - resource.setCallback(new Drawable.Callback() { - @Override - public void invalidateDrawable(@NonNull Drawable drawable) { - if (callback != null) { - callback.invalidateDrawable(drawable); - } - view.invalidate(); - } - - @Override - public void scheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable, long l) { - if (callback != null) { - callback.scheduleDrawable(drawable, runnable, l); - } - } - - @Override - public void unscheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable) { - if (callback != null) { - callback.unscheduleDrawable(drawable, runnable); - } - } - }); - ((Animatable) resource).start(); - } - imageDrawable = resource; - view.invalidate(); - } - - @Override - public void onLoadCleared(@Nullable Drawable placeholder) { - - } - }; - } } diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java index 84ade8340..bbd119d77 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java @@ -878,19 +878,18 @@ public class StatusAdapter extends RecyclerView.Adapter break; } //--- MAIN CONTENT --- - if (statusToDeal.emojis != null && statusToDeal.emojis.size() > 0) { - CustomEmoji.displayEmoji(statusToDeal.emojis, statusToDeal.span_content, holder.binding.statusContent); - holder.binding.statusContent.postDelayed(new Runnable() { - @Override - public void run() { - holder.binding.statusContent.setText(statusToDeal.span_content, TextView.BufferType.SPANNABLE); + + CustomEmoji.displayEmoji(context, statusToDeal.emojis, statusToDeal.span_content, holder.binding.statusContent, status.id, id -> { + if (!status.emojiFetched) { + status.emojiFetched = true; + if (timelineType == Timeline.TimeLineEnum.UNKNOWN) { + return; } - }, 100); - } else { - holder.binding.statusContent.setText(statusToDeal.span_content, TextView.BufferType.SPANNABLE); - } - - + holder.binding.statusContent.post(() -> adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, id))); + } + }); + // CustomEmoji.displayEmoji(statusToDeal.emojis, statusToDeal.span_content, holder.binding.statusContent, null, null); + holder.binding.statusContent.setText(statusToDeal.span_content, TextView.BufferType.SPANNABLE); if (truncate_toots_size > 0) { holder.binding.statusContent.setMaxLines(truncate_toots_size); holder.binding.statusContent.setEllipsize(TextUtils.TruncateAt.END); @@ -1733,6 +1732,34 @@ public class StatusAdapter extends RecyclerView.Adapter return position; } + /** + * Will manage the current position of the element in the adapter. Action is async, and position might have changed + * + * @param notificationList List - Not null when calling from notification adapter + * @param statusList ist statusList - Not null when calling from status adapter + * @param id String - Current status + * @return int - position in real time + */ + public static int getPositionAsync(List notificationList, List statusList, String id) { + int position = 0; + if (statusList != null) { + for (Status _status : statusList) { + if (id != null && ((_status.id != null && _status.id.compareTo(id) == 0) || (_status.reblog != null && _status.reblog.id != null && _status.reblog.id.compareTo(id) == 0))) { + break; + } + position++; + } + } else if (notificationList != null) { + for (Notification notification : notificationList) { + if (notification.status != null && notification.status.id.compareTo(id) == 0) { + break; + } + position++; + } + } + return position; + } + @Override public int getItemViewType(int position) { if (timelineType == Timeline.TimeLineEnum.ART) { diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/StatusesVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/StatusesVM.java index 39118e99c..c97c2daf0 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/StatusesVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/StatusesVM.java @@ -461,7 +461,9 @@ public class StatusesVM extends AndroidViewModel { Handler mainHandler = new Handler(Looper.getMainLooper()); Accounts accountsPagination = new Accounts(); accountsPagination.accounts = accounts; - accountsPagination.pagination = MastodonHelper.getPagination(headers); + if (headers != null) { + accountsPagination.pagination = MastodonHelper.getPagination(headers); + } Runnable myRunnable = () -> accountsMutableLiveData.setValue(accountsPagination); mainHandler.post(myRunnable); }).start();