package cl.monsoon.s1next.widget; import android.content.Context; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.text.Html; import android.view.View; import android.webkit.URLUtil; import android.widget.TextView; import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.request.Request; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.target.ViewTarget; import java.util.Collections; import java.util.Set; import java.util.WeakHashMap; import cl.monsoon.s1next.App; import cl.monsoon.s1next.R; import cl.monsoon.s1next.data.api.Api; import cl.monsoon.s1next.util.TransformationUtil; /** * Implements {@link android.text.Html.ImageGetter} * in order to show images in the TextView. * <p> * Uses {@link com.bumptech.glide.request.target.ViewTarget} * to make an asynchronous HTTP GET to load the image. * <p> * Forked from https://github.com/goofyz/testGlide/pull/1 * See https://github.com/bumptech/glide/issues/550 */ public final class GlideImageGetter implements Html.ImageGetter, View.OnAttachStateChangeListener, Drawable.Callback { private final Context mContext; private final TextView mTextView; /** * Weak {@link java.util.HashSet}. */ private final Set<ViewTarget<TextView, GlideDrawable>> mViewTargetSet = Collections.newSetFromMap(new WeakHashMap<>()); public GlideImageGetter(Context context, TextView textView) { this.mContext = context; this.mTextView = textView; // save Drawable.Callback in TextView // and get back when finish fetching image // see https://github.com/goofyz/testGlide/pull/1 for more details mTextView.setTag(R.id.tag_drawable_callback, this); // add this listener in order to clean any pending images loading // and set drawable callback tag to null when detached from window mTextView.addOnAttachStateChangeListener(this); } /** * We display image depends on settings and Wi-Fi status, * but display emoticons at any time. */ @Override public Drawable getDrawable(String url) { UrlDrawable urlDrawable = new UrlDrawable(); // url has no domain if it comes from server. if (!URLUtil.isNetworkUrl(url)) { ImageGetterViewTarget imageGetterViewTarget = new ImageGetterViewTarget(mTextView, urlDrawable); // We may have this image in assets if this is emoticon. if (url.startsWith(Api.URL_EMOTICON_IMAGE_PREFIX)) { String emoticonFileName = url.substring(Api.URL_EMOTICON_IMAGE_PREFIX.length()); TransformationUtil.SizeMultiplierBitmapTransformation sizeMultiplierBitmapTransformation = new TransformationUtil.SizeMultiplierBitmapTransformation(mContext, mContext.getResources().getDisplayMetrics().density); Glide.with(mContext) .load(Uri.parse(EmoticonFactory.ASSET_PATH_EMOTICON + emoticonFileName)) .transform(sizeMultiplierBitmapTransformation) .listener(new RequestListener<Uri, GlideDrawable>() { /** * Occurs If we don't have this image (maybe a new emoticon) in assets. */ @Override public boolean onException(Exception e, Uri model, Target<GlideDrawable> target, boolean isFirstResource) { // append domain to this url Glide.with(mContext) .load(Api.BASE_URL + url) .transform(sizeMultiplierBitmapTransformation) .into(imageGetterViewTarget); return true; } @Override public boolean onResourceReady(GlideDrawable resource, Uri model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) { return false; } }) .into(imageGetterViewTarget); } else { Glide.with(mContext) .load(Api.BASE_URL + url) .transform(new TransformationUtil.GlMaxTextureSizeBitmapTransformation( mContext)) .into(imageGetterViewTarget); } mViewTargetSet.add(imageGetterViewTarget); return urlDrawable; } if (App.getAppComponent(mContext).getDownloadPreferencesManager().isImagesDownload()) { ImageGetterViewTarget imageGetterViewTarget = new ImageGetterViewTarget(mTextView, urlDrawable); Glide.with(mContext) .load(url) .diskCacheStrategy(DiskCacheStrategy.SOURCE) .transform(new TransformationUtil.GlMaxTextureSizeBitmapTransformation(mContext)) .into(imageGetterViewTarget); mViewTargetSet.add(imageGetterViewTarget); return urlDrawable; } else { return null; } } @Override public void onViewAttachedToWindow(View v) {} @Override public void onViewDetachedFromWindow(View v) { // cancels any pending images loading for (ViewTarget<TextView, GlideDrawable> viewTarget : mViewTargetSet) { Glide.clear(viewTarget); } mViewTargetSet.clear(); v.removeOnAttachStateChangeListener(this); v.setTag(R.id.tag_drawable_callback, null); } /** * Implements {@link Drawable.Callback} in order to * redraw the TextView which contains the animated GIFs. */ @Override public void invalidateDrawable(Drawable who) { mTextView.invalidate(); } @Override public void scheduleDrawable(Drawable who, Runnable what, long when) {} @Override public void unscheduleDrawable(Drawable who, Runnable what) {} private static final class ImageGetterViewTarget extends ViewTarget<TextView, GlideDrawable> { private final UrlDrawable mDrawable; private Request mRequest; private ImageGetterViewTarget(TextView view, UrlDrawable drawable) { super(view); this.mDrawable = drawable; } @Override public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) { // resize this drawable's width & height to fit its container final int resWidth = resource.getIntrinsicWidth(); final int resHeight = resource.getIntrinsicHeight(); int width, height; TextView textView = getView(); if (textView.getWidth() >= resWidth) { width = resWidth; height = resHeight; } else { width = textView.getWidth(); height = (int) (resHeight / ((float) resWidth / width)); } Rect rect = new Rect(0, 0, width, height); resource.setBounds(rect); mDrawable.setBounds(rect); mDrawable.setDrawable(resource); if (resource.isAnimated()) { Drawable.Callback callback = (Drawable.Callback) textView.getTag( R.id.tag_drawable_callback); // note: not sure whether callback would be null sometimes // when this Drawable' host view is detached from View if (callback != null) { // set callback to drawable in order to // signal its container to be redrawn // to show the animated GIF mDrawable.setCallback(callback); resource.setLoopCount(GlideDrawable.LOOP_FOREVER); resource.start(); } } else { textView.setTag(R.id.tag_drawable_callback, null); } // see http://stackoverflow.com/questions/7870312/android-imagegetter-images-overlapping-text#comment-22289166 textView.setText(textView.getText()); } /** * See https://github.com/bumptech/glide/issues/550#issuecomment-123693051 * * @see com.bumptech.glide.GenericRequestBuilder#into(com.bumptech.glide.request.target.Target) */ @Override public Request getRequest() { return mRequest; } @Override public void setRequest(Request request) { this.mRequest = request; } } }