mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-19 20:48:27 +00:00
Jump to the oldest unread message after loading a draft.
This commit is contained in:
parent
d5a9efa96a
commit
67190774cc
@ -180,6 +180,9 @@ import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.thoughtcrime.securesms.TransportOption.Type;
|
||||
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
||||
@ -294,7 +297,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
initializeProfiles();
|
||||
initializeDraft();
|
||||
initializeDraft().addListener(new AssertedSuccessListener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
if (result != null && result) {
|
||||
Util.runOnMain(() -> {
|
||||
if (fragment != null && fragment.isResumed()) {
|
||||
fragment.moveToLastSeen();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -983,19 +997,29 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
///// Initializers
|
||||
|
||||
private void initializeDraft() {
|
||||
private ListenableFuture<Boolean> initializeDraft() {
|
||||
final SettableFuture<Boolean> result = new SettableFuture<>();
|
||||
|
||||
final String draftText = getIntent().getStringExtra(TEXT_EXTRA);
|
||||
final Uri draftMedia = getIntent().getData();
|
||||
final MediaType draftMediaType = MediaType.from(getIntent().getType());
|
||||
|
||||
if (draftText != null) composeText.setText(draftText);
|
||||
if (draftMedia != null && draftMediaType != null) setMedia(draftMedia, draftMediaType);
|
||||
|
||||
if (draftText != null) {
|
||||
composeText.setText(draftText);
|
||||
result.set(true);
|
||||
}
|
||||
if (draftMedia != null && draftMediaType != null) {
|
||||
return setMedia(draftMedia, draftMediaType);
|
||||
}
|
||||
|
||||
if (draftText == null && draftMedia == null && draftMediaType == null) {
|
||||
initializeDraftFromDatabase();
|
||||
return initializeDraftFromDatabase();
|
||||
} else {
|
||||
updateToggleButtonState();
|
||||
result.set(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void initializeEnabledCheck() {
|
||||
@ -1005,7 +1029,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
attachButton.setEnabled(enabled);
|
||||
}
|
||||
|
||||
private void initializeDraftFromDatabase() {
|
||||
private ListenableFuture<Boolean> initializeDraftFromDatabase() {
|
||||
SettableFuture<Boolean> future = new SettableFuture<>();
|
||||
|
||||
new AsyncTask<Void, Void, List<Draft>>() {
|
||||
@Override
|
||||
protected List<Draft> doInBackground(Void... params) {
|
||||
@ -1019,26 +1045,42 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<Draft> drafts) {
|
||||
AtomicInteger draftsRemaining = new AtomicInteger(drafts.size());
|
||||
AtomicBoolean success = new AtomicBoolean(false);
|
||||
ListenableFuture.Listener<Boolean> listener = new AssertedSuccessListener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
success.compareAndSet(false, result);
|
||||
|
||||
if (draftsRemaining.decrementAndGet() <= 0) {
|
||||
future.set(success.get());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (Draft draft : drafts) {
|
||||
try {
|
||||
switch (draft.getType()) {
|
||||
case Draft.TEXT:
|
||||
composeText.setText(draft.getValue());
|
||||
listener.onSuccess(true);
|
||||
break;
|
||||
case Draft.LOCATION:
|
||||
attachmentManager.setLocation(SignalPlace.deserialize(draft.getValue()), getCurrentMediaConstraints());
|
||||
attachmentManager.setLocation(SignalPlace.deserialize(draft.getValue()), getCurrentMediaConstraints()).addListener(listener);
|
||||
break;
|
||||
case Draft.IMAGE:
|
||||
setMedia(Uri.parse(draft.getValue()), MediaType.IMAGE);
|
||||
setMedia(Uri.parse(draft.getValue()), MediaType.IMAGE).addListener(listener);
|
||||
break;
|
||||
case Draft.AUDIO:
|
||||
setMedia(Uri.parse(draft.getValue()), MediaType.AUDIO);
|
||||
setMedia(Uri.parse(draft.getValue()), MediaType.AUDIO).addListener(listener);
|
||||
break;
|
||||
case Draft.VIDEO:
|
||||
setMedia(Uri.parse(draft.getValue()), MediaType.VIDEO);
|
||||
setMedia(Uri.parse(draft.getValue()), MediaType.VIDEO).addListener(listener);
|
||||
break;
|
||||
case Draft.QUOTE:
|
||||
new QuoteRestorationTask(draft.getValue()).execute();
|
||||
SettableFuture<Boolean> quoteResult = new SettableFuture<>();
|
||||
new QuoteRestorationTask(draft.getValue(), quoteResult).execute();
|
||||
quoteResult.addListener(listener);
|
||||
break;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
@ -1049,6 +1091,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
updateToggleButtonState();
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> initializeSecurity(final boolean currentSecureText,
|
||||
@ -1384,17 +1428,20 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void setMedia(@Nullable Uri uri, @NonNull MediaType mediaType) {
|
||||
setMedia(uri, mediaType, 0, 0);
|
||||
private ListenableFuture<Boolean> setMedia(@Nullable Uri uri, @NonNull MediaType mediaType) {
|
||||
return setMedia(uri, mediaType, 0, 0);
|
||||
}
|
||||
|
||||
private void setMedia(@Nullable Uri uri, @NonNull MediaType mediaType, int width, int height) {
|
||||
if (uri == null) return;
|
||||
private ListenableFuture<Boolean> setMedia(@Nullable Uri uri, @NonNull MediaType mediaType, int width, int height) {
|
||||
if (uri == null) {
|
||||
return new SettableFuture<>(false);
|
||||
}
|
||||
|
||||
if (MediaType.VCARD.equals(mediaType) && isSecureText) {
|
||||
openContactShareEditor(uri);
|
||||
return new SettableFuture<>(false);
|
||||
} else {
|
||||
attachmentManager.setMedia(glideRequests, uri, mediaType, getCurrentMediaConstraints(), width, height);
|
||||
return attachmentManager.setMedia(glideRequests, uri, mediaType, getCurrentMediaConstraints(), width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2183,10 +2230,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private class QuoteRestorationTask extends AsyncTask<Void, Void, MessageRecord> {
|
||||
|
||||
private final String serialized;
|
||||
private final String serialized;
|
||||
private final SettableFuture<Boolean> future;
|
||||
|
||||
QuoteRestorationTask(@NonNull String serialized) {
|
||||
QuoteRestorationTask(@NonNull String serialized, @NonNull SettableFuture<Boolean> future) {
|
||||
this.serialized = serialized;
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -2204,8 +2253,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
protected void onPostExecute(MessageRecord messageRecord) {
|
||||
if (messageRecord != null) {
|
||||
handleReplyMessage(messageRecord);
|
||||
future.set(true);
|
||||
} else {
|
||||
Log.e(TAG, "Failed to restore a quote from a draft. No matching message record.");
|
||||
future.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -190,6 +190,13 @@ public class ConversationFragment extends Fragment
|
||||
getLoaderManager().restartLoader(0, Bundle.EMPTY, this);
|
||||
}
|
||||
|
||||
public void moveToLastSeen() {
|
||||
if (lastSeen > 0) {
|
||||
int position = getListAdapter().findLastSeenPosition(lastSeen);
|
||||
scrollToLastSeenPosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeResources() {
|
||||
this.recipient = Recipient.from(getActivity(), getActivity().getIntent().getParcelableExtra(ConversationActivity.ADDRESS_EXTRA), true);
|
||||
this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1);
|
||||
|
@ -0,0 +1,32 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.request.target.DrawableImageViewTarget;
|
||||
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
|
||||
public class GlideListeningTarget extends DrawableImageViewTarget {
|
||||
|
||||
private final SettableFuture<Boolean> loaded;
|
||||
|
||||
public GlideListeningTarget(@NonNull ImageView view, @NonNull SettableFuture<Boolean> loaded) {
|
||||
super(view);
|
||||
this.loaded = loaded;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setResource(@Nullable Drawable resource) {
|
||||
super.setResource(resource);
|
||||
loaded.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFailed(@Nullable Drawable errorDrawable) {
|
||||
super.onLoadFailed(errorDrawable);
|
||||
loaded.set(true);
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import android.content.res.TypedArray;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
@ -14,12 +15,16 @@ import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.load.DataSource;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.engine.GlideException;
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
|
||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||
import com.bumptech.glide.load.resource.bitmap.FitCenter;
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||
import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
@ -30,6 +35,8 @@ import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Locale;
|
||||
@ -221,16 +228,16 @@ public class ThumbnailView extends FrameLayout {
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide,
|
||||
boolean showControls, boolean isPreview)
|
||||
public ListenableFuture<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide,
|
||||
boolean showControls, boolean isPreview)
|
||||
{
|
||||
setImageResource(glideRequests, slide, showControls, isPreview, 0, 0);
|
||||
return setImageResource(glideRequests, slide, showControls, isPreview, 0, 0);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide,
|
||||
boolean showControls, boolean isPreview, int naturalWidth,
|
||||
int naturalHeight)
|
||||
public ListenableFuture<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide,
|
||||
boolean showControls, boolean isPreview, int naturalWidth,
|
||||
int naturalHeight)
|
||||
{
|
||||
if (showControls) {
|
||||
getTransferControls().setSlide(slide);
|
||||
@ -249,7 +256,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
|
||||
if (Util.equals(slide, this.slide)) {
|
||||
Log.w(TAG, "Not re-loading slide " + slide.asAttachment().getDataUri());
|
||||
return;
|
||||
return new SettableFuture<>(false);
|
||||
}
|
||||
|
||||
if (this.slide != null && this.slide.getFastPreflightId() != null &&
|
||||
@ -257,7 +264,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
{
|
||||
Log.w(TAG, "Not re-loading slide for fast preflight: " + slide.getFastPreflightId());
|
||||
this.slide = slide;
|
||||
return;
|
||||
return new SettableFuture<>(false);
|
||||
}
|
||||
|
||||
Log.w(TAG, "loading part with id " + slide.asAttachment().getDataUri()
|
||||
@ -270,19 +277,31 @@ public class ThumbnailView extends FrameLayout {
|
||||
dimens[HEIGHT] = naturalHeight;
|
||||
invalidate();
|
||||
|
||||
if (slide.getThumbnailUri() != null) buildThumbnailGlideRequest(glideRequests, slide).into(image);
|
||||
else if (slide.hasPlaceholder()) buildPlaceholderGlideRequest(glideRequests, slide).into(image);
|
||||
else glideRequests.clear(image);
|
||||
SettableFuture<Boolean> result = new SettableFuture<>();
|
||||
|
||||
if (slide.getThumbnailUri() != null) {
|
||||
buildThumbnailGlideRequest(glideRequests, slide).into(new GlideListeningTarget(image, result));
|
||||
} else if (slide.hasPlaceholder()) {
|
||||
buildPlaceholderGlideRequest(glideRequests, slide).into(new GlideListeningTarget(image, result));
|
||||
} else {
|
||||
glideRequests.clear(image);
|
||||
result.set(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
||||
public ListenableFuture<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
||||
SettableFuture<Boolean> future = new SettableFuture<>();
|
||||
|
||||
if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE);
|
||||
glideRequests.load(new DecryptableUri(uri))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transforms(new CenterCrop(), new RoundedCorners(radius))
|
||||
.transition(withCrossFade())
|
||||
.into(image);
|
||||
.into(new GlideListeningTarget(image, future));
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
public void setThumbnailClickListener(SlideClickListener listener) {
|
||||
|
@ -63,6 +63,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
@ -181,12 +182,13 @@ public class AttachmentManager {
|
||||
this.slide = Optional.of(slide);
|
||||
}
|
||||
|
||||
public void setLocation(@NonNull final SignalPlace place,
|
||||
@NonNull final MediaConstraints constraints)
|
||||
public ListenableFuture<Boolean> setLocation(@NonNull final SignalPlace place,
|
||||
@NonNull final MediaConstraints constraints)
|
||||
{
|
||||
inflateStub();
|
||||
|
||||
ListenableFuture<Bitmap> future = mapView.display(place);
|
||||
SettableFuture<Boolean> returnResult = new SettableFuture<>();
|
||||
ListenableFuture<Bitmap> future = mapView.display(place);
|
||||
|
||||
attachmentViewStub.get().setVisibility(View.VISIBLE);
|
||||
removableMediaView.display(mapView, false);
|
||||
@ -201,20 +203,25 @@ public class AttachmentManager {
|
||||
|
||||
setSlide(locationSlide);
|
||||
attachmentListener.onAttachmentChanged();
|
||||
returnResult.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
return returnResult;
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public void setMedia(@NonNull final GlideRequests glideRequests,
|
||||
@NonNull final Uri uri,
|
||||
@NonNull final MediaType mediaType,
|
||||
@NonNull final MediaConstraints constraints,
|
||||
final int width,
|
||||
final int height)
|
||||
public ListenableFuture<Boolean> setMedia(@NonNull final GlideRequests glideRequests,
|
||||
@NonNull final Uri uri,
|
||||
@NonNull final MediaType mediaType,
|
||||
@NonNull final MediaConstraints constraints,
|
||||
final int width,
|
||||
final int height)
|
||||
{
|
||||
inflateStub();
|
||||
|
||||
final SettableFuture<Boolean> result = new SettableFuture<>();
|
||||
|
||||
new AsyncTask<Void, Void, Slide>() {
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
@ -247,11 +254,13 @@ public class AttachmentManager {
|
||||
Toast.makeText(context,
|
||||
R.string.ConversationActivity_sorry_there_was_an_error_setting_your_attachment,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
result.set(false);
|
||||
} else if (!areConstraintsSatisfied(context, slide, constraints)) {
|
||||
attachmentViewStub.get().setVisibility(View.GONE);
|
||||
Toast.makeText(context,
|
||||
R.string.ConversationActivity_attachment_exceeds_size_limits,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
result.set(false);
|
||||
} else {
|
||||
setSlide(slide);
|
||||
attachmentViewStub.get().setVisibility(View.VISIBLE);
|
||||
@ -259,12 +268,14 @@ public class AttachmentManager {
|
||||
if (slide.hasAudio()) {
|
||||
audioView.setAudio((AudioSlide) slide, false);
|
||||
removableMediaView.display(audioView, false);
|
||||
result.set(true);
|
||||
} else if (slide.hasDocument()) {
|
||||
documentView.setDocument((DocumentSlide) slide, false);
|
||||
removableMediaView.display(documentView, false);
|
||||
result.set(true);
|
||||
} else {
|
||||
Attachment attachment = slide.asAttachment();
|
||||
thumbnail.setImageResource(glideRequests, slide, false, true, attachment.getWidth(), attachment.getHeight());
|
||||
result.deferTo(thumbnail.setImageResource(glideRequests, slide, false, true, attachment.getWidth(), attachment.getHeight()));
|
||||
removableMediaView.display(thumbnail, mediaType == MediaType.IMAGE);
|
||||
}
|
||||
|
||||
@ -330,6 +341,8 @@ public class AttachmentManager {
|
||||
return mediaType.createSlide(context, uri, fileName, mimeType, mediaSize, width, height);
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isAttachmentPresent() {
|
||||
|
@ -16,6 +16,13 @@ public class SettableFuture<T> implements ListenableFuture<T> {
|
||||
private volatile T result;
|
||||
private volatile Throwable exception;
|
||||
|
||||
public SettableFuture() { }
|
||||
|
||||
public SettableFuture(T value) {
|
||||
this.result = value;
|
||||
this.completed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean cancel(boolean mayInterruptIfRunning) {
|
||||
if (!completed && !canceled) {
|
||||
@ -64,6 +71,20 @@ public class SettableFuture<T> implements ListenableFuture<T> {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void deferTo(ListenableFuture<T> other) {
|
||||
other.addListener(new Listener<T>() {
|
||||
@Override
|
||||
public void onSuccess(T result) {
|
||||
SettableFuture.this.set(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(ExecutionException e) {
|
||||
SettableFuture.this.setException(e.getCause());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized T get() throws InterruptedException, ExecutionException {
|
||||
while (!completed) wait();
|
||||
|
Loading…
x
Reference in New Issue
Block a user