/* This file is part of Reactive Cascade which is released under The MIT License. See license.md , https://github.com/futurice/cascade and http://reactivecascade.com for details. This is open source for the common good. Please contribute improvements by pull request or contact paulirotta@gmail.com */ package com.reactivecascade.reactive.ui; import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StyleRes; import android.support.annotation.UiThread; import android.util.AttributeSet; import android.widget.ImageView; import com.reactivecascade.functional.ImmutableValue; import com.reactivecascade.i.IAsyncOrigin; import com.reactivecascade.i.IReactiveSource; import com.reactivecascade.i.IReactiveTarget; import com.reactivecascade.i.NotCallOrigin; import com.reactivecascade.util.AssertUtil; import com.reactivecascade.util.RCLog; import java.util.concurrent.CopyOnWriteArraySet; import static com.reactivecascade.Async.UI; import static com.reactivecascade.Async.isUiThread; /** * An ImageView which can be directly bound to change the screen when an up-chain from changes */ public class ReactiveImageView extends ImageView implements IReactiveTarget<Bitmap>, IAsyncOrigin { @NonNull private final ImmutableValue<String> mOrigin = isInEditMode() ? RCLog.DEFAULT_ORIGIN : RCLog.originAsync(); private final CopyOnWriteArraySet<IReactiveSource<Bitmap>> reactiveSources = new CopyOnWriteArraySet<>(); public ReactiveImageView(@NonNull Context context) { super(context); } public ReactiveImageView( @NonNull Context context, @NonNull AttributeSet attrs) { super(context, attrs); } public ReactiveImageView( @NonNull Context context, @NonNull AttributeSet attrs, @StyleRes int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override // IReactiveTarget @NotCallOrigin public void fire(@NonNull Bitmap bitmap) { AssertUtil.assertNotNull(mOrigin); RCLog.v(this, "fire bitmap"); if (isUiThread()) { setImageBitmap(bitmap); } else { UI.execute(() -> setImageBitmap(bitmap)); } } @Override // IReactiveTarget @NotCallOrigin public void fireNext(@NonNull Bitmap bitmap) { //FIXME is not really fire next fire(bitmap); } @Override public void setImageURI(@Nullable Uri uri) { throw new UnsupportedOperationException(); } @Override // IReactiveTarget @NotCallOrigin public void subscribeSource( @NonNull String reason, @NonNull IReactiveSource<Bitmap> reactiveSource) { RCLog.v(this, "Subscribing ReactiveImageView: reason=" + reason + " source=" + reactiveSource.getName()); if (reactiveSources.add(reactiveSource)) { RCLog.v(this, reactiveSource.getName() + " says hello: reason=" + reason); } else { RCLog.d(this, "Did you say hello several times or create some other mess? Upchain says hello, but we already have a hello from \"" + reactiveSource.getName() + "\" at \"" + getName() + "\""); } } @Override // IReactiveTarget @NotCallOrigin public void unsubscribeSource( @NonNull String reason, @NonNull IReactiveSource<Bitmap> reactiveSource) { AssertUtil.assertNotNull(mOrigin); if (reactiveSources.remove(reactiveSource)) { RCLog.v(this, "Upchain says goodbye: reason=" + reason + " reactiveSource=" + reactiveSource.getName()); reactiveSource.unsubscribe(reason, this); } else { RCLog.throwIllegalStateException(this, "Upchain says goodbye, reason=" + reason + ", but upchain \"" + reactiveSource.getName() + "\" is not currently subscribed to \"" + getName() + "\""); } } @Override // IReactiveTarget public void unsubscribeAllSources(@NonNull String reason) { for (final IReactiveSource<Bitmap> reactiveSource : reactiveSources) { reactiveSource.unsubscribeAll(reason); } } @Override // INamed @NonNull public String getName() { return "ReactiveTextView-" + getId(); } @Override // View @UiThread public void onDetachedFromWindow() { unsubscribeAllSources("onDetachedFromWindow"); super.onDetachedFromWindow(); } @NonNull @Override public ImmutableValue<String> getOrigin() { return mOrigin; } }