mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-16 12:49:55 +01:00
[android] display image attachments for conversation messages
This commit is contained in:
@@ -104,6 +104,7 @@ dependencies {
|
|||||||
}
|
}
|
||||||
implementation "com.android.support:cardview-v7:$supportVersion"
|
implementation "com.android.support:cardview-v7:$supportVersion"
|
||||||
implementation "com.android.support:support-annotations:$supportVersion"
|
implementation "com.android.support:support-annotations:$supportVersion"
|
||||||
|
implementation "com.android.support:exifinterface:$supportVersion"
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||||
implementation "android.arch.lifecycle:extensions:1.1.1"
|
implementation "android.arch.lifecycle:extensions:1.1.1"
|
||||||
|
|
||||||
@@ -117,8 +118,17 @@ dependencies {
|
|||||||
implementation 'com.google.zxing:core:3.3.3'
|
implementation 'com.google.zxing:core:3.3.3'
|
||||||
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.12.4'
|
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.12.4'
|
||||||
implementation 'com.vanniktech:emoji-google:0.5.1'
|
implementation 'com.vanniktech:emoji-google:0.5.1'
|
||||||
|
def glideVersion = '4.8.0'
|
||||||
|
implementation("com.github.bumptech.glide:glide:$glideVersion") {
|
||||||
|
exclude group: 'com.android.support'
|
||||||
|
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it
|
||||||
|
}
|
||||||
|
implementation('jp.wasabeef:glide-transformations:3.3.0') {
|
||||||
|
exclude module: 'disklrucache' // this gets pulled in here otherwise
|
||||||
|
}
|
||||||
|
|
||||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'
|
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'
|
||||||
|
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
|
||||||
|
|
||||||
compileOnly 'javax.annotation:jsr250-api:1.0'
|
compileOnly 'javax.annotation:jsr250-api:1.0'
|
||||||
|
|
||||||
|
|||||||
@@ -30,3 +30,6 @@
|
|||||||
|
|
||||||
# Emoji
|
# Emoji
|
||||||
-keep class com.vanniktech.emoji.**
|
-keep class com.vanniktech.emoji.**
|
||||||
|
|
||||||
|
# Glide
|
||||||
|
-dontwarn com.bumptech.glide.load.engine.cache.DiskLruCacheWrapper
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
|||||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||||
import org.briarproject.briar.BriarCoreEagerSingletons;
|
import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||||
import org.briarproject.briar.BriarCoreModule;
|
import org.briarproject.briar.BriarCoreModule;
|
||||||
|
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
|
||||||
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
||||||
import org.briarproject.briar.android.reporting.BriarReportSender;
|
import org.briarproject.briar.android.reporting.BriarReportSender;
|
||||||
import org.briarproject.briar.android.view.TextInputView;
|
import org.briarproject.briar.android.view.TextInputView;
|
||||||
@@ -170,6 +171,8 @@ public interface AndroidComponent
|
|||||||
|
|
||||||
void inject(TextInputView textInputView);
|
void inject(TextInputView textInputView);
|
||||||
|
|
||||||
|
void inject(BriarModelLoader briarModelLoader);
|
||||||
|
|
||||||
// Eager singleton load
|
// Eager singleton load
|
||||||
void inject(AppModule.EagerSingletons init);
|
void inject(AppModule.EagerSingletons init);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.media.ExifInterface;
|
||||||
|
|
||||||
|
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.MessageId;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_270;
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_90;
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_TRANSPOSE;
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_TRANSVERSE;
|
||||||
|
import static android.support.media.ExifInterface.TAG_IMAGE_LENGTH;
|
||||||
|
import static android.support.media.ExifInterface.TAG_IMAGE_WIDTH;
|
||||||
|
import static android.support.media.ExifInterface.TAG_ORIENTATION;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class AttachmentController {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(AttachmentController.class.getName());
|
||||||
|
|
||||||
|
private final MessagingManager messagingManager;
|
||||||
|
private final int defaultSize;
|
||||||
|
private final int minWidth, maxWidth;
|
||||||
|
private final int minHeight, maxHeight;
|
||||||
|
|
||||||
|
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
AttachmentController(MessagingManager messagingManager, Resources res) {
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
|
defaultSize =
|
||||||
|
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
||||||
|
minWidth = res.getDimensionPixelSize(
|
||||||
|
R.dimen.message_bubble_image_min_width);
|
||||||
|
maxWidth = res.getDimensionPixelSize(
|
||||||
|
R.dimen.message_bubble_image_max_width);
|
||||||
|
minHeight = res.getDimensionPixelSize(
|
||||||
|
R.dimen.message_bubble_image_min_height);
|
||||||
|
maxHeight = res.getDimensionPixelSize(
|
||||||
|
R.dimen.message_bubble_image_max_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(MessageId messageId, List<AttachmentItem> attachments) {
|
||||||
|
attachmentCache.put(messageId, attachments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
List<AttachmentItem> get(MessageId messageId) {
|
||||||
|
return attachmentCache.get(messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DatabaseExecutor
|
||||||
|
List<Pair<AttachmentHeader, Attachment>> getMessageAttachments(
|
||||||
|
List<AttachmentHeader> headers) throws DbException {
|
||||||
|
long start = now();
|
||||||
|
List<Pair<AttachmentHeader, Attachment>> attachments =
|
||||||
|
new ArrayList<>(headers.size());
|
||||||
|
for (AttachmentHeader h : headers) {
|
||||||
|
Attachment a =
|
||||||
|
messagingManager.getAttachment(h.getMessageId());
|
||||||
|
attachments.add(new Pair<>(h, a));
|
||||||
|
}
|
||||||
|
logDuration(LOG, "Loading attachment", start);
|
||||||
|
return attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AttachmentItem> getAttachmentItems(
|
||||||
|
List<Pair<AttachmentHeader, Attachment>> attachments) {
|
||||||
|
List<AttachmentItem> items = new ArrayList<>(attachments.size());
|
||||||
|
for (Pair<AttachmentHeader, Attachment> a : attachments) {
|
||||||
|
AttachmentItem item =
|
||||||
|
getAttachmentItem(a.getFirst(), a.getSecond());
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a) {
|
||||||
|
MessageId messageId = h.getMessageId();
|
||||||
|
Size size = new Size();
|
||||||
|
|
||||||
|
InputStream is = a.getStream();
|
||||||
|
is.mark(Integer.MAX_VALUE);
|
||||||
|
try {
|
||||||
|
// use exif to get size
|
||||||
|
if (h.getContentType().equals("image/jpeg")) {
|
||||||
|
size = getSizeFromExif(is);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// use BitmapFactory to get size
|
||||||
|
if (size.error) {
|
||||||
|
is.reset();
|
||||||
|
size = getSizeFromBitmap(is);
|
||||||
|
}
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate thumbnail size
|
||||||
|
Size thumbnailSize = new Size(defaultSize, defaultSize);
|
||||||
|
if (!size.error) {
|
||||||
|
thumbnailSize = getThumbnailSize(size.width, size.height);
|
||||||
|
}
|
||||||
|
return new AttachmentItem(messageId, size.width, size.height,
|
||||||
|
thumbnailSize.width, thumbnailSize.height, size.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the size of a JPEG {@link InputStream} if EXIF info is available.
|
||||||
|
*/
|
||||||
|
private static Size getSizeFromExif(InputStream is)
|
||||||
|
throws IOException {
|
||||||
|
ExifInterface exif = new ExifInterface(is);
|
||||||
|
// these can return 0 independent of default value
|
||||||
|
int width = exif.getAttributeInt(TAG_IMAGE_WIDTH, 0);
|
||||||
|
int height = exif.getAttributeInt(TAG_IMAGE_LENGTH, 0);
|
||||||
|
if (width == 0 || height == 0) return new Size();
|
||||||
|
int orientation = exif.getAttributeInt(TAG_ORIENTATION, 0);
|
||||||
|
if (orientation == ORIENTATION_ROTATE_90 ||
|
||||||
|
orientation == ORIENTATION_ROTATE_270 ||
|
||||||
|
orientation == ORIENTATION_TRANSVERSE ||
|
||||||
|
orientation == ORIENTATION_TRANSPOSE) {
|
||||||
|
//noinspection SuspiciousNameCombination
|
||||||
|
return new Size(height, width);
|
||||||
|
}
|
||||||
|
return new Size(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the size of any image {@link InputStream}.
|
||||||
|
*/
|
||||||
|
private static Size getSizeFromBitmap(InputStream is) {
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inJustDecodeBounds = true;
|
||||||
|
BitmapFactory.decodeStream(is, null, options);
|
||||||
|
if (options.outWidth == -1 || options.outHeight == -1)
|
||||||
|
return new Size();
|
||||||
|
return new Size(options.outWidth, options.outHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Size getThumbnailSize(int width, int height) {
|
||||||
|
float widthPercentage = maxWidth / (float) width;
|
||||||
|
float heightPercentage = maxHeight / (float) height;
|
||||||
|
float scaleFactor = Math.min(widthPercentage, heightPercentage);
|
||||||
|
int thumbnailWidth = (int) (width * scaleFactor);
|
||||||
|
int thumbnailHeight = (int) (height * scaleFactor);
|
||||||
|
if (thumbnailWidth < minWidth) thumbnailWidth = minWidth;
|
||||||
|
if (thumbnailHeight < minHeight) thumbnailHeight = minHeight;
|
||||||
|
return new Size(thumbnailWidth, thumbnailHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Size {
|
||||||
|
private int width;
|
||||||
|
private int height;
|
||||||
|
private boolean error;
|
||||||
|
|
||||||
|
private Size(int width, int height) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.error = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Size() {
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
|
this.error = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class AttachmentItem {
|
||||||
|
|
||||||
|
private final MessageId messageId;
|
||||||
|
private final int width, height;
|
||||||
|
private final int thumbnailWidth, thumbnailHeight;
|
||||||
|
private final boolean hasError;
|
||||||
|
|
||||||
|
AttachmentItem(MessageId messageId, int width, int height,
|
||||||
|
int thumbnailWidth, int thumbnailHeight, boolean hasError) {
|
||||||
|
this.messageId = messageId;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.thumbnailWidth = thumbnailWidth;
|
||||||
|
this.thumbnailHeight = thumbnailHeight;
|
||||||
|
this.hasError = hasError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageId getMessageId() {
|
||||||
|
return messageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getThumbnailWidth() {
|
||||||
|
return thumbnailWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getThumbnailHeight() {
|
||||||
|
return thumbnailHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasError() {
|
||||||
|
return hasError;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||||
@@ -51,6 +52,7 @@ import org.briarproject.briar.R;
|
|||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
import org.briarproject.briar.android.blog.BlogActivity;
|
import org.briarproject.briar.android.blog.BlogActivity;
|
||||||
|
import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache;
|
||||||
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
|
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
|
||||||
import org.briarproject.briar.android.forum.ForumActivity;
|
import org.briarproject.briar.android.forum.ForumActivity;
|
||||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||||
@@ -69,6 +71,8 @@ import org.briarproject.briar.api.conversation.ConversationResponse;
|
|||||||
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
|
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
|
||||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionManager;
|
import org.briarproject.briar.api.introduction.IntroductionManager;
|
||||||
|
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.MessagingManager;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||||
@@ -116,7 +120,7 @@ import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.S
|
|||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class ConversationActivity extends BriarActivity
|
public class ConversationActivity extends BriarActivity
|
||||||
implements EventListener, ConversationListener, TextInputListener,
|
implements EventListener, ConversationListener, TextInputListener,
|
||||||
TextCache {
|
TextCache, AttachmentCache {
|
||||||
|
|
||||||
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
||||||
|
|
||||||
@@ -134,6 +138,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
Executor cryptoExecutor;
|
Executor cryptoExecutor;
|
||||||
|
|
||||||
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
||||||
|
private AttachmentController attachmentController;
|
||||||
|
|
||||||
private ConversationViewModel viewModel;
|
private ConversationViewModel viewModel;
|
||||||
private ConversationVisitor visitor;
|
private ConversationVisitor visitor;
|
||||||
@@ -191,6 +196,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
viewModel = ViewModelProviders.of(this, viewModelFactory)
|
viewModel = ViewModelProviders.of(this, viewModelFactory)
|
||||||
.get(ConversationViewModel.class);
|
.get(ConversationViewModel.class);
|
||||||
viewModel.setContactId(contactId);
|
viewModel.setContactId(contactId);
|
||||||
|
attachmentController = viewModel.getAttachmentController();
|
||||||
|
|
||||||
setContentView(R.layout.activity_conversation);
|
setContentView(R.layout.activity_conversation);
|
||||||
|
|
||||||
@@ -217,7 +223,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
||||||
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
|
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
|
||||||
|
|
||||||
visitor = new ConversationVisitor(this, this,
|
visitor = new ConversationVisitor(this, this, this,
|
||||||
viewModel.getContactDisplayName());
|
viewModel.getContactDisplayName());
|
||||||
adapter = new ConversationAdapter(this, this);
|
adapter = new ConversationAdapter(this, this);
|
||||||
list = findViewById(R.id.conversationView);
|
list = findViewById(R.id.conversationView);
|
||||||
@@ -340,14 +346,30 @@ public class ConversationActivity extends BriarActivity
|
|||||||
// If the latest header is a private message, eagerly load
|
// If the latest header is a private message, eagerly load
|
||||||
// its text so we can set the scroll position correctly
|
// its text so we can set the scroll position correctly
|
||||||
ConversationMessageHeader latest = sorted.get(0);
|
ConversationMessageHeader latest = sorted.get(0);
|
||||||
if (latest instanceof PrivateMessageHeader &&
|
if (latest instanceof PrivateMessageHeader) {
|
||||||
((PrivateMessageHeader) latest).hasText()) {
|
|
||||||
MessageId id = latest.getId();
|
MessageId id = latest.getId();
|
||||||
String text = textCache.get(id);
|
PrivateMessageHeader h = (PrivateMessageHeader) latest;
|
||||||
if (text == null) {
|
if (h.hasText()) {
|
||||||
LOG.info("Eagerly loading text of latest message");
|
String text = textCache.get(id);
|
||||||
text = messagingManager.getMessageText(id);
|
if (text == null) {
|
||||||
textCache.put(id, text);
|
LOG.info(
|
||||||
|
"Eagerly loading text of latest message");
|
||||||
|
text = messagingManager.getMessageText(id);
|
||||||
|
textCache.put(id, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!h.getAttachmentHeaders().isEmpty()) {
|
||||||
|
List<AttachmentItem> items =
|
||||||
|
attachmentController.get(id);
|
||||||
|
if (items == null) {
|
||||||
|
LOG.info(
|
||||||
|
"Eagerly loading image size for latest message");
|
||||||
|
items = attachmentController.getAttachmentItems(
|
||||||
|
attachmentController
|
||||||
|
.getMessageAttachments(
|
||||||
|
h.getAttachmentHeaders()));
|
||||||
|
attachmentController.put(id, items);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -408,16 +430,40 @@ public class ConversationActivity extends BriarActivity
|
|||||||
private void displayMessageText(MessageId m, String text) {
|
private void displayMessageText(MessageId m, String text) {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
textCache.put(m, text);
|
textCache.put(m, text);
|
||||||
SparseArray<ConversationMessageItem> messages =
|
Pair<Integer, ConversationMessageItem> pair =
|
||||||
adapter.getMessageItems();
|
adapter.getMessageItem(m);
|
||||||
for (int i = 0; i < messages.size(); i++) {
|
if (pair != null) {
|
||||||
ConversationItem item = messages.valueAt(i);
|
pair.getSecond().setText(text);
|
||||||
if (item.getId().equals(m)) {
|
adapter.notifyItemChanged(pair.getFirst());
|
||||||
item.setText(text);
|
list.scrollToPosition(adapter.getItemCount() - 1);
|
||||||
adapter.notifyItemChanged(messages.keyAt(i));
|
}
|
||||||
list.scrollToPosition(adapter.getItemCount() - 1);
|
});
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
private void loadMessageAttachments(MessageId messageId,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
|
runOnDbThread(() -> {
|
||||||
|
try {
|
||||||
|
displayMessageAttachments(messageId,
|
||||||
|
attachmentController.getMessageAttachments(headers));
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayMessageAttachments(MessageId m,
|
||||||
|
List<Pair<AttachmentHeader, Attachment>> attachments) {
|
||||||
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
|
List<AttachmentItem> items =
|
||||||
|
attachmentController.getAttachmentItems(attachments);
|
||||||
|
attachmentController.put(m, items);
|
||||||
|
Pair<Integer, ConversationMessageItem> pair =
|
||||||
|
adapter.getMessageItem(m);
|
||||||
|
if (pair != null) {
|
||||||
|
pair.getSecond().setAttachments(items);
|
||||||
|
adapter.notifyItemChanged(pair.getFirst());
|
||||||
|
list.scrollToPosition(adapter.getItemCount() - 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -782,4 +828,13 @@ public class ConversationActivity extends BriarActivity
|
|||||||
if (text == null) loadMessageText(m);
|
if (text == null) loadMessageText(m);
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public List<AttachmentItem> getAttachmentItems(MessageId m,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
|
List<AttachmentItem> attachments = attachmentController.get(m);
|
||||||
|
if (attachments == null) loadMessageAttachments(m, headers);
|
||||||
|
return attachments;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.util.BriarAdapter;
|
import org.briarproject.briar.android.util.BriarAdapter;
|
||||||
|
|
||||||
@@ -98,16 +100,16 @@ class ConversationAdapter
|
|||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
SparseArray<ConversationMessageItem> getMessageItems() {
|
@Nullable
|
||||||
SparseArray<ConversationMessageItem> messages = new SparseArray<>();
|
Pair<Integer, ConversationMessageItem> getMessageItem(MessageId messageId) {
|
||||||
|
|
||||||
for (int i = 0; i < items.size(); i++) {
|
for (int i = 0; i < items.size(); i++) {
|
||||||
ConversationItem item = items.get(i);
|
ConversationItem item = items.get(i);
|
||||||
if (item instanceof ConversationMessageItem) {
|
if (item instanceof ConversationMessageItem &&
|
||||||
messages.put(i, (ConversationMessageItem) item);
|
item.getId().equals(messageId)) {
|
||||||
|
return new Pair<>(i, (ConversationMessageItem) item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return messages;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package org.briarproject.briar.android.conversation;
|
|||||||
import android.support.annotation.CallSuper;
|
import android.support.annotation.CallSuper;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
import android.support.constraint.ConstraintLayout;
|
||||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
@@ -18,11 +18,11 @@ import static org.briarproject.briar.android.util.UiUtils.formatDate;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
abstract class ConversationItemViewHolder extends ViewHolder {
|
abstract class ConversationItemViewHolder extends ViewHolder {
|
||||||
|
|
||||||
protected final ViewGroup layout;
|
protected final ConstraintLayout layout;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final OutItemViewHolder outViewHolder;
|
private final OutItemViewHolder outViewHolder;
|
||||||
private final TextView text;
|
private final TextView text;
|
||||||
private final TextView time;
|
protected final TextView time;
|
||||||
|
|
||||||
ConversationItemViewHolder(View v, boolean isIncoming) {
|
ConversationItemViewHolder(View v, boolean isIncoming) {
|
||||||
super(v);
|
super(v);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
import android.support.annotation.LayoutRes;
|
import android.support.annotation.LayoutRes;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -14,15 +14,22 @@ import javax.annotation.concurrent.NotThreadSafe;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ConversationMessageItem extends ConversationItem {
|
class ConversationMessageItem extends ConversationItem {
|
||||||
|
|
||||||
private final List<AttachmentHeader> attachments;
|
@Nullable
|
||||||
|
private List<AttachmentItem> attachments;
|
||||||
|
|
||||||
ConversationMessageItem(@LayoutRes int layoutRes, PrivateMessageHeader h) {
|
ConversationMessageItem(@LayoutRes int layoutRes, PrivateMessageHeader h,
|
||||||
|
@Nullable List<AttachmentItem> attachments) {
|
||||||
super(layoutRes, h);
|
super(layoutRes, h);
|
||||||
this.attachments = h.getAttachmentHeaders();
|
this.attachments = attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<AttachmentHeader> getAttachments() {
|
@Nullable
|
||||||
|
List<AttachmentItem> getAttachments() {
|
||||||
return attachments;
|
return attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setAttachments(List<AttachmentItem> attachments) {
|
||||||
|
this.attachments = attachments;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,165 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
import android.support.constraint.ConstraintSet;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.MultiTransformation;
|
||||||
|
import com.bumptech.glide.load.Transformation;
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||||
|
|
||||||
|
import jp.wasabeef.glide.transformations.RoundedCornersTransformation;
|
||||||
|
|
||||||
|
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
||||||
|
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
||||||
|
import static jp.wasabeef.glide.transformations.RoundedCornersTransformation.CornerType.BOTTOM;
|
||||||
|
import static jp.wasabeef.glide.transformations.RoundedCornersTransformation.CornerType.TOP_LEFT;
|
||||||
|
import static jp.wasabeef.glide.transformations.RoundedCornersTransformation.CornerType.TOP_RIGHT;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
||||||
|
|
||||||
// image support will be added here (#1242)
|
@DrawableRes
|
||||||
|
private static final int errorRes = R.drawable.ic_image_broken;
|
||||||
|
|
||||||
|
private final ImageView imageView;
|
||||||
|
private final ViewGroup statusLayout;
|
||||||
|
private final int timeColor, timeColorBubble;
|
||||||
|
private final int radiusBig, radiusSmall;
|
||||||
|
private final ConstraintSet textConstraints = new ConstraintSet();
|
||||||
|
private final ConstraintSet imageConstraints = new ConstraintSet();
|
||||||
|
private final ConstraintSet imageTextConstraints = new ConstraintSet();
|
||||||
|
|
||||||
ConversationMessageViewHolder(View v, boolean isIncoming) {
|
ConversationMessageViewHolder(View v, boolean isIncoming) {
|
||||||
super(v, isIncoming);
|
super(v, isIncoming);
|
||||||
|
imageView = v.findViewById(R.id.imageView);
|
||||||
|
statusLayout = v.findViewById(R.id.statusLayout);
|
||||||
|
radiusBig = v.getContext().getResources()
|
||||||
|
.getDimensionPixelSize(R.dimen.message_bubble_radius_big);
|
||||||
|
radiusSmall = v.getContext().getResources()
|
||||||
|
.getDimensionPixelSize(R.dimen.message_bubble_radius_small);
|
||||||
|
|
||||||
|
// remember original status text color
|
||||||
|
timeColor = time.getCurrentTextColor();
|
||||||
|
timeColorBubble =
|
||||||
|
ContextCompat.getColor(v.getContext(), R.color.briar_white);
|
||||||
|
|
||||||
|
// clone constraint sets from layout files
|
||||||
|
textConstraints
|
||||||
|
.clone(v.getContext(), R.layout.list_item_conversation_msg_in);
|
||||||
|
imageConstraints.clone(v.getContext(),
|
||||||
|
R.layout.list_item_conversation_msg_image);
|
||||||
|
imageTextConstraints.clone(v.getContext(),
|
||||||
|
R.layout.list_item_conversation_msg_image_text);
|
||||||
|
|
||||||
|
// in/out are different layouts, so we need to do this only once
|
||||||
|
textConstraints
|
||||||
|
.setHorizontalBias(R.id.statusLayout, isIncoming() ? 1 : 0);
|
||||||
|
imageConstraints
|
||||||
|
.setHorizontalBias(R.id.statusLayout, isIncoming() ? 1 : 0);
|
||||||
|
imageTextConstraints
|
||||||
|
.setHorizontalBias(R.id.statusLayout, isIncoming() ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void bind(ConversationItem conversationItem,
|
||||||
|
ConversationListener listener) {
|
||||||
|
super.bind(conversationItem, listener);
|
||||||
|
ConversationMessageItem item =
|
||||||
|
(ConversationMessageItem) conversationItem;
|
||||||
|
if (item.getAttachments() == null || item.getAttachments().isEmpty()) {
|
||||||
|
bindTextItem();
|
||||||
|
} else {
|
||||||
|
bindImageItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindTextItem() {
|
||||||
|
clearImage();
|
||||||
|
statusLayout.setBackgroundResource(0);
|
||||||
|
// also reset padding (the background drawable defines some)
|
||||||
|
statusLayout.setPadding(0, 0, 0, 0);
|
||||||
|
time.setTextColor(timeColor);
|
||||||
|
textConstraints.applyTo(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindImageItem(ConversationMessageItem item) {
|
||||||
|
// TODO show more than just the first image
|
||||||
|
AttachmentItem attachment = item.getAttachments().get(0);
|
||||||
|
|
||||||
|
ConstraintSet constraintSet;
|
||||||
|
if (item.getText() == null) {
|
||||||
|
statusLayout
|
||||||
|
.setBackgroundResource(R.drawable.msg_status_bubble);
|
||||||
|
time.setTextColor(timeColorBubble);
|
||||||
|
constraintSet = imageConstraints;
|
||||||
|
} else {
|
||||||
|
statusLayout.setBackgroundResource(0);
|
||||||
|
// also reset padding (the background drawable defines some)
|
||||||
|
statusLayout.setPadding(0, 0, 0, 0);
|
||||||
|
time.setTextColor(timeColor);
|
||||||
|
constraintSet = imageTextConstraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply image size constraints, so glides picks them up for scaling
|
||||||
|
int width = attachment.getThumbnailWidth();
|
||||||
|
int height = attachment.getThumbnailHeight();
|
||||||
|
constraintSet.constrainWidth(R.id.imageView, width);
|
||||||
|
constraintSet.constrainHeight(R.id.imageView, height);
|
||||||
|
constraintSet.applyTo(layout);
|
||||||
|
|
||||||
|
if (attachment.hasError()) {
|
||||||
|
clearImage();
|
||||||
|
imageView.setImageResource(errorRes);
|
||||||
|
} else {
|
||||||
|
loadImage(item, attachment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearImage() {
|
||||||
|
GlideApp.with(imageView)
|
||||||
|
.clear(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadImage(ConversationMessageItem item,
|
||||||
|
AttachmentItem attachment) {
|
||||||
|
// these transformations can be optimized by writing our own
|
||||||
|
Transformation<Bitmap> transformation;
|
||||||
|
if (item.getText() == null) {
|
||||||
|
transformation = new MultiTransformation<>(new CenterCrop(),
|
||||||
|
new RoundedCornersTransformation(radiusSmall, 0,
|
||||||
|
isIncoming() ? TOP_LEFT : TOP_RIGHT),
|
||||||
|
new RoundedCornersTransformation(radiusBig, 0,
|
||||||
|
isIncoming() ? TOP_RIGHT : TOP_LEFT),
|
||||||
|
new RoundedCornersTransformation(radiusBig, 0, BOTTOM)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
transformation = new MultiTransformation<>(new CenterCrop(),
|
||||||
|
new RoundedCornersTransformation(radiusSmall, 0,
|
||||||
|
isIncoming() ? TOP_LEFT : TOP_RIGHT),
|
||||||
|
new RoundedCornersTransformation(radiusBig, 0,
|
||||||
|
isIncoming() ? TOP_RIGHT : TOP_LEFT)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GlideApp.with(imageView)
|
||||||
|
.load(attachment)
|
||||||
|
.diskCacheStrategy(NONE)
|
||||||
|
.error(errorRes)
|
||||||
|
.transform(transformation)
|
||||||
|
.transition(withCrossFade())
|
||||||
|
.into(imageView)
|
||||||
|
.waitForLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.briarproject.bramble.api.db.NoSuchContactException;
|
|||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -38,6 +39,7 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
private final ContactManager contactManager;
|
private final ContactManager contactManager;
|
||||||
|
private final AttachmentController attachmentController;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private ContactId contactId = null;
|
private ContactId contactId = null;
|
||||||
@@ -52,10 +54,12 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
@Inject
|
@Inject
|
||||||
ConversationViewModel(Application application,
|
ConversationViewModel(Application application,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
ContactManager contactManager) {
|
ContactManager contactManager, MessagingManager messagingManager) {
|
||||||
super(application);
|
super(application);
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
|
this.attachmentController = new AttachmentController(messagingManager,
|
||||||
|
application.getResources());
|
||||||
contactDeleted.setValue(false);
|
contactDeleted.setValue(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +100,10 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AttachmentController getAttachmentController() {
|
||||||
|
return attachmentController;
|
||||||
|
}
|
||||||
|
|
||||||
LiveData<Contact> getContact() {
|
LiveData<Contact> getContact() {
|
||||||
return contact;
|
return contact;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,16 @@ import org.briarproject.briar.api.forum.ForumInvitationRequest;
|
|||||||
import org.briarproject.briar.api.forum.ForumInvitationResponse;
|
import org.briarproject.briar.api.forum.ForumInvitationResponse;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest;
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest;
|
||||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.BLOG;
|
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.BLOG;
|
||||||
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.FORUM;
|
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.FORUM;
|
||||||
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.GROUP;
|
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.GROUP;
|
||||||
@@ -33,24 +37,33 @@ class ConversationVisitor implements
|
|||||||
|
|
||||||
private final Context ctx;
|
private final Context ctx;
|
||||||
private final TextCache textCache;
|
private final TextCache textCache;
|
||||||
|
private final AttachmentCache attachmentCache;
|
||||||
private final LiveData<String> contactName;
|
private final LiveData<String> contactName;
|
||||||
|
|
||||||
ConversationVisitor(Context ctx, TextCache textCache,
|
ConversationVisitor(Context ctx, TextCache textCache,
|
||||||
LiveData<String> contactName) {
|
AttachmentCache attachmentCache, LiveData<String> contactName) {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.textCache = textCache;
|
this.textCache = textCache;
|
||||||
|
this.attachmentCache = attachmentCache;
|
||||||
this.contactName = contactName;
|
this.contactName = contactName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ConversationItem visitPrivateMessageHeader(PrivateMessageHeader h) {
|
public ConversationItem visitPrivateMessageHeader(PrivateMessageHeader h) {
|
||||||
ConversationItem item;
|
ConversationItem item;
|
||||||
|
List<AttachmentItem> attachments;
|
||||||
|
if (h.getAttachmentHeaders().isEmpty()) {
|
||||||
|
attachments = emptyList();
|
||||||
|
} else {
|
||||||
|
attachments = attachmentCache
|
||||||
|
.getAttachmentItems(h.getId(), h.getAttachmentHeaders());
|
||||||
|
}
|
||||||
if (h.isLocal()) {
|
if (h.isLocal()) {
|
||||||
item = new ConversationMessageItem(
|
item = new ConversationMessageItem(
|
||||||
R.layout.list_item_conversation_msg_out, h);
|
R.layout.list_item_conversation_msg_out, h, attachments);
|
||||||
} else {
|
} else {
|
||||||
item = new ConversationMessageItem(
|
item = new ConversationMessageItem(
|
||||||
R.layout.list_item_conversation_msg_in, h);
|
R.layout.list_item_conversation_msg_in, h, attachments);
|
||||||
}
|
}
|
||||||
if (h.hasText()) {
|
if (h.hasText()) {
|
||||||
String text = textCache.getText(h.getId());
|
String text = textCache.getText(h.getId());
|
||||||
@@ -279,4 +292,10 @@ class ConversationVisitor implements
|
|||||||
@Nullable
|
@Nullable
|
||||||
String getText(MessageId m);
|
String getText(MessageId m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AttachmentCache {
|
||||||
|
@Nullable
|
||||||
|
List<AttachmentItem> getAttachmentItems(MessageId m,
|
||||||
|
List<AttachmentHeader> headers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package org.briarproject.briar.android.conversation.glide;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Priority;
|
||||||
|
import com.bumptech.glide.load.DataSource;
|
||||||
|
import com.bumptech.glide.load.data.DataFetcher;
|
||||||
|
|
||||||
|
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.MessageId;
|
||||||
|
import org.briarproject.briar.android.conversation.AttachmentItem;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static com.bumptech.glide.load.DataSource.LOCAL;
|
||||||
|
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.LogUtils.logException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class BriarDataFetcher implements DataFetcher<InputStream> {
|
||||||
|
|
||||||
|
private final static Logger LOG =
|
||||||
|
getLogger(BriarDataFetcher.class.getName());
|
||||||
|
|
||||||
|
private final MessagingManager messagingManager;
|
||||||
|
@DatabaseExecutor
|
||||||
|
private final Executor dbExecutor;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private AttachmentItem attachment;
|
||||||
|
@Nullable
|
||||||
|
private volatile InputStream inputStream;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public BriarDataFetcher(MessagingManager messagingManager,
|
||||||
|
@DatabaseExecutor Executor dbExecutor) {
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
|
this.dbExecutor = dbExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadData(Priority priority,
|
||||||
|
DataCallback<? super InputStream> callback) {
|
||||||
|
MessageId id = requireNonNull(attachment).getMessageId();
|
||||||
|
dbExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
inputStream = messagingManager.getAttachment(id).getStream();
|
||||||
|
callback.onDataReady(inputStream);
|
||||||
|
} catch (DbException e) {
|
||||||
|
callback.onLoadFailed(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanup() {
|
||||||
|
final InputStream stream = inputStream;
|
||||||
|
if (stream != null) {
|
||||||
|
try {
|
||||||
|
stream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
// does it make sense to cancel a database load?
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<InputStream> getDataClass() {
|
||||||
|
return InputStream.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSource getDataSource() {
|
||||||
|
return LOCAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttachment(AttachmentItem attachment) {
|
||||||
|
this.attachment = attachment;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.briarproject.briar.android.conversation.glide;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.GlideBuilder;
|
||||||
|
import com.bumptech.glide.Registry;
|
||||||
|
import com.bumptech.glide.annotation.GlideModule;
|
||||||
|
import com.bumptech.glide.module.AppGlideModule;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
|
import org.briarproject.briar.android.conversation.AttachmentItem;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import static android.util.Log.DEBUG;
|
||||||
|
import static android.util.Log.WARN;
|
||||||
|
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||||
|
|
||||||
|
@GlideModule
|
||||||
|
@NotNullByDefault
|
||||||
|
public final class BriarGlideModule extends AppGlideModule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerComponents(Context context, Glide glide,
|
||||||
|
Registry registry) {
|
||||||
|
BriarModelLoaderFactory factory =
|
||||||
|
new BriarModelLoaderFactory((BriarApplication) context);
|
||||||
|
registry.prepend(AttachmentItem.class, InputStream.class, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyOptions(Context context, GlideBuilder builder) {
|
||||||
|
builder.setLogLevel(IS_DEBUG_BUILD ? DEBUG : WARN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isManifestParsingEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.briarproject.briar.android.conversation.glide;
|
||||||
|
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.Options;
|
||||||
|
import com.bumptech.glide.load.model.ModelLoader;
|
||||||
|
import com.bumptech.glide.signature.ObjectKey;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
|
import org.briarproject.briar.android.conversation.AttachmentItem;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
public final class BriarModelLoader
|
||||||
|
implements ModelLoader<AttachmentItem, InputStream> {
|
||||||
|
|
||||||
|
private final BriarApplication app;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
BriarDataFetcher dataFetcher;
|
||||||
|
|
||||||
|
public BriarModelLoader(BriarApplication app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LoadData<InputStream> buildLoadData(AttachmentItem model, int width,
|
||||||
|
int height, Options options) {
|
||||||
|
app.getApplicationComponent().inject(this);
|
||||||
|
ObjectKey key = new ObjectKey(model.getMessageId());
|
||||||
|
dataFetcher.setAttachment(model);
|
||||||
|
return new LoadData<>(key, dataFetcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handles(AttachmentItem model) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.briarproject.briar.android.conversation.glide;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.model.ModelLoader;
|
||||||
|
import com.bumptech.glide.load.model.ModelLoaderFactory;
|
||||||
|
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
|
import org.briarproject.briar.android.conversation.AttachmentItem;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class BriarModelLoaderFactory
|
||||||
|
implements ModelLoaderFactory<AttachmentItem, InputStream> {
|
||||||
|
|
||||||
|
private final BriarApplication app;
|
||||||
|
|
||||||
|
public BriarModelLoaderFactory(BriarApplication app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModelLoader<AttachmentItem, InputStream> build(
|
||||||
|
MultiModelLoaderFactory multiFactory) {
|
||||||
|
return new BriarModelLoader(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void teardown() {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
9
briar-android/src/main/res/drawable/ic_image_broken.xml
Normal file
9
briar-android/src/main/res/drawable/ic_image_broken.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="200dp"
|
||||||
|
android:height="200dp"
|
||||||
|
android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#808080"
|
||||||
|
android:pathData="M21,5v6.59l-3,-3.01 -4,4.01 -4,-4 -4,4 -3,-3.01L3,5c0,-1.1 0.9,-2 2,-2h14c1.1,0 2,0.9 2,2zM18,11.42l3,3.01L21,19c0,1.1 -0.9,2 -2,2L5,21c-1.1,0 -2,-0.9 -2,-2v-6.58l3,2.99 4,-4 4,4 4,-3.99z"/>
|
||||||
|
</vector>
|
||||||
@@ -8,10 +8,10 @@
|
|||||||
android:topLeftRadius="@dimen/message_bubble_radius_top_inner"
|
android:topLeftRadius="@dimen/message_bubble_radius_top_inner"
|
||||||
android:topRightRadius="@dimen/message_bubble_radius_top_outer"/>
|
android:topRightRadius="@dimen/message_bubble_radius_top_outer"/>
|
||||||
<padding
|
<padding
|
||||||
android:bottom="@dimen/message_bubble_padding_bottom"
|
android:bottom="@dimen/message_bubble_border"
|
||||||
android:left="@dimen/message_bubble_padding_sides"
|
android:left="@dimen/message_bubble_border"
|
||||||
android:right="@dimen/message_bubble_padding_sides"
|
android:right="@dimen/message_bubble_border"
|
||||||
android:top="@dimen/message_bubble_padding_top"/>
|
android:top="@dimen/message_bubble_border"/>
|
||||||
<solid
|
<solid
|
||||||
android:color="@color/msg_in"/>
|
android:color="@color/msg_in"/>
|
||||||
<stroke
|
<stroke
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
android:topLeftRadius="@dimen/message_bubble_radius_top_outer"
|
android:topLeftRadius="@dimen/message_bubble_radius_top_outer"
|
||||||
android:topRightRadius="@dimen/message_bubble_radius_top_inner"/>
|
android:topRightRadius="@dimen/message_bubble_radius_top_inner"/>
|
||||||
<padding
|
<padding
|
||||||
android:bottom="@dimen/message_bubble_padding_bottom"
|
android:bottom="@dimen/message_bubble_border"
|
||||||
android:left="@dimen/message_bubble_padding_sides"
|
android:left="@dimen/message_bubble_border"
|
||||||
android:right="@dimen/message_bubble_padding_sides"
|
android:right="@dimen/message_bubble_border"
|
||||||
android:top="@dimen/message_bubble_padding_top"/>
|
android:top="@dimen/message_bubble_border"/>
|
||||||
<solid
|
<solid
|
||||||
android:color="@color/msg_out"/>
|
android:color="@color/msg_out"/>
|
||||||
<stroke
|
<stroke
|
||||||
|
|||||||
14
briar-android/src/main/res/drawable/msg_status_bubble.xml
Normal file
14
briar-android/src/main/res/drawable/msg_status_bubble.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners
|
||||||
|
android:radius="@dimen/message_bubble_radius_big"/>
|
||||||
|
<padding
|
||||||
|
android:bottom="3dp"
|
||||||
|
android:left="7dp"
|
||||||
|
android:right="7dp"
|
||||||
|
android:top="2dp"/>
|
||||||
|
<solid
|
||||||
|
android:color="@color/msg_status_bubble_background"/>
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
android:id="@+id/layout"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_margin"
|
||||||
|
android:layout_marginEnd="@dimen/message_bubble_margin_non_tail"
|
||||||
|
android:layout_marginLeft="@dimen/message_bubble_margin_tail"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_margin_non_tail"
|
||||||
|
android:layout_marginStart="@dimen/message_bubble_margin_tail"
|
||||||
|
android:layout_marginTop="@dimen/message_bubble_margin"
|
||||||
|
android:background="@drawable/msg_in"
|
||||||
|
android:elevation="@dimen/message_bubble_elevation">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="@dimen/message_bubble_image_default"
|
||||||
|
android:layout_height="@dimen/message_bubble_image_default"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/text"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
tools:src="@drawable/alerts_and_states_error"/>
|
||||||
|
|
||||||
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
style="@style/TextMessage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
|
||||||
|
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginTop="@dimen/message_bubble_padding_top_inner"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/imageView"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
||||||
|
tools:text="The text of a message which can sometimes be a bit longer as well"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/statusLayout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_padding_top"
|
||||||
|
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:background="@drawable/msg_status_bubble"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/imageView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="1.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/time"
|
||||||
|
style="@style/TextMessage.Timestamp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="Dec 24, 13:37"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
android:id="@+id/layout"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_margin"
|
||||||
|
android:layout_marginEnd="@dimen/message_bubble_margin_non_tail"
|
||||||
|
android:layout_marginLeft="@dimen/message_bubble_margin_tail"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_margin_non_tail"
|
||||||
|
android:layout_marginStart="@dimen/message_bubble_margin_tail"
|
||||||
|
android:layout_marginTop="@dimen/message_bubble_margin"
|
||||||
|
android:background="@drawable/msg_in"
|
||||||
|
android:elevation="@dimen/message_bubble_elevation">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="@dimen/message_bubble_image_default"
|
||||||
|
android:layout_height="@dimen/message_bubble_image_default"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/text"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
tools:src="@drawable/alerts_and_states_error"/>
|
||||||
|
|
||||||
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
style="@style/TextMessage"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
|
||||||
|
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginTop="@dimen/message_bubble_padding_top_inner"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/imageView"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
||||||
|
tools:text="The text of a message which can sometimes be a bit longer as well"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/statusLayout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
|
||||||
|
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="1.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/text">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/time"
|
||||||
|
style="@style/TextMessage.Timestamp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="Dec 24, 13:37"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
@@ -15,29 +15,55 @@
|
|||||||
android:background="@drawable/msg_in"
|
android:background="@drawable/msg_in"
|
||||||
android:elevation="@dimen/message_bubble_elevation">
|
android:elevation="@dimen/message_bubble_elevation">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="@dimen/message_bubble_image_default"
|
||||||
|
android:layout_height="@dimen/message_bubble_image_default"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/text"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
<com.vanniktech.emoji.EmojiTextView
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
style="@style/TextMessage"
|
style="@style/TextMessage"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
|
||||||
|
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginTop="@dimen/message_bubble_padding_top_inner"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/time"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintEnd_toEndOf="@+id/time"
|
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.0"
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
||||||
tools:text="Short message"/>
|
tools:text="The text of a message which can sometimes be a bit longer as well"/>
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/time"
|
android:id="@+id/statusLayout"
|
||||||
style="@style/TextMessage.Timestamp"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
|
||||||
|
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:orientation="horizontal"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="1.0"
|
app:layout_constraintHorizontal_bias="1.0"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/text"
|
app:layout_constraintTop_toBottomOf="@+id/text">
|
||||||
tools:text="Dec 24, 13:37"/>
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/time"
|
||||||
|
style="@style/TextMessage.Timestamp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="Dec 24, 13:37"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|||||||
@@ -21,39 +21,68 @@
|
|||||||
android:background="@drawable/msg_out"
|
android:background="@drawable/msg_out"
|
||||||
android:elevation="@dimen/message_bubble_elevation">
|
android:elevation="@dimen/message_bubble_elevation">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="@dimen/message_bubble_image_default"
|
||||||
|
android:layout_height="@dimen/message_bubble_image_default"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/text"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
tools:src="@drawable/alerts_and_states_error"/>
|
||||||
|
|
||||||
<com.vanniktech.emoji.EmojiTextView
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
style="@style/TextMessage"
|
style="@style/TextMessage"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
|
||||||
|
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginTop="@dimen/message_bubble_padding_top_inner"
|
||||||
android:textColor="@color/briar_text_primary_inverse"
|
android:textColor="@color/briar_text_primary_inverse"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/time"
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
||||||
tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."/>
|
tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."/>
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/time"
|
android:id="@+id/statusLayout"
|
||||||
style="@style/TextMessage.Timestamp"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
|
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
|
||||||
android:textColor="@color/private_message_date_inverse"
|
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/text"
|
app:layout_constraintTop_toBottomOf="@+id/text"
|
||||||
tools:text="Dec 24, 13:37"/>
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
<ImageView
|
<TextView
|
||||||
android:id="@+id/status"
|
android:id="@+id/time"
|
||||||
android:layout_width="wrap_content"
|
style="@style/TextMessage.Timestamp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_marginLeft="@dimen/margin_medium"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/margin_medium"
|
android:layout_marginEnd="6dp"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/time"
|
android:layout_marginRight="6dp"
|
||||||
app:layout_constraintStart_toEndOf="@+id/time"
|
android:textColor="@color/private_message_date_inverse"
|
||||||
app:layout_constraintTop_toTopOf="@+id/time"
|
tools:text="Dec 24, 13:37"/>
|
||||||
tools:ignore="ContentDescription"
|
|
||||||
tools:src="@drawable/message_delivered"/>
|
<ImageView
|
||||||
|
android:id="@+id/status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
tools:src="@drawable/message_delivered"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
<color name="msg_stroke_light">#cbcbcb</color>
|
<color name="msg_stroke_light">#cbcbcb</color>
|
||||||
<color name="msg_stroke_dark">#333333</color>
|
<color name="msg_stroke_dark">#333333</color>
|
||||||
<color name="msg_stroke">@color/msg_stroke_light</color>
|
<color name="msg_stroke">@color/msg_stroke_light</color>
|
||||||
|
<color name="msg_status_bubble_background">#66000000</color>
|
||||||
|
|
||||||
<!-- text colors -->
|
<!-- text colors -->
|
||||||
<color name="briar_text_link">@color/briar_blue_light</color>
|
<color name="briar_text_link">@color/briar_blue_light</color>
|
||||||
|
|||||||
@@ -43,9 +43,18 @@
|
|||||||
<dimen name="message_bubble_radius_top_inner">@dimen/message_bubble_radius_small</dimen>
|
<dimen name="message_bubble_radius_top_inner">@dimen/message_bubble_radius_small</dimen>
|
||||||
<dimen name="message_bubble_radius_top_outer">@dimen/message_bubble_radius_big</dimen>
|
<dimen name="message_bubble_radius_top_outer">@dimen/message_bubble_radius_big</dimen>
|
||||||
<dimen name="message_bubble_margin">6dp</dimen>
|
<dimen name="message_bubble_margin">6dp</dimen>
|
||||||
|
<dimen name="message_bubble_image_default">210dp</dimen>
|
||||||
|
<dimen name="message_bubble_image_min_width">150dp</dimen>
|
||||||
|
<dimen name="message_bubble_image_max_width">240dp</dimen>
|
||||||
|
<dimen name="message_bubble_image_min_height">100dp</dimen>
|
||||||
|
<dimen name="message_bubble_image_max_height">320dp</dimen>
|
||||||
|
<dimen name="message_bubble_border">2dp</dimen>
|
||||||
<dimen name="message_bubble_padding_sides">12dp</dimen>
|
<dimen name="message_bubble_padding_sides">12dp</dimen>
|
||||||
|
<dimen name="message_bubble_padding_sides_inner">10dp</dimen>
|
||||||
<dimen name="message_bubble_padding_top">6dp</dimen>
|
<dimen name="message_bubble_padding_top">6dp</dimen>
|
||||||
|
<dimen name="message_bubble_padding_top_inner">4dp</dimen>
|
||||||
<dimen name="message_bubble_padding_bottom">4dp</dimen>
|
<dimen name="message_bubble_padding_bottom">4dp</dimen>
|
||||||
|
<dimen name="message_bubble_padding_bottom_inner">2dp</dimen>
|
||||||
<dimen name="message_bubble_timestamp_margin">4dp</dimen>
|
<dimen name="message_bubble_timestamp_margin">4dp</dimen>
|
||||||
<dimen name="message_bubble_elevation">2dp</dimen>
|
<dimen name="message_bubble_elevation">2dp</dimen>
|
||||||
<dimen name="message_bubble_margin_tail">8dp</dimen>
|
<dimen name="message_bubble_margin_tail">8dp</dimen>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ dependencyVerification {
|
|||||||
'com.android.support:design:28.0.0:design-28.0.0.aar:7874ad1904eedc74aa41cffffb7f759d8990056f3bbbc9264911651c67c42f5f',
|
'com.android.support:design:28.0.0:design-28.0.0.aar:7874ad1904eedc74aa41cffffb7f759d8990056f3bbbc9264911651c67c42f5f',
|
||||||
'com.android.support:documentfile:28.0.0:documentfile-28.0.0.aar:47cdcd3e9302b7b064923f05487a5c03babbd9bbda4726b71e97791fab5d4779',
|
'com.android.support:documentfile:28.0.0:documentfile-28.0.0.aar:47cdcd3e9302b7b064923f05487a5c03babbd9bbda4726b71e97791fab5d4779',
|
||||||
'com.android.support:drawerlayout:28.0.0:drawerlayout-28.0.0.aar:8f6809afae4793550c37461c9810e954ae6a23dbb4d23e5333bf18148df1150a',
|
'com.android.support:drawerlayout:28.0.0:drawerlayout-28.0.0.aar:8f6809afae4793550c37461c9810e954ae6a23dbb4d23e5333bf18148df1150a',
|
||||||
|
'com.android.support:exifinterface:28.0.0:exifinterface-28.0.0.aar:bbf44e519edd6333a24a3285aa21fd00181b920b81ca8aa89a8899f03ab4d6b0',
|
||||||
'com.android.support:interpolator:28.0.0:interpolator-28.0.0.aar:7bc7ee86a0db39a4b51956f3e89842d2bd962118d57d779eb6ed6b34ba0677ea',
|
'com.android.support:interpolator:28.0.0:interpolator-28.0.0.aar:7bc7ee86a0db39a4b51956f3e89842d2bd962118d57d779eb6ed6b34ba0677ea',
|
||||||
'com.android.support:loader:28.0.0:loader-28.0.0.aar:920b85efd72dc33e915b0f88a883fe73b88483c6df8751a741e17611f2460341',
|
'com.android.support:loader:28.0.0:loader-28.0.0.aar:920b85efd72dc33e915b0f88a883fe73b88483c6df8751a741e17611f2460341',
|
||||||
'com.android.support:localbroadcastmanager:28.0.0:localbroadcastmanager-28.0.0.aar:d287c823af5fdde72c099fcfc5f630efe9687af7a914343ae6fd92de32c8a806',
|
'com.android.support:localbroadcastmanager:28.0.0:localbroadcastmanager-28.0.0.aar:d287c823af5fdde72c099fcfc5f630efe9687af7a914343ae6fd92de32c8a806',
|
||||||
@@ -84,6 +85,10 @@ dependencyVerification {
|
|||||||
'com.android.tools:repository:26.2.1:repository-26.2.1.jar:fa74dae09103faef703df38550ad8fa244c5b6d1bf90d6198be932292b3d9cc1',
|
'com.android.tools:repository:26.2.1:repository-26.2.1.jar:fa74dae09103faef703df38550ad8fa244c5b6d1bf90d6198be932292b3d9cc1',
|
||||||
'com.android.tools:sdk-common:26.2.1:sdk-common-26.2.1.jar:759d4b292ca69a35cf961fca377b54158fc6c88108978006999442e80a011cf4',
|
'com.android.tools:sdk-common:26.2.1:sdk-common-26.2.1.jar:759d4b292ca69a35cf961fca377b54158fc6c88108978006999442e80a011cf4',
|
||||||
'com.android.tools:sdklib:26.2.1:sdklib-26.2.1.jar:248df7ad5eac4aeb6f96c394c76760de4b7b89ac056e54d0c21a739368b91b45',
|
'com.android.tools:sdklib:26.2.1:sdklib-26.2.1.jar:248df7ad5eac4aeb6f96c394c76760de4b7b89ac056e54d0c21a739368b91b45',
|
||||||
|
'com.github.bumptech.glide:annotations:4.8.0:annotations-4.8.0.jar:4ea82e59874673105165820336c6ac268fc46149892486aad8e7a131a4449446',
|
||||||
|
'com.github.bumptech.glide:compiler:4.8.0:compiler-4.8.0.jar:1fa93dd0cf7ef0b8b98a59a67a1ee84915416c2d677d83a771ea3e32ad15e6bf',
|
||||||
|
'com.github.bumptech.glide:gifdecoder:4.8.0:gifdecoder-4.8.0.aar:b00c5454a023a9488ea49603930d9c25e09192e5ceaadf64977aa52946b3c1b4',
|
||||||
|
'com.github.bumptech.glide:glide:4.8.0:glide-4.8.0.aar:5ddf08b12cc43332e812988f16c2c39e7fce49d1c4d94b7948dcde7f00bf49d6',
|
||||||
'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.0:accessibility-test-framework-2.0.jar:cdf16ef8f5b8023d003ce3cc1b0d51bda737762e2dab2fedf43d1c4292353f7f',
|
'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.0:accessibility-test-framework-2.0.jar:cdf16ef8f5b8023d003ce3cc1b0d51bda737762e2dab2fedf43d1c4292353f7f',
|
||||||
'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.1:accessibility-test-framework-2.1.jar:7b0aa6ed7553597ce0610684a9f7eca8021eee218f2e2f427c04a7fbf5f920bd',
|
'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.1:accessibility-test-framework-2.1.jar:7b0aa6ed7553597ce0610684a9f7eca8021eee218f2e2f427c04a7fbf5f920bd',
|
||||||
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
|
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
|
||||||
@@ -125,6 +130,7 @@ dependencyVerification {
|
|||||||
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
|
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
|
||||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||||
'javax.xml.bind:jaxb-api:2.2.12-b140109.1041:jaxb-api-2.2.12-b140109.1041.jar:b5e60cd8b7b5ff01ce4a74c5dd008f4fbd14ced3495d0b47b85cfedc182211f2',
|
'javax.xml.bind:jaxb-api:2.2.12-b140109.1041:jaxb-api-2.2.12-b140109.1041.jar:b5e60cd8b7b5ff01ce4a74c5dd008f4fbd14ced3495d0b47b85cfedc182211f2',
|
||||||
|
'jp.wasabeef:glide-transformations:3.3.0:glide-transformations-3.3.0.aar:340c482364b84be768e7cb96975aa78b448b9f067913c8a23ae2339e0e908adf',
|
||||||
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
|
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
|
||||||
'nekohtml:nekohtml:1.9.6.2:nekohtml-1.9.6.2.jar:fdff6cfa9ed9cc911c842a5d2395f209ec621ef1239d46810e9e495809d3ae09',
|
'nekohtml:nekohtml:1.9.6.2:nekohtml-1.9.6.2.jar:fdff6cfa9ed9cc911c842a5d2395f209ec621ef1239d46810e9e495809d3ae09',
|
||||||
'nekohtml:xercesMinimal:1.9.6.2:xercesMinimal-1.9.6.2.jar:95b8b357d19f63797dd7d67622fd3f18374d64acbc6584faba1c7759a31e8438',
|
'nekohtml:xercesMinimal:1.9.6.2:xercesMinimal-1.9.6.2.jar:95b8b357d19f63797dd7d67622fd3f18374d64acbc6584faba1c7759a31e8438',
|
||||||
|
|||||||
Reference in New Issue
Block a user