Show placeholders for missing attachments in ImageActivity

and display attachments as they arrive while ImageActivity is open.
parent b152dd08
Pipeline #3674 passed with stage
in 17 minutes and 28 seconds
......@@ -6,6 +6,7 @@ import android.content.Context;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
......@@ -32,14 +33,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<Drawable> {
private final static String IS_FIRST = "isFirst";
@DrawableRes
private static final int ERROR_RES = R.drawable.ic_image_broken;
@Inject
ViewModelProvider.Factory viewModelFactory;
......@@ -88,55 +94,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<Drawable> listener = new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e,
Object model, Target<Drawable> target,
boolean isFirstResource) {
if (getActivity() != null && isFirst)
getActivity().supportStartPostponedEnterTransition();
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model,
Target<Drawable> 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
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)
// .override(SIZE_ORIGINAL)
.diskCacheStrategy(NONE)
.error(R.drawable.ic_image_broken)
.addListener(listener)
.error(ERROR_RES)
.addListener(this)
.into(photoView);
}
return v;
private void onAttachmentLoaded(MessageId messageId) {
if (attachment.getMessageId().equals(messageId)) loadImage();
}
@Override
public boolean onLoadFailed(@Nullable GlideException e,
Object model, Target<Drawable> target,
boolean isFirstResource) {
startPostponedTransition();
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model,
Target<Drawable> 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();
}
}
}
......@@ -55,12 +55,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,
......
......@@ -10,13 +10,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;
......@@ -40,16 +45,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<MessageId> attachmentLoaded =
new MutableLiveEvent<>();
/**
* true means there was an error saving the image, false if image was saved.
*/
......@@ -61,13 +69,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<MessageId> getOnAttachmentLoaded() {
return attachmentLoaded;
}
void clickImage() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment