content: package im.actor.sdk.view;
  
  import android.annotation.TargetApi;
  import android.content.Context;
  import android.graphics.Paint;
  import android.graphics.Typeface;
  import android.os.Build;
  import android.text.Layout;
  import android.text.StaticLayout;
  import android.text.TextPaint;
  import android.text.TextUtils;
  import android.util.AttributeSet;
  import android.view.View;
  
  import im.actor.runtime.function.CancellableSimple;
  import im.actor.sdk.util.Screen;
  
  public class AsyncView<T, L> extends View {
  
      private ThreadLocal<InvalidationContext> contexts = new ThreadLocal<>();
      private ViewAsyncDispatch asyncDispatch = new ViewAsyncDispatch();
      private int currentWidth;
      private int currentHeight;
      private boolean isMeasured;
      private boolean isRequested;
      private T currentObject;
      private InvalidationContext currentLayoutingContext;
      private L currentLayout;
  
      public AsyncView(Context context) {
          super(context);
      }
  
      public AsyncView(Context context, AttributeSet attrs) {
          super(context, attrs);
      }
  
      public AsyncView(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
      }
  
      @TargetApi(Build.VERSION_CODES.LOLLIPOP)
      public AsyncView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
      }
  
      @Override
      protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
          super.onLayout(changed, left, top, right, bottom);
  
          if (!isMeasured || currentWidth != getWidth() || currentHeight != getHeight()) {
              isMeasured = true;
              currentWidth = getWidth();
              currentHeight = getHeight();
              if (currentObject != null) {
                  startLayout();
              }
          }
      }
  
      public void requestLayout(T arg) {
          requestLayout(arg, true);
      }
  
      public void requestLayout(T arg, boolean clearOld) {
          if (clearOld) {
              this.currentLayout = null;
          }
          this.currentObject = arg;
  
          if (currentLayoutingContext != null) {
              currentLayoutingContext.cancel();
              currentLayoutingContext = null;
          }
  
          invalidate();
  
          if (isMeasured) {
              startLayout();
          }
      }
  
      public void cancelLayout() {
          this.currentObject = null;
          if (currentLayoutingContext != null) {
              currentLayoutingContext.cancel();
              currentLayoutingContext = null;
          }
      }
  
      private void startLayout() {
          T arg = this.currentObject;
          isRequested = true;
          int w = currentWidth;
          int h = currentHeight;
          if (currentLayoutingContext != null) {
              currentLayoutingContext.cancel();
              currentLayoutingContext = null;
          }
  
          currentLayoutingContext = new InvalidationContext(arg);
          InvalidationContext context = currentLayoutingContext;
          contexts.set(context);
          asyncDispatch.dispatch(currentLayoutingContext, () -> {
              contexts.set(currentLayoutingContext);
              contexts.set(context);
              L res = buildLayout(arg, w, h);
              contexts.set(null);
              asyncDispatch.complete(() -> {
                  currentLayout = res;
                  layoutReady(res);
                  invalidate();
              });
          });
      }
  
      public InvalidationContext getCurrentLayoutContext() {
          return contexts.get();
      }
  
      public L getLayout() {
          return currentLayout;
      }
  
      public L buildLayout(T arg, int width, int height) {
          return null;
      }
  
      public void layoutReady(L res) {
  
      }
  
      public TextPaint createTextPaint(Typeface typeface, int size, int color) {
          TextPaint res = new TextPaint(Paint.ANTI_ALIAS_FLAG);
          res.setSubpixelText(true);
          res.setTypeface(typeface);
          res.setTextSize(Screen.sp(size));
          res.setColor(color);
          return res;
      }
  
      public Paint createFilledPaint(int color) {
          Paint res = new Paint(Paint.ANTI_ALIAS_FLAG);
          res.setColor(color);
          res.setStyle(Paint.Style.FILL);
          return res;
      }
  
      public StaticLayout singleLineText(CharSequence sequence, TextPaint paint, int maxWidth) {
          CharSequence ellipsizedTitle = TextUtils.ellipsize(sequence, paint, maxWidth - Screen.dp(2),
                  TextUtils.TruncateAt.END);
          return new StaticLayout(ellipsizedTitle, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL,
                  1.0f, 0.0f, false);
      }
  
      public class InvalidationContext extends CancellableSimple {
  
          private T arg;
          private boolean isInvalidated = false;
  
          private InvalidationContext(T arg) {
              this.arg = arg;
          }
  
          public void invalidate() {
              if (isCancelled()) {
                  return;
              }
              if (isInvalidated) {
                  return;
              }
              isInvalidated = true;
              post(() -> {
                  if (currentLayoutingContext == this) {
                      requestLayout(arg, false);
                  }
              });
          }
      }
  }
  