From 443b8373718d53fd8ad519e5fba5c9422568d360 Mon Sep 17 00:00:00 2001 From: tom79 Date: Thu, 25 Jul 2019 11:45:56 +0200 Subject: [PATCH 1/8] Gif --- .../android/client/Entities/Status.java | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Status.java b/app/src/main/java/app/fedilab/android/client/Entities/Status.java index 64553ad24..8d11b0a82 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/Status.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/Status.java @@ -44,6 +44,7 @@ import android.view.View; import com.bumptech.glide.Glide; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.load.resource.gif.GifDrawable; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.target.Target; @@ -1081,11 +1082,11 @@ public class Status implements Parcelable{ final int[] i = {0}; for (final Emojis emoji : emojis) { Glide.with(context) - .asBitmap() + .asGif() .load(emoji.getUrl()) - .listener(new RequestListener() { + .listener(new RequestListener() { @Override - public boolean onResourceReady(Bitmap resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + public boolean onResourceReady(GifDrawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { return false; } @@ -1098,32 +1099,34 @@ public class Status implements Parcelable{ return false; } }) - .into(new SimpleTarget() { + .into(new SimpleTarget() { @Override - public void onResourceReady(@NonNull Bitmap resource, Transition transition) { + public void onResourceReady(@NonNull GifDrawable resource, @Nullable Transition transition) { final String targetedEmoji = ":" + emoji.getShortcode() + ":"; if (contentSpan != null && contentSpan.toString().contains(targetedEmoji)) { //emojis can be used several times so we have to loop for (int startPosition = -1; (startPosition = contentSpan.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); - if( endPosition <= contentSpan.toString().length() && endPosition >= startPosition) + if( endPosition <= contentSpan.toString().length() && endPosition >= startPosition) { + resource.setBounds(0, 0, (int) Helper.convertDpToPixel(20, context), (int) Helper.convertDpToPixel(20, context)); + ImageSpan imageSpan = new ImageSpan(resource, ImageSpan.ALIGN_BASELINE); contentSpan.setSpan( - new ImageSpan(context, - Bitmap.createScaledBitmap(resource, (int) Helper.convertDpToPixel(20, context), - (int) Helper.convertDpToPixel(20, context), false)), startPosition, - endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + imageSpan, startPosition, + endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } } } if (displayNameSpan != null && displayNameSpan.toString().contains(targetedEmoji)) { //emojis can be used several times so we have to loop for (int startPosition = -1; (startPosition = displayNameSpan.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); - if(endPosition <= displayNameSpan.toString().length() && endPosition >= startPosition) - displayNameSpan.setSpan( - new ImageSpan(context, - Bitmap.createScaledBitmap(resource, (int) Helper.convertDpToPixel(20, context), - (int) Helper.convertDpToPixel(20, context), false)), startPosition, + if(endPosition <= displayNameSpan.toString().length() && endPosition >= startPosition) { + resource.setBounds(0, 0, (int) Helper.convertDpToPixel(20, context), (int) Helper.convertDpToPixel(20, context)); + ImageSpan imageSpan = new ImageSpan(resource, ImageSpan.ALIGN_BASELINE); + contentSpan.setSpan( + imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } } } status.setDisplayNameSpan(displayNameSpan); @@ -1131,12 +1134,13 @@ public class Status implements Parcelable{ //emojis can be used several times so we have to loop for (int startPosition = -1; (startPosition = contentSpanCW.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); - if( endPosition <= contentSpan.toString().length() && endPosition >= startPosition) - contentSpanCW.setSpan( - new ImageSpan(context, - Bitmap.createScaledBitmap(resource, (int) Helper.convertDpToPixel(20, context), - (int) Helper.convertDpToPixel(20, context), false)), startPosition, - endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + if( endPosition <= contentSpan.toString().length() && endPosition >= startPosition) { + resource.setBounds(0, 0, (int) Helper.convertDpToPixel(20, context), (int) Helper.convertDpToPixel(20, context)); + ImageSpan imageSpan = new ImageSpan(resource, ImageSpan.ALIGN_BASELINE); + contentSpan.setSpan( + imageSpan, startPosition, + endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } } } i[0]++; From a6abb262ef8c3d0d5c253893bce806fa1d70cc50 Mon Sep 17 00:00:00 2001 From: tom79 Date: Thu, 25 Jul 2019 15:50:29 +0200 Subject: [PATCH 2/8] Gif --- app/build.gradle | 8 ++- .../android/client/Entities/Status.java | 62 ++++++++++++++----- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 41fa12b33..37a9ead8b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,7 @@ android { compileSdkVersion 28 buildToolsVersion "28.0.3" defaultConfig { - minSdkVersion 18 + minSdkVersion 19 targetSdkVersion 28 versionCode 295 versionName "2.11.0" @@ -52,6 +52,7 @@ allprojects { maven { url "https://jitpack.io" } maven { url "https://maven.google.com"} maven { url "https://oss.sonatype.org/content/repositories/snapshots" } + maven { url "https://dl.bintray.com/osborn/Android" } } } ext.supportLibraryVersion = '28.0.0' @@ -120,4 +121,9 @@ dependencies { implementation "ch.acra:acra-notification:$acraVersion" implementation 'com.github.stom79:Android-WYSIWYG-Editor:3.2.1' implementation 'com.github.duanhong169:colorpicker:1.1.6' + implementation 'com.linecorp:apng:1.6.0' + + implementation 'com.github.pengfeizhou.android.animation:awebp:0.2.16' + implementation 'com.github.pengfeizhou.android.animation:apng:0.2.16' + implementation 'com.github.pengfeizhou.android.animation:glide-plugin:0.2.16' } diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Status.java b/app/src/main/java/app/fedilab/android/client/Entities/Status.java index 8d11b0a82..91478f004 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/Status.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/Status.java @@ -20,6 +20,9 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; +import android.graphics.drawable.AnimatedImageDrawable; +import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -38,18 +41,22 @@ import android.text.TextUtils; import android.text.style.ClickableSpan; import android.text.style.ImageSpan; import android.text.style.URLSpan; +import android.util.Log; import android.util.Patterns; import android.view.View; import com.bumptech.glide.Glide; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.GlideException; -import com.bumptech.glide.load.resource.gif.GifDrawable; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.transition.Transition; +import com.linecorp.apng.ApngDrawable; +import com.linecorp.apng.decoder.ApngException; +import java.io.File; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; @@ -1082,14 +1089,13 @@ public class Status implements Parcelable{ final int[] i = {0}; for (final Emojis emoji : emojis) { Glide.with(context) - .asGif() + .asFile() .load(emoji.getUrl()) - .listener(new RequestListener() { + .listener(new RequestListener() { @Override - public boolean onResourceReady(GifDrawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + public boolean onResourceReady(File resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { return false; } - @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { i[0]++; @@ -1099,20 +1105,45 @@ public class Status implements Parcelable{ return false; } }) - .into(new SimpleTarget() { + .into(new SimpleTarget() { @Override - public void onResourceReady(@NonNull GifDrawable resource, @Nullable Transition transition) { + public void onResourceReady(@NonNull File resource, @Nullable Transition transition) { + + ApngDrawable emojo = null; + Drawable emojoD = null; + + if( ApngDrawable.Companion.isApng(resource)){ + try { + emojo = ApngDrawable.Companion.decode(resource,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); + Log.v(Helper.TAG,"frame count " + emojo.getFrameCount()); + emojo.setLoopCount(ApngDrawable.LOOP_FOREVER); + emojo.start(); + + } catch (ApngException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + }else{ + emojoD = Drawable.createFromPath(resource.getAbsolutePath()); + assert emojoD != null; + emojoD.setBounds(0, 0, (int) Helper.convertDpToPixel(20, context), (int) Helper.convertDpToPixel(20, context)); + } + Log.v(Helper.TAG,"-> " + ApngDrawable.Companion.isApng(resource)); final String targetedEmoji = ":" + emoji.getShortcode() + ":"; if (contentSpan != null && contentSpan.toString().contains(targetedEmoji)) { //emojis can be used several times so we have to loop for (int startPosition = -1; (startPosition = contentSpan.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); if( endPosition <= contentSpan.toString().length() && endPosition >= startPosition) { - resource.setBounds(0, 0, (int) Helper.convertDpToPixel(20, context), (int) Helper.convertDpToPixel(20, context)); - ImageSpan imageSpan = new ImageSpan(resource, ImageSpan.ALIGN_BASELINE); + + ImageSpan imageSpan = new ImageSpan(emojo!=null?emojo:emojoD); contentSpan.setSpan( imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + if (emojo != null) { + emojo.start(); + } } } } @@ -1121,9 +1152,8 @@ public class Status implements Parcelable{ for (int startPosition = -1; (startPosition = displayNameSpan.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); if(endPosition <= displayNameSpan.toString().length() && endPosition >= startPosition) { - resource.setBounds(0, 0, (int) Helper.convertDpToPixel(20, context), (int) Helper.convertDpToPixel(20, context)); - ImageSpan imageSpan = new ImageSpan(resource, ImageSpan.ALIGN_BASELINE); - contentSpan.setSpan( + ImageSpan imageSpan = new ImageSpan(emojo!=null?emojo:emojoD, ImageSpan.ALIGN_BASELINE); + displayNameSpan.setSpan( imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); } @@ -1134,10 +1164,9 @@ public class Status implements Parcelable{ //emojis can be used several times so we have to loop for (int startPosition = -1; (startPosition = contentSpanCW.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); - if( endPosition <= contentSpan.toString().length() && endPosition >= startPosition) { - resource.setBounds(0, 0, (int) Helper.convertDpToPixel(20, context), (int) Helper.convertDpToPixel(20, context)); - ImageSpan imageSpan = new ImageSpan(resource, ImageSpan.ALIGN_BASELINE); - contentSpan.setSpan( + if( endPosition <= contentSpanCW.toString().length() && endPosition >= startPosition) { + ImageSpan imageSpan = new ImageSpan(emojo!=null?emojo:emojoD, ImageSpan.ALIGN_BASELINE); + contentSpanCW.setSpan( imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); } @@ -1151,6 +1180,7 @@ public class Status implements Parcelable{ listener.onRetrieveEmoji(status, false); } } + }); } From 1d3e53a00175fc639cd2323effa40a50f2fb8c0f Mon Sep 17 00:00:00 2001 From: tom79 Date: Thu, 25 Jul 2019 17:20:04 +0200 Subject: [PATCH 3/8] Gif --- app/build.gradle | 1 - .../android/client/Entities/Status.java | 53 ++++++------------- 2 files changed, 16 insertions(+), 38 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 37a9ead8b..37e6f83e6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -121,7 +121,6 @@ dependencies { implementation "ch.acra:acra-notification:$acraVersion" implementation 'com.github.stom79:Android-WYSIWYG-Editor:3.2.1' implementation 'com.github.duanhong169:colorpicker:1.1.6' - implementation 'com.linecorp:apng:1.6.0' implementation 'com.github.pengfeizhou.android.animation:awebp:0.2.16' implementation 'com.github.pengfeizhou.android.animation:apng:0.2.16' diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Status.java b/app/src/main/java/app/fedilab/android/client/Entities/Status.java index 91478f004..53143ada5 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/Status.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/Status.java @@ -52,11 +52,11 @@ import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.transition.Transition; -import com.linecorp.apng.ApngDrawable; -import com.linecorp.apng.decoder.ApngException; +import com.github.pengfeizhou.animation.apng.APNGDrawable; +import com.github.pengfeizhou.animation.loader.AssetStreamLoader; +import com.github.pengfeizhou.animation.loader.StreamLoader; import java.io.File; -import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; @@ -1089,13 +1089,14 @@ public class Status implements Parcelable{ final int[] i = {0}; for (final Emojis emoji : emojis) { Glide.with(context) - .asFile() + .asDrawable() .load(emoji.getUrl()) - .listener(new RequestListener() { + .listener(new RequestListener() { @Override - public boolean onResourceReady(File resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { return false; } + @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { i[0]++; @@ -1105,45 +1106,20 @@ public class Status implements Parcelable{ return false; } }) - .into(new SimpleTarget() { + .into(new SimpleTarget() { @Override - public void onResourceReady(@NonNull File resource, @Nullable Transition transition) { - - ApngDrawable emojo = null; - Drawable emojoD = null; - - if( ApngDrawable.Companion.isApng(resource)){ - try { - emojo = ApngDrawable.Companion.decode(resource,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); - Log.v(Helper.TAG,"frame count " + emojo.getFrameCount()); - emojo.setLoopCount(ApngDrawable.LOOP_FOREVER); - emojo.start(); - - } catch (ApngException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - }else{ - emojoD = Drawable.createFromPath(resource.getAbsolutePath()); - assert emojoD != null; - emojoD.setBounds(0, 0, (int) Helper.convertDpToPixel(20, context), (int) Helper.convertDpToPixel(20, context)); - } - Log.v(Helper.TAG,"-> " + ApngDrawable.Companion.isApng(resource)); + public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { final String targetedEmoji = ":" + emoji.getShortcode() + ":"; if (contentSpan != null && contentSpan.toString().contains(targetedEmoji)) { //emojis can be used several times so we have to loop for (int startPosition = -1; (startPosition = contentSpan.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); if( endPosition <= contentSpan.toString().length() && endPosition >= startPosition) { - - ImageSpan imageSpan = new ImageSpan(emojo!=null?emojo:emojoD); + resource.setBounds(0,0,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); + ImageSpan imageSpan = new ImageSpan(resource, ImageSpan.ALIGN_BASELINE); contentSpan.setSpan( imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - if (emojo != null) { - emojo.start(); - } } } } @@ -1152,7 +1128,8 @@ public class Status implements Parcelable{ for (int startPosition = -1; (startPosition = displayNameSpan.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); if(endPosition <= displayNameSpan.toString().length() && endPosition >= startPosition) { - ImageSpan imageSpan = new ImageSpan(emojo!=null?emojo:emojoD, ImageSpan.ALIGN_BASELINE); + resource.setBounds(0,0,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); + ImageSpan imageSpan = new ImageSpan(resource, ImageSpan.ALIGN_BASELINE); displayNameSpan.setSpan( imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); @@ -1165,7 +1142,8 @@ public class Status implements Parcelable{ for (int startPosition = -1; (startPosition = contentSpanCW.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); if( endPosition <= contentSpanCW.toString().length() && endPosition >= startPosition) { - ImageSpan imageSpan = new ImageSpan(emojo!=null?emojo:emojoD, ImageSpan.ALIGN_BASELINE); + resource.setBounds(0,0,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); + ImageSpan imageSpan = new ImageSpan(resource, ImageSpan.ALIGN_BASELINE); contentSpanCW.setSpan( imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); @@ -1181,6 +1159,7 @@ public class Status implements Parcelable{ } } + }); } From 92b3a6d3cb8e410cc688bea2c74d8e23eeaf3fdc Mon Sep 17 00:00:00 2001 From: tom79 Date: Thu, 25 Jul 2019 17:43:39 +0200 Subject: [PATCH 4/8] Gif --- .../main/java/app/fedilab/android/client/Entities/Status.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Status.java b/app/src/main/java/app/fedilab/android/client/Entities/Status.java index 53143ada5..89a7cc8a2 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/Status.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/Status.java @@ -1089,7 +1089,7 @@ public class Status implements Parcelable{ final int[] i = {0}; for (final Emojis emoji : emojis) { Glide.with(context) - .asDrawable() + // .asDrawable() .load(emoji.getUrl()) .listener(new RequestListener() { @Override From 3c319394987875212287ddd3dbac7639b0a05275 Mon Sep 17 00:00:00 2001 From: tom79 Date: Thu, 25 Jul 2019 19:10:05 +0200 Subject: [PATCH 5/8] animate emoji --- app/src/main/assets/test.png | Bin 0 -> 22658 bytes .../android/client/Entities/Status.java | 25 +++++----- .../android/helper/EmojiDrawableSpan.java | 45 ++++++++++++++++++ 3 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 app/src/main/assets/test.png create mode 100644 app/src/main/java/app/fedilab/android/helper/EmojiDrawableSpan.java diff --git a/app/src/main/assets/test.png b/app/src/main/assets/test.png new file mode 100644 index 0000000000000000000000000000000000000000..6498fe4c9a4c6fc8b5d6e54143a201a3fee4ff09 GIT binary patch literal 22658 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 Ita() { + .listener(new RequestListener() { @Override - public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + public boolean onResourceReady(File resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { return false; } @@ -1106,17 +1107,19 @@ public class Status implements Parcelable{ return false; } }) - .into(new SimpleTarget() { + .into(new SimpleTarget() { @Override - public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { + public void onResourceReady(@NonNull File resource, @Nullable Transition transition) { + AssetStreamLoader assetLoader = new AssetStreamLoader(context, "test.png"); + APNGDrawable apngDrawable = APNGDrawable.fromFile(resource.getPath()); final String targetedEmoji = ":" + emoji.getShortcode() + ":"; if (contentSpan != null && contentSpan.toString().contains(targetedEmoji)) { //emojis can be used several times so we have to loop for (int startPosition = -1; (startPosition = contentSpan.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); if( endPosition <= contentSpan.toString().length() && endPosition >= startPosition) { - resource.setBounds(0,0,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); - ImageSpan imageSpan = new ImageSpan(resource, ImageSpan.ALIGN_BASELINE); + //resource.setBounds(0,0,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); + EmojiDrawableSpan imageSpan = new EmojiDrawableSpan(context,apngDrawable); contentSpan.setSpan( imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); @@ -1128,8 +1131,7 @@ public class Status implements Parcelable{ for (int startPosition = -1; (startPosition = displayNameSpan.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); if(endPosition <= displayNameSpan.toString().length() && endPosition >= startPosition) { - resource.setBounds(0,0,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); - ImageSpan imageSpan = new ImageSpan(resource, ImageSpan.ALIGN_BASELINE); + EmojiDrawableSpan imageSpan = new EmojiDrawableSpan(context,apngDrawable); displayNameSpan.setSpan( imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); @@ -1142,8 +1144,7 @@ public class Status implements Parcelable{ for (int startPosition = -1; (startPosition = contentSpanCW.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); if( endPosition <= contentSpanCW.toString().length() && endPosition >= startPosition) { - resource.setBounds(0,0,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); - ImageSpan imageSpan = new ImageSpan(resource, ImageSpan.ALIGN_BASELINE); + EmojiDrawableSpan imageSpan = new EmojiDrawableSpan(context,apngDrawable); contentSpanCW.setSpan( imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); @@ -1160,6 +1161,8 @@ public class Status implements Parcelable{ } + + }); } diff --git a/app/src/main/java/app/fedilab/android/helper/EmojiDrawableSpan.java b/app/src/main/java/app/fedilab/android/helper/EmojiDrawableSpan.java new file mode 100644 index 000000000..0dbfa0955 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/helper/EmojiDrawableSpan.java @@ -0,0 +1,45 @@ +package app.fedilab.android.helper; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.text.style.DynamicDrawableSpan; +import android.util.Log; + +import androidx.annotation.DrawableRes; +import com.github.pengfeizhou.animation.apng.APNGDrawable; +import org.jetbrains.annotations.NotNull; + +public class EmojiDrawableSpan extends DynamicDrawableSpan { + + + private APNGDrawable mDrawable; + private int size; + public EmojiDrawableSpan(Context context, APNGDrawable apngDrawable) { + mDrawable = apngDrawable; + size = (int) Helper.convertDpToPixel(20, context); + } + + + + @Override + public Drawable getDrawable() { + return mDrawable; + } + + @Override + public void draw(@NotNull Canvas canvas, CharSequence text, + int start, int end, float x, + int top, int y, int bottom, @NotNull Paint paint) { + Drawable b = mDrawable; + canvas.save(); + int transY = bottom - b.getBounds().bottom; + canvas.translate(x, transY); + + mDrawable.setBounds(0, 0, size, size); + b.draw(canvas); + canvas.restore(); + mDrawable.start(); + } +} \ No newline at end of file From 181f052904c19ccdd39a1c3050628c714154244e Mon Sep 17 00:00:00 2001 From: tom79 Date: Fri, 26 Jul 2019 10:30:07 +0200 Subject: [PATCH 6/8] animate emoji --- .../android/client/Entities/Notification.java | 45 ++++++++++-------- .../android/client/Entities/Status.java | 46 +++++++++++-------- .../drawers/NotificationsListAdapter.java | 12 ++++- .../android/drawers/StatusListAdapter.java | 12 ++++- 4 files changed, 74 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Notification.java b/app/src/main/java/app/fedilab/android/client/Entities/Notification.java index 287bc1cd9..9c39aa431 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/Notification.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/Notification.java @@ -18,6 +18,7 @@ package app.fedilab.android.client.Entities; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; @@ -150,7 +151,7 @@ public class Notification implements Parcelable { status.getAccount().makeAccountNameEmoji(context, null, status.getAccount()); - // SpannableString displayNameSpan = status.getDisplayNameSpan(); + //SpannableString displayNameSpan = status.getDisplayNameSpan(); SpannableString contentSpan = status.getContentSpan(); SpannableString contentSpanCW = status.getContentSpanCW(); if( emojisAccounts != null) @@ -159,11 +160,11 @@ public class Notification implements Parcelable { final int[] i = {0}; for (final Emojis emoji : emojis) { Glide.with(context) - .asBitmap() + .asDrawable() .load(emoji.getUrl()) - .listener(new RequestListener() { + .listener(new RequestListener() { @Override - public boolean onResourceReady(Bitmap resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { return false; } @@ -176,32 +177,36 @@ public class Notification implements Parcelable { return false; } }) - .into(new SimpleTarget() { + .into(new SimpleTarget() { @Override - public void onResourceReady(@NonNull Bitmap resource, Transition transition) { + public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { final String targetedEmoji = ":" + emoji.getShortcode() + ":"; if (contentSpan != null && contentSpan.toString().contains(targetedEmoji)) { //emojis can be used several times so we have to loop for (int startPosition = -1; (startPosition = contentSpan.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); - if( endPosition <= contentSpan.toString().length() && endPosition >= startPosition) + if( endPosition <= contentSpan.toString().length() && endPosition >= startPosition) { + resource.setBounds(0,0,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); + resource.setVisible(true, true); + ImageSpan imageSpan = new ImageSpan(resource); contentSpan.setSpan( - new ImageSpan(context, - Bitmap.createScaledBitmap(resource, (int) Helper.convertDpToPixel(20, context), - (int) Helper.convertDpToPixel(20, context), false)), startPosition, + imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } } } - /*if (displayNameSpan != null && displayNameSpan.toString().contains(targetedEmoji)) { + /* if (displayNameSpan != null && displayNameSpan.toString().contains(targetedEmoji)) { //emojis can be used several times so we have to loop for (int startPosition = -1; (startPosition = displayNameSpan.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); - if(endPosition <= displayNameSpan.toString().length() && endPosition >= startPosition) + if(endPosition <= displayNameSpan.toString().length() && endPosition >= startPosition) { + resource.setBounds(0,0,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); + resource.setVisible(true, true); + ImageSpan imageSpan = new ImageSpan(resource); displayNameSpan.setSpan( - new ImageSpan(context, - Bitmap.createScaledBitmap(resource, (int) Helper.convertDpToPixel(20, context), - (int) Helper.convertDpToPixel(20, context), false)), startPosition, + imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } } } status.setDisplayNameSpan(displayNameSpan);*/ @@ -209,12 +214,14 @@ public class Notification implements Parcelable { //emojis can be used several times so we have to loop for (int startPosition = -1; (startPosition = contentSpanCW.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); - if( endPosition <= contentSpan.toString().length() && endPosition >= startPosition) + if( endPosition <= contentSpanCW.toString().length() && endPosition >= startPosition) { + resource.setBounds(0,0,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); + resource.setVisible(true, true); + ImageSpan imageSpan = new ImageSpan(resource); contentSpanCW.setSpan( - new ImageSpan(context, - Bitmap.createScaledBitmap(resource, (int) Helper.convertDpToPixel(20, context), - (int) Helper.convertDpToPixel(20, context), false)), startPosition, + imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } } } i[0]++; diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Status.java b/app/src/main/java/app/fedilab/android/client/Entities/Status.java index 929e0078d..2c83adc76 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/Status.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/Status.java @@ -20,8 +20,6 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; -import android.graphics.drawable.AnimatedImageDrawable; -import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; @@ -41,7 +39,6 @@ import android.text.TextUtils; import android.text.style.ClickableSpan; import android.text.style.ImageSpan; import android.text.style.URLSpan; -import android.util.Log; import android.util.Patterns; import android.view.View; @@ -52,11 +49,6 @@ import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.transition.Transition; -import com.github.pengfeizhou.animation.apng.APNGDrawable; -import com.github.pengfeizhou.animation.loader.AssetStreamLoader; -import com.github.pengfeizhou.animation.loader.StreamLoader; - -import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; @@ -77,7 +69,6 @@ import app.fedilab.android.activities.ShowAccountActivity; import app.fedilab.android.asynctasks.RetrieveFeedsAsyncTask; import app.fedilab.android.asynctasks.UpdateAccountInfoAsyncTask; import app.fedilab.android.helper.CrossActions; -import app.fedilab.android.helper.EmojiDrawableSpan; import app.fedilab.android.helper.Helper; import app.fedilab.android.interfaces.OnRetrieveEmojiInterface; import app.fedilab.android.interfaces.OnRetrieveImageInterface; @@ -157,6 +148,7 @@ public class Status implements Parcelable{ private int warningFetched = -1; private List imageURL; + private boolean statusAnimated = false; @Override public void writeToParcel(Parcel dest, int flags) { @@ -1090,11 +1082,11 @@ public class Status implements Parcelable{ final int[] i = {0}; for (final Emojis emoji : emojis) { Glide.with(context) - .asFile() + .asDrawable() .load(emoji.getUrl()) - .listener(new RequestListener() { + .listener(new RequestListener() { @Override - public boolean onResourceReady(File resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { return false; } @@ -1107,19 +1099,19 @@ public class Status implements Parcelable{ return false; } }) - .into(new SimpleTarget() { + .into(new SimpleTarget() { @Override - public void onResourceReady(@NonNull File resource, @Nullable Transition transition) { - AssetStreamLoader assetLoader = new AssetStreamLoader(context, "test.png"); - APNGDrawable apngDrawable = APNGDrawable.fromFile(resource.getPath()); + public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { + final String targetedEmoji = ":" + emoji.getShortcode() + ":"; if (contentSpan != null && contentSpan.toString().contains(targetedEmoji)) { //emojis can be used several times so we have to loop for (int startPosition = -1; (startPosition = contentSpan.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); if( endPosition <= contentSpan.toString().length() && endPosition >= startPosition) { - //resource.setBounds(0,0,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); - EmojiDrawableSpan imageSpan = new EmojiDrawableSpan(context,apngDrawable); + resource.setBounds(0,0,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); + resource.setVisible(true, true); + ImageSpan imageSpan = new ImageSpan(resource); contentSpan.setSpan( imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); @@ -1131,7 +1123,9 @@ public class Status implements Parcelable{ for (int startPosition = -1; (startPosition = displayNameSpan.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); if(endPosition <= displayNameSpan.toString().length() && endPosition >= startPosition) { - EmojiDrawableSpan imageSpan = new EmojiDrawableSpan(context,apngDrawable); + resource.setBounds(0,0,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); + resource.setVisible(true, true); + ImageSpan imageSpan = new ImageSpan(resource); displayNameSpan.setSpan( imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); @@ -1144,7 +1138,9 @@ public class Status implements Parcelable{ for (int startPosition = -1; (startPosition = contentSpanCW.toString().indexOf(targetedEmoji, startPosition + 1)) != -1; startPosition++) { final int endPosition = startPosition + targetedEmoji.length(); if( endPosition <= contentSpanCW.toString().length() && endPosition >= startPosition) { - EmojiDrawableSpan imageSpan = new EmojiDrawableSpan(context,apngDrawable); + resource.setBounds(0,0,(int) Helper.convertDpToPixel(20, context),(int) Helper.convertDpToPixel(20, context)); + resource.setVisible(true, true); + ImageSpan imageSpan = new ImageSpan(resource); contentSpanCW.setSpan( imageSpan, startPosition, endPosition, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); @@ -1163,6 +1159,8 @@ public class Status implements Parcelable{ + + }); } @@ -1573,4 +1571,12 @@ public class Status implements Parcelable{ public void setImageURL(List imageURL) { this.imageURL = imageURL; } + + public boolean isStatusAnimated() { + return statusAnimated; + } + + public void setStatusAnimated(boolean statusAnimated) { + this.statusAnimated = statusAnimated; + } } diff --git a/app/src/main/java/app/fedilab/android/drawers/NotificationsListAdapter.java b/app/src/main/java/app/fedilab/android/drawers/NotificationsListAdapter.java index 9bdaac7d6..5737ec5f4 100644 --- a/app/src/main/java/app/fedilab/android/drawers/NotificationsListAdapter.java +++ b/app/src/main/java/app/fedilab/android/drawers/NotificationsListAdapter.java @@ -62,6 +62,8 @@ import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import app.fedilab.android.activities.AccountReportActivity; import app.fedilab.android.client.API; @@ -372,7 +374,15 @@ public class NotificationsListAdapter extends RecyclerView.Adapter implements On holder.status_document_container.setVisibility(View.GONE); else holder.status_document_container.setVisibility(View.VISIBLE); - + if( !status.isStatusAnimated()) { + status.setStatusAnimated(true); + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + holder.notification_status_content.invalidate(); + } + }, 0, 100, TimeUnit.MILLISECONDS); + } if( !status.isClickable()) Status.transform(context, status); if( !status.isEmojiFound()) diff --git a/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java b/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java index 0b50f2842..dcde40d91 100644 --- a/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java +++ b/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java @@ -105,6 +105,8 @@ import java.util.Date; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -1171,7 +1173,15 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct holder.status_toot_date.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12 * textSizePercent / 100); holder.status_content_translated.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14 * textSizePercent / 100); } - + if( !status.isStatusAnimated()) { + status.setStatusAnimated(true); + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + holder.status_content.invalidate(); + } + }, 0, 100, TimeUnit.MILLISECONDS); + } holder.status_spoiler.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14 * textSizePercent / 100); switch (translator) { From 3da8d8ff0cb5771aa6b9b337535f06c6c154548e Mon Sep 17 00:00:00 2001 From: tom79 Date: Fri, 26 Jul 2019 10:32:21 +0200 Subject: [PATCH 7/8] animate emoji --- app/build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 37e6f83e6..0ca92ea4b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -122,7 +122,5 @@ dependencies { implementation 'com.github.stom79:Android-WYSIWYG-Editor:3.2.1' implementation 'com.github.duanhong169:colorpicker:1.1.6' - implementation 'com.github.pengfeizhou.android.animation:awebp:0.2.16' - implementation 'com.github.pengfeizhou.android.animation:apng:0.2.16' implementation 'com.github.pengfeizhou.android.animation:glide-plugin:0.2.16' } From c31067093613d1367e442b29920216d76e11572d Mon Sep 17 00:00:00 2001 From: tom79 Date: Fri, 26 Jul 2019 11:36:08 +0200 Subject: [PATCH 8/8] Some fixes --- .../android/client/Entities/Notification.java | 8 ++++++++ .../drawers/NotificationsListAdapter.java | 20 +++++++++++++++---- .../android/drawers/StatusListAdapter.java | 19 +++++++++++------- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Notification.java b/app/src/main/java/app/fedilab/android/client/Entities/Notification.java index 9c39aa431..ea84905e3 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/Notification.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/Notification.java @@ -52,6 +52,7 @@ public class Notification implements Parcelable { private Date created_at; private Account account; private Status status; + private boolean notificationAnimated = false; protected Notification(Parcel in) { id = in.readString(); @@ -238,4 +239,11 @@ public class Notification implements Parcelable { } } + public boolean isNotificationAnimated() { + return notificationAnimated; + } + + public void setNotificationAnimated(boolean notificationAnimated) { + this.notificationAnimated = notificationAnimated; + } } diff --git a/app/src/main/java/app/fedilab/android/drawers/NotificationsListAdapter.java b/app/src/main/java/app/fedilab/android/drawers/NotificationsListAdapter.java index 5737ec5f4..2240566a6 100644 --- a/app/src/main/java/app/fedilab/android/drawers/NotificationsListAdapter.java +++ b/app/src/main/java/app/fedilab/android/drawers/NotificationsListAdapter.java @@ -62,6 +62,8 @@ import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Timer; +import java.util.TimerTask; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -374,14 +376,24 @@ public class NotificationsListAdapter extends RecyclerView.Adapter implements On holder.status_document_container.setVisibility(View.GONE); else holder.status_document_container.setVisibility(View.VISIBLE); - if( !status.isStatusAnimated()) { - status.setStatusAnimated(true); - Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() { + if( !notification.isNotificationAnimated() && status.getEmojis().size() > 0) { + notification.setNotificationAnimated(true); + try{ + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + holder.notification_status_content.invalidate(); + } + }, 0, 130, TimeUnit.MILLISECONDS); + }catch (Exception ignored){} + + + /*new Timer().scheduleAtFixedRate(new TimerTask() { @Override public void run() { holder.notification_status_content.invalidate(); } - }, 0, 100, TimeUnit.MILLISECONDS); + }, 0, 500);*/ } if( !status.isClickable()) Status.transform(context, status); diff --git a/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java b/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java index dcde40d91..2147bd55f 100644 --- a/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java +++ b/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java @@ -105,6 +105,8 @@ import java.util.Date; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.List; +import java.util.Timer; +import java.util.TimerTask; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; @@ -1173,14 +1175,17 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct holder.status_toot_date.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12 * textSizePercent / 100); holder.status_content_translated.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14 * textSizePercent / 100); } - if( !status.isStatusAnimated()) { + if( !status.isStatusAnimated() && status.getEmojis().size() > 0 ) { status.setStatusAnimated(true); - Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - holder.status_content.invalidate(); - } - }, 0, 100, TimeUnit.MILLISECONDS); + try{ + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + holder.status_content.invalidate(); + } + }, 0, 130, TimeUnit.MILLISECONDS); + }catch (Exception ignored){} + } holder.status_spoiler.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14 * textSizePercent / 100);