[android] Create attachments before showing previews

This commit is contained in:
Torsten Grote
2019-02-15 09:13:36 -02:00
parent 249e1e28fe
commit 55f4600a69
19 changed files with 291 additions and 112 deletions

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android.blog;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -21,6 +20,7 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.List;
@@ -121,7 +121,8 @@ public class ReblogFragment extends BaseFragment implements SendListener {
}
@Override
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
public void onSendClick(@Nullable String text,
List<AttachmentHeader> headers) {
ui.input.hideSoftKeyboard();
feedController.repeatPost(item, text,
new UiExceptionHandler<DbException>(this) {

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.blog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.KeyEvent;
@@ -27,6 +26,7 @@ import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPost;
import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.security.GeneralSecurityException;
import java.util.List;
@@ -120,7 +120,8 @@ public class WriteBlogPostActivity extends BriarActivity
}
@Override
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
public void onSendClick(@Nullable String text,
List<AttachmentHeader> headers) {
if (isNullOrEmpty(text)) throw new AssertionError();
// hide publish button, show progress bar

View File

@@ -1,8 +1,11 @@
package org.briarproject.briar.android.conversation;
import android.content.ContentResolver;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.media.ExifInterface;
import android.webkit.MimeTypeMap;
@@ -12,6 +15,7 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.conversation.ImageHelper.DecodeResult;
import org.briarproject.briar.api.messaging.Attachment;
@@ -25,6 +29,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_270;
@@ -54,6 +59,7 @@ class AttachmentController {
private final int minWidth, maxWidth;
private final int minHeight, maxHeight;
private final List<AttachmentHeader> unsent = new CopyOnWriteArrayList<>();
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
new ConcurrentHashMap<>();
@@ -114,6 +120,54 @@ class AttachmentController {
return attachments;
}
@DatabaseExecutor
AttachmentHeader createAttachmentHeader(ContentResolver contentResolver,
GroupId groupId, Uri uri)
throws IOException, DbException {
InputStream is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException();
String contentType = contentResolver.getType(uri);
if (contentType == null) throw new IOException("null content type");
long timestamp = System.currentTimeMillis();
AttachmentHeader h = messagingManager
.addLocalAttachment(groupId, timestamp, contentType, is);
tryToClose(is, LOG, WARNING);
unsent.add(h);
return h;
}
@DatabaseExecutor
void deleteUnsentAttachments() {
for (AttachmentHeader h : unsent) {
try {
messagingManager.removeAttachment(h);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
}
List<AttachmentHeader> getUnsentAttachments() {
return new ArrayList<>(unsent);
}
void markAttachmentsSent() {
unsent.clear();
}
@DatabaseExecutor
AttachmentItem getAttachmentItem(ContentResolver contentResolver, Uri uri,
AttachmentHeader h, boolean needsSize) throws IOException {
InputStream is = null;
try {
is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException();
return getAttachmentItem(h, new Attachment(is), needsSize);
} finally {
if (is != null) tryToClose(is, LOG, WARNING);
}
}
/**
* Creates {@link AttachmentItem}s from the passed headers and Attachments.
* <p>
@@ -135,6 +189,7 @@ class AttachmentController {
* Creates an {@link AttachmentItem} from the {@link Attachment}'s
* {@link InputStream} which will be closed when this method returns.
*/
@VisibleForTesting
AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a,
boolean needsSize) {
MessageId messageId = h.getMessageId();

View File

@@ -6,7 +6,6 @@ import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.Nullable;
@@ -264,7 +263,7 @@ public class ConversationActivity extends BriarActivity
if (FEATURE_FLAG_IMAGE_ATTACHMENTS) {
ImagePreview imagePreview = findViewById(R.id.imagePreview);
sendController = new TextAttachmentController(textInputView,
imagePreview, this, this);
imagePreview, this, this, viewModel);
observeOnce(viewModel.hasImageSupport(), this, hasSupport -> {
if (hasSupport != null && hasSupport) {
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
@@ -658,12 +657,13 @@ public class ConversationActivity extends BriarActivity
}
@Override
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
if (isNullOrEmpty(text) && imageUris.isEmpty())
public void onSendClick(@Nullable String text,
List<AttachmentHeader> attachmentHeaders) {
if (isNullOrEmpty(text) && attachmentHeaders.isEmpty())
throw new AssertionError();
long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
viewModel.sendMessage(text, imageUris, timestamp);
viewModel.sendMessage(text, attachmentHeaders, timestamp);
textInputView.clearText();
}

View File

@@ -11,7 +11,6 @@ import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
@@ -27,10 +26,11 @@ import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.view.TextAttachmentController.AttachmentManager;
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.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage;
@@ -38,10 +38,11 @@ import org.briarproject.briar.api.messaging.PrivateMessageFactory;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -50,7 +51,6 @@ import javax.inject.Inject;
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.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@@ -59,7 +59,8 @@ import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
@NotNullByDefault
public class ConversationViewModel extends AndroidViewModel {
public class ConversationViewModel extends AndroidViewModel implements
AttachmentManager {
private static Logger LOG =
getLogger(ConversationViewModel.class.getName());
@@ -73,6 +74,7 @@ public class ConversationViewModel extends AndroidViewModel {
@CryptoExecutor
private final Executor cryptoExecutor;
private final TransactionManager db;
private final AndroidExecutor androidExecutor;
private final MessagingManager messagingManager;
private final ContactManager contactManager;
private final SettingsManager settingsManager;
@@ -100,17 +102,20 @@ public class ConversationViewModel extends AndroidViewModel {
new MutableLiveData<>();
private final MutableLiveData<PrivateMessageHeader> addedHeader =
new MutableLiveData<>();
// TODO move to AttachmentController
private final Map<Uri, AttachmentItem> unsentItems = new HashMap<>();
@Inject
ConversationViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
@CryptoExecutor Executor cryptoExecutor, TransactionManager db,
MessagingManager messagingManager, ContactManager contactManager,
SettingsManager settingsManager,
AndroidExecutor androidExecutor, MessagingManager messagingManager,
ContactManager contactManager, SettingsManager settingsManager,
PrivateMessageFactory privateMessageFactory) {
super(application);
this.dbExecutor = dbExecutor;
this.cryptoExecutor = cryptoExecutor;
this.androidExecutor = androidExecutor;
this.db = db;
this.messagingManager = messagingManager;
this.contactManager = contactManager;
@@ -121,6 +126,12 @@ public class ConversationViewModel extends AndroidViewModel {
contactDeleted.setValue(false);
}
@Override
protected void onCleared() {
super.onCleared();
attachmentController.deleteUnsentAttachments();
}
/**
* Setting the {@link ContactId} automatically triggers loading of other
* data.
@@ -176,15 +187,56 @@ public class ConversationViewModel extends AndroidViewModel {
});
}
void sendMessage(@Nullable String text, List<Uri> uris, long timestamp) {
void sendMessage(@Nullable String text,
List<AttachmentHeader> attachmentHeaders, long timestamp) {
if (messagingGroupId.getValue() == null) loadGroupId();
observeForeverOnce(messagingGroupId, groupId -> {
if (groupId == null) return;
// calls through to creating and storing the message
storeAttachments(groupId, text, uris, timestamp);
createMessage(groupId, text, attachmentHeaders, timestamp);
});
}
@Override
public void storeAttachment(Uri uri, boolean needsSize, Runnable onSuccess,
Runnable onError) {
if (unsentItems.containsKey(uri)) {
// This can happen due to configuration (screen orientation) change.
// So don't create a new attachment, if we have one already.
androidExecutor.runOnUiThread(onSuccess);
return;
}
if (messagingGroupId.getValue() == null) loadGroupId();
observeForeverOnce(messagingGroupId, groupId -> dbExecutor.execute(()
-> {
if (groupId == null) throw new IllegalStateException();
long start = now();
try {
ContentResolver contentResolver =
getApplication().getContentResolver();
AttachmentHeader h = attachmentController
.createAttachmentHeader(contentResolver, groupId, uri);
unsentItems.put(uri, attachmentController
.getAttachmentItem(contentResolver, uri, h, needsSize));
androidExecutor.runOnUiThread(onSuccess);
} catch (DbException | IOException e) {
logException(LOG, WARNING, e);
androidExecutor.runOnUiThread(onError);
}
logDuration(LOG, "Storing attachment", start);
}));
}
@Override
public List<AttachmentHeader> getAttachments() {
return attachmentController.getUnsentAttachments();
}
@Override
public void removeAttachments() {
unsentItems.clear();
dbExecutor.execute(attachmentController::deleteUnsentAttachments);
}
private void loadGroupId() {
if (contactId == null) throw new IllegalStateException();
dbExecutor.execute(() -> {
@@ -252,58 +304,8 @@ public class ConversationViewModel extends AndroidViewModel {
});
}
private void storeAttachments(GroupId groupId, @Nullable String text,
List<Uri> uris, long timestamp) {
dbExecutor.execute(() -> {
long start = now();
List<AttachmentHeader> attachments = new ArrayList<>();
List<AttachmentItem> items = new ArrayList<>();
boolean needsSize = uris.size() == 1;
for (Uri uri : uris) {
Pair<AttachmentHeader, AttachmentItem> pair =
createAttachmentHeader(groupId, uri, timestamp,
needsSize);
if (pair == null) continue;
attachments.add(pair.getFirst());
items.add(pair.getSecond());
}
logDuration(LOG, "Storing attachments", start);
createMessage(groupId, text, attachments, items, timestamp);
});
}
@Nullable
@DatabaseExecutor
private Pair<AttachmentHeader, AttachmentItem> createAttachmentHeader(
GroupId groupId, Uri uri, long timestamp, boolean needsSize) {
InputStream is = null;
try {
ContentResolver contentResolver =
getApplication().getContentResolver();
is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException();
String contentType = contentResolver.getType(uri);
if (contentType == null) throw new IOException("null content type");
AttachmentHeader h = messagingManager
.addLocalAttachment(groupId, timestamp, contentType, is);
is.close();
// re-open stream to get AttachmentItem
is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException();
AttachmentItem item = attachmentController
.getAttachmentItem(h, new Attachment(is), needsSize);
return new Pair<>(h, item);
} catch (DbException | IOException e) {
logException(LOG, WARNING, e);
return null;
} finally {
if (is != null) tryToClose(is, LOG, WARNING);
}
}
private void createMessage(GroupId groupId, @Nullable String text,
List<AttachmentHeader> attachments, List<AttachmentItem> aItems,
long timestamp) {
List<AttachmentHeader> attachments, long timestamp) {
cryptoExecutor.execute(() -> {
try {
// TODO remove when text can be null in the backend
@@ -311,7 +313,6 @@ public class ConversationViewModel extends AndroidViewModel {
PrivateMessage pm = privateMessageFactory
.createPrivateMessage(groupId, timestamp, msgText,
attachments);
attachmentController.put(pm.getMessage().getId(), aItems);
storeMessage(pm, msgText, attachments);
} catch (FormatException e) {
throw new RuntimeException(e);
@@ -331,6 +332,10 @@ public class ConversationViewModel extends AndroidViewModel {
message.getId(), message.getGroupId(),
message.getTimestamp(), true, true, false, false,
text != null, attachments);
attachmentController.put(m.getMessage().getId(),
new ArrayList<>(unsentItems.values()));
unsentItems.clear();
attachmentController.markAttachmentsSent();
// TODO add text to cache when available here
addedHeader.postValue(h);
} catch (DbException e) {

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.introduction;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
@@ -26,6 +25,7 @@ import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.List;
import java.util.logging.Logger;
@@ -193,7 +193,8 @@ public class IntroductionMessageFragment extends BaseFragment
}
@Override
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
public void onSendClick(@Nullable String text,
List<AttachmentHeader> headers) {
// disable button to prevent accidental double invitations
ui.message.setReady(false);

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.sharing;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
@@ -19,6 +18,7 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.view.LargeTextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.List;
@@ -83,7 +83,8 @@ public abstract class BaseMessageFragment extends BaseFragment
}
@Override
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
public void onSendClick(@Nullable String text,
List<AttachmentHeader> headers) {
// disable button to prevent accidental double actions
sendController.setReady(false);
message.hideSoftKeyboard();

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.threaded;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.CallSuper;
@@ -34,6 +33,7 @@ import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.android.view.UnreadMessageButton;
import org.briarproject.briar.api.client.NamedGroup;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.Collection;
import java.util.List;
@@ -341,7 +341,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
}
@Override
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
public void onSendClick(@Nullable String text,
List<AttachmentHeader> headers) {
if (isNullOrEmpty(text)) throw new AssertionError();
I replyItem = adapter.getHighlightedItem();

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.view;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.constraint.ConstraintLayout;
import android.support.v7.widget.RecyclerView;
@@ -60,21 +59,22 @@ public class ImagePreview extends ConstraintLayout {
this.listener = listener;
}
void showPreview(Collection<Uri> imageUris) {
void showPreview(Collection<ImagePreviewItem> items) {
if (listener == null) throw new IllegalStateException();
if (imageUris.size() == 1) {
if (items.size() == 1) {
LayoutParams params = (LayoutParams) imageList.getLayoutParams();
params.width = MATCH_PARENT;
imageList.setLayoutParams(params);
}
setVisibility(VISIBLE);
imageList.setAdapter(new ImagePreviewAdapter(imageUris, listener));
ImagePreviewAdapter adapter = new ImagePreviewAdapter(items, listener);
imageList.setAdapter(adapter);
}
void removeUri(Uri uri) {
void loadPreviewImage(ImagePreviewItem item) {
ImagePreviewAdapter adapter =
(ImagePreviewAdapter) imageList.getAdapter();
requireNonNull(adapter).removeUri(uri);
((ImagePreviewAdapter) imageList.getAdapter());
requireNonNull(adapter).loadItemPreview(item);
}
interface ImagePreviewListener {
@@ -86,7 +86,7 @@ public class ImagePreview extends ConstraintLayout {
*
* Warning: Glide may call this multiple times.
*/
void onUriError(Uri uri);
void onError();
void onCancel();
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android.view;
import android.net.Uri;
import android.support.annotation.LayoutRes;
import android.support.v7.widget.RecyclerView.Adapter;
import android.view.LayoutInflater;
@@ -15,17 +14,19 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static android.support.v7.widget.RecyclerView.NO_POSITION;
import static java.util.Objects.requireNonNull;
@NotNullByDefault
class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
private final List<Uri> items;
private final List<ImagePreviewItem> items;
private final ImagePreviewListener listener;
@LayoutRes
private final int layout;
ImagePreviewAdapter(Collection<Uri> items, ImagePreviewListener listener) {
ImagePreviewAdapter(Collection<ImagePreviewItem> items,
ImagePreviewListener listener) {
this.items = new ArrayList<>(items);
this.listener = listener;
this.layout = items.size() == 1 ?
@@ -52,11 +53,12 @@ class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
return items.size();
}
void removeUri(Uri uri) {
int pos = items.indexOf(uri);
if (pos == -1) return;
items.remove(uri);
notifyItemRemoved(pos);
void loadItemPreview(ImagePreviewItem item) {
int pos = items.indexOf(item);
if (pos == NO_POSITION) throw new AssertionError();
ImagePreviewItem newItem = items.get(pos);
newItem.setWaitForLoading(false);
notifyItemChanged(pos, newItem);
}
}

View File

@@ -0,0 +1,53 @@
package org.briarproject.briar.android.view;
import android.net.Uri;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@NotNullByDefault
class ImagePreviewItem {
private final Uri uri;
private boolean waitForLoading = true;
private ImagePreviewItem(Uri uri) {
this.uri = uri;
}
Uri getUri() {
return uri;
}
void setWaitForLoading(boolean waitForLoading) {
this.waitForLoading = waitForLoading;
}
boolean waitForLoading() {
return waitForLoading;
}
static List<ImagePreviewItem> fromUris(Collection<Uri> uris) {
List<ImagePreviewItem> items = new ArrayList<>(uris.size());
for (Uri uri : uris) {
items.add(new ImagePreviewItem(uri));
}
return items;
}
@Override
public boolean equals(@Nullable Object o) {
return o instanceof ImagePreviewItem &&
uri.equals(((ImagePreviewItem) o).uri);
}
@Override
public int hashCode() {
return uri.hashCode();
}
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.view;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView.ViewHolder;
@@ -42,9 +41,10 @@ class ImagePreviewViewHolder extends ViewHolder {
this.progressBar = v.findViewById(R.id.progressBar);
}
void bind(Uri uri) {
void bind(ImagePreviewItem item) {
if (item.waitForLoading()) return;
GlideApp.with(imageView)
.load(uri)
.load(item.getUri())
.diskCacheStrategy(NONE)
.error(ERROR_RES)
.downsample(FIT_CENTER)
@@ -55,7 +55,7 @@ class ImagePreviewViewHolder extends ViewHolder {
Object model, Target<Drawable> target,
boolean isFirstResource) {
progressBar.setVisibility(INVISIBLE);
listener.onUriError(uri);
listener.onError();
return false;
}

View File

@@ -16,6 +16,7 @@ import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.ArrayList;
import java.util.List;
@@ -46,6 +47,7 @@ public class TextAttachmentController extends TextSendController
private final ImagePreview imagePreview;
private final AttachImageListener imageListener;
private final CompositeSendButton sendButton;
private final AttachmentManager attachmentManager;
private CharSequence textHint;
private List<Uri> imageUris = emptyList();
@@ -53,10 +55,12 @@ public class TextAttachmentController extends TextSendController
private boolean loadingPreviews = false;
public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
SendListener listener, AttachImageListener imageListener) {
SendListener listener, AttachImageListener imageListener,
AttachmentManager attachmentManager) {
super(v, listener, false);
this.imageListener = imageListener;
this.imagePreview = imagePreview;
this.attachmentManager = attachmentManager;
this.imagePreview.setImagePreviewListener(this);
sendButton = (CompositeSendButton) compositeSendButton;
@@ -84,7 +88,8 @@ public class TextAttachmentController extends TextSendController
@Override
public void onSendEvent() {
if (canSend()) {
listener.onSendClick(textInput.getText(), imageUris);
listener.onSendClick(textInput.getText(),
attachmentManager.getAttachments());
reset();
}
}
@@ -139,7 +144,15 @@ public class TextAttachmentController extends TextSendController
loadingPreviews = true;
updateViewState();
textInput.setHint(R.string.image_caption_hint);
imagePreview.showPreview(imageUris);
List<ImagePreviewItem> items = ImagePreviewItem.fromUris(imageUris);
imagePreview.showPreview(items);
// store attachments and show preview when successful
boolean needsSize = items.size() == 1;
for (ImagePreviewItem item : items) {
attachmentManager.storeAttachment(item.getUri(), needsSize,
() -> imagePreview.loadPreviewImage(item),
this::onError);
}
}
private void reset() {
@@ -180,22 +193,16 @@ public class TextAttachmentController extends TextSendController
}
@Override
public void onUriError(Uri uri) {
boolean removed = imageUris.remove(uri);
if (!removed) {
// we have removed this Uri already, do not remove it again
return;
}
imagePreview.removeUri(uri);
if (imageUris.isEmpty()) onCancel();
public void onError() {
Toast.makeText(textInput.getContext(), R.string.image_attach_error,
LENGTH_LONG).show();
checkAllPreviewsLoaded();
onCancel();
}
@Override
public void onCancel() {
textInput.clearText();
attachmentManager.removeAttachments();
reset();
}
@@ -265,4 +272,20 @@ public class TextAttachmentController extends TextSendController
void onAttachImage(Intent intent);
}
public interface AttachmentManager {
/**
* Stores a new attachment in the database.
*
* @param uri The Uri of the attachment to store.
* @param onSuccess will be run on the UiThread when the attachment was stored successfully.
* @param onError will be run on the UiThread when the attachment could not be stored.
*/
void storeAttachment(Uri uri, boolean needsSize, Runnable onSuccess,
Runnable onError);
List<AttachmentHeader> getAttachments();
void removeAttachments();
}
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android.view;
import android.net.Uri;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
@@ -10,6 +9,7 @@ import android.view.View;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.view.EmojiTextInputView.TextInputListener;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.List;
@@ -85,7 +85,7 @@ public class TextSendController implements TextInputListener {
}
public interface SendListener {
void onSendClick(@Nullable String text, List<Uri> imageUris);
void onSendClick(@Nullable String text, List<AttachmentHeader> headers);
}
}

View File

@@ -129,7 +129,7 @@
<string name="message_hint">Type message</string>
<string name="image_caption_hint">Add a caption (optional)</string>
<string name="image_attach">Attach image</string>
<string name="image_attach_error">Could not attach image</string>
<string name="image_attach_error">Could not attach image(s)</string>
<string name="set_contact_alias">Change contact name</string>
<string name="set_contact_alias_hint">Contact name</string>
<string name="set_alias_button">Change</string>