From 4122e0852ad15ed3329e8ac776339375b80dc0a2 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 24 Sep 2019 13:45:28 -0300 Subject: [PATCH] Show placeholders for missing attachments in ImageActivity and display attachments as they arrive while ImageActivity is open. --- .../android/conversation/ImageFragment.java | 109 +++++++++++------- .../android/conversation/ImageViewHolder.java | 8 +- .../android/conversation/ImageViewModel.java | 33 +++++- 3 files changed, 101 insertions(+), 49 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageFragment.java index c2f368df6..4a61b0dfb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageFragment.java @@ -24,6 +24,7 @@ import org.briarproject.briar.android.conversation.glide.GlideApp; import javax.annotation.ParametersAreNonnullByDefault; import javax.inject.Inject; +import androidx.annotation.DrawableRes; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; @@ -33,14 +34,19 @@ import static android.os.Build.VERSION.SDK_INT; import static android.widget.ImageView.ScaleType.FIT_START; import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; +import static org.briarproject.briar.android.attachment.AttachmentItem.State.AVAILABLE; +import static org.briarproject.briar.android.attachment.AttachmentItem.State.ERROR; import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT_POSITION; import static org.briarproject.briar.android.conversation.ImageActivity.ITEM_ID; @MethodsNotNullByDefault @ParametersAreNonnullByDefault -public class ImageFragment extends Fragment { +public class ImageFragment extends Fragment + implements RequestListener { private final static String IS_FIRST = "isFirst"; + @DrawableRes + private static final int ERROR_RES = R.drawable.ic_image_broken; @Inject ViewModelProvider.Factory viewModelFactory; @@ -89,55 +95,72 @@ public class ImageFragment extends Fragment { viewModel = ViewModelProviders.of(requireNonNull(getActivity()), viewModelFactory).get(ImageViewModel.class); + viewModel.getOnAttachmentLoaded() + .observeEvent(this, this::onAttachmentLoaded); photoView = v.findViewById(R.id.photoView); photoView.setScaleLevels(1, 2, 4); photoView.setOnClickListener(view -> viewModel.clickImage()); - // Request Listener - RequestListener listener = new RequestListener() { - - @Override - public boolean onLoadFailed(@Nullable GlideException e, - Object model, Target target, - boolean isFirstResource) { - if (getActivity() != null && isFirst) - getActivity().supportStartPostponedEnterTransition(); - return false; - } - - @Override - public boolean onResourceReady(Drawable resource, Object model, - Target target, DataSource dataSource, - boolean isFirstResource) { - if (SDK_INT >= 21 && !(resource instanceof Animatable)) { - // set transition name only when not animatable, - // because the animation won't start otherwise - photoView.setTransitionName( - attachment.getTransitionName(conversationItemId)); - } - // Move image to the top if overlapping toolbar - if (viewModel.isOverlappingToolbar(photoView, resource)) { - photoView.setScaleType(FIT_START); - } - if (getActivity() != null && isFirst) { - getActivity().supportStartPostponedEnterTransition(); - } - return false; - } - }; - - // Load Image - GlideApp.with(this) - .load(attachment) - // TODO allow if size < maxTextureSize ? -// .override(SIZE_ORIGINAL) - .diskCacheStrategy(NONE) - .error(R.drawable.ic_image_broken) - .addListener(listener) - .into(photoView); + if (attachment.getState() == AVAILABLE) { + loadImage(); + // postponed transition will be started when Image was loaded + } else if (attachment.getState() == ERROR) { + photoView.setImageResource(ERROR_RES); + startPostponedTransition(); + } else { + photoView.setImageResource(R.drawable.ic_image_missing); + startPostponedTransition(); + } return v; } + private void loadImage() { + GlideApp.with(this) + .load(attachment) + // TODO allow if size < maxTextureSize ? +// .override(SIZE_ORIGINAL) + .diskCacheStrategy(NONE) + .error(ERROR_RES) + .addListener(this) + .into(photoView); + } + + private void onAttachmentLoaded(MessageId messageId) { + if (attachment.getMessageId().equals(messageId)) loadImage(); + } + + @Override + public boolean onLoadFailed(@Nullable GlideException e, + Object model, Target target, + boolean isFirstResource) { + startPostponedTransition(); + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, + Target target, DataSource dataSource, + boolean isFirstResource) { + if (SDK_INT >= 21 && !(resource instanceof Animatable)) { + // set transition name only when not animatable, + // because the animation won't start otherwise + photoView.setTransitionName( + attachment.getTransitionName(conversationItemId)); + } + // Move image to the top if overlapping toolbar + if (viewModel.isOverlappingToolbar(photoView, resource)) { + photoView.setScaleType(FIT_START); + } + startPostponedTransition(); + return false; + } + + private void startPostponedTransition() { + if (getActivity() != null && isFirst) { + getActivity().supportStartPostponedEnterTransition(); + } + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java index 344d7dde0..659bcf29e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java @@ -56,12 +56,12 @@ class ImageViewHolder extends ViewHolder { imageView.setScaleType(FIT_CENTER); } else { loadImage(attachment, r); - if (SDK_INT >= 21) { - imageView.setTransitionName( - attachment.getTransitionName(conversationItemId)); - } imageView.setScaleType(CENTER_CROP); } + if (SDK_INT >= 21) { + imageView.setTransitionName( + attachment.getTransitionName(conversationItemId)); + } } private void setImageViewDimensions(AttachmentItem a, boolean single, diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java index 0c251cf60..58ef47dba 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java @@ -7,13 +7,18 @@ import android.view.View; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.messaging.MessagingManager; +import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent; import java.io.File; import java.io.FileOutputStream; @@ -41,16 +46,19 @@ import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.LogUtils.logException; @NotNullByDefault -public class ImageViewModel extends AndroidViewModel { +public class ImageViewModel extends AndroidViewModel implements EventListener { private static Logger LOG = getLogger(ImageViewModel.class.getName()); private final MessagingManager messagingManager; + private final EventBus eventBus; @DatabaseExecutor private final Executor dbExecutor; @IoExecutor private final Executor ioExecutor; + private final MutableLiveEvent attachmentLoaded = + new MutableLiveEvent<>(); /** * true means there was an error saving the image, false if image was saved. */ @@ -62,13 +70,34 @@ public class ImageViewModel extends AndroidViewModel { @Inject ImageViewModel(Application application, - MessagingManager messagingManager, + MessagingManager messagingManager, EventBus eventBus, @DatabaseExecutor Executor dbExecutor, @IoExecutor Executor ioExecutor) { super(application); this.messagingManager = messagingManager; + this.eventBus = eventBus; this.dbExecutor = dbExecutor; this.ioExecutor = ioExecutor; + + eventBus.addListener(this); + } + + @Override + protected void onCleared() { + super.onCleared(); + eventBus.removeListener(this); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof AttachmentReceivedEvent) { + attachmentLoaded + .postEvent(((AttachmentReceivedEvent) e).getMessageId()); + } + } + + LiveEvent getOnAttachmentLoaded() { + return attachmentLoaded; } void clickImage() {