diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java index bf39383ad..3bbf22011 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java @@ -1109,6 +1109,10 @@ public class ConversationActivity extends BriarActivity attachmentRetriever.getAttachmentItems(h); List items = new ArrayList<>(liveDataList.size()); for (LiveData liveData : liveDataList) { + // first remove all our observers to avoid having more than one + // in case we reload the conversation, e.g. after deleting messages + liveData.removeObservers(this); + // add a new observer liveData.observe(this, new AttachmentObserver(h.getId(), liveData)); items.add(requireNonNull(liveData.getValue())); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java index 874e4b95f..7f8e3bedb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java @@ -103,9 +103,20 @@ public class ImageActivity extends BriarActivity setSceneTransitionAnimation(transition, null, transition); } + // Intent Extras + Intent i = getIntent(); + attachments = + requireNonNull(i.getParcelableArrayListExtra(ATTACHMENTS)); + int position = i.getIntExtra(ATTACHMENT_POSITION, -1); + if (position == -1) throw new IllegalStateException(); + String name = i.getStringExtra(NAME); + long time = i.getLongExtra(DATE, 0); + byte[] messageIdBytes = requireNonNull(i.getByteArrayExtra(ITEM_ID)); + // get View Model viewModel = ViewModelProviders.of(this, viewModelFactory) .get(ImageViewModel.class); + viewModel.expectAttachments(attachments); viewModel.getSaveState().observeEvent(this, this::onImageSaveStateChanged); @@ -129,17 +140,11 @@ public class ImageActivity extends BriarActivity TextView contactName = toolbar.findViewById(R.id.contactName); TextView dateView = toolbar.findViewById(R.id.dateView); - // Intent Extras - Intent i = getIntent(); - attachments = i.getParcelableArrayListExtra(ATTACHMENTS); - int position = i.getIntExtra(ATTACHMENT_POSITION, -1); - if (position == -1) throw new IllegalStateException(); - String name = i.getStringExtra(NAME); - long time = i.getLongExtra(DATE, 0); + // Set contact name and message time String date = formatDateAbsolute(this, time); contactName.setText(name); dateView.setText(date); - conversationMessageId = new MessageId(i.getByteArrayExtra(ITEM_ID)); + conversationMessageId = new MessageId(messageIdBytes); // Set up image ViewPager viewPager = findViewById(R.id.viewPager); 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 4a61b0dfb..09ec190eb 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 @@ -95,8 +95,6 @@ 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); @@ -111,6 +109,9 @@ public class ImageFragment extends Fragment } else { photoView.setImageResource(R.drawable.ic_image_missing); startPostponedTransition(); + // state is not final, so observe state changes + viewModel.getOnAttachmentReceived(attachment.getMessageId()) + .observeEvent(this, this::onAttachmentReceived); } return v; @@ -127,8 +128,8 @@ public class ImageFragment extends Fragment .into(photoView); } - private void onAttachmentLoaded(MessageId messageId) { - if (attachment.getMessageId().equals(messageId)) loadImage(); + private void onAttachmentReceived(Boolean received) { + if (received) loadImage(); } @Override 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 58ef47dba..d53f26e7b 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 @@ -27,7 +27,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; import java.util.Locale; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -40,6 +42,7 @@ import androidx.lifecycle.AndroidViewModel; import static android.media.MediaScannerConnection.scanFile; import static android.os.Environment.DIRECTORY_PICTURES; import static android.os.Environment.getExternalStoragePublicDirectory; +import static java.util.Objects.requireNonNull; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.util.IoUtils.copyAndClose; @@ -57,8 +60,10 @@ public class ImageViewModel extends AndroidViewModel implements EventListener { @IoExecutor private final Executor ioExecutor; - private final MutableLiveEvent attachmentLoaded = - new MutableLiveEvent<>(); + private volatile boolean receivedAttachmentsInitialized = false; + private ConcurrentHashMap> + receivedAttachments = new ConcurrentHashMap<>(); + /** * true means there was an error saving the image, false if image was saved. */ @@ -91,13 +96,36 @@ public class ImageViewModel extends AndroidViewModel implements EventListener { @Override public void eventOccurred(Event e) { if (e instanceof AttachmentReceivedEvent) { - attachmentLoaded - .postEvent(((AttachmentReceivedEvent) e).getMessageId()); + MessageId id = ((AttachmentReceivedEvent) e).getMessageId(); + MutableLiveEvent oldEvent; + if (receivedAttachmentsInitialized) { + oldEvent = receivedAttachments.get(id); + } else { + oldEvent = receivedAttachments + .putIfAbsent(id, new MutableLiveEvent<>(true)); + } + if (oldEvent != null) oldEvent.postEvent(true); } } - LiveEvent getOnAttachmentLoaded() { - return attachmentLoaded; + @UiThread + public void expectAttachments(List attachments) { + for (AttachmentItem item : attachments) { + // no need to track items that are in a final state already + if (item.getState().isFinal()) continue; + // add new live events, if not added concurrently by Event + MessageId id = item.getMessageId(); + receivedAttachments.putIfAbsent(id, new MutableLiveEvent<>()); + } + receivedAttachmentsInitialized = true; + } + + @UiThread + LiveEvent getOnAttachmentReceived(MessageId messageId) { + if (receivedAttachments.size() == 0) { + throw new IllegalStateException("expectAttachments() not called"); + } + return requireNonNull(receivedAttachments.get(messageId)); } void clickImage() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/LiveEvent.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/LiveEvent.java index d7ff0cef5..d4e1b9cff 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/LiveEvent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/LiveEvent.java @@ -12,6 +12,22 @@ import androidx.lifecycle.Observer; @NotNullByDefault public class LiveEvent extends LiveData> { + /** + * Creates a LiveEvent initialized with the given {@code value}. + * + * @param value initial value + */ + public LiveEvent(T value) { + super(new ConsumableEvent<>(value)); + } + + /** + * Creates a LiveEvent with no value assigned to it. + */ + public LiveEvent() { + super(); + } + public void observeEvent(LifecycleOwner owner, LiveEventHandler handler) { LiveEventObserver observer = new LiveEventObserver<>(handler); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/MutableLiveEvent.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/MutableLiveEvent.java index dac33ecfc..fda307d5d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/MutableLiveEvent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/MutableLiveEvent.java @@ -5,6 +5,22 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @NotNullByDefault public class MutableLiveEvent extends LiveEvent { + /** + * Creates a MutableLiveEvent initialized with the given {@code value}. + * + * @param value initial value + */ + public MutableLiveEvent(T value) { + super(value); + } + + /** + * Creates a MutableLiveEvent with no value assigned to it. + */ + public MutableLiveEvent() { + super(); + } + public void postEvent(T value) { super.postValue(new ConsumableEvent<>(value)); }