mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-15 04:18:53 +01:00
Merge branch '1473-display-multiple-images' into 'master'
UX for displaying multiple image attachments Closes #1473 See merge request briar/briar!1010
This commit is contained in:
@@ -98,10 +98,11 @@ class AttachmentController {
|
|||||||
*/
|
*/
|
||||||
List<AttachmentItem> getAttachmentItems(
|
List<AttachmentItem> getAttachmentItems(
|
||||||
List<Pair<AttachmentHeader, Attachment>> attachments) {
|
List<Pair<AttachmentHeader, Attachment>> attachments) {
|
||||||
|
boolean needsSize = attachments.size() == 1;
|
||||||
List<AttachmentItem> items = new ArrayList<>(attachments.size());
|
List<AttachmentItem> items = new ArrayList<>(attachments.size());
|
||||||
for (Pair<AttachmentHeader, Attachment> a : attachments) {
|
for (Pair<AttachmentHeader, Attachment> a : attachments) {
|
||||||
AttachmentItem item =
|
AttachmentItem item =
|
||||||
getAttachmentItem(a.getFirst(), a.getSecond());
|
getAttachmentItem(a.getFirst(), a.getSecond(), needsSize);
|
||||||
items.add(item);
|
items.add(item);
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
@@ -111,10 +112,22 @@ class AttachmentController {
|
|||||||
* Creates an {@link AttachmentItem} from the {@link Attachment}'s
|
* Creates an {@link AttachmentItem} from the {@link Attachment}'s
|
||||||
* {@link InputStream} which will be closed when this method returns.
|
* {@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();
|
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());
|
InputStream is = new BufferedInputStream(a.getStream());
|
||||||
is.mark(Integer.MAX_VALUE);
|
is.mark(Integer.MAX_VALUE);
|
||||||
try {
|
try {
|
||||||
@@ -144,14 +157,19 @@ class AttachmentController {
|
|||||||
getThumbnailSize(size.width, size.height, size.mimeType);
|
getThumbnailSize(size.width, size.height, size.mimeType);
|
||||||
}
|
}
|
||||||
// get file extension
|
// get file extension
|
||||||
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
String extension = getExtensionFromMimeType(size.mimeType);
|
||||||
String extension = mimeTypeMap.getExtensionFromMimeType(size.mimeType);
|
|
||||||
if (extension == null) {
|
if (extension == null) {
|
||||||
return new AttachmentItem(messageId, 0, 0, "", "", 0, 0, true);
|
return new AttachmentItem(messageId, 0, 0, "", "", 0, 0, true);
|
||||||
}
|
}
|
||||||
return new AttachmentItem(messageId, size.width, size.height,
|
return new AttachmentItem(messageId, size.width, size.height,
|
||||||
size.mimeType, extension, thumbnailSize.width, thumbnailSize.height,
|
size.mimeType, extension, thumbnailSize.width,
|
||||||
size.error);
|
thumbnailSize.height, size.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String getExtensionFromMimeType(String mimeType) {
|
||||||
|
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||||
|
return mimeTypeMap.getExtensionFromMimeType(mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
textCache.put(id, text);
|
textCache.put(id, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!h.getAttachmentHeaders().isEmpty()) {
|
if (h.getAttachmentHeaders().size() == 1) {
|
||||||
List<AttachmentItem> items =
|
List<AttachmentItem> items =
|
||||||
attachmentController.get(id);
|
attachmentController.get(id);
|
||||||
if (items == null) {
|
if (items == null) {
|
||||||
@@ -483,7 +483,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
try {
|
try {
|
||||||
List<Pair<AttachmentHeader, Attachment>> attachments =
|
List<Pair<AttachmentHeader, Attachment>> attachments =
|
||||||
attachmentController.getMessageAttachments(headers);
|
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 =
|
List<AttachmentItem> items =
|
||||||
attachmentController.getAttachmentItems(attachments);
|
attachmentController.getAttachmentItems(attachments);
|
||||||
displayMessageAttachments(messageId, items);
|
displayMessageAttachments(messageId, items);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android.conversation;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.LayoutRes;
|
import android.support.annotation.LayoutRes;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView.RecycledViewPool;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -21,11 +22,17 @@ class ConversationAdapter
|
|||||||
extends BriarAdapter<ConversationItem, ConversationItemViewHolder> {
|
extends BriarAdapter<ConversationItem, ConversationItemViewHolder> {
|
||||||
|
|
||||||
private ConversationListener listener;
|
private ConversationListener listener;
|
||||||
|
private final RecycledViewPool imageViewPool;
|
||||||
|
private final ImageItemDecoration imageItemDecoration;
|
||||||
|
|
||||||
ConversationAdapter(Context ctx,
|
ConversationAdapter(Context ctx,
|
||||||
ConversationListener conversationListener) {
|
ConversationListener conversationListener) {
|
||||||
super(ctx, ConversationItem.class);
|
super(ctx, ConversationItem.class);
|
||||||
listener = conversationListener;
|
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
|
@LayoutRes
|
||||||
@@ -42,15 +49,17 @@ class ConversationAdapter
|
|||||||
type, viewGroup, false);
|
type, viewGroup, false);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case R.layout.list_item_conversation_msg_in:
|
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:
|
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:
|
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:
|
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:
|
case R.layout.list_item_conversation_request:
|
||||||
return new ConversationRequestViewHolder(v, true);
|
return new ConversationRequestViewHolder(v, listener, true);
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown ConversationItem");
|
throw new IllegalArgumentException("Unknown ConversationItem");
|
||||||
}
|
}
|
||||||
@@ -59,7 +68,7 @@ class ConversationAdapter
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(ConversationItemViewHolder ui, int position) {
|
public void onBindViewHolder(ConversationItemViewHolder ui, int position) {
|
||||||
ConversationItem item = items.get(position);
|
ConversationItem item = items.get(position);
|
||||||
ui.bind(item, listener);
|
ui.bind(item);
|
||||||
listener.onItemVisible(item);
|
listener.onItemVisible(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,14 +18,17 @@ import static org.briarproject.briar.android.util.UiUtils.formatDate;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
abstract class ConversationItemViewHolder extends ViewHolder {
|
abstract class ConversationItemViewHolder extends ViewHolder {
|
||||||
|
|
||||||
|
protected final ConversationListener listener;
|
||||||
protected final ConstraintLayout layout;
|
protected final ConstraintLayout layout;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final OutItemViewHolder outViewHolder;
|
private final OutItemViewHolder outViewHolder;
|
||||||
private final TextView text;
|
private final TextView text;
|
||||||
protected final TextView time;
|
protected final TextView time;
|
||||||
|
|
||||||
ConversationItemViewHolder(View v, boolean isIncoming) {
|
ConversationItemViewHolder(View v, ConversationListener listener,
|
||||||
|
boolean isIncoming) {
|
||||||
super(v);
|
super(v);
|
||||||
|
this.listener = listener;
|
||||||
this.outViewHolder = isIncoming ? null : new OutItemViewHolder(v);
|
this.outViewHolder = isIncoming ? null : new OutItemViewHolder(v);
|
||||||
layout = v.findViewById(R.id.layout);
|
layout = v.findViewById(R.id.layout);
|
||||||
text = v.findViewById(R.id.text);
|
text = v.findViewById(R.id.text);
|
||||||
@@ -33,7 +36,7 @@ abstract class ConversationItemViewHolder extends ViewHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
void bind(ConversationItem item, ConversationListener listener) {
|
void bind(ConversationItem item) {
|
||||||
if (item.getText() != null) {
|
if (item.getText() != null) {
|
||||||
text.setText(trim(item.getText()));
|
text.setText(trim(item.getText()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +1,45 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
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.annotation.UiThread;
|
||||||
import android.support.constraint.ConstraintSet;
|
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.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import com.bumptech.glide.load.Transformation;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
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.constraint.ConstraintSet.WRAP_CONTENT;
|
||||||
import static android.support.v4.view.ViewCompat.LAYOUT_DIRECTION_RTL;
|
import static android.support.v4.content.ContextCompat.getColor;
|
||||||
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
|
||||||
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
||||||
|
|
||||||
@DrawableRes
|
private final ImageAdapter adapter;
|
||||||
private static final int ERROR_RES = R.drawable.ic_image_broken;
|
|
||||||
|
|
||||||
private final ImageView imageView;
|
|
||||||
private final ViewGroup statusLayout;
|
private final ViewGroup statusLayout;
|
||||||
private final int timeColor, timeColorBubble;
|
private final int timeColor, timeColorBubble;
|
||||||
private final int radiusBig, radiusSmall;
|
|
||||||
private final boolean isRtl;
|
|
||||||
private final ConstraintSet textConstraints = new ConstraintSet();
|
private final ConstraintSet textConstraints = new ConstraintSet();
|
||||||
private final ConstraintSet imageConstraints = new ConstraintSet();
|
private final ConstraintSet imageConstraints = new ConstraintSet();
|
||||||
private final ConstraintSet imageTextConstraints = new ConstraintSet();
|
private final ConstraintSet imageTextConstraints = new ConstraintSet();
|
||||||
|
|
||||||
ConversationMessageViewHolder(View v, boolean isIncoming) {
|
ConversationMessageViewHolder(View v, ConversationListener listener,
|
||||||
super(v, isIncoming);
|
boolean isIncoming, RecycledViewPool imageViewPool,
|
||||||
imageView = v.findViewById(R.id.imageView);
|
ImageItemDecoration imageItemDecoration) {
|
||||||
|
super(v, listener, isIncoming);
|
||||||
statusLayout = v.findViewById(R.id.statusLayout);
|
statusLayout = v.findViewById(R.id.statusLayout);
|
||||||
radiusBig = v.getContext().getResources()
|
|
||||||
.getDimensionPixelSize(R.dimen.message_bubble_radius_big);
|
// image list
|
||||||
radiusSmall = v.getContext().getResources()
|
RecyclerView list = v.findViewById(R.id.imageList);
|
||||||
.getDimensionPixelSize(R.dimen.message_bubble_radius_small);
|
list.setRecycledViewPool(imageViewPool);
|
||||||
|
adapter = new ImageAdapter(v.getContext(), listener);
|
||||||
|
list.setAdapter(adapter);
|
||||||
|
list.addItemDecoration(imageItemDecoration);
|
||||||
|
|
||||||
// remember original status text color
|
// remember original status text color
|
||||||
timeColor = time.getCurrentTextColor();
|
timeColor = time.getCurrentTextColor();
|
||||||
timeColorBubble =
|
timeColorBubble = getColor(v.getContext(), R.color.briar_white);
|
||||||
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;
|
|
||||||
|
|
||||||
// clone constraint sets from layout files
|
// clone constraint sets from layout files
|
||||||
textConstraints
|
textConstraints
|
||||||
@@ -77,85 +59,55 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void bind(ConversationItem conversationItem,
|
void bind(ConversationItem conversationItem) {
|
||||||
ConversationListener listener) {
|
super.bind(conversationItem);
|
||||||
super.bind(conversationItem, listener);
|
|
||||||
ConversationMessageItem item =
|
ConversationMessageItem item =
|
||||||
(ConversationMessageItem) conversationItem;
|
(ConversationMessageItem) conversationItem;
|
||||||
if (item.getAttachments().isEmpty()) {
|
if (item.getAttachments().isEmpty()) {
|
||||||
bindTextItem();
|
bindTextItem();
|
||||||
} else {
|
} else {
|
||||||
bindImageItem(item, listener);
|
bindImageItem(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindTextItem() {
|
private void bindTextItem() {
|
||||||
clearImage();
|
resetStatusLayoutForText();
|
||||||
|
textConstraints.applyTo(layout);
|
||||||
|
adapter.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindImageItem(ConversationMessageItem item) {
|
||||||
|
ConstraintSet constraintSet;
|
||||||
|
if (item.getText() == null) {
|
||||||
|
statusLayout.setBackgroundResource(R.drawable.msg_status_bubble);
|
||||||
|
time.setTextColor(timeColorBubble);
|
||||||
|
constraintSet = imageConstraints;
|
||||||
|
} else {
|
||||||
|
resetStatusLayoutForText();
|
||||||
|
constraintSet = imageTextConstraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
// 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 resetStatusLayoutForText() {
|
||||||
statusLayout.setBackgroundResource(0);
|
statusLayout.setBackgroundResource(0);
|
||||||
// also reset padding (the background drawable defines some)
|
// also reset padding (the background drawable defines some)
|
||||||
statusLayout.setPadding(0, 0, 0, 0);
|
statusLayout.setPadding(0, 0, 0, 0);
|
||||||
time.setTextColor(timeColor);
|
time.setTextColor(timeColor);
|
||||||
textConstraints.applyTo(layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void bindImageItem(ConversationMessageItem item,
|
|
||||||
ConversationListener listener) {
|
|
||||||
// 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(ERROR_RES);
|
|
||||||
} else {
|
|
||||||
loadImage(item, attachment, listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,16 +19,17 @@ class ConversationNoticeViewHolder extends ConversationItemViewHolder {
|
|||||||
|
|
||||||
private final TextView msgText;
|
private final TextView msgText;
|
||||||
|
|
||||||
ConversationNoticeViewHolder(View v, boolean isIncoming) {
|
ConversationNoticeViewHolder(View v, ConversationListener listener,
|
||||||
super(v, isIncoming);
|
boolean isIncoming) {
|
||||||
|
super(v, listener, isIncoming);
|
||||||
msgText = v.findViewById(R.id.msgText);
|
msgText = v.findViewById(R.id.msgText);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@CallSuper
|
@CallSuper
|
||||||
void bind(ConversationItem item, ConversationListener listener) {
|
void bind(ConversationItem item) {
|
||||||
ConversationNoticeItem notice = (ConversationNoticeItem) item;
|
ConversationNoticeItem notice = (ConversationNoticeItem) item;
|
||||||
super.bind(notice, listener);
|
super.bind(notice);
|
||||||
|
|
||||||
String text = notice.getMsgText();
|
String text = notice.getMsgText();
|
||||||
if (isNullOrEmpty(text)) {
|
if (isNullOrEmpty(text)) {
|
||||||
|
|||||||
@@ -17,16 +17,17 @@ class ConversationRequestViewHolder extends ConversationNoticeViewHolder {
|
|||||||
private final Button acceptButton;
|
private final Button acceptButton;
|
||||||
private final Button declineButton;
|
private final Button declineButton;
|
||||||
|
|
||||||
ConversationRequestViewHolder(View v, boolean isIncoming) {
|
ConversationRequestViewHolder(View v, ConversationListener listener,
|
||||||
super(v, isIncoming);
|
boolean isIncoming) {
|
||||||
|
super(v, listener, isIncoming);
|
||||||
acceptButton = v.findViewById(R.id.acceptButton);
|
acceptButton = v.findViewById(R.id.acceptButton);
|
||||||
declineButton = v.findViewById(R.id.declineButton);
|
declineButton = v.findViewById(R.id.declineButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void bind(ConversationItem item, ConversationListener listener) {
|
void bind(ConversationItem item) {
|
||||||
ConversationRequestItem request = (ConversationRequestItem) item;
|
ConversationRequestItem request = (ConversationRequestItem) item;
|
||||||
super.bind(request, listener);
|
super.bind(request);
|
||||||
|
|
||||||
if (request.wasAnswered() && request.canBeOpened()) {
|
if (request.wasAnswered() && request.canBeOpened()) {
|
||||||
acceptButton.setVisibility(VISIBLE);
|
acceptButton.setVisibility(VISIBLE);
|
||||||
|
|||||||
@@ -160,9 +160,11 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
long start = now();
|
long start = now();
|
||||||
List<AttachmentHeader> attachments = new ArrayList<>();
|
List<AttachmentHeader> attachments = new ArrayList<>();
|
||||||
List<AttachmentItem> items = new ArrayList<>();
|
List<AttachmentItem> items = new ArrayList<>();
|
||||||
|
boolean needsSize = uris.size() == 1;
|
||||||
for (Uri uri : uris) {
|
for (Uri uri : uris) {
|
||||||
Pair<AttachmentHeader, AttachmentItem> pair =
|
Pair<AttachmentHeader, AttachmentItem> pair =
|
||||||
createAttachmentHeader(groupId, uri, timestamp);
|
createAttachmentHeader(groupId, uri, timestamp,
|
||||||
|
needsSize);
|
||||||
if (pair == null) continue;
|
if (pair == null) continue;
|
||||||
attachments.add(pair.getFirst());
|
attachments.add(pair.getFirst());
|
||||||
items.add(pair.getSecond());
|
items.add(pair.getSecond());
|
||||||
@@ -175,7 +177,7 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private Pair<AttachmentHeader, AttachmentItem> createAttachmentHeader(
|
private Pair<AttachmentHeader, AttachmentItem> createAttachmentHeader(
|
||||||
GroupId groupId, Uri uri, long timestamp) {
|
GroupId groupId, Uri uri, long timestamp, boolean needsSize) {
|
||||||
InputStream is = null;
|
InputStream is = null;
|
||||||
try {
|
try {
|
||||||
ContentResolver contentResolver =
|
ContentResolver contentResolver =
|
||||||
@@ -191,7 +193,7 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
is = contentResolver.openInputStream(uri);
|
is = contentResolver.openInputStream(uri);
|
||||||
if (is == null) throw new IOException();
|
if (is == null) throw new IOException();
|
||||||
AttachmentItem item = attachmentController
|
AttachmentItem item = attachmentController
|
||||||
.getAttachmentItem(h, new Attachment(is));
|
.getAttachmentItem(h, new Attachment(is), needsSize);
|
||||||
return new Pair<>(h, item);
|
return new Pair<>(h, item);
|
||||||
} catch (DbException | IOException e) {
|
} catch (DbException | IOException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -48,10 +48,10 @@ public class ImageViewModel extends AndroidViewModel {
|
|||||||
@IoExecutor
|
@IoExecutor
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
|
|
||||||
private MutableLiveData<Boolean> saveState = new MutableLiveData<>();
|
private final MutableLiveData<Boolean> saveState = new MutableLiveData<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ImageViewModel(Application application,
|
ImageViewModel(Application application,
|
||||||
MessagingManager messagingManager,
|
MessagingManager messagingManager,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
@IoExecutor Executor ioExecutor) {
|
@IoExecutor Executor ioExecutor) {
|
||||||
|
|||||||
@@ -7,10 +7,8 @@ import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
|||||||
|
|
||||||
public class BriarImageTransformation extends MultiTransformation<Bitmap> {
|
public class BriarImageTransformation extends MultiTransformation<Bitmap> {
|
||||||
|
|
||||||
public BriarImageTransformation(int smallRadius, int radius,
|
public BriarImageTransformation(Radii r) {
|
||||||
boolean leftCornerSmall, boolean bottomRound) {
|
super(new CenterCrop(), new CustomCornersTransformation(r));
|
||||||
super(new CenterCrop(), new ImageCornerTransformation(
|
|
||||||
smallRadius, radius, leftCornerSmall, bottomRound));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -56,6 +56,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
|||||||
import static android.os.Build.MANUFACTURER;
|
import static android.os.Build.MANUFACTURER;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS;
|
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_AUTO;
|
||||||
import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
|
import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
|
||||||
import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_NO;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,15 +15,19 @@
|
|||||||
android:background="@drawable/msg_in"
|
android:background="@drawable/msg_in"
|
||||||
android:elevation="@dimen/message_bubble_elevation">
|
android:elevation="@dimen/message_bubble_elevation">
|
||||||
|
|
||||||
<ImageView
|
<android.support.v7.widget.RecyclerView
|
||||||
android:id="@+id/imageView"
|
android:id="@+id/imageList"
|
||||||
android:layout_width="@dimen/message_bubble_image_default"
|
android:layout_width="@dimen/message_bubble_image_default"
|
||||||
android:layout_height="@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_constraintBottom_toTopOf="@+id/text"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:spanCount="2"
|
||||||
tools:ignore="ContentDescription"
|
tools:ignore="ContentDescription"
|
||||||
tools:src="@drawable/alerts_and_states_error"/>
|
tools:listitem="@layout/list_item_image"/>
|
||||||
|
|
||||||
<com.vanniktech.emoji.EmojiTextView
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
@@ -38,10 +42,10 @@
|
|||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
|
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_constraintHorizontal_bias="0.0"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
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"/>
|
tools:text="The text of a message which can sometimes be a bit longer as well"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -53,7 +57,7 @@
|
|||||||
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
|
||||||
android:background="@drawable/msg_status_bubble"
|
android:background="@drawable/msg_status_bubble"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/imageView"
|
app:layout_constraintBottom_toBottomOf="@+id/imageList"
|
||||||
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">
|
||||||
|
|||||||
@@ -15,15 +15,19 @@
|
|||||||
android:background="@drawable/msg_in"
|
android:background="@drawable/msg_in"
|
||||||
android:elevation="@dimen/message_bubble_elevation">
|
android:elevation="@dimen/message_bubble_elevation">
|
||||||
|
|
||||||
<ImageView
|
<android.support.v7.widget.RecyclerView
|
||||||
android:id="@+id/imageView"
|
android:id="@+id/imageList"
|
||||||
android:layout_width="@dimen/message_bubble_image_default"
|
android:layout_width="@dimen/message_bubble_image_default"
|
||||||
android:layout_height="@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_constraintBottom_toTopOf="@+id/text"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:spanCount="2"
|
||||||
tools:ignore="ContentDescription"
|
tools:ignore="ContentDescription"
|
||||||
tools:src="@drawable/alerts_and_states_error"/>
|
tools:listitem="@layout/list_item_image"/>
|
||||||
|
|
||||||
<com.vanniktech.emoji.EmojiTextView
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
@@ -37,10 +41,10 @@
|
|||||||
android:textColor="?android:attr/textColorPrimary"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
|
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_constraintHorizontal_bias="0.0"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
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"/>
|
tools:text="The text of a message which can sometimes be a bit longer as well"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
@@ -15,15 +15,20 @@
|
|||||||
android:background="@drawable/msg_in"
|
android:background="@drawable/msg_in"
|
||||||
android:elevation="@dimen/message_bubble_elevation">
|
android:elevation="@dimen/message_bubble_elevation">
|
||||||
|
|
||||||
<ImageView
|
<android.support.v7.widget.RecyclerView
|
||||||
android:id="@+id/imageView"
|
android:id="@+id/imageList"
|
||||||
android:layout_width="@dimen/message_bubble_image_default"
|
android:layout_width="@dimen/message_bubble_image_default"
|
||||||
android:layout_height="@dimen/message_bubble_image_default"
|
android:layout_height="@dimen/message_bubble_image_default"
|
||||||
|
android:orientation="vertical"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
app:layoutManager="android.support.v7.widget.StaggeredGridLayoutManager"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/text"
|
app:layout_constraintBottom_toTopOf="@+id/text"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="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
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
@@ -40,7 +45,7 @@
|
|||||||
app:layout_constraintEnd_toEndOf="parent"
|
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_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"/>
|
tools:text="The text of a message which can sometimes be a bit longer as well"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
@@ -21,14 +21,18 @@
|
|||||||
android:background="@drawable/msg_out"
|
android:background="@drawable/msg_out"
|
||||||
android:elevation="@dimen/message_bubble_elevation">
|
android:elevation="@dimen/message_bubble_elevation">
|
||||||
|
|
||||||
<ImageView
|
<android.support.v7.widget.RecyclerView
|
||||||
android:id="@+id/imageView"
|
android:id="@+id/imageList"
|
||||||
android:layout_width="@dimen/message_bubble_image_default"
|
android:layout_width="@dimen/message_bubble_image_default"
|
||||||
android:layout_height="@dimen/message_bubble_image_default"
|
android:layout_height="@dimen/message_bubble_image_default"
|
||||||
|
android:orientation="vertical"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
app:layoutManager="android.support.v7.widget.StaggeredGridLayoutManager"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/text"
|
app:layout_constraintBottom_toTopOf="@+id/text"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:spanCount="2"
|
||||||
tools:ignore="ContentDescription"
|
tools:ignore="ContentDescription"
|
||||||
tools:src="@drawable/alerts_and_states_error"/>
|
tools:src="@drawable/alerts_and_states_error"/>
|
||||||
|
|
||||||
@@ -47,7 +51,7 @@
|
|||||||
app:layout_constraintEnd_toEndOf="parent"
|
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_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."/>
|
tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
10
briar-android/src/main/res/layout/list_item_image.xml
Normal file
10
briar-android/src/main/res/layout/list_item_image.xml
Normal file
@@ -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"/>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<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_default">115dp</dimen>
|
||||||
<dimen name="message_bubble_image_min_width">150dp</dimen>
|
<dimen name="message_bubble_image_min_width">150dp</dimen>
|
||||||
<dimen name="message_bubble_image_max_width">240dp</dimen>
|
<dimen name="message_bubble_image_max_width">240dp</dimen>
|
||||||
<dimen name="message_bubble_image_min_height">100dp</dimen>
|
<dimen name="message_bubble_image_min_height">100dp</dimen>
|
||||||
|
|||||||
Reference in New Issue
Block a user