diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java index bf28e2458a62492f4b05b5fb3bd6e4f85df58fd8..ed82c5adff3ed528d09dc47047decf5f59c5e296 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java @@ -98,10 +98,11 @@ class AttachmentController { */ List<AttachmentItem> getAttachmentItems( List<Pair<AttachmentHeader, Attachment>> attachments) { + boolean needsSize = attachments.size() == 1; List<AttachmentItem> items = new ArrayList<>(attachments.size()); for (Pair<AttachmentHeader, Attachment> a : attachments) { AttachmentItem item = - getAttachmentItem(a.getFirst(), a.getSecond()); + getAttachmentItem(a.getFirst(), a.getSecond(), needsSize); items.add(item); } return items; @@ -111,10 +112,22 @@ class AttachmentController { * Creates an {@link AttachmentItem} from the {@link Attachment}'s * {@link InputStream} which will be closed when this method returns. */ - AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a) { + AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a, + boolean needsSize) { MessageId messageId = h.getMessageId(); - Size size = new Size(); + if (!needsSize) { + String mimeType = h.getContentType(); + String extension = getExtensionFromMimeType(mimeType); + boolean hasError = false; + if (extension == null) { + extension = ""; + hasError = true; + } + return new AttachmentItem(messageId, 0, 0, mimeType, extension, 0, + 0, hasError); + } + Size size = new Size(); InputStream is = new BufferedInputStream(a.getStream()); is.mark(Integer.MAX_VALUE); try { @@ -144,14 +157,19 @@ class AttachmentController { getThumbnailSize(size.width, size.height, size.mimeType); } // get file extension - MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); - String extension = mimeTypeMap.getExtensionFromMimeType(size.mimeType); + String extension = getExtensionFromMimeType(size.mimeType); if (extension == null) { return new AttachmentItem(messageId, 0, 0, "", "", 0, 0, true); } return new AttachmentItem(messageId, size.width, size.height, - size.mimeType, extension, thumbnailSize.width, thumbnailSize.height, - size.error); + size.mimeType, extension, thumbnailSize.width, + thumbnailSize.height, size.error); + } + + @Nullable + private String getExtensionFromMimeType(String mimeType) { + MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); + return mimeTypeMap.getExtensionFromMimeType(mimeType); } /** diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java index 9f48b562838c64fb37b9bf3e4eeaff84c58b563b..f5f6ff37aeac3afabc3bd543407b7107107e80b3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java @@ -394,7 +394,7 @@ public class ConversationActivity extends BriarActivity textCache.put(id, text); } } - if (!h.getAttachmentHeaders().isEmpty()) { + if (h.getAttachmentHeaders().size() == 1) { List<AttachmentItem> items = attachmentController.get(id); if (items == null) { @@ -483,7 +483,7 @@ public class ConversationActivity extends BriarActivity try { List<Pair<AttachmentHeader, Attachment>> attachments = attachmentController.getMessageAttachments(headers); - // TODO move getting the items off to the IoExecutor + // TODO move getting the items off to IoExecutor, if size == 1 List<AttachmentItem> items = attachmentController.getAttachmentItems(attachments); displayMessageAttachments(messageId, items); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationAdapter.java index e185db3865d08f2a280c2f8405cd3110c4f9de6a..0194a2d7fdc63029d26a4e2fb5d40ac694137466 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationAdapter.java @@ -3,6 +3,7 @@ package org.briarproject.briar.android.conversation; import android.content.Context; import android.support.annotation.LayoutRes; import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView.RecycledViewPool; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; @@ -21,11 +22,17 @@ class ConversationAdapter extends BriarAdapter<ConversationItem, ConversationItemViewHolder> { private ConversationListener listener; + private final RecycledViewPool imageViewPool; + private final ImageItemDecoration imageItemDecoration; ConversationAdapter(Context ctx, ConversationListener conversationListener) { super(ctx, ConversationItem.class); listener = conversationListener; + // This shares the same pool for view recycling between all image lists + imageViewPool = new RecycledViewPool(); + // Share the item decoration as well + imageItemDecoration = new ImageItemDecoration(ctx); } @LayoutRes @@ -42,15 +49,17 @@ class ConversationAdapter type, viewGroup, false); switch (type) { case R.layout.list_item_conversation_msg_in: - return new ConversationMessageViewHolder(v, true); + return new ConversationMessageViewHolder(v, listener, true, + imageViewPool, imageItemDecoration); case R.layout.list_item_conversation_msg_out: - return new ConversationMessageViewHolder(v, false); + return new ConversationMessageViewHolder(v, listener, false, + imageViewPool, imageItemDecoration); case R.layout.list_item_conversation_notice_in: - return new ConversationNoticeViewHolder(v, true); + return new ConversationNoticeViewHolder(v, listener, true); case R.layout.list_item_conversation_notice_out: - return new ConversationNoticeViewHolder(v, false); + return new ConversationNoticeViewHolder(v, listener, false); case R.layout.list_item_conversation_request: - return new ConversationRequestViewHolder(v, true); + return new ConversationRequestViewHolder(v, listener, true); default: throw new IllegalArgumentException("Unknown ConversationItem"); } @@ -59,7 +68,7 @@ class ConversationAdapter @Override public void onBindViewHolder(ConversationItemViewHolder ui, int position) { ConversationItem item = items.get(position); - ui.bind(item, listener); + ui.bind(item); listener.onItemVisible(item); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationItemViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationItemViewHolder.java index 7d0e25d949ce1573ea67ba6a987e3d9a535b0636..8ffa2ab4b26d05a7229e7eb5e346274950acc67d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationItemViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationItemViewHolder.java @@ -18,14 +18,17 @@ import static org.briarproject.briar.android.util.UiUtils.formatDate; @NotNullByDefault abstract class ConversationItemViewHolder extends ViewHolder { + protected final ConversationListener listener; protected final ConstraintLayout layout; @Nullable private final OutItemViewHolder outViewHolder; private final TextView text; protected final TextView time; - ConversationItemViewHolder(View v, boolean isIncoming) { + ConversationItemViewHolder(View v, ConversationListener listener, + boolean isIncoming) { super(v); + this.listener = listener; this.outViewHolder = isIncoming ? null : new OutItemViewHolder(v); layout = v.findViewById(R.id.layout); text = v.findViewById(R.id.text); @@ -33,7 +36,7 @@ abstract class ConversationItemViewHolder extends ViewHolder { } @CallSuper - void bind(ConversationItem item, ConversationListener listener) { + void bind(ConversationItem item) { if (item.getText() != null) { text.setText(trim(item.getText())); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageViewHolder.java index abef0bffdb36a4afc6e66a4594cd2550d3a73844..94c80639a0857c3ba3f9d336a06ebb72dae6f544 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationMessageViewHolder.java @@ -1,63 +1,45 @@ package org.briarproject.briar.android.conversation; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.support.annotation.DrawableRes; import android.support.annotation.UiThread; import android.support.constraint.ConstraintSet; -import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.RecycledViewPool; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; - -import com.bumptech.glide.load.Transformation; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; -import org.briarproject.briar.android.conversation.glide.BriarImageTransformation; -import org.briarproject.briar.android.conversation.glide.GlideApp; -import static android.os.Build.VERSION.SDK_INT; -import static android.support.v4.view.ViewCompat.LAYOUT_DIRECTION_RTL; -import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE; -import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade; +import static android.support.constraint.ConstraintSet.WRAP_CONTENT; +import static android.support.v4.content.ContextCompat.getColor; @UiThread @NotNullByDefault class ConversationMessageViewHolder extends ConversationItemViewHolder { - @DrawableRes - private static final int ERROR_RES = R.drawable.ic_image_broken; - - private final ImageView imageView; + private final ImageAdapter adapter; private final ViewGroup statusLayout; private final int timeColor, timeColorBubble; - private final int radiusBig, radiusSmall; - private final boolean isRtl; private final ConstraintSet textConstraints = new ConstraintSet(); private final ConstraintSet imageConstraints = new ConstraintSet(); private final ConstraintSet imageTextConstraints = new ConstraintSet(); - ConversationMessageViewHolder(View v, boolean isIncoming) { - super(v, isIncoming); - imageView = v.findViewById(R.id.imageView); + ConversationMessageViewHolder(View v, ConversationListener listener, + boolean isIncoming, RecycledViewPool imageViewPool, + ImageItemDecoration imageItemDecoration) { + super(v, listener, isIncoming); 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); + + // image list + RecyclerView list = v.findViewById(R.id.imageList); + list.setRecycledViewPool(imageViewPool); + adapter = new ImageAdapter(v.getContext(), listener); + list.setAdapter(adapter); + list.addItemDecoration(imageItemDecoration); // remember original status text color timeColor = time.getCurrentTextColor(); - timeColorBubble = - ContextCompat.getColor(v.getContext(), R.color.briar_white); - - // find out if we are showing a RTL language, Use the configuration, - // because getting the layout direction of views is not reliable - Configuration config = - imageView.getContext().getResources().getConfiguration(); - isRtl = SDK_INT >= 17 && - config.getLayoutDirection() == LAYOUT_DIRECTION_RTL; + timeColorBubble = getColor(v.getContext(), R.color.briar_white); // clone constraint sets from layout files textConstraints @@ -77,85 +59,55 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder { } @Override - void bind(ConversationItem conversationItem, - ConversationListener listener) { - super.bind(conversationItem, listener); + void bind(ConversationItem conversationItem) { + super.bind(conversationItem); ConversationMessageItem item = (ConversationMessageItem) conversationItem; if (item.getAttachments().isEmpty()) { bindTextItem(); } else { - bindImageItem(item, listener); + 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); + resetStatusLayoutForText(); textConstraints.applyTo(layout); + adapter.clear(); } - private void bindImageItem(ConversationMessageItem item, - ConversationListener listener) { - // TODO show more than just the first image - AttachmentItem attachment = item.getAttachments().get(0); - + private void bindImageItem(ConversationMessageItem item) { ConstraintSet constraintSet; if (item.getText() == null) { - statusLayout - .setBackgroundResource(R.drawable.msg_status_bubble); + 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); + resetStatusLayoutForText(); 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(ERROR_RES); + if (item.getAttachments().size() == 1) { + // apply image size constraints for a single image + AttachmentItem attachment = item.getAttachments().get(0); + int width = attachment.getThumbnailWidth(); + int height = attachment.getThumbnailHeight(); + constraintSet.constrainWidth(R.id.imageList, width); + constraintSet.constrainHeight(R.id.imageList, height); } else { - loadImage(item, attachment, listener); + // bubble adapts to size of image list + constraintSet.constrainWidth(R.id.imageList, WRAP_CONTENT); + constraintSet.constrainHeight(R.id.imageList, WRAP_CONTENT); } + constraintSet.applyTo(layout); + adapter.setConversationItem(item); } - private void clearImage() { - GlideApp.with(imageView) - .clear(imageView); - imageView.setOnClickListener(null); - } - - private void loadImage(ConversationMessageItem item, - AttachmentItem attachment, ConversationListener listener) { - boolean leftCornerSmall = - (isIncoming() && !isRtl) || (!isIncoming() && isRtl); - boolean bottomRound = item.getText() == null; - Transformation<Bitmap> transformation = new BriarImageTransformation( - radiusSmall, radiusBig, leftCornerSmall, bottomRound); - - GlideApp.with(imageView) - .load(attachment) - .diskCacheStrategy(NONE) - .error(ERROR_RES) - .transform(transformation) - .transition(withCrossFade()) - .into(imageView) - .waitForLayout(); - imageView.setOnClickListener( - view -> listener.onAttachmentClicked(view, item, attachment)); + private void resetStatusLayoutForText() { + statusLayout.setBackgroundResource(0); + // also reset padding (the background drawable defines some) + statusLayout.setPadding(0, 0, 0, 0); + time.setTextColor(timeColor); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationNoticeViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationNoticeViewHolder.java index 75b58bcca33a3cde7f7239540b2ec64673591ab2..8f34e168be712d794b509300de090b9bdf6c755b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationNoticeViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationNoticeViewHolder.java @@ -19,16 +19,17 @@ class ConversationNoticeViewHolder extends ConversationItemViewHolder { private final TextView msgText; - ConversationNoticeViewHolder(View v, boolean isIncoming) { - super(v, isIncoming); + ConversationNoticeViewHolder(View v, ConversationListener listener, + boolean isIncoming) { + super(v, listener, isIncoming); msgText = v.findViewById(R.id.msgText); } @Override @CallSuper - void bind(ConversationItem item, ConversationListener listener) { + void bind(ConversationItem item) { ConversationNoticeItem notice = (ConversationNoticeItem) item; - super.bind(notice, listener); + super.bind(notice); String text = notice.getMsgText(); if (isNullOrEmpty(text)) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationRequestViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationRequestViewHolder.java index 76637131fdbddce5b98064b3ae0f08ebf6ccb2d8..4afaba399aeb6901820f745d978d01ec37b35c84 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationRequestViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationRequestViewHolder.java @@ -17,16 +17,17 @@ class ConversationRequestViewHolder extends ConversationNoticeViewHolder { private final Button acceptButton; private final Button declineButton; - ConversationRequestViewHolder(View v, boolean isIncoming) { - super(v, isIncoming); + ConversationRequestViewHolder(View v, ConversationListener listener, + boolean isIncoming) { + super(v, listener, isIncoming); acceptButton = v.findViewById(R.id.acceptButton); declineButton = v.findViewById(R.id.declineButton); } @Override - void bind(ConversationItem item, ConversationListener listener) { + void bind(ConversationItem item) { ConversationRequestItem request = (ConversationRequestItem) item; - super.bind(request, listener); + super.bind(request); if (request.wasAnswered() && request.canBeOpened()) { acceptButton.setVisibility(VISIBLE); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java index 7070121572f9472462bc531e8471567ab5ebd271..732c9fb7e32b5eb114d3a55f25c2de2ff6d49e6d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java @@ -160,9 +160,11 @@ public class ConversationViewModel extends AndroidViewModel { 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); + createAttachmentHeader(groupId, uri, timestamp, + needsSize); if (pair == null) continue; attachments.add(pair.getFirst()); items.add(pair.getSecond()); @@ -175,7 +177,7 @@ public class ConversationViewModel extends AndroidViewModel { @Nullable @DatabaseExecutor private Pair<AttachmentHeader, AttachmentItem> createAttachmentHeader( - GroupId groupId, Uri uri, long timestamp) { + GroupId groupId, Uri uri, long timestamp, boolean needsSize) { InputStream is = null; try { ContentResolver contentResolver = @@ -191,7 +193,7 @@ public class ConversationViewModel extends AndroidViewModel { is = contentResolver.openInputStream(uri); if (is == null) throw new IOException(); AttachmentItem item = attachmentController - .getAttachmentItem(h, new Attachment(is)); + .getAttachmentItem(h, new Attachment(is), needsSize); return new Pair<>(h, item); } catch (DbException | IOException e) { logException(LOG, WARNING, e); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..8c9e15d30ed42eea7ae20666bd894681cba2cf6c --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageAdapter.java @@ -0,0 +1,155 @@ +package org.briarproject.briar.android.conversation; + +import android.content.Context; +import android.content.res.Resources; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView.Adapter; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.R; +import org.briarproject.briar.android.conversation.glide.Radii; + +import java.util.ArrayList; +import java.util.List; + +import static android.content.Context.WINDOW_SERVICE; +import static java.util.Objects.requireNonNull; +import static org.briarproject.briar.android.util.UiUtils.isRtl; + +@NotNullByDefault +class ImageAdapter extends Adapter<ImageViewHolder> { + + private final List<AttachmentItem> items = new ArrayList<>(); + private final ConversationListener listener; + private final int imageSize; + private final int radiusBig, radiusSmall; + private final boolean isRtl; + @Nullable + private ConversationMessageItem conversationItem; + + ImageAdapter(Context ctx, ConversationListener listener) { + this.listener = listener; + imageSize = getImageSize(ctx); + Resources res = ctx.getResources(); + radiusBig = + res.getDimensionPixelSize(R.dimen.message_bubble_radius_big); + radiusSmall = + res.getDimensionPixelSize(R.dimen.message_bubble_radius_small); + isRtl = isRtl(ctx); + } + + @Override + public ImageViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) { + View v = LayoutInflater.from(viewGroup.getContext()).inflate( + R.layout.list_item_image, viewGroup, false); + return new ImageViewHolder(v, imageSize); + } + + @Override + public void onBindViewHolder(ImageViewHolder imageViewHolder, + int position) { + // get item + requireNonNull(conversationItem); + AttachmentItem item = items.get(position); + // set onClick listener + imageViewHolder.itemView.setOnClickListener(v -> + listener.onAttachmentClicked(v, conversationItem, item) + ); + // bind view holder + int size = items.size(); + boolean isIncoming = conversationItem.isIncoming(); + boolean hasText = conversationItem.getText() != null; + Radii r = getRadii(position, size, isIncoming, hasText); + imageViewHolder.bind(item, r, size == 1, singleInRow(position, size)); + } + + @Override + public int getItemCount() { + return items.size(); + } + + void setConversationItem(ConversationMessageItem item) { + this.conversationItem = item; + this.items.clear(); + this.items.addAll(item.getAttachments()); + notifyDataSetChanged(); + } + + private int getImageSize(Context ctx) { + Resources res = ctx.getResources(); + WindowManager windowManager = + (WindowManager) ctx.getSystemService(WINDOW_SERVICE); + DisplayMetrics displayMetrics = new DisplayMetrics(); + if (windowManager == null) { + return res.getDimensionPixelSize( + R.dimen.message_bubble_image_default); + } + windowManager.getDefaultDisplay().getMetrics(displayMetrics); + int imageSize = displayMetrics.widthPixels / 3; + int maxSize = res.getDimensionPixelSize( + R.dimen.message_bubble_image_max_width); + return Math.min(imageSize, maxSize); + } + + private Radii getRadii(int pos, int num, boolean isIncoming, + boolean hasText) { + boolean left = isLeft(pos); + boolean single = num == 1; + // Top Row + int topLeft; + int topRight; + if (single) { + topLeft = isIncoming ? radiusSmall : radiusBig; + topRight = !isIncoming ? radiusSmall : radiusBig; + } else if (isTopRow(pos)) { + topLeft = left ? (isIncoming ? radiusSmall : radiusBig) : 0; + topRight = !left ? (!isIncoming ? radiusSmall : radiusBig) : 0; + } else { + topLeft = 0; + topRight = 0; + } + // Bottom Row + boolean singleInRow = singleInRow(pos, num); + int bottomLeft; + int bottomRight; + if (!hasText && isBottomRow(pos, num)) { + bottomLeft = singleInRow || left ? radiusBig : 0; + bottomRight = singleInRow || !left ? radiusBig : 0; + } else { + bottomLeft = 0; + bottomRight = 0; + } + if (isRtl) return new Radii(topRight, topLeft, bottomRight, bottomLeft); + return new Radii(topLeft, topRight, bottomLeft, bottomRight); + } + + void clear() { + items.clear(); + notifyDataSetChanged(); + } + + static boolean isTopRow(int pos) { + return pos < 2; + } + + static boolean isLeft(int pos) { + return pos % 2 == 0; + } + + static boolean isBottomRow(int pos, int num) { + return num % 2 == 0 ? + pos >= num - 2 : // last two, if even + pos > num - 2; // last one, if odd + } + + static boolean singleInRow(int pos, int num) { + // last item of an odd number + return num % 2 != 0 && pos == num -1; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageItemDecoration.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageItemDecoration.java new file mode 100644 index 0000000000000000000000000000000000000000..92df42e4037a52537143529df49439401f62ce62 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageItemDecoration.java @@ -0,0 +1,54 @@ +package org.briarproject.briar.android.conversation; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.ItemDecoration; +import android.support.v7.widget.RecyclerView.State; +import android.view.View; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.R; +import org.briarproject.briar.android.util.UiUtils; + +import static org.briarproject.briar.android.conversation.ImageAdapter.isBottomRow; +import static org.briarproject.briar.android.conversation.ImageAdapter.isLeft; +import static org.briarproject.briar.android.conversation.ImageAdapter.isTopRow; +import static org.briarproject.briar.android.conversation.ImageAdapter.singleInRow; + +@NotNullByDefault +class ImageItemDecoration extends ItemDecoration { + + private final int border; + private final boolean isRtl; + + ImageItemDecoration(Context ctx) { + Resources res = ctx.getResources(); + + // for pixel perfection, add a pixel to the border if it has an odd size + int b = res.getDimensionPixelSize(R.dimen.message_bubble_border); + int realBorderSize = b % 2 == 0 ? b : b + 1; + + // we are applying half the border around the insides of each image + // to prevent differently sized images looking slightly broken + border = realBorderSize / 2; + + // find out if we are showing a RTL language + isRtl = UiUtils.isRtl(ctx); + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, + State state) { + if (state.getItemCount() == 1) return; + int pos = parent.getChildAdapterPosition(view); + int num = state.getItemCount(); + boolean start = isLeft(pos) ^ isRtl; + outRect.top = isTopRow(pos) ? 0 : border; + outRect.left = start ? 0 : border; + outRect.right = start && !singleInRow(pos, num) ? border : 0; + outRect.bottom = isBottomRow(pos, num) ? 0 : border; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..301fa3c13f4550ad4466fd4865ac74e8445d073f --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java @@ -0,0 +1,70 @@ +package org.briarproject.briar.android.conversation; + +import android.graphics.Bitmap; +import android.support.annotation.DrawableRes; +import android.support.v7.widget.RecyclerView.ViewHolder; +import android.support.v7.widget.StaggeredGridLayoutManager.LayoutParams; +import android.view.View; +import android.widget.ImageView; + +import com.bumptech.glide.load.Transformation; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.R; +import org.briarproject.briar.android.conversation.glide.BriarImageTransformation; +import org.briarproject.briar.android.conversation.glide.GlideApp; +import org.briarproject.briar.android.conversation.glide.Radii; + +import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE; +import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade; + +@NotNullByDefault +class ImageViewHolder extends ViewHolder { + + @DrawableRes + private static final int ERROR_RES = R.drawable.ic_image_broken; + + protected final ImageView imageView; + private final int imageSize; + + ImageViewHolder(View v, int imageSize) { + super(v); + imageView = v.findViewById(R.id.imageView); + this.imageSize = imageSize; + } + + void bind(AttachmentItem attachment, Radii r, boolean single, + boolean needsStretch) { + if (attachment.hasError()) { + GlideApp.with(imageView) + .clear(imageView); + imageView.setImageResource(ERROR_RES); + } else { + setImageViewDimensions(attachment, single, needsStretch); + loadImage(attachment, r); + } + } + + private void setImageViewDimensions(AttachmentItem a, boolean single, + boolean needsStretch) { + LayoutParams params = (LayoutParams) imageView.getLayoutParams(); + int width = needsStretch ? imageSize * 2 : imageSize; + params.width = single ? a.getThumbnailWidth() : width; + params.height = single ? a.getThumbnailHeight() : imageSize; + params.setFullSpan(!single && needsStretch); + imageView.setLayoutParams(params); + } + + private void loadImage(AttachmentItem a, Radii r) { + Transformation<Bitmap> transformation = new BriarImageTransformation(r); + GlideApp.with(imageView) + .load(a) + .diskCacheStrategy(NONE) + .error(ERROR_RES) + .transform(transformation) + .transition(withCrossFade()) + .into(imageView) + .waitForLayout(); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java index 694887c911460e4decc98b5908c5fdd8514c52e4..d3cc620196f439edd1dfb3e576902440b8d0f5c5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewModel.java @@ -48,10 +48,10 @@ public class ImageViewModel extends AndroidViewModel { @IoExecutor private final Executor ioExecutor; - private MutableLiveData<Boolean> saveState = new MutableLiveData<>(); + private final MutableLiveData<Boolean> saveState = new MutableLiveData<>(); @Inject - public ImageViewModel(Application application, + ImageViewModel(Application application, MessagingManager messagingManager, @DatabaseExecutor Executor dbExecutor, @IoExecutor Executor ioExecutor) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarImageTransformation.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarImageTransformation.java index 5488efa402e09a0705d6f74243a5a75c1c6aac2c..c2bb1dbc0d8cbb2fd00d99dd611f1af4bd85d997 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarImageTransformation.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/BriarImageTransformation.java @@ -7,10 +7,8 @@ import com.bumptech.glide.load.resource.bitmap.CenterCrop; public class BriarImageTransformation extends MultiTransformation<Bitmap> { - public BriarImageTransformation(int smallRadius, int radius, - boolean leftCornerSmall, boolean bottomRound) { - super(new CenterCrop(), new ImageCornerTransformation( - smallRadius, radius, leftCornerSmall, bottomRound)); + public BriarImageTransformation(Radii r) { + super(new CenterCrop(), new CustomCornersTransformation(r)); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/CustomCornersTransformation.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/CustomCornersTransformation.java new file mode 100644 index 0000000000000000000000000000000000000000..fc013750c0ccf202ed22297ec14ed360881791e0 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/CustomCornersTransformation.java @@ -0,0 +1,128 @@ +package org.briarproject.briar.android.conversation.glide; + +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.support.annotation.NonNull; + +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.security.MessageDigest; + +import javax.annotation.concurrent.Immutable; + +import static android.graphics.Bitmap.Config.ARGB_8888; +import static android.graphics.Shader.TileMode.CLAMP; + +@Immutable +@NotNullByDefault +public class CustomCornersTransformation extends BitmapTransformation { + + private static final String ID = CustomCornersTransformation.class.getName(); + + private final Radii radii; + + public CustomCornersTransformation(Radii radii) { + this.radii = radii; + } + + @Override + protected Bitmap transform(BitmapPool pool, Bitmap toTransform, + int outWidth, int outHeight) { + int width = toTransform.getWidth(); + int height = toTransform.getHeight(); + + Bitmap bitmap = pool.get(width, height, ARGB_8888); + bitmap.setHasAlpha(true); + + Canvas canvas = new Canvas(bitmap); + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setShader(new BitmapShader(toTransform, CLAMP, CLAMP)); + drawRect(canvas, paint, width, height); + return bitmap; + } + + private void drawRect(Canvas canvas, Paint paint, float width, + float height) { + drawTopLeft(canvas, paint, radii.topLeft, width, height); + drawTopRight(canvas, paint, radii.topRight, width, height); + drawBottomLeft(canvas, paint, radii.bottomLeft, width, height); + drawBottomRight(canvas, paint, radii.bottomRight, width, height); + } + + private void drawTopLeft(Canvas canvas, Paint paint, int radius, + float width, float height) { + RectF rect = new RectF( + 0, + 0, + width / 2 + radius + 1, + height / 2 + radius + 1 + ); + if (radius == 0) canvas.drawRect(rect, paint); + else canvas.drawRoundRect(rect, radius, radius, paint); + } + + private void drawTopRight(Canvas canvas, Paint paint, int radius, + float width, float height) { + RectF rect = new RectF( + width / 2 - radius, + 0, + width, + height / 2 + radius + 1 + ); + if (radius == 0) canvas.drawRect(rect, paint); + else canvas.drawRoundRect(rect, radius, radius, paint); + } + + private void drawBottomLeft(Canvas canvas, Paint paint, int radius, + float width, float height) { + RectF rect = new RectF( + 0, + height / 2 - radius, + width / 2 + radius + 1, + height + ); + if (radius == 0) canvas.drawRect(rect, paint); + else canvas.drawRoundRect(rect, radius, radius, paint); + } + + private void drawBottomRight(Canvas canvas, Paint paint, int radius, + float width, float height) { + RectF rect = new RectF( + width / 2 - radius, + height / 2 - radius, + width, + height + ); + if (radius == 0) canvas.drawRect(rect, paint); + else canvas.drawRoundRect(rect, radius, radius, paint); + } + + @Override + public String toString() { + return "ImageCornerTransformation(" + radii + ")"; + } + + @Override + public boolean equals(Object o) { + return o instanceof CustomCornersTransformation && + radii.equals(((CustomCornersTransformation) o).radii); + } + + @Override + public int hashCode() { + return ID.hashCode() + radii.hashCode(); + } + + @Override + public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { + messageDigest.update((ID + radii).getBytes(CHARSET)); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/ImageCornerTransformation.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/ImageCornerTransformation.java deleted file mode 100644 index b41c0cf3177b253036d689fd1c4baafc33340df9..0000000000000000000000000000000000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/ImageCornerTransformation.java +++ /dev/null @@ -1,111 +0,0 @@ -package org.briarproject.briar.android.conversation.glide; - -import android.graphics.Bitmap; -import android.graphics.BitmapShader; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.support.annotation.NonNull; - -import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; -import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; - -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; - -import java.security.MessageDigest; - -import javax.annotation.concurrent.Immutable; - -import static android.graphics.Bitmap.Config.ARGB_8888; -import static android.graphics.Shader.TileMode.CLAMP; - -@Immutable -@NotNullByDefault -class ImageCornerTransformation extends BitmapTransformation { - - private static final String ID = ImageCornerTransformation.class.getName(); - - private final int smallRadius, radius; - private final boolean leftCornerSmall, bottomRound; - - ImageCornerTransformation(int smallRadius, int radius, - boolean leftCornerSmall, boolean bottomRound) { - this.smallRadius = smallRadius; - this.radius = radius; - this.leftCornerSmall = leftCornerSmall; - this.bottomRound = bottomRound; - } - - @Override - protected Bitmap transform(BitmapPool pool, Bitmap toTransform, - int outWidth, int outHeight) { - int width = toTransform.getWidth(); - int height = toTransform.getHeight(); - - Bitmap bitmap = pool.get(width, height, ARGB_8888); - bitmap.setHasAlpha(true); - - Canvas canvas = new Canvas(bitmap); - Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setShader(new BitmapShader(toTransform, CLAMP, CLAMP)); - drawRect(canvas, paint, width, height); - return bitmap; - } - - private void drawRect(Canvas canvas, Paint paint, float width, - float height) { - drawSmallCorner(canvas, paint, width); - drawBigCorners(canvas, paint, width, height); - } - - private void drawSmallCorner(Canvas canvas, Paint paint, float width) { - float left = leftCornerSmall ? 0 : width - radius; - float right = leftCornerSmall ? radius : width; - canvas.drawRoundRect(new RectF(left, 0, right, radius), - smallRadius, smallRadius, paint); - } - - private void drawBigCorners(Canvas canvas, Paint paint, float width, - float height) { - float top = bottomRound ? 0 : radius; - RectF rect = new RectF(0, top, width, height); - if (bottomRound) { - canvas.drawRoundRect(rect, radius, radius, paint); - } else { - canvas.drawRect(rect, paint); - canvas.drawRoundRect(new RectF(0, 0, width, radius * 2), - radius, radius, paint); - } - } - - @Override - public String toString() { - return "ImageCornerTransformation(smallRadius=" + smallRadius + - ", radius=" + radius + ", leftCornerSmall=" + leftCornerSmall + - ", bottomRound=" + bottomRound + ")"; - } - - @Override - public boolean equals(Object o) { - return o instanceof ImageCornerTransformation && - ((ImageCornerTransformation) o).smallRadius == smallRadius && - ((ImageCornerTransformation) o).radius == radius && - ((ImageCornerTransformation) o).leftCornerSmall == - leftCornerSmall && - ((ImageCornerTransformation) o).bottomRound == bottomRound; - } - - @Override - public int hashCode() { - return ID.hashCode() + (smallRadius << 16) ^ (radius << 2) ^ - (leftCornerSmall ? 2 : 0) ^ (bottomRound ? 1 : 0); - } - - @Override - public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { - messageDigest.update((ID + '|' + smallRadius + '|' + radius + '|' + - leftCornerSmall + '|' + bottomRound).getBytes(CHARSET)); - } - -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/Radii.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/Radii.java new file mode 100644 index 0000000000000000000000000000000000000000..318140ff0149422a26bdaab01c1fdf9abf343f26 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/glide/Radii.java @@ -0,0 +1,41 @@ +package org.briarproject.briar.android.conversation.glide; + +import android.support.annotation.Nullable; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +@NotNullByDefault +public class Radii { + + public final int topLeft, topRight, bottomLeft, bottomRight; + + public Radii(int topLeft, int topRight, int bottomLeft, int bottomRight) { + this.topLeft = topLeft; + this.topRight = topRight; + this.bottomLeft = bottomLeft; + this.bottomRight = bottomRight; + } + + @Override + public boolean equals(@Nullable Object o) { + return o instanceof Radii && + topLeft == ((Radii) o).topLeft && + topRight == ((Radii) o).topRight && + bottomLeft == ((Radii) o).bottomLeft && + bottomRight == ((Radii) o).bottomRight; + } + + @Override + public int hashCode() { + return topLeft << 24 ^ topRight << 16 ^ bottomLeft << 8 ^ bottomRight; + } + + @Override + public String toString() { + return "Radii(topLeft=" + topLeft + + ",topRight=" + topRight + + ",bottomLeft=" + bottomLeft + + ",bottomRight=" + bottomRight; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java index b23f24dcde92b8fddef180d17d1cf91220ad20d2..b57a1c9c181b7d88142446cbb7d12fc81a70c51b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java @@ -56,6 +56,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.os.Build.MANUFACTURER; import static android.os.Build.VERSION.SDK_INT; import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS; +import static android.support.v4.view.ViewCompat.LAYOUT_DIRECTION_RTL; import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_AUTO; import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_NO; @@ -372,4 +373,10 @@ public class UiUtils { }); } + public static boolean isRtl(Context ctx) { + if (SDK_INT < 17) return false; + return ctx.getResources().getConfiguration().getLayoutDirection() == + LAYOUT_DIRECTION_RTL; + } + } diff --git a/briar-android/src/main/res/layout/list_item_conversation_msg_image.xml b/briar-android/src/main/res/layout/list_item_conversation_msg_image.xml index c36a3080c5c3b8e004ffd5566dda27d74269e4bc..c7459d36fc7353d74fa21691a4814526030dca13 100644 --- a/briar-android/src/main/res/layout/list_item_conversation_msg_image.xml +++ b/briar-android/src/main/res/layout/list_item_conversation_msg_image.xml @@ -15,15 +15,19 @@ android:background="@drawable/msg_in" android:elevation="@dimen/message_bubble_elevation"> - <ImageView - android:id="@+id/imageView" + <android.support.v7.widget.RecyclerView + android:id="@+id/imageList" android:layout_width="@dimen/message_bubble_image_default" android:layout_height="@dimen/message_bubble_image_default" + android:orientation="vertical" + app:layoutManager="android.support.v7.widget.StaggeredGridLayoutManager" app:layout_constraintBottom_toTopOf="@+id/text" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + app:spanCount="2" tools:ignore="ContentDescription" - tools:src="@drawable/alerts_and_states_error"/> + tools:listitem="@layout/list_item_image"/> <com.vanniktech.emoji.EmojiTextView android:id="@+id/text" @@ -38,10 +42,10 @@ android:visibility="gone" app:layout_constrainedWidth="true" app:layout_constraintBottom_toTopOf="@+id/statusLayout" - app:layout_constraintEnd_toEndOf="@+id/imageView" + app:layout_constraintEnd_toEndOf="@+id/imageList" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/imageView" + app:layout_constraintTop_toBottomOf="@+id/imageList" tools:text="The text of a message which can sometimes be a bit longer as well"/> <LinearLayout @@ -53,7 +57,7 @@ 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_constraintBottom_toBottomOf="@+id/imageList" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="parent"> diff --git a/briar-android/src/main/res/layout/list_item_conversation_msg_image_text.xml b/briar-android/src/main/res/layout/list_item_conversation_msg_image_text.xml index 5507d041da0fa1861548d7e3a4f9a799af55d7e6..e90bd02211dcc6ef4f37bf0ee09b2d7cbcebbeea 100644 --- a/briar-android/src/main/res/layout/list_item_conversation_msg_image_text.xml +++ b/briar-android/src/main/res/layout/list_item_conversation_msg_image_text.xml @@ -15,15 +15,19 @@ android:background="@drawable/msg_in" android:elevation="@dimen/message_bubble_elevation"> - <ImageView - android:id="@+id/imageView" + <android.support.v7.widget.RecyclerView + android:id="@+id/imageList" android:layout_width="@dimen/message_bubble_image_default" android:layout_height="@dimen/message_bubble_image_default" + android:orientation="vertical" + app:layoutManager="android.support.v7.widget.StaggeredGridLayoutManager" app:layout_constraintBottom_toTopOf="@+id/text" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + app:spanCount="2" tools:ignore="ContentDescription" - tools:src="@drawable/alerts_and_states_error"/> + tools:listitem="@layout/list_item_image"/> <com.vanniktech.emoji.EmojiTextView android:id="@+id/text" @@ -37,10 +41,10 @@ android:textColor="?android:attr/textColorPrimary" app:layout_constrainedWidth="true" app:layout_constraintBottom_toTopOf="@+id/statusLayout" - app:layout_constraintEnd_toEndOf="@+id/imageView" + app:layout_constraintEnd_toEndOf="@+id/imageList" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/imageView" + app:layout_constraintTop_toBottomOf="@+id/imageList" tools:text="The text of a message which can sometimes be a bit longer as well"/> <LinearLayout diff --git a/briar-android/src/main/res/layout/list_item_conversation_msg_in.xml b/briar-android/src/main/res/layout/list_item_conversation_msg_in.xml index 4e3950a870172fdea5cffa277e507915e83f0dc3..17f3937085187325fe737537b8864575b0bc0fcf 100644 --- a/briar-android/src/main/res/layout/list_item_conversation_msg_in.xml +++ b/briar-android/src/main/res/layout/list_item_conversation_msg_in.xml @@ -15,15 +15,20 @@ android:background="@drawable/msg_in" android:elevation="@dimen/message_bubble_elevation"> - <ImageView - android:id="@+id/imageView" + <android.support.v7.widget.RecyclerView + android:id="@+id/imageList" android:layout_width="@dimen/message_bubble_image_default" android:layout_height="@dimen/message_bubble_image_default" + android:orientation="vertical" android:visibility="gone" + app:layoutManager="android.support.v7.widget.StaggeredGridLayoutManager" app:layout_constraintBottom_toTopOf="@+id/text" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - tools:ignore="ContentDescription"/> + app:spanCount="2" + tools:ignore="ContentDescription" + tools:listitem="@layout/list_item_image"/> <com.vanniktech.emoji.EmojiTextView android:id="@+id/text" @@ -40,7 +45,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/imageView" + app:layout_constraintTop_toBottomOf="@+id/imageList" tools:text="The text of a message which can sometimes be a bit longer as well"/> <LinearLayout diff --git a/briar-android/src/main/res/layout/list_item_conversation_msg_out.xml b/briar-android/src/main/res/layout/list_item_conversation_msg_out.xml index d0dc1736e0e00186dab8ce63aafdbb7edce38934..71fcde591a3a26305ecaa186da3bbfc1856e2fbe 100644 --- a/briar-android/src/main/res/layout/list_item_conversation_msg_out.xml +++ b/briar-android/src/main/res/layout/list_item_conversation_msg_out.xml @@ -21,14 +21,18 @@ android:background="@drawable/msg_out" android:elevation="@dimen/message_bubble_elevation"> - <ImageView - android:id="@+id/imageView" + <android.support.v7.widget.RecyclerView + android:id="@+id/imageList" android:layout_width="@dimen/message_bubble_image_default" android:layout_height="@dimen/message_bubble_image_default" + android:orientation="vertical" android:visibility="gone" + app:layoutManager="android.support.v7.widget.StaggeredGridLayoutManager" app:layout_constraintBottom_toTopOf="@+id/text" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + app:spanCount="2" tools:ignore="ContentDescription" tools:src="@drawable/alerts_and_states_error"/> @@ -47,7 +51,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/imageView" + app:layout_constraintTop_toBottomOf="@+id/imageList" tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."/> <LinearLayout diff --git a/briar-android/src/main/res/layout/list_item_image.xml b/briar-android/src/main/res/layout/list_item_image.xml new file mode 100644 index 0000000000000000000000000000000000000000..275d9df1d1587bc4273de3fe3107fe7fe16a72b8 --- /dev/null +++ b/briar-android/src/main/res/layout/list_item_image.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<ImageView + android:id="@+id/imageView" + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="@dimen/message_bubble_image_default" + android:layout_height="@dimen/message_bubble_image_default" + android:scaleType="centerCrop" + tools:ignore="ContentDescription" + tools:srcCompat="@tools:sample/avatars"/> diff --git a/briar-android/src/main/res/values/dimens.xml b/briar-android/src/main/res/values/dimens.xml index 81f7d4377f355872cad298ab03b76e25fc908541..8828a0799449e809127a0ea4d4f78aa21a9e8252 100644 --- a/briar-android/src/main/res/values/dimens.xml +++ b/briar-android/src/main/res/values/dimens.xml @@ -43,7 +43,7 @@ <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_margin">6dp</dimen> - <dimen name="message_bubble_image_default">210dp</dimen> + <dimen name="message_bubble_image_default">115dp</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>