diff --git a/AXrLottie/AXrLottie-release.aar b/AXrLottie/AXrLottie-release.aar index 0e46f7f..bdc71fc 100644 Binary files a/AXrLottie/AXrLottie-release.aar and b/AXrLottie/AXrLottie-release.aar differ diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottie.java b/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottie.java index fa1ed6a..6f09f0c 100644 --- a/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottie.java +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottie.java @@ -22,36 +22,70 @@ import android.view.Display; import android.view.WindowManager; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.aghajari.rlottie.network.AXrLottieNetworkFetcher; +import com.aghajari.rlottie.network.AXrLottieTaskCache; +import com.aghajari.rlottie.network.AXrLottieTaskFactory; +import com.aghajari.rlottie.network.AXrNetworkFetcher; +import com.aghajari.rlottie.network.AXrSimpleNetworkFetcher; +import com.aghajari.rlottie.network.AXrFileExtension; +import com.aghajari.rlottie.network.JsonFileExtension; +import com.aghajari.rlottie.network.ZipFileExtension; import java.io.File; import java.io.InputStream; - +import java.util.HashMap; +import java.util.Map; /** - * * @author Amir Hossein Aghajari * @version 1.0.4 - * */ public class AXrLottie { static { System.loadLibrary("jlottie"); } - private AXrLottie(){} + private AXrLottie() { + } static Context context; + static float screenRefreshRate = 60; - private static boolean urlCacheEnabled = true; - private static int timeOut = 10000; - public static void init(Context context){ + @Nullable + private static AXrNetworkFetcher networkFetcher; + + @Nullable + private static AXrLottieCacheManager cacheManager; + + private static boolean networkCacheEnabled = true; + + private static final Map fileExtensions = new HashMap<>(); + + public static void init(Context context) { AXrLottie.context = context.getApplicationContext(); loadScreenRefreshRate(context); + + addFileExtension(ZipFileExtension.ZIP); + addFileExtension(JsonFileExtension.JSON); + } + + public static Map getSupportedFileExtensions() { + return fileExtensions; } - public static void configureModelCacheSize(int cacheSize){ + public static void addFileExtension(AXrFileExtension fileExtension) { + fileExtensions.put(fileExtension.extension.toLowerCase(), fileExtension); + } + + public static void removeFileExtension(AXrFileExtension fileExtension) { + fileExtensions.remove(fileExtension.extension.toLowerCase()); + } + + public static void configureModelCacheSize(int cacheSize) { AXrLottieNative.configureModelCacheSize(cacheSize); } @@ -73,146 +107,205 @@ public static float getScreenRefreshRate() { return screenRefreshRate; } - public static void setNetworkCacheEnabled(boolean urlCacheEnabled) { - AXrLottie.urlCacheEnabled = urlCacheEnabled; + /** + * Lottie has a default network fetching stack built on {@link java.net.HttpURLConnection}. + * However, if you would like to hook into your own network stack + * for performance, caching, or analytics, you may replace the internal stack with your own. + */ + public static void setNetworkFetcher(@Nullable AXrLottieNetworkFetcher networkFetcher) { + AXrLottie.networkFetcher = new AXrNetworkFetcher(networkFetcher!=null ? networkFetcher : new AXrSimpleNetworkFetcher()); } - public static boolean isNetworkCacheEnabled() { - return urlCacheEnabled; + /** + * Provide your own network cache directory. + * By default, animations will be saved in your application's cacheDir/lottie_network. + */ + public static void setNetworkCacheDir(@NonNull File file) { + if (!file.isDirectory()) + throw new IllegalArgumentException("cache file must be a directory"); + getLottieCacheManager().networkCacheDir = file; } - public static void setNetworkTimeOut(int timeOut) { - AXrLottie.timeOut = timeOut; + /** + * Provide your own network cache directory. + * By default, animations will be saved in your application's cacheDir/lottie. + */ + public static void setLocalCacheDir(@NonNull File file) { + if (!file.isDirectory()) + throw new IllegalArgumentException("cache file must be a directory"); + getLottieCacheManager().localCacheDir = file; } - public static int getNetworkTimeOut() { - return timeOut; + public static void setNetworkCacheEnabled(boolean cacheEnabled) { + networkCacheEnabled = cacheEnabled; } - public static AXrLottieDrawable createFromPath(String path, int width, int height, boolean precache, boolean limitFps){ - return AXrLottieDrawable.fromPath(path) - .setSize(width,height) - .setCacheEnabled(precache) - .setFpsLimit(limitFps) - .build(); + public static boolean isNetworkCacheEnabled() { + return networkCacheEnabled; } - public static AXrLottieDrawable createFromFile(File file,int width,int height,boolean precache, boolean limitFps){ - return AXrLottieDrawable.fromFile(file) - .setSize(width,height) - .setCacheEnabled(precache) - .setFpsLimit(limitFps) - .build(); + /** + * Set the maximum number of compositions to keep cached in memory. + * This must be {@literal >} 0. + */ + public static void setMaxNetworkCacheSize(int cacheSize) { + AXrLottieTaskCache.getInstance().resize(cacheSize); } - public static AXrLottieDrawable createFromURL(String url,int width,int height,boolean precache, boolean limitFps){ - return AXrLottieDrawable.fromURL(url) - .setSize(width,height) - .setCacheEnabled(precache) - .setFpsLimit(limitFps) - .build(); + public static void clearCache() { + AXrLottieTaskFactory.clearCache(); + getLottieCacheManager().clear(); } - public static AXrLottieDrawable createFromURL(String url, AXrLottieNetworkFetcher fetcher, int width, int height, boolean precache, boolean limitFps){ - return AXrLottieDrawable.fromURL(url,fetcher) - .setSize(width,height) - .setCacheEnabled(precache) - .setFpsLimit(limitFps) - .build(); + @NonNull + public static AXrNetworkFetcher getNetworkFetcher() { + AXrNetworkFetcher local = networkFetcher; + if (local == null) { + synchronized (AXrNetworkFetcher.class) { + local = networkFetcher; + if (local == null) { + networkFetcher = local = new AXrNetworkFetcher(new AXrSimpleNetworkFetcher()); + } + } + } + return local; } - public static AXrLottieDrawable createFromJson(String json, String name, int width, int height) { - return AXrLottieDrawable.fromJson(json,name) - .setSize(width,height) - .setCacheEnabled(false) - .setFpsLimit(false) - .build(); + @NonNull + public static AXrLottieCacheManager getLottieCacheManager() { + AXrLottieCacheManager local = cacheManager; + if (local == null) { + synchronized (AXrLottieCacheManager.class) { + local = cacheManager; + if (local == null) { + cacheManager = local = new AXrLottieCacheManager( + new File(context.getCacheDir(), "lottie_network"), + new File(context.getCacheDir(), "lottie")); + } + } + } + return local; } - public static AXrLottieDrawable createFromJson(String json, String name, int width, int height,boolean cache,boolean limitFps) { - return AXrLottieDrawable.fromJson(json,name) - .setSize(width,height) - .setCacheEnabled(cache) - .setFpsLimit(limitFps) - .build(); - } - public static AXrLottieDrawable createFromAssets(Context context,String fileName, String name, int width, int height) { - return AXrLottieDrawable.fromAssets(context,fileName) - .setCacheName(name) - .setSize(width,height) - .setCacheEnabled(false) - .setFpsLimit(false) - .build(); - } + public static class Loader { + public static AXrLottieDrawable createFromPath(String path, int width, int height, boolean precache, boolean limitFps) { + return AXrLottieDrawable.fromPath(path) + .setSize(width, height) + .setCacheEnabled(precache) + .setFpsLimit(limitFps) + .build(); + } - public static AXrLottieDrawable createFromAssets(Context context,String fileName, String name, int width, int height,boolean cache,boolean limitFps) { - return AXrLottieDrawable.fromAssets(context,fileName) - .setCacheName(name) - .setSize(width,height) - .setCacheEnabled(cache) - .setFpsLimit(limitFps) - .build(); - } + public static AXrLottieDrawable createFromFile(File file, int width, int height, boolean precache, boolean limitFps) { + return AXrLottieDrawable.fromFile(file) + .setSize(width, height) + .setCacheEnabled(precache) + .setFpsLimit(limitFps) + .build(); + } - public static AXrLottieDrawable createFromAssets(Context context,String fileName, String name, int width, int height,boolean startDecode) { - return AXrLottieDrawable.fromAssets(context,fileName) - .setCacheName(name) - .setSize(width,height) - .setCacheEnabled(false) - .setFpsLimit(false) - .setAllowDecodeSingleFrame(startDecode) - .build(); - } + public static AXrLottieDrawable createFromURL(String url, int width, int height, boolean precache, boolean limitFps) { + return AXrLottieDrawable.fromURL(url) + .setSize(width, height) + .setCacheEnabled(precache) + .setFpsLimit(limitFps) + .build(); + } - public static AXrLottieDrawable createFromRes(Context context,int res, String name, int width, int height) { - return AXrLottieDrawable.fromRes(context,res,name) - .setSize(width,height) - .setCacheEnabled(false) - .setFpsLimit(false) - .build(); - } + public static AXrLottieDrawable createFromJson(String json, String name, int width, int height) { + return AXrLottieDrawable.fromJson(json, name) + .setSize(width, height) + .setCacheEnabled(false) + .setFpsLimit(false) + .build(); + } - public static AXrLottieDrawable createFromRes(Context context,int res, String name, int width, int height,boolean startDecode) { - return AXrLottieDrawable.fromRes(context,res,name) - .setSize(width,height) - .setCacheEnabled(false) - .setFpsLimit(false) - .setAllowDecodeSingleFrame(startDecode) - .build(); - } - - public static AXrLottieDrawable createFromRes(Context context,int res, String name, int width, int height,boolean cache,boolean limitFps) { - return AXrLottieDrawable.fromRes(context,res,name) - .setSize(width,height) - .setCacheEnabled(cache) - .setFpsLimit(limitFps) - .build(); - } - - public static AXrLottieDrawable createFromInputStream(InputStream inputStream, String name, int width, int height) { - return AXrLottieDrawable.fromInputStream(inputStream,name) - .setSize(width,height) - .setCacheEnabled(false) - .setFpsLimit(false) - .build(); - } - - public static AXrLottieDrawable createFromInputStream(InputStream inputStream, String name, int width, int height,boolean startDecode) { - return AXrLottieDrawable.fromInputStream(inputStream,name) - .setSize(width,height) - .setCacheEnabled(false) - .setFpsLimit(false) - .setAllowDecodeSingleFrame(startDecode) - .build(); - } - - public static AXrLottieDrawable createFromInputStream(InputStream inputStream, String name, int width, int height,boolean cache,boolean limitFps) { - return AXrLottieDrawable.fromInputStream(inputStream,name) - .setSize(width,height) - .setCacheEnabled(cache) - .setFpsLimit(limitFps) - .build(); + public static AXrLottieDrawable createFromJson(String json, String name, int width, int height, boolean cache, boolean limitFps) { + return AXrLottieDrawable.fromJson(json, name) + .setSize(width, height) + .setCacheEnabled(cache) + .setFpsLimit(limitFps) + .build(); + } + + public static AXrLottieDrawable createFromAssets(Context context, String fileName, String name, int width, int height) { + return AXrLottieDrawable.fromAssets(context, fileName) + .setCacheName(name) + .setSize(width, height) + .setCacheEnabled(false) + .setFpsLimit(false) + .build(); + } + + public static AXrLottieDrawable createFromAssets(Context context, String fileName, String name, int width, int height, boolean cache, boolean limitFps) { + return AXrLottieDrawable.fromAssets(context, fileName) + .setCacheName(name) + .setSize(width, height) + .setCacheEnabled(cache) + .setFpsLimit(limitFps) + .build(); + } + + public static AXrLottieDrawable createFromAssets(Context context, String fileName, String name, int width, int height, boolean startDecode) { + return AXrLottieDrawable.fromAssets(context, fileName) + .setCacheName(name) + .setSize(width, height) + .setCacheEnabled(false) + .setFpsLimit(false) + .setAllowDecodeSingleFrame(startDecode) + .build(); + } + + public static AXrLottieDrawable createFromRes(Context context, int res, String name, int width, int height) { + return AXrLottieDrawable.fromRes(context, res, name) + .setSize(width, height) + .setCacheEnabled(false) + .setFpsLimit(false) + .build(); + } + + public static AXrLottieDrawable createFromRes(Context context, int res, String name, int width, int height, boolean startDecode) { + return AXrLottieDrawable.fromRes(context, res, name) + .setSize(width, height) + .setCacheEnabled(false) + .setFpsLimit(false) + .setAllowDecodeSingleFrame(startDecode) + .build(); + } + + public static AXrLottieDrawable createFromRes(Context context, int res, String name, int width, int height, boolean cache, boolean limitFps) { + return AXrLottieDrawable.fromRes(context, res, name) + .setSize(width, height) + .setCacheEnabled(cache) + .setFpsLimit(limitFps) + .build(); + } + + public static AXrLottieDrawable createFromInputStream(InputStream inputStream, String name, int width, int height) { + return AXrLottieDrawable.fromInputStream(inputStream, name) + .setSize(width, height) + .setCacheEnabled(false) + .setFpsLimit(false) + .build(); + } + + public static AXrLottieDrawable createFromInputStream(InputStream inputStream, String name, int width, int height, boolean startDecode) { + return AXrLottieDrawable.fromInputStream(inputStream, name) + .setSize(width, height) + .setCacheEnabled(false) + .setFpsLimit(false) + .setAllowDecodeSingleFrame(startDecode) + .build(); + } + + public static AXrLottieDrawable createFromInputStream(InputStream inputStream, String name, int width, int height, boolean cache, boolean limitFps) { + return AXrLottieDrawable.fromInputStream(inputStream, name) + .setSize(width, height) + .setCacheEnabled(cache) + .setFpsLimit(limitFps) + .build(); + } } } diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottie2Gif.java b/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottie2Gif.java index eb420a7..ae99f7e 100644 --- a/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottie2Gif.java +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottie2Gif.java @@ -31,7 +31,9 @@ public class AXrLottie2Gif { public interface Lottie2GifListener { void onStarted(); - void onProgress(int frame,int totalFrame); + + void onProgress(int frame, int totalFrame); + void onFinished(); } @@ -39,20 +41,20 @@ public interface Lottie2GifListener { private boolean successful; private boolean destroyed = false; private Builder builder; - private int mFrame,mTotalFrame; + private int mFrame, mTotalFrame; private Lottie2GifListener listener = new Lottie2GifListener() { @Override public void onStarted() { running = true; - if (builder.listener!=null) builder.listener.onStarted(); + if (builder.listener != null) builder.listener.onStarted(); } @Override public void onProgress(int frame, int totalFrame) { mFrame = frame; mTotalFrame = totalFrame; - if (builder.listener!=null) builder.listener.onProgress(frame,totalFrame); + if (builder.listener != null) builder.listener.onProgress(frame, totalFrame); } @Override @@ -60,20 +62,20 @@ public void onFinished() { running = false; if (builder.destroyable) destroy(); - if (builder.listener!=null) builder.listener.onFinished(); + if (builder.listener != null) builder.listener.onFinished(); } }; - AXrLottie2Gif(Builder builder){ - if (runnableQueue==null) runnableQueue = new DispatchQueuePool(2); + AXrLottie2Gif(Builder builder) { + if (runnableQueue == null) runnableQueue = new DispatchQueuePool(2); this.builder = builder; build(); } - public boolean buildAgain(){ + public boolean buildAgain() { if (isRunning()) return false; - if (destroyed){ - throw new RuntimeException("can't build a destroyable lottie again!"); + if (destroyed) { + throw new RuntimeException("can't build a destroyable lottie again!"); } build(); return successful; @@ -84,7 +86,7 @@ public boolean buildAgain(){ Runnable converter = new Runnable() { @Override public void run() { - if (bitmap==null){ + if (bitmap == null) { try { bitmap = Bitmap.createBitmap(builder.w, builder.h, Bitmap.Config.ARGB_8888); } catch (Throwable e) { @@ -92,11 +94,11 @@ public void run() { } } - if (bitmap!=null) { - successful = AXrLottieNative.lottie2gif(builder.lottie, bitmap ,builder.w, builder.h, bitmap.getRowBytes(), + if (bitmap != null) { + successful = AXrLottieNative.lottie2gif(builder.lottie, bitmap, builder.w, builder.h, bitmap.getRowBytes(), builder.bgColor, builder.path.getAbsolutePath(), - builder.delay,builder.bitDepth,builder.dither,builder.frameStart,builder.frameEnd, listener); - }else { + builder.delay, builder.bitDepth, builder.dither, builder.frameStart, builder.frameEnd, listener); + } else { successful = false; } @@ -104,15 +106,15 @@ public void run() { } }; - private void build(){ + private void build() { if (builder.async) { runnableQueue.execute(converter); - }else{ + } else { converter.run(); } } - private void destroy(){ + private void destroy() { destroyed = true; AXrLottieNative.destroy(builder.lottie); } @@ -125,7 +127,7 @@ public boolean isSuccessful() { return successful; } - public Builder getBuilder(){ + public Builder getBuilder() { return builder; } @@ -137,15 +139,15 @@ public int getTotalFrame() { return mTotalFrame; } - public File getGifPath(){ + public File getGifPath() { return builder.path; } - public static Builder create(@NonNull AXrLottieDrawable lottie){ + public static Builder create(@NonNull AXrLottieDrawable lottie) { return new Builder(lottie); } - public static Builder create(long lottie){ + public static Builder create(long lottie) { return new Builder(lottie); } @@ -154,8 +156,8 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || !(o instanceof AXrLottie2Gif)) return false; AXrLottie2Gif c = (AXrLottie2Gif) o; - if (c.getBuilder()==null) return false; - return (c.getGifPath().equals(getGifPath()) && c.getBuilder().lottie==builder.lottie); + if (c.getBuilder() == null) return false; + return (c.getGifPath().equals(getGifPath()) && c.getBuilder().lottie == builder.lottie); } @Override @@ -163,9 +165,9 @@ public String toString() { return getGifPath().getAbsolutePath(); } - public static class Builder{ + public static class Builder { long lottie; - int w,h; + int w, h; int bgColor = Color.WHITE; int delay = 2; Lottie2GifListener listener = null; @@ -178,106 +180,121 @@ public static class Builder{ int frameEnd = -1; boolean cancelable = false; - public Builder(@NonNull AXrLottieDrawable animation){ + public Builder(@NonNull AXrLottieDrawable animation) { this.lottie = animation.getNativePtr(); float density = 1; - if (AXrLottie.context!=null) density = AXrLottie.context.getResources().getDisplayMetrics().density; - setSize((int) (animation.getMinimumWidth()/density), (int) (animation.getMinimumHeight()/density)); + if (AXrLottie.context != null) + density = AXrLottie.context.getResources().getDisplayMetrics().density; + setSize((int) (animation.getMinimumWidth() / density), (int) (animation.getMinimumHeight() / density)); } - public Builder(long ptr){ + public Builder(long ptr) { this.lottie = ptr; - setSize(200,200); + setSize(200, 200); } - public Builder setLottieAnimation(@NonNull AXrLottieDrawable animation){ + public Builder setLottieAnimation(@NonNull AXrLottieDrawable animation) { this.lottie = animation.getNativePtr(); return this; } - public Builder setLottieAnimation(long ptr){ + public Builder setLottieAnimation(long ptr) { this.lottie = ptr; return this; } - /** set the output gif background color */ + /** + * set the output gif background color + */ public Builder setBackgroundColor(int bgColor) { this.bgColor = bgColor; return this; } - /** set the output gif width and height */ - public Builder setSize(@Px int width,@Px int height){ + /** + * set the output gif width and height + */ + public Builder setSize(@Px int width, @Px int height) { this.w = width; this.h = height; return this; } - public Builder setListener(Lottie2GifListener listener){ + public Builder setListener(Lottie2GifListener listener) { this.listener = listener; return this; } - /** The delay value is the time between frames in hundredths of a second */ - public Builder setDelay(int delay){ + /** + * The delay value is the time between frames in hundredths of a second + */ + public Builder setDelay(int delay) { this.delay = delay; return this; } - /** set the output gif path */ - public Builder setOutputPath(@NonNull File gif){ + /** + * set the output gif path + */ + public Builder setOutputPath(@NonNull File gif) { this.path = gif; return this; } - /** set the output gif path */ - public Builder setOutputPath(@NonNull String gif){ + /** + * set the output gif path + */ + public Builder setOutputPath(@NonNull String gif) { this.path = new File(gif); return this; } - /** destroy lottie when gif created */ - public Builder setDestroyable(boolean destroyable){ - this.destroyable =destroyable; + /** + * destroy lottie when gif created + */ + public Builder setDestroyable(boolean destroyable) { + this.destroyable = destroyable; return this; } - public Builder setBackgroundTask(boolean enabled){ + public Builder setBackgroundTask(boolean enabled) { async = enabled; return this; } - /** Implements Floyd-Steinberg dithering, writes palette value to alpha */ - public Builder setDithering(boolean enabled){ + /** + * Implements Floyd-Steinberg dithering, writes palette value to alpha + */ + public Builder setDithering(boolean enabled) { dither = enabled; return this; } - public Builder setBitDepth(int bit){ + public Builder setBitDepth(int bit) { bitDepth = bit; return this; } - public Builder setFrameStartAt(int frameStart){ + public Builder setFrameStartAt(int frameStart) { this.frameStart = frameStart; return this; } - public Builder setFrameEndAt(int frameEnd){ + public Builder setFrameEndAt(int frameEnd) { this.frameEnd = frameEnd; return this; } - public Builder setCancelable(boolean cancelable){ + public Builder setCancelable(boolean cancelable) { this.cancelable = cancelable; return this; } - public AXrLottie2Gif build(){ - if (path==null){ + public AXrLottie2Gif build() { + if (path == null) { throw new RuntimeException("output gif path can't be null!"); } - if (w<=0 || h<=0){ + if (w <= 0 || h <= 0) { throw new RuntimeException("output gif width and height must be > 0"); } diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieCacheManager.java b/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieCacheManager.java new file mode 100644 index 0000000..3a6cb79 --- /dev/null +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieCacheManager.java @@ -0,0 +1,196 @@ +package com.aghajari.rlottie; + +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; +import androidx.core.util.Pair; + +import com.aghajari.rlottie.network.AXrFileExtension; +import com.aghajari.rlottie.network.JsonFileExtension; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; + +/** + * Lottie cache manager + */ +public class AXrLottieCacheManager { + + private static final String TAG = AXrLottieCacheManager.class.getSimpleName(); + + File networkCacheDir, localCacheDir; + + public AXrLottieCacheManager(File networkCacheDir, File localCacheDir) { + this.networkCacheDir = networkCacheDir; + this.localCacheDir = localCacheDir; + } + + public void clear() { + clear(getLocalCacheParent()); + clear(getNetworkCacheParent()); + } + + private void clear(File parentDir) { + if (parentDir.exists()) { + File[] files = parentDir.listFiles(); + if (files != null && files.length > 0) { + for (File file : parentDir.listFiles()) { + file.delete(); + } + } + parentDir.delete(); + } + } + + /** + * Returns null if the animation doesn't exist in the cache. + */ + @Nullable + @WorkerThread + public File fetchURLFromCache(String url) { + Pair cacheResult = fetch(url); + if (cacheResult == null) { + return null; + } + File f = cacheResult.second; + if (f == null || !f.exists()) return null; + return f; + } + + public File fetchLocalFromCache(final String json, final String name) { + File f = new File(localCacheDir, name + ".cache"); + if (f.exists()) return f; + return writeLocalCache(json, name); + } + + private File writeLocalCache(final String json, final String name) { + try { + File f = new File(AXrLottie.context.getCacheDir(), "lottie"); + if (!f.mkdir()) return null; + File f2 = new File(f, name + ".cache"); + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(f2)); + outputStreamWriter.write(json); + outputStreamWriter.close(); + + return f2; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * If the animation doesn't exist in the cache, null will be returned. + *

+ * Once the animation is successfully parsed, {@link #loadTempFile(String)} must be + * called to move the file from a temporary location to its permanent cache location so it can + * be used in the future. + */ + @Nullable + @WorkerThread + private Pair fetch(String url) { + File cachedFile = null; + for (AXrFileExtension extension : AXrLottie.getSupportedFileExtensions().values()) { + File file = new File(getNetworkCacheParent(), filenameForUrl(url, extension, false)); + if (file.exists()) { + cachedFile = file; + break; + } + } + if (cachedFile == null) { + return null; + } + + AXrFileExtension extension = AXrLottie.getSupportedFileExtensions().get(cachedFile.getAbsolutePath().substring(cachedFile.getAbsolutePath().lastIndexOf(".")).toLowerCase()); + if (extension == null) + extension = new AXrFileExtension(cachedFile.getAbsolutePath().substring(cachedFile.getAbsolutePath().lastIndexOf("."))) { + }; + + return new Pair<>(extension, cachedFile); + } + + /** + * Writes an InputStream from a network response to a temporary file. If the file successfully parses + * to an composition, {@link #loadTempFile(String)} should be called to move the file + * to its final location for future cache hits. + */ + public File writeTempCacheFile(String url, InputStream stream, AXrFileExtension extension) throws IOException { + String fileName = filenameForUrl(url, extension, true); + File file = new File(getNetworkCacheParent(), fileName); + try { + OutputStream output = new FileOutputStream(file); + //noinspection TryFinallyCanBeTryWithResources + try { + byte[] buffer = new byte[1024]; + int read; + + while ((read = stream.read(buffer)) != -1) { + output.write(buffer, 0, read); + } + + output.flush(); + } catch (Exception e) { + Log.e(TAG, "writeTempCacheFile: ", e); + } finally { + output.close(); + } + } catch (Exception e) { + Log.e(TAG, "writeTempCacheFile: ", e); + } finally { + stream.close(); + } + return file; + } + + /** + * If the file created by {@link #writeTempCacheFile(String, InputStream, AXrFileExtension)} was successfully parsed, + * this should be called to remove the temporary part of its name which will allow it to be a cache hit in the future. + */ + public File loadTempFile(String url) { + String fileName = filenameForUrl(url, JsonFileExtension.JSON, true); + File file = new File(getNetworkCacheParent(), fileName); + String newFileName = file.getAbsolutePath().replace(".temp", ""); + File newFile = new File(newFileName); + file.renameTo(newFile); + return newFile; + } + + /** + * Returns the cache file for the given url if it exists. + * Returns null if neither exist. + */ + public File getCachedFile(String url, AXrFileExtension extension, boolean isTemp) { + return new File(getNetworkCacheParent(), filenameForUrl(url, extension, isTemp)); + } + + public File getNetworkCacheParent() { + File file = networkCacheDir; + if (file.isFile()) { + file.delete(); + } + if (!file.exists()) { + file.mkdirs(); + } + return file; + } + + public File getLocalCacheParent() { + File file = localCacheDir; + if (file.isFile()) { + file.delete(); + } + if (!file.exists()) { + file.mkdirs(); + } + return file; + } + + private String filenameForUrl(String url, AXrFileExtension extension, boolean isTemp) { + return "lottie_cache_" + url.replaceAll("\\W+", "") + (isTemp ? extension.tempExtension() : extension.extension); + } +} \ No newline at end of file diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieDrawable.java b/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieDrawable.java index bff7157..244a039 100755 --- a/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieDrawable.java +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieDrawable.java @@ -29,6 +29,7 @@ import android.os.Handler; import android.os.Looper; import android.os.SystemClock; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -42,8 +43,9 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import com.aghajari.rlottie.network.AXrLottieNetworkFetcher; -import com.aghajari.rlottie.network.SimpleNetworkFetcher; +import com.aghajari.rlottie.network.AXrLottieTask; +import com.aghajari.rlottie.network.AXrLottieTaskFactory; +import com.aghajari.rlottie.network.AXrNetworkFetcher; import static com.aghajari.rlottie.AXrLottieNative.destroy; import static com.aghajari.rlottie.AXrLottieNative.create; @@ -53,8 +55,10 @@ public class AXrLottieDrawable extends BitmapDrawable implements Animatable { - private int width; - private int height; + private static final String TAG = AXrLottieDrawable.class.getSimpleName(); + + private final int width; + private final int height; private final int[] metaData = new int[3]; private int timeBetweenFrames; private float speed = 1f; @@ -116,16 +120,41 @@ public class AXrLottieDrawable extends BitmapDrawable implements Animatable { private OnFrameRenderListener render = null; private OnLottieLoaderListener loaderListener = null; + @Nullable + private AXrLottieTask networkTask; + + private final AXrLottieTask.Listener networkLoadedListener = new AXrLottieTask.Listener() { + @Override + public void onResult(File file) { + if (file != null && !hasLoaded()) { + initFromNetwork(file); + } + } + }; + + private final AXrLottieTask.Listener networkFailureListener = new AXrLottieTask.Listener() { + @Override + public void onResult(Throwable result) { + // Failure Listener + Log.e(TAG,result.toString()); + } + }; + public interface OnFrameChangedListener { void onFrameChanged(AXrLottieDrawable drawable, int frame); - void onRepeat (int repeatedCount,boolean lastFrame); + + void onRepeat(int repeatedCount, boolean lastFrame); + void onStop(); + void onStart(); + void onRecycle(); } public interface OnFrameRenderListener { void onUpdate(AXrLottieDrawable drawable, int frame, long timeDiff, boolean force); + Bitmap renderFrame(AXrLottieDrawable drawable, Bitmap bitmap, int frame); } @@ -222,7 +251,7 @@ private void recycleResources() { @Override public void run() { if (isRecycled) { - if (listener!=null) listener.onRecycle(); + if (listener != null) listener.onRecycle(); return; } if (nativePtr == 0) { @@ -285,23 +314,23 @@ public void run() { nextFrameIsLast = true; } } - if (currentFrame < startFrame){ + if (currentFrame < startFrame) { currentFrame = startFrame; nextFrameIsLast = true; } } else { boolean seemsItsLastFrame = false; - if (repeatMode == REPEAT_MODE_REVERSE){ + if (repeatMode == REPEAT_MODE_REVERSE) { if (repeatChangeDirection) { currentFrame -= framesPerUpdates; - if (currentFrame <= startFrame){ + if (currentFrame <= startFrame) { repeatChangeDirection = false; seemsItsLastFrame = true; } } else { currentFrame += framesPerUpdates; - if (currentFrame >= endFrame){ + if (currentFrame >= endFrame) { repeatChangeDirection = true; seemsItsLastFrame = true; } @@ -314,30 +343,30 @@ public void run() { currentFrame = startFrame; nextFrameIsLast = false; - if (listener!=null) - listener.onRepeat(AUTO_REPEAT_INFINITE,false); + if (listener != null) + listener.onRepeat(AUTO_REPEAT_INFINITE, false); } else { seemsItsLastFrame = true; } - if (seemsItsLastFrame && autoRepeat >= 0){ + if (seemsItsLastFrame && autoRepeat >= 0) { autoRepeatPlayCount++; if (autoRepeatPlayCount >= autoRepeat) { repeatChangeDirection = false; nextFrameIsLast = true; - if (listener!=null) - listener.onRepeat(autoRepeatPlayCount,true); + if (listener != null) + listener.onRepeat(autoRepeatPlayCount, true); } else if (repeatMode == REPEAT_MODE_RESTART) { currentFrame = startFrame; - if (listener!=null) - listener.onRepeat(autoRepeatPlayCount,false); + if (listener != null) + listener.onRepeat(autoRepeatPlayCount, false); } - } else if (currentFrame > endFrame){ + } else if (currentFrame > endFrame) { currentFrame = endFrame; - } else if (currentFrame < startFrame){ + } else if (currentFrame < startFrame) { currentFrame = startFrame; } } @@ -352,29 +381,29 @@ public void run() { } }; - protected int getFramesPerUpdate(){ + protected int getFramesPerUpdate() { return shouldLimitFps ? 2 : 1; } - protected boolean hasCustomEndFrame(){ - return customEndFrame > 0 || getSelectedMarker()!=null; + protected boolean hasCustomEndFrame() { + return customEndFrame > 0 || getSelectedMarker() != null; } - protected int findEndFrame(){ - if (getSelectedMarker() != null && getSelectedMarker().getOutFrame()>0) - return Math.min(getSelectedMarker().getOutFrame(),metaData[0]); + protected int findEndFrame() { + if (getSelectedMarker() != null && getSelectedMarker().getOutFrame() > 0) + return Math.min(getSelectedMarker().getOutFrame(), metaData[0]); return customEndFrame > 0 ? customEndFrame : metaData[0]; } - protected int findStartFrame(){ - if (getSelectedMarker() != null && getSelectedMarker().getInFrame()>=0) - return Math.min(getSelectedMarker().getInFrame(),metaData[0]); + protected int findStartFrame() { + if (getSelectedMarker() != null && getSelectedMarker().getInFrame() >= 0) + return Math.min(getSelectedMarker().getInFrame(), metaData[0]); - return Math.min(Math.max(customStartFrame, 0),metaData[0]); + return Math.min(Math.max(customStartFrame, 0), metaData[0]); } - protected int findTimeBetweenFrames(){ + protected int findTimeBetweenFrames() { return (int) (timeBetweenFrames / speed); } @@ -385,18 +414,21 @@ public AXrLottieDrawable(Builder builder) { if (builder.loaderListener != null) setOnLottieLoaderListener(loaderListener); + this.width = builder.w; + this.height = builder.h; + shouldLimitFps = builder.limitFps; + this.cacheName = builder.cacheName; + getPaint().setFlags(Paint.FILTER_BITMAP_FLAG); + switch (builder.type) { case FILE: - initFromFile(builder.file, builder.cacheName, builder.w, builder.h, builder.cache, builder.limitFps); + initFromFile(builder.file, builder.cache); break; case JSON: - initFromJson(builder.json, builder.cacheName, builder.w, builder.h, builder.cache, builder.limitFps, builder.startDecode); + initFromJson(builder.json, builder.cache, builder.startDecode); break; case URL: - this.width = builder.w; - this.height = builder.h; - cacheName = builder.cacheName; - builder.fetcher.attachToDrawable(AXrLottie.context,this,builder.url,true); + initFromNetwork(builder.url, builder.cache); break; } @@ -433,21 +465,16 @@ public AXrLottieDrawable(Builder builder) { start(); } - private void initFromJson(String json, String name, int width, int height, boolean cache, boolean limitFps, boolean startDecode) { + private void initFromJson(String json, boolean cache, boolean startDecode) { if (cache) { - File f = CacheWriter.load(json, name); + File f = AXrLottie.getLottieCacheManager().fetchLocalFromCache(json, getCacheName()); if (f != null) { - initFromFile(f, name, width, height, true, limitFps); + initFromFile(f, true); return; } } - this.width = width; - this.height = height; - shouldLimitFps = limitFps; - this.cacheName = name; - getPaint().setFlags(Paint.FILTER_BITMAP_FLAG); - nativePtr = createWithJson(json, name, metaData); + nativePtr = createWithJson(json, getCacheName(), metaData); timeBetweenFrames = Math.max(shouldLimitFps ? 33 : 16, (int) (1000.0f / metaData[1])); if (startDecode) { setAllowDecodeSingleFrame(true); @@ -455,13 +482,7 @@ private void initFromJson(String json, String name, int width, int height, boole lottieLoaded(); } - private void initFromFile(File file, String name, int width, int height, boolean precache, boolean limitFps) { - this.width = width; - this.height = height; - shouldLimitFps = limitFps; - this.cacheName = name; - getPaint().setFlags(Paint.FILTER_BITMAP_FLAG); - + private void initFromFile(File file, boolean precache) { nativePtr = create(file.getAbsolutePath(), width, height, metaData, precache, shouldLimitFps); if (precache && lottieCacheGenerateQueue == null) { lottieCacheGenerateQueue = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); @@ -473,6 +494,41 @@ private void initFromFile(File file, String name, int width, int height, boolean lottieLoaded(); } + private void initFromNetwork(String url, boolean cache) { + cancelNetworkTask(); + + networkTask = AXrLottieTaskFactory.fromUrl(url, cache); + networkTask.addListener(networkLoadedListener) + .addFailureListener(networkFailureListener); + } + + private void cancelNetworkTask() { + if (networkTask != null) { + networkTask.removeListener(networkLoadedListener); + networkTask.removeFailureListener(networkFailureListener); + } + } + + /** + * called when animation loaded from {@link com.aghajari.rlottie.network.AXrNetworkFetcher} + */ + private void initFromNetwork(File file) { + if (hasLoaded()) return; + + initFromFile(file, builder.cache); + uiHandler.post(new Runnable() { + @Override + public void run() { + if (isRunning) { + isRunning = false; + start(); + } else { + invalidateInternal(); + } + } + }); + } + public AXrLottieDrawable newDrawable() { return builder.build(); } @@ -563,22 +619,22 @@ public void setCustomStartFrame(int frame) { if (customStartFrame > metaData[0]) { return; } - customStartFrame = Math.max(frame,0); + customStartFrame = Math.max(frame, 0); } - public int getStartFrame(){ + public int getStartFrame() { return findStartFrame(); } - public int getEndFrame(){ + public int getEndFrame() { return findEndFrame(); } - public int getCustomStartFrame(){ + public int getCustomStartFrame() { return customStartFrame; } - public int getCustomEndFrame(){ + public int getCustomEndFrame() { return customEndFrame; } @@ -597,12 +653,13 @@ public void selectMarker(@Nullable AXrLottieMarker marker) { * @see AXrLottieMarker * @see AXrLottieDrawable#getMarkers() */ - public @Nullable AXrLottieMarker getSelectedMarker() { + public @Nullable + AXrLottieMarker getSelectedMarker() { return selectedMarker; } public void setSpeed(float speed) { - if (speed<=0) return; + if (speed <= 0) return; this.speed = speed; } @@ -620,7 +677,7 @@ public void setAllowDecodeSingleFrame(boolean value) { } public void recycle() { - if (isRunning && listener!=null) listener.onRecycle(); + if (isRunning && listener != null) listener.onRecycle(); isRunning = false; isRecycled = true; @@ -643,7 +700,7 @@ public void recycle() { * @see AXrLottieDrawable#AUTO_REPEAT_INFINITE */ public void setAutoRepeat(int repeatCount) { - if (repeatCount>=0 && autoRepeatPlayCount>=repeatCount) return; + if (repeatCount >= 0 && autoRepeatPlayCount >= repeatCount) return; if (repeatMode < AUTO_REPEAT_INFINITE) return; autoRepeat = repeatCount; @@ -664,7 +721,7 @@ public void setAutoRepeat(boolean enabled) { * @see AXrLottieDrawable#REPEAT_MODE_RESTART * @see AXrLottieDrawable#REPEAT_MODE_REVERSE */ - public void setAutoRepeatMode (int autoRepeatMode){ + public void setAutoRepeatMode(int autoRepeatMode) { if (autoRepeatMode != REPEAT_MODE_RESTART && autoRepeatMode != REPEAT_MODE_REVERSE) return; //ignore invalid modes @@ -690,7 +747,7 @@ public int getOpacity() { @Override public void start() { if (isRunning) return; - if (listener!=null) listener.onStart(); + if (listener != null) listener.onStart(); isRunning = true; repeatChangeDirection = false; if (invalidateOnProgressSet) { @@ -706,8 +763,8 @@ public void start() { public void restart() { autoRepeatPlayCount = 0; repeatChangeDirection = false; - setCurrentFrame(findStartFrame(),true,true); - if (isRunning){ + setCurrentFrame(findStartFrame(), true, true); + if (isRunning) { isRunning = false; start(); } @@ -723,7 +780,7 @@ public void commitApplyLayerProperties() { } applyingLayerColors = false; if (!isRunning && decodeSingleFrame) { - if (currentFrame <= findStartFrame()+2) { + if (currentFrame <= findStartFrame() + 2) { currentFrame = findStartFrame(); } nextFrameIsLast = false; @@ -753,7 +810,7 @@ void setLayerProperties(List list) { private void requestRedraw() { if (!applyingLayerColors && !isRunning && decodeSingleFrame) { - if (currentFrame <= findStartFrame()+2) { + if (currentFrame <= findStartFrame() + 2) { currentFrame = findStartFrame(); } nextFrameIsLast = false; @@ -781,7 +838,7 @@ private boolean scheduleNextGetFrame() { @Override public void stop() { isRunning = false; - if (listener!=null) listener.onStop(); + if (listener != null) listener.onStop(); } public void setCurrentFrame(int frame) { @@ -1138,26 +1195,6 @@ protected void lottieLoaded() { if (loaderListener != null) loaderListener.onLoaded(this); } - /** - * called when animation loaded from {@link AXrLottieNetworkFetcher} - */ - public void initFromNetwork(File file, AXrLottieNetworkFetcher fetcher) { - if (hasLoaded() || !builder.cacheName.equalsIgnoreCase(fetcher.getCacheName())) return; - - initFromFile(file, builder.cacheName, builder.w, builder.h, builder.cache, builder.limitFps); - uiHandler.post(new Runnable() { - @Override - public void run() { - if (isRunning) { - isRunning = false; - start(); - }else { - invalidateInternal(); - } - } - }); - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -1242,11 +1279,7 @@ public static Builder fromFile(File file) { } public static Builder fromURL(String url) { - return new Builder(url, SimpleNetworkFetcher.create()); - } - - public static Builder fromURL(String url, AXrLottieNetworkFetcher networkFetcher) { - return new Builder(url, networkFetcher); + return new Builder(url); } public static Builder fromJson(String JSON, String cacheName) { @@ -1276,7 +1309,6 @@ public static class Builder { private final File file; private final String json; private final String url; - private final AXrLottieNetworkFetcher fetcher; private String cacheName; private int w = 200, h = 200; private boolean cache = true; @@ -1302,7 +1334,6 @@ public Builder(File file) { this.cacheName = file.getAbsolutePath(); this.json = null; this.url = null; - this.fetcher = null; this.type = BuilderType.FILE; } @@ -1311,20 +1342,15 @@ public Builder(String json, String cacheName) { this.json = json; this.url = null; this.type = BuilderType.JSON; - this.fetcher = null; setCacheName(cacheName); } - public Builder(String url, AXrLottieNetworkFetcher fetcher) { - if (fetcher == null) { - throw new NullPointerException("NetworkFetcher can't be null!"); - } + public Builder(String url) { this.file = null; this.json = null; this.url = url; this.cacheName = "lottie_cache_" + url.replaceAll("\\W+", ""); this.type = BuilderType.URL; - this.fetcher = fetcher; } /** @@ -1428,7 +1454,7 @@ public Builder setAutoRepeat(boolean enabled) { * @see AXrLottieDrawable#REPEAT_MODE_RESTART * @see AXrLottieDrawable#REPEAT_MODE_REVERSE */ - public Builder setAutoRepeatMode (int autoRepeatMode){ + public Builder setAutoRepeatMode(int autoRepeatMode) { this.repeatMode = autoRepeatMode; return this; } diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieLayerInfo.java b/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieLayerInfo.java index ea5639e..f3df081 100644 --- a/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieLayerInfo.java +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieLayerInfo.java @@ -28,11 +28,11 @@ public class AXrLottieLayerInfo { public enum LayerType { UNKNOWN(-1), PRECOM(0), - SOLID (1), - IMAGE (2), - NULL (3), - SHAPE (4), - TEXT (5); + SOLID(1), + IMAGE(2), + NULL(3), + SHAPE(4), + TEXT(5); private int type; @@ -53,8 +53,8 @@ public int getType() { outFrame = Integer.parseInt(data[2]); try { - type = LayerType.values()[Integer.parseInt(data[3])+1]; // +1 for skipping UNKNOWN - }catch (Exception ignore){ + type = LayerType.values()[Integer.parseInt(data[3]) + 1]; // +1 for skipping UNKNOWN + } catch (Exception ignore) { } } } catch (Exception ignore) { diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieMarker.java b/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieMarker.java index 0866ca6..3df83ff 100644 --- a/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieMarker.java +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/AXrLottieMarker.java @@ -19,9 +19,9 @@ package com.aghajari.rlottie; /** - * Markers exported form AE are used to describe a segment of an animation {comment/tag , startFrame, endFrame} - * Marker can be use to divide a resource in to separate animations by tagging the segment with comment string , - * start frame and duration of that segment. + * Markers exported form AE are used to describe a segment of an animation {comment/tag , startFrame, endFrame} + * Marker can be use to divide a resource in to separate animations by tagging the segment with comment string , + * start frame and duration of that segment. */ public class AXrLottieMarker { @@ -40,7 +40,7 @@ public class AXrLottieMarker { } } - public AXrLottieMarker(String marker,int inFrame,int outFrame){ + public AXrLottieMarker(String marker, int inFrame, int outFrame) { this.marker = marker; this.inFrame = inFrame; this.outFrame = outFrame; diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/CacheWriter.java b/AXrLottie/src/main/java/com/aghajari/rlottie/CacheWriter.java deleted file mode 100644 index ab6fdee..0000000 --- a/AXrLottie/src/main/java/com/aghajari/rlottie/CacheWriter.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2020 - Amir Hossein Aghajari - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - - -package com.aghajari.rlottie; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; - -class CacheWriter { - - public static File load(final String json, final String name) { - if (AXrLottie.context==null) return null; - File f = new File(new File(AXrLottie.context.getCacheDir(), "lottie"), name + ".cache"); - if (f.exists()) return f; - return write(json, name); - } - - private static File write(final String json, final String name) { - try { - File f = new File(AXrLottie.context.getCacheDir(), "lottie"); - if (!f.mkdir()) return null; - File f2 = new File(f, name + ".cache"); - OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(f2)); - outputStreamWriter.write(json); - outputStreamWriter.close(); - - return f2; - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } -} diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/DispatchQueue.java b/AXrLottie/src/main/java/com/aghajari/rlottie/DispatchQueue.java index dfc0659..93bd010 100755 --- a/AXrLottie/src/main/java/com/aghajari/rlottie/DispatchQueue.java +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/DispatchQueue.java @@ -55,6 +55,7 @@ public DispatchQueue(final String threadName, boolean start) { start(); } } + public void cancelRunnable(Runnable runnable) { try { syncLatch.await(); diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrFileExtension.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrFileExtension.java new file mode 100644 index 0000000..05014c7 --- /dev/null +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrFileExtension.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 - Amir Hossein Aghajari + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +package com.aghajari.rlottie.network; + +import com.aghajari.rlottie.AXrLottie; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +/** + * Helpers for known Lottie downloader file types. + */ +public abstract class AXrFileExtension { + + public final String extension; + + public AXrFileExtension(String extension) { + this.extension = extension; + } + + public String tempExtension() { + return ".temp" + extension; + } + + public boolean canParseContent(String contentType) { + return false; + } + + public File saveAsTempFile(String url, InputStream stream) throws IOException { + return AXrLottie.getLottieCacheManager().writeTempCacheFile(url, stream, this); + } + + @Override + public String toString() { + return extension; + } +} diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieFetchResult.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieFetchResult.java new file mode 100644 index 0000000..e78c4fb --- /dev/null +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieFetchResult.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 - Amir Hossein Aghajari + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +package com.aghajari.rlottie.network; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; + +/** + * @author kienht + * @since 29/01/2021 + *

+ * The result of the operation of obtaining a Lottie animation + */ +public interface AXrLottieFetchResult extends Closeable { + /** + * @return Is the operation successful + */ + boolean isSuccessful(); + + /** + * @return Received content stream + */ + @NonNull + InputStream bodyByteStream() throws IOException; + + /** + * @return Type of content received + */ + @Nullable + String contentType(); + + /** + * @return Operation error + */ + @Nullable + String error(); +} \ No newline at end of file diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieNetworkFetcher.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieNetworkFetcher.java index a7826c6..89901aa 100644 --- a/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieNetworkFetcher.java +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieNetworkFetcher.java @@ -18,124 +18,32 @@ package com.aghajari.rlottie.network; -import android.content.Context; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; -import androidx.core.util.Pair; +import androidx.annotation.NonNull; -import com.aghajari.rlottie.AXrLottie; -import com.aghajari.rlottie.AXrLottieDrawable; - -import java.io.File; -import java.io.InputStream; +import java.io.IOException; public abstract class AXrLottieNetworkFetcher { - protected AXrLottieDrawable drawable; - protected String cacheName; - protected String url; - - public void attachToDrawable(Context context,AXrLottieDrawable drawable, String url,boolean load) { - this.drawable = drawable; - this.cacheName = this.drawable.getCacheName(); - this.url = url; - - if (load) fetchSync(context); - } - - public FileExtension[] getSupportedExtensions() { - return new FileExtension[]{ - FileExtension.ZIP(), - FileExtension.JSON() - }; - } + public abstract AXrLottieFetchResult fetchSync(@NonNull String url) throws IOException; - public String getURL() { - return url; - } + private int connectTimeout = 10_000; + private int readTimeout = 10_000; - public String getCacheName() { - return cacheName; + public void setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; } - public boolean isCacheEnabled() { - return AXrLottie.isNetworkCacheEnabled(); + public int getConnectTimeout() { + return connectTimeout; } - public AXrLottieDrawable getDrawable() { - return drawable; - } - - @WorkerThread - public void fetchSync(Context context) { - File res = null; - if (isCacheEnabled()) res = fetchFromCache(context); - - if (res == null && !NetworkCache.checkLoading(url, this)) { - fetchFromNetwork(context); - return; - } - if (res == null) return; - onLoad(res); + public void setReadTimeout(int readTimeout) { + this.readTimeout = readTimeout; } - /** - * Returns null if the animation doesn't exist in the cache. - */ - @Nullable - @WorkerThread - protected File fetchFromCache(Context context) { - Pair cacheResult = NetworkCache.fetch(context, getCacheName(), getSupportedExtensions()); - if (cacheResult == null) { - return null; - } - File f = cacheResult.second; - if (f == null || !f.exists()) return null; - return f; + public int getReadTimeout() { + return readTimeout; } - @WorkerThread - protected abstract void fetchFromNetwork(Context context); - - @WorkerThread - protected void parseStream(Context context, InputStream inputStream, String contentType) { - try { - File file; - if (contentType == null) { - // Assume JSON for best effort parsing. If it fails, it will just deliver the parse exception - // in the result which is more useful than failing here. - contentType = "application/json"; - } - - FileExtension[] fileExtensions = getSupportedExtensions(); - boolean parsed = false; - for (FileExtension fileExtension : fileExtensions) { - if (fileExtension.canParseContent(contentType)){ - parsed = fileExtension.saveAsTempFile(context, getCacheName(),inputStream) != null; - } - if (parsed) break; - } - if (!parsed) - FileExtension.JSON().saveAsTempFile(context, getCacheName(),inputStream); - - file = NetworkCache.loadTempFile(context, getCacheName()); - - onLoad(file); - } catch (Exception e) { - onError(new ParserException(e)); - } - } - - @WorkerThread - public void onLoad(File file) { - if (file != null && file.exists()) { - drawable.initFromNetwork(file,this); - } - NetworkCache.finishLoading(file, getURL(), this); - } - - public void onError(Exception e){ - e.printStackTrace(); - } } diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieResult.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieResult.java new file mode 100644 index 0000000..2e8a3f2 --- /dev/null +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieResult.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2021 - Amir Hossein Aghajari + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +package com.aghajari.rlottie.network; + +import androidx.annotation.Nullable; + +import java.util.Arrays; + +/** + * @author kienht + * @since 29/01/2021 + *

+ * Contains class to hold the resulting value of an async task or an exception if it failed. + * Either value or exception will be non-null. + */ +public final class AXrLottieResult { + + @Nullable + private final V value; + @Nullable + private final Throwable exception; + + public AXrLottieResult(V value) { + this.value = value; + exception = null; + } + + public AXrLottieResult(Throwable exception) { + this.exception = exception; + value = null; + } + + @Nullable + public V getValue() { + return value; + } + + @Nullable + public Throwable getException() { + return exception; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof AXrLottieResult)) { + return false; + } + AXrLottieResult that = (AXrLottieResult) o; + if (getValue() != null && getValue().equals(that.getValue())) { + return true; + } + if (getException() != null && that.getException() != null) { + return getException().toString().equals(getException().toString()); + } + return false; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{getValue(), getException()}); + } +} + diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieTask.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieTask.java new file mode 100644 index 0000000..f6fae97 --- /dev/null +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieTask.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2021 - Amir Hossein Aghajari + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +package com.aghajari.rlottie.network; + +import android.os.Handler; +import android.os.Looper; + +import androidx.annotation.Nullable; +import androidx.annotation.RestrictTo; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; + +/** + * @author kienht + * @since 29/01/2021 + *

+ * Helper to run asynchronous tasks with a result. + * Results can be obtained with {@link #addListener(Listener)}. + * Failures can be obtained with {@link #addFailureListener(Listener)}. + *

+ * A task will produce a single result or a single failure. + */ +public class AXrLottieTask { + + /** + * Set this to change the executor that LottieTasks are run on. This will be the executor that composition parsing and url + * fetching happens on. + *

+ * You may change this to run deserialization synchronously for testing. + */ + @SuppressWarnings("WeakerAccess") + public static Executor EXECUTOR = Executors.newCachedThreadPool(); + + /* Preserve add order. */ + private final Set> successListeners = new LinkedHashSet<>(1); + private final Set> failureListeners = new LinkedHashSet<>(1); + private final Handler handler = new Handler(Looper.getMainLooper()); + + @Nullable + private volatile AXrLottieResult result = null; + + @RestrictTo(RestrictTo.Scope.LIBRARY) + public AXrLottieTask(Callable> runnable) { + this(runnable, false); + } + + /** + * runNow is only used for testing. + */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + AXrLottieTask(Callable> runnable, boolean runNow) { + if (runNow) { + try { + setResult(runnable.call()); + } catch (Throwable e) { + setResult(new AXrLottieResult(e)); + } + } else { + EXECUTOR.execute(new LottieFutureTask(runnable)); + } + } + + private void setResult(@Nullable AXrLottieResult result) { + if (this.result != null) { + throw new IllegalStateException("A task may only be set once."); + } + this.result = result; + notifyListeners(); + } + + /** + * Add a task listener. If the task has completed, the listener will be called synchronously. + * + * @return the task for call chaining. + */ + public synchronized AXrLottieTask addListener(Listener listener) { + if (result != null && result.getValue() != null) { + listener.onResult(result.getValue()); + } + + successListeners.add(listener); + return this; + } + + /** + * Remove a given task listener. The task will continue to execute so you can re-add + * a listener if neccesary. + * + * @return the task for call chaining. + */ + public synchronized AXrLottieTask removeListener(Listener listener) { + successListeners.remove(listener); + return this; + } + + /** + * Add a task failure listener. This will only be called in the even that an exception + * occurs. If an exception has already occurred, the listener will be called immediately. + * + * @return the task for call chaining. + */ + public synchronized AXrLottieTask addFailureListener(Listener listener) { + if (result != null && result.getException() != null) { + listener.onResult(result.getException()); + } + + failureListeners.add(listener); + return this; + } + + /** + * Remove a given task failure listener. The task will continue to execute so you can re-add + * a listener if neccesary. + * + * @return the task for call chaining. + */ + public synchronized AXrLottieTask removeFailureListener(Listener listener) { + failureListeners.remove(listener); + return this; + } + + private void notifyListeners() { + // Listeners should be called on the main thread. + handler.post(new Runnable() { + @Override + public void run() { + if (result == null) { + return; + } + // Local reference in case it gets set on a background thread. + AXrLottieResult result = AXrLottieTask.this.result; + if (result.getValue() != null) { + notifySuccessListeners(result.getValue()); + } else { + notifyFailureListeners(result.getException()); + } + } + }); + } + + private synchronized void notifySuccessListeners(T value) { + // Allows listeners to remove themselves in onResult. + // Otherwise we risk ConcurrentModificationException. + List> listenersCopy = new ArrayList<>(successListeners); + for (Listener l : listenersCopy) { + l.onResult(value); + } + } + + private synchronized void notifyFailureListeners(Throwable e) { + // Allows listeners to remove themselves in onResult. + // Otherwise we risk ConcurrentModificationException. + List> listenersCopy = new ArrayList<>(failureListeners); + if (listenersCopy.isEmpty()) { + return; + } + + for (Listener l : listenersCopy) { + l.onResult(e); + } + } + + private class LottieFutureTask extends FutureTask> { + LottieFutureTask(Callable> callable) { + super(callable); + } + + @Override + protected void done() { + if (isCancelled()) { + // We don't need to notify and listeners if the task is cancelled. + return; + } + + try { + setResult(get()); + } catch (InterruptedException | ExecutionException e) { + setResult(new AXrLottieResult(e)); + } + } + } + + /** + * Receive a result with either the value or exception for a {@link AXrLottieTask} + */ + public interface Listener { + void onResult(T result); + } +} \ No newline at end of file diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieTaskCache.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieTaskCache.java new file mode 100644 index 0000000..f6553fc --- /dev/null +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieTaskCache.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021 - Amir Hossein Aghajari + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +package com.aghajari.rlottie.network; + +import androidx.annotation.Nullable; +import androidx.annotation.RestrictTo; +import androidx.annotation.VisibleForTesting; +import androidx.collection.LruCache; + +import java.io.File; + +/** + * @author kienht + * @since 01/02/2021 + */ +@RestrictTo(RestrictTo.Scope.LIBRARY) +public class AXrLottieTaskCache { + + private static final AXrLottieTaskCache INSTANCE = new AXrLottieTaskCache(); + + public static AXrLottieTaskCache getInstance() { + return INSTANCE; + } + + private final LruCache cache = new LruCache<>(20); + + @VisibleForTesting + AXrLottieTaskCache() { + } + + @Nullable + public File get(@Nullable String cacheKey) { + if (cacheKey == null) { + return null; + } + return cache.get(cacheKey); + } + + public void put(@Nullable String cacheKey, File composition) { + if (cacheKey == null) { + return; + } + cache.put(cacheKey, composition); + } + + public void clear() { + cache.evictAll(); + } + + /** + * Set the maximum number of compositions to keep cached in memory. + * This must be {@literal >} 0. + */ + public void resize(int size) { + cache.resize(size); + } +} \ No newline at end of file diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieTaskFactory.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieTaskFactory.java new file mode 100644 index 0000000..522a56d --- /dev/null +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrLottieTaskFactory.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2021 - Amir Hossein Aghajari + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +package com.aghajari.rlottie.network; + +import androidx.annotation.Nullable; + +import com.aghajari.rlottie.AXrLottie; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; + +/** + * @author kienht + * @since 01/02/2021 + */ +public class AXrLottieTaskFactory { + + /** + * Keep a map of cache keys to in-progress tasks and return them for new requests. + * Without this, simultaneous requests to parse a composition will trigger multiple parallel + * parse tasks prior to the cache getting populated. + */ + private static final Map> taskCache = new HashMap<>(); + + public static void clearCache() { + taskCache.clear(); + AXrLottieTaskCache.getInstance().clear(); + } + + /** + * Fetch an animation from an http url. Once it is downloaded once, Lottie will cache the file to disk for + * future use. Because of this, you may call `fromUrl` ahead of time to warm the cache if you think you + * might need an animation in the future. + *

+ * To skip the cache, add null as a third parameter. + */ + public static AXrLottieTask fromUrl(final String url, final boolean cache) { + final String cacheKey = "url_" + url; + return cache(cacheKey, new Callable>() { + @Override + public AXrLottieResult call() { + AXrLottieResult result = AXrLottie.getNetworkFetcher().fetchSync(url, cache); + if (cacheKey != null && result.getValue() != null) { + AXrLottieTaskCache.getInstance().put(cacheKey, result.getValue()); + } + return result; + } + }); + } + + /** + * First, check to see if there are any in-progress tasks associated with the cache key and return it if there is. + * If not, create a new task for the callable. + * Then, add the new task to the task cache and set up listeners so it gets cleared when done. + */ + private static AXrLottieTask cache( + @Nullable final String cacheKey, Callable> callable) { + final File cachedFile = cacheKey == null ? null : AXrLottieTaskCache.getInstance().get(cacheKey); + if (cachedFile != null) { + return new AXrLottieTask<>(new Callable>() { + @Override + public AXrLottieResult call() { + return new AXrLottieResult<>(cachedFile); + } + }); + } + if (cacheKey != null && taskCache.containsKey(cacheKey)) { + return taskCache.get(cacheKey); + } + + AXrLottieTask task = new AXrLottieTask<>(callable); + if (cacheKey != null) { + task.addListener(new AXrLottieTask.Listener() { + @Override + public void onResult(File result) { + taskCache.remove(cacheKey); + } + }); + task.addFailureListener(new AXrLottieTask.Listener() { + @Override + public void onResult(Throwable result) { + taskCache.remove(cacheKey); + } + }); + taskCache.put(cacheKey, task); + } + return task; + } +} \ No newline at end of file diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrNetworkFetcher.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrNetworkFetcher.java new file mode 100644 index 0000000..0c05ebb --- /dev/null +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrNetworkFetcher.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2021 - Amir Hossein Aghajari + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +package com.aghajari.rlottie.network; + +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; + +import com.aghajari.rlottie.AXrLottie; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +/** + * @author kienht + * @since 29/01/2021 + */ +public class AXrNetworkFetcher { + + private static final String TAG = AXrNetworkFetcher.class.getSimpleName(); + + @NonNull + private final AXrLottieNetworkFetcher fetcher; + + public AXrNetworkFetcher(@NonNull AXrLottieNetworkFetcher fetcher) { + this.fetcher = fetcher; + } + + @NonNull + @WorkerThread + public AXrLottieResult fetchSync(@NonNull final String url, @NonNull final Boolean cache) { + AXrLottieFetchResult fetchResult = null; + File file = null; + try { + if (AXrLottie.isNetworkCacheEnabled() && cache) { + file = AXrLottie.getLottieCacheManager().fetchURLFromCache(url); + } + if (file != null) { + return new AXrLottieResult<>(file); + } else { + fetchResult = fetcher.fetchSync(url); + + if (fetchResult.isSuccessful()) { + InputStream inputStream = fetchResult.bodyByteStream(); + String contentType = fetchResult.contentType(); + + return parseStream(inputStream, contentType, url); + } else { + return new AXrLottieResult<>(new IllegalArgumentException(fetchResult.error())); + } + } + } catch (Exception e) { + return new AXrLottieResult<>(e); + } finally { + if (fetchResult != null) { + try { + fetchResult.close(); + } catch (IOException e) { + Log.e(TAG, "LottieFetchResult close failed ", e); + } + } + } + } + + @WorkerThread + protected AXrLottieResult parseStream(InputStream inputStream, String contentType, String url) { + try { + File file; + if (contentType == null) { + // Assume JSON for best effort parsing. If it fails, it will just deliver the parse exception + // in the result which is more useful than failing here. + contentType = "application/json"; + } + + boolean parsed = false; + for (AXrFileExtension fileExtension : AXrLottie.getSupportedFileExtensions().values()) { + if (fileExtension.canParseContent(contentType)) { + parsed = fileExtension.saveAsTempFile(url, inputStream) != null; + } + if (parsed) break; + } + if (!parsed) + JsonFileExtension.JSON.saveAsTempFile(url, inputStream); + + file = AXrLottie.getLottieCacheManager().loadTempFile(url); + + return new AXrLottieResult<>(file); + } catch (Exception e) { + return new AXrLottieResult<>(e); + } + } + +} diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrSimpleNetworkFetcher.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrSimpleNetworkFetcher.java new file mode 100644 index 0000000..0806093 --- /dev/null +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/network/AXrSimpleNetworkFetcher.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2021 - Amir Hossein Aghajari + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +package com.aghajari.rlottie.network; + +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * @author kienht + * @since 29/01/2021 + */ +public class AXrSimpleNetworkFetcher extends AXrLottieNetworkFetcher { + + private static final String TAG = AXrSimpleNetworkFetcher.class.getSimpleName(); + + @NonNull + @Override + public AXrLottieFetchResult fetchSync(@NonNull String url) throws IOException { + final HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); + connection.setInstanceFollowRedirects(true); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(getConnectTimeout()); + connection.setReadTimeout(getReadTimeout()); + connection.connect(); + return new AXrSimpleLottieFetchResult(connection); + } + + public static class AXrSimpleLottieFetchResult implements AXrLottieFetchResult { + + @NonNull + private final HttpURLConnection connection; + + public AXrSimpleLottieFetchResult(@NonNull HttpURLConnection connection) { + this.connection = connection; + } + + @Override + public boolean isSuccessful() { + try { + return connection.getResponseCode() / 100 == 2; + } catch (IOException e) { + return false; + } + } + + @NonNull + @Override + public InputStream bodyByteStream() throws IOException { + return connection.getInputStream(); + } + + @Nullable + @Override + public String contentType() { + return connection.getContentType(); + } + + @Nullable + @Override + public String error() { + try { + return isSuccessful() ? null : + "Unable to fetch " + connection.getURL() + ". Failed with " + connection.getResponseCode() + "\n" + getErrorFromConnection(connection); + } catch (IOException e) { + return e.getMessage(); + } + } + + @Override + public void close() { + connection.disconnect(); + } + + private String getErrorFromConnection(HttpURLConnection connection) throws IOException { + BufferedReader r = new BufferedReader(new InputStreamReader(connection.getErrorStream())); + StringBuilder error = new StringBuilder(); + String line; + + try { + while ((line = r.readLine()) != null) { + error.append(line).append('\n'); + } + } catch (Exception e) { + Log.e(TAG, "getErrorFromConnection: ", e); + } finally { + try { + r.close(); + } catch (Exception e) { + // Do nothing. + } + } + return error.toString(); + } + } +} diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/FileExtension.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/FileExtension.java deleted file mode 100644 index bc35608..0000000 --- a/AXrLottie/src/main/java/com/aghajari/rlottie/network/FileExtension.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2020 - Amir Hossein Aghajari - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - - -package com.aghajari.rlottie.network; - -import android.content.Context; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.zip.ZipInputStream; - -/** - * Helpers for known Lottie downloader file types. - */ -public abstract class FileExtension { - - public static FileExtension ZIP() { - return new FileExtension(".zip") { - - @Override - public boolean canParseContent(String contentType) { - return ZipCompositionFactory.isZipContent(contentType); - } - - public File fromZipStream(File file, File output, ZipInputStream stream) { - return ZipCompositionFactory.fromZipStreamSyncInternal(file, output, stream); - } - - @Override - public File saveAsTempFile(Context context, String cacheName, InputStream stream) throws IOException { - File file = NetworkCache.writeTempCacheFile(context, cacheName, stream, FileExtension.ZIP()); - file = fromZipStream(file, - NetworkCache.getCachedFile(context, NetworkCache.filenameForUrl(cacheName, FileExtension.JSON(), true)), - new ZipInputStream(new FileInputStream(file))); - return file; - } - }; - } - - public static FileExtension JSON() { - return new FileExtension(".json") { - @Override - public boolean canParseContent(String contentType) { - return contentType.toLowerCase().contains("application/json"); - } - }; - } - - public final String extension; - - public FileExtension(String extension) { - this.extension = extension; - } - - public String tempExtension() { - return ".temp" + extension; - } - - public boolean canParseContent(String contentType) { - return false; - } - - public File saveAsTempFile(Context context, String cacheName, InputStream stream) throws IOException { - return NetworkCache.writeTempCacheFile(context,cacheName, stream, FileExtension.JSON()); - } - - @Override - public String toString() { - return extension; - } -} diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/ParserException.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/JsonFileExtension.java similarity index 66% rename from AXrLottie/src/main/java/com/aghajari/rlottie/network/ParserException.java rename to AXrLottie/src/main/java/com/aghajari/rlottie/network/JsonFileExtension.java index ca8958a..81c2c17 100644 --- a/AXrLottie/src/main/java/com/aghajari/rlottie/network/ParserException.java +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/network/JsonFileExtension.java @@ -18,9 +18,16 @@ package com.aghajari.rlottie.network; -public class ParserException extends Exception{ +public class JsonFileExtension extends AXrFileExtension { - public ParserException (Exception e){ - super(e); + public static final JsonFileExtension JSON = new JsonFileExtension(); + + JsonFileExtension() { + super(".json"); + } + + @Override + public boolean canParseContent(String contentType) { + return contentType.toLowerCase().contains("application/json"); } } diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/NetworkCache.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/NetworkCache.java deleted file mode 100644 index aa3401d..0000000 --- a/AXrLottie/src/main/java/com/aghajari/rlottie/network/NetworkCache.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2020 - Amir Hossein Aghajari - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - - -package com.aghajari.rlottie.network; - -import android.content.Context; -import android.os.Handler; -import android.os.Looper; - -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; -import androidx.core.util.Pair; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Helper class to save and restore animations fetched from an URL to the app disk cache. - */ -public class NetworkCache { - - private static Map> loadingUrls = null; - - static boolean checkLoading(String url, AXrLottieNetworkFetcher fetcher) { - if (loadingUrls == null) loadingUrls = new HashMap<>(); - if (loadingUrls.containsKey(url)) { - List list = loadingUrls.get(url); - list.add(fetcher); - loadingUrls.put(url, list); - return true; - } else { - List list = new ArrayList<>(); - list.add(fetcher); - loadingUrls.put(url, list); - return false; - } - } - - static void finishLoading(final File res, final String url,final AXrLottieNetworkFetcher fetcher) { - if (loadingUrls == null || !loadingUrls.containsKey(url)) return; - Handler handler = new Handler(Looper.getMainLooper()); - handler.post(new Runnable() { - @Override - public void run() { - List list = loadingUrls.get(url); - for (int i = 0; i < list.size(); i++) { - if (list.get(i)!=fetcher){ - list.get(i).onLoad(res); - } - } - loadingUrls.remove(url); - } - }); - } - - /** - * If the animation doesn't exist in the cache, null will be returned. - *

- * Once the animation is successfully parsed, {@link #renameTempFile(Context, String, FileExtension)} must be - * called to move the file from a temporary location to its permanent cache location so it can - * be used in the future. - */ - @Nullable - @WorkerThread - public static Pair fetch(Context context, String cacheName,FileExtension[] extensions) { - final File cachedFile = getCachedFile(context,cacheName,extensions); - if (cachedFile == null) { - return null; - } - - FileExtension extension = new FileExtension( - cachedFile.getAbsolutePath().substring(cachedFile.getAbsolutePath().lastIndexOf("."))){}; - - return new Pair<>(extension, cachedFile); - } - - /** - * Writes an InputStream from a network response to a temporary file. If the file successfully parses - * to an composition, {@link #renameTempFile(Context, String, FileExtension)} should be called to move the file - * to its final location for future cache hits. - */ - public static File writeTempCacheFile(Context context,String cacheName, InputStream stream, FileExtension extension) throws IOException { - String fileName = filenameForUrl(cacheName, extension, true); - File file = new File(context.getCacheDir(), fileName); - try { - OutputStream output = new FileOutputStream(file); - //noinspection TryFinallyCanBeTryWithResources - try { - byte[] buffer = new byte[1024]; - int read; - - while ((read = stream.read(buffer)) != -1) { - output.write(buffer, 0, read); - } - - output.flush(); - } finally { - output.close(); - } - } finally { - stream.close(); - } - return file; - } - - /** - * If the file created by {@link #writeTempCacheFile(Context, String, InputStream, FileExtension)} was successfully parsed, - * this should be called to remove the temporary part of its name which will allow it to be a cache hit in the future. - */ - public static File renameTempFile(Context context, String cacheName, FileExtension extension) { - String fileName = filenameForUrl(cacheName,extension, true); - File file = new File(context.getCacheDir(), fileName); - String newFileName = file.getAbsolutePath().replace(".temp", ""); - File newFile = new File(newFileName); - file.renameTo(newFile); - return newFile; - } - - public static File loadTempFile(Context context,String cacheName){ - return NetworkCache.renameTempFile(context, cacheName, FileExtension.JSON()); - } - - /** - * Returns the cache file for the given url if it exists. Checks for both json and zip. - * Returns null if neither exist. - */ - @Nullable - public static File getCachedFile(Context context, String cacheName, FileExtension[] extensions) { - for (FileExtension extension : extensions){ - File file = new File(context.getCacheDir(), filenameForUrl(cacheName, extension, false)); - if (file.exists()) { - return file; - } - } - return null; - } - - public static File getCachedFile(Context context, String fileName) { - return new File(context.getCacheDir(), fileName); - } - - public static String filenameForUrl(String cacheName, FileExtension extension, boolean isTemp) { - return cacheName + (isTemp ? extension.tempExtension() : extension.extension); - } -} diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/SimpleNetworkFetcher.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/SimpleNetworkFetcher.java deleted file mode 100644 index ad66f33..0000000 --- a/AXrLottie/src/main/java/com/aghajari/rlottie/network/SimpleNetworkFetcher.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2020 - Amir Hossein Aghajari - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - - -package com.aghajari.rlottie.network; - -import android.content.Context; - -import com.aghajari.rlottie.AXrLottie; - -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; - -public class SimpleNetworkFetcher extends AXrLottieNetworkFetcher { - - private SimpleNetworkFetcher(){} - - public static SimpleNetworkFetcher create(){ - return new SimpleNetworkFetcher(); - } - - @Override - protected void fetchFromNetwork(final Context context) { - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - HttpURLConnection connection; - try { - connection = (HttpURLConnection) new URL(getURL()).openConnection(); - connection.setInstanceFollowRedirects(true); - connection.setRequestMethod("GET"); - connection.setConnectTimeout(AXrLottie.getNetworkTimeOut()); - connection.setReadTimeout(AXrLottie.getNetworkTimeOut()); - - try { - connection.connect(); - if (connection.getErrorStream() != null || connection.getResponseCode() != HttpURLConnection.HTTP_OK) { - onError(connection.getErrorStream(), connection.getResponseCode()); - return; - } - - parseStream(context,connection.getInputStream(),connection.getContentType()); - } catch (Exception e) { - onError(e); - } finally { - connection.disconnect(); - } - } catch (MalformedURLException e1) { - onError(e1); - } catch (IOException e) { - onError(e); - } - } - }); - thread.start(); - } - - public void onError(InputStream errorInputStream, int responseCode) { - onError(new Exception("code=" + responseCode)); - } - -} diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/ZipCompositionFactory.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/ZipCompositionFactory.java index f945bce..f8d8e34 100644 --- a/AXrLottie/src/main/java/com/aghajari/rlottie/network/ZipCompositionFactory.java +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/network/ZipCompositionFactory.java @@ -28,7 +28,7 @@ import androidx.annotation.WorkerThread; -public class ZipCompositionFactory { +class ZipCompositionFactory { public static boolean isZipContent(String contentType) { return contentType.toLowerCase().contains("application/zip") || @@ -90,13 +90,13 @@ public static File fromZipStreamSyncInternal(File file, File output, ZipInputStr closeQuietly(inputStream); return f; } - /**} else if (entryName.contains(".png") || entryName.contains(".webp")) { - } else { - File f = toFile(file, output, inputStream, entry); - if (f != null) { - closeQuietly(inputStream); - return f; - } */ + /**} else if (entryName.contains(".png") || entryName.contains(".webp")) { + } else { + File f = toFile(file, output, inputStream, entry); + if (f != null) { + closeQuietly(inputStream); + return f; + } */ } entry = inputStream.getNextEntry(); } diff --git a/AXrLottie/src/main/java/com/aghajari/rlottie/network/ZipFileExtension.java b/AXrLottie/src/main/java/com/aghajari/rlottie/network/ZipFileExtension.java new file mode 100644 index 0000000..6ad6f7c --- /dev/null +++ b/AXrLottie/src/main/java/com/aghajari/rlottie/network/ZipFileExtension.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 - Amir Hossein Aghajari + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +package com.aghajari.rlottie.network; + +import com.aghajari.rlottie.AXrLottie; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipInputStream; + +public class ZipFileExtension extends AXrFileExtension { + public static final ZipFileExtension ZIP = new ZipFileExtension(); + + ZipFileExtension() { + super(".zip"); + } + + @Override + public boolean canParseContent(String contentType) { + return ZipCompositionFactory.isZipContent(contentType); + } + + public File fromZipStream(File file, File output, ZipInputStream stream) { + return ZipCompositionFactory.fromZipStreamSyncInternal(file, output, stream); + } + + @Override + public File saveAsTempFile(String url, InputStream stream) throws IOException { + File file = AXrLottie.getLottieCacheManager().writeTempCacheFile(url, stream, this); + file = fromZipStream(file, + AXrLottie.getLottieCacheManager().getCachedFile(url, JsonFileExtension.JSON, true), + new ZipInputStream(new FileInputStream(file))); + return file; + } +} diff --git a/app/src/main/java/com/aghajari/sample/axrlottie/MainActivity.java b/app/src/main/java/com/aghajari/sample/axrlottie/MainActivity.java index 7782507..f1929f3 100644 --- a/app/src/main/java/com/aghajari/sample/axrlottie/MainActivity.java +++ b/app/src/main/java/com/aghajari/sample/axrlottie/MainActivity.java @@ -30,10 +30,10 @@ public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); AXrLottie.init(this); - initEmojiView(); + AXrLottie.setNetworkFetcher(OkHttpNetworkFetcher.create()); + initEmojiView(); setContentView(R.layout.activity_main); - } public void simpleActivity(View view){ diff --git a/app/src/main/java/com/aghajari/sample/axrlottie/OkHttpNetworkFetcher.java b/app/src/main/java/com/aghajari/sample/axrlottie/OkHttpNetworkFetcher.java index d2a36e1..b9d7968 100644 --- a/app/src/main/java/com/aghajari/sample/axrlottie/OkHttpNetworkFetcher.java +++ b/app/src/main/java/com/aghajari/sample/axrlottie/OkHttpNetworkFetcher.java @@ -1,10 +1,31 @@ -package com.aghajari.sample.axrlottie; +/* + * Copyright (C) 2021 - Amir Hossein Aghajari + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */package com.aghajari.sample.axrlottie; import android.content.Context; +import android.util.Log; -import com.aghajari.rlottie.AXrLottie; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.aghajari.rlottie.network.AXrLottieFetchResult; import com.aghajari.rlottie.network.AXrLottieNetworkFetcher; +import java.io.IOException; +import java.io.InputStream; import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; @@ -19,34 +40,62 @@ public static OkHttpNetworkFetcher create(){ return new OkHttpNetworkFetcher(); } + @NonNull @Override - protected void fetchFromNetwork(final Context context) { - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - try { - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - builder.followRedirects(true).followSslRedirects(true); - builder.connectTimeout(AXrLottie.getNetworkTimeOut(), TimeUnit.MILLISECONDS); - builder.readTimeout(AXrLottie.getNetworkTimeOut(), TimeUnit.MILLISECONDS); - - Request request = new Request.Builder().url(getURL()).build(); - Response response = builder.build().newCall(request).execute(); - - if (response.isSuccessful()) { - String contentType = null; - if (response.body().contentType()!=null) - contentType = response.body().contentType().toString(); - - parseStream(context,response.body().byteStream(),contentType); - } else - onError(new Exception(response.message()+" : code="+response.code())); - - } catch (Exception e) { - onError(e); - } - } - }); - thread.start(); + public AXrLottieFetchResult fetchSync(@NonNull String url) throws IOException { + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.followRedirects(true).followSslRedirects(true); + builder.connectTimeout(getConnectTimeout(), TimeUnit.MILLISECONDS); + builder.readTimeout(getReadTimeout(), TimeUnit.MILLISECONDS); + + Request request = new Request.Builder().url(url).build(); + Response response = builder.build().newCall(request).execute(); + + return new OkHttpNetworkFetchResult(response); + } + + public static class OkHttpNetworkFetchResult implements AXrLottieFetchResult { + + @NonNull + private final Response response; + + public OkHttpNetworkFetchResult(@NonNull Response response) { + this.response = response; + } + + @Override + public boolean isSuccessful() { + return response.isSuccessful(); + } + + @NonNull + @Override + public InputStream bodyByteStream() throws IOException { + return response.body().byteStream(); + } + + @Nullable + @Override + public String contentType() { + String contentType = null; + if (response.body().contentType()!=null) + contentType = response.body().contentType().toString(); + return contentType; + } + + @Nullable + @Override + public String error() { + return isSuccessful() ? null : + "Unable to fetch " + response.request().url() + + ". Failed with " + response.code() + "\n" + + response.message(); + } + + @Override + public void close() { + response.close(); + } + } } diff --git a/app/src/main/java/com/aghajari/sample/axrlottie/activity/ColorLayerActivity.java b/app/src/main/java/com/aghajari/sample/axrlottie/activity/ColorLayerActivity.java index d43f61d..8149167 100644 --- a/app/src/main/java/com/aghajari/sample/axrlottie/activity/ColorLayerActivity.java +++ b/app/src/main/java/com/aghajari/sample/axrlottie/activity/ColorLayerActivity.java @@ -27,7 +27,7 @@ protected void onCreate(Bundle savedInstanceState) { lottieView = findViewById(R.id.lottie_view); - lottieView.setLottieDrawable(AXrLottie.createFromAssets(this, "tractor.json", + lottieView.setLottieDrawable(AXrLottie.Loader.createFromAssets(this, "tractor.json", "tractor", 512, 512, false, false)); lottieView.playAnimation(); diff --git a/app/src/main/java/com/aghajari/sample/axrlottie/activity/LottieEditorActivity.java b/app/src/main/java/com/aghajari/sample/axrlottie/activity/LottieEditorActivity.java index 1fc0197..d0e1c5c 100644 --- a/app/src/main/java/com/aghajari/sample/axrlottie/activity/LottieEditorActivity.java +++ b/app/src/main/java/com/aghajari/sample/axrlottie/activity/LottieEditorActivity.java @@ -36,7 +36,7 @@ protected void onCreate(Bundle savedInstanceState) { lottieView = findViewById(R.id.lottie_view); rv = findViewById(R.id.rv); - lottieView.setLottieDrawable(AXrLottie.createFromAssets(this, "mountain.json", + lottieView.setLottieDrawable(AXrLottie.Loader.createFromAssets(this, "mountain.json", "editor", 512, 512, false, false)); lottieView.playAnimation(); diff --git a/app/src/main/java/com/aghajari/sample/axrlottie/activity/MarkerActivity.java b/app/src/main/java/com/aghajari/sample/axrlottie/activity/MarkerActivity.java index 418fa1a..5205262 100644 --- a/app/src/main/java/com/aghajari/sample/axrlottie/activity/MarkerActivity.java +++ b/app/src/main/java/com/aghajari/sample/axrlottie/activity/MarkerActivity.java @@ -1,6 +1,5 @@ package com.aghajari.sample.axrlottie.activity; -import android.graphics.Color; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -14,16 +13,12 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.aghajari.rlottie.AXrLottie; import com.aghajari.rlottie.AXrLottieDrawable; import com.aghajari.rlottie.AXrLottieImageView; -import com.aghajari.rlottie.AXrLottieLayerInfo; import com.aghajari.rlottie.AXrLottieMarker; -import com.aghajari.rlottie.AXrLottieProperty; import com.aghajari.sample.axrlottie.R; import java.util.List; -import java.util.Random; public class MarkerActivity extends AppCompatActivity { AXrLottieImageView lottieView; diff --git a/app/src/main/java/com/aghajari/sample/axrlottie/activity/SimpleActivity.java b/app/src/main/java/com/aghajari/sample/axrlottie/activity/SimpleActivity.java index 39307f9..c02658a 100644 --- a/app/src/main/java/com/aghajari/sample/axrlottie/activity/SimpleActivity.java +++ b/app/src/main/java/com/aghajari/sample/axrlottie/activity/SimpleActivity.java @@ -2,10 +2,8 @@ import androidx.appcompat.app.AppCompatActivity; -import android.graphics.Color; import android.os.Bundle; -import com.aghajari.rlottie.AXrLottieProperty; import com.aghajari.sample.axrlottie.R; import com.aghajari.rlottie.AXrLottie; @@ -20,7 +18,7 @@ protected void onCreate(Bundle savedInstanceState) { final AXrLottieImageView lottieView = findViewById(R.id.lottie_view); - lottieView.setLottieDrawable(AXrLottie.createFromAssets(this, "emoji_simple.json", + lottieView.setLottieDrawable(AXrLottie.Loader.createFromAssets(this, "emoji_simple.json", "emoji", 256, 256)); lottieView.playAnimation();