mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
[android] Create attachments before showing previews
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -25,4 +25,15 @@ public class AttachmentHeader {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof AttachmentHeader &&
|
||||
messageId.equals(((AttachmentHeader) o).messageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return messageId.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,4 +8,19 @@ public interface MessagingConstants {
|
||||
* The maximum length of a private message's text in UTF-8 bytes.
|
||||
*/
|
||||
int MAX_PRIVATE_MESSAGE_TEXT_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
|
||||
|
||||
/**
|
||||
* The supported mime types for image attachments.
|
||||
*/
|
||||
String[] IMAGE_MIME_TYPES = {
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
};
|
||||
|
||||
/**
|
||||
* The maximum allowed size of image attachments.
|
||||
*/
|
||||
int MAX_IMAGE_SIZE = 6 * 1024 * 1024;
|
||||
|
||||
}
|
||||
|
||||
@@ -41,6 +41,11 @@ public interface MessagingManager extends ConversationClient {
|
||||
AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
|
||||
String contentType, InputStream is) throws DbException, IOException;
|
||||
|
||||
/**
|
||||
* Removes an unsent attachment.
|
||||
*/
|
||||
void removeAttachment(AttachmentHeader header) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the ID of the contact with the given private conversation.
|
||||
*/
|
||||
|
||||
@@ -166,6 +166,11 @@ class MessagingManagerImpl extends ConversationClientImpl
|
||||
return new AttachmentHeader(new MessageId(b), "image/png");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttachment(AttachmentHeader header) throws DbException {
|
||||
// TODO add real implementation
|
||||
}
|
||||
|
||||
private ContactId getContactId(Transaction txn, GroupId g)
|
||||
throws DbException {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user