mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
[android] Show multiple images in message bubble
This commit is contained in:
@@ -23,6 +23,7 @@ class ConversationAdapter
|
||||
|
||||
private ConversationListener listener;
|
||||
private final RecycledViewPool imageViewPool;
|
||||
private final ImageItemDecoration imageItemDecoration;
|
||||
|
||||
ConversationAdapter(Context ctx,
|
||||
ConversationListener conversationListener) {
|
||||
@@ -30,6 +31,8 @@ class ConversationAdapter
|
||||
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
|
||||
@@ -47,10 +50,10 @@ class ConversationAdapter
|
||||
switch (type) {
|
||||
case R.layout.list_item_conversation_msg_in:
|
||||
return new ConversationMessageViewHolder(v, listener, true,
|
||||
imageViewPool);
|
||||
imageViewPool, imageItemDecoration);
|
||||
case R.layout.list_item_conversation_msg_out:
|
||||
return new ConversationMessageViewHolder(v, listener, false,
|
||||
imageViewPool);
|
||||
imageViewPool, imageItemDecoration);
|
||||
case R.layout.list_item_conversation_notice_in:
|
||||
return new ConversationNoticeViewHolder(v, listener, true);
|
||||
case R.layout.list_item_conversation_notice_out:
|
||||
|
||||
@@ -2,8 +2,6 @@ package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.constraint.ConstraintSet;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.RecyclerView.RecycledViewPool;
|
||||
import android.view.View;
|
||||
@@ -13,6 +11,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import static android.support.constraint.ConstraintSet.WRAP_CONTENT;
|
||||
import static android.support.v4.content.ContextCompat.getColor;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
@@ -26,21 +25,22 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
||||
private final ConstraintSet imageTextConstraints = new ConstraintSet();
|
||||
|
||||
ConversationMessageViewHolder(View v, ConversationListener listener,
|
||||
boolean isIncoming, RecycledViewPool imageViewPool) {
|
||||
boolean isIncoming, RecycledViewPool imageViewPool,
|
||||
ImageItemDecoration imageItemDecoration) {
|
||||
super(v, listener, isIncoming);
|
||||
statusLayout = v.findViewById(R.id.statusLayout);
|
||||
|
||||
// image list
|
||||
RecyclerView list = v.findViewById(R.id.imageView);
|
||||
RecyclerView list = v.findViewById(R.id.imageList);
|
||||
list.setRecycledViewPool(imageViewPool);
|
||||
list.setLayoutManager(new GridLayoutManager(v.getContext(), 2));
|
||||
adapter = new ImageAdapter(listener);
|
||||
adapter =
|
||||
new ImageAdapter(v.getContext(), imageItemDecoration, listener);
|
||||
list.setAdapter(adapter);
|
||||
list.addItemDecoration(imageItemDecoration);
|
||||
|
||||
// remember original status text color
|
||||
timeColor = time.getCurrentTextColor();
|
||||
timeColorBubble =
|
||||
ContextCompat.getColor(v.getContext(), R.color.briar_white);
|
||||
timeColorBubble = getColor(v.getContext(), R.color.briar_white);
|
||||
|
||||
// clone constraint sets from layout files
|
||||
textConstraints
|
||||
@@ -80,8 +80,7 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
||||
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 {
|
||||
@@ -94,11 +93,12 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
||||
AttachmentItem attachment = item.getAttachments().get(0);
|
||||
int width = attachment.getThumbnailWidth();
|
||||
int height = attachment.getThumbnailHeight();
|
||||
constraintSet.constrainWidth(R.id.imageView, width);
|
||||
constraintSet.constrainHeight(R.id.imageView, height);
|
||||
constraintSet.constrainWidth(R.id.imageList, width);
|
||||
constraintSet.constrainHeight(R.id.imageList, height);
|
||||
} else {
|
||||
constraintSet.constrainWidth(R.id.imageView, WRAP_CONTENT);
|
||||
constraintSet.constrainHeight(R.id.imageView, WRAP_CONTENT);
|
||||
// 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);
|
||||
|
||||
@@ -1,64 +1,72 @@
|
||||
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;
|
||||
|
||||
@NotNullByDefault
|
||||
class ImageAdapter extends Adapter<ImageViewHolder> {
|
||||
|
||||
private final static int TYPE_SINGLE = 0;
|
||||
private final static int TYPE_MULTIPLE = 1;
|
||||
|
||||
private final List<AttachmentItem> items = new ArrayList<>();
|
||||
private final ConversationListener listener;
|
||||
private final int imageSize, borderSize;
|
||||
private final int radiusBig, radiusSmall;
|
||||
private final boolean isRtl;
|
||||
@Nullable
|
||||
private ConversationMessageItem conversationItem;
|
||||
|
||||
public ImageAdapter(ConversationListener listener) {
|
||||
super();
|
||||
public ImageAdapter(Context ctx, ImageItemDecoration imageItemDecoration,
|
||||
ConversationListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return items.size() == 1 ? TYPE_SINGLE : TYPE_MULTIPLE;
|
||||
borderSize = imageItemDecoration.getBorderSize();
|
||||
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 = imageItemDecoration.isRtl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
|
||||
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
|
||||
R.layout.list_item_image, viewGroup, false);
|
||||
return type == TYPE_SINGLE ? new SingleImageViewHolder(v) :
|
||||
new ImageViewHolder(v);
|
||||
return new ImageViewHolder(v, imageSize, borderSize);
|
||||
}
|
||||
|
||||
@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)
|
||||
);
|
||||
if (imageViewHolder instanceof SingleImageViewHolder) {
|
||||
boolean isIncoming = conversationItem.isIncoming();
|
||||
boolean hasText = conversationItem.getText() != null;
|
||||
((SingleImageViewHolder) imageViewHolder)
|
||||
.bind(item, isIncoming, hasText);
|
||||
} else {
|
||||
imageViewHolder.bind(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
|
||||
@@ -73,9 +81,76 @@ class ImageAdapter extends Adapter<ImageViewHolder> {
|
||||
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,66 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
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 static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.v4.view.ViewCompat.LAYOUT_DIRECTION_RTL;
|
||||
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 realBorderSize, border;
|
||||
private final boolean isRtl;
|
||||
|
||||
public 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);
|
||||
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
|
||||
Configuration config = res.getConfiguration();
|
||||
isRtl = SDK_INT >= 17 &&
|
||||
config.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
|
||||
}
|
||||
|
||||
@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 left = isLeft(pos) ^ isRtl;
|
||||
outRect.top = isTopRow(pos) ? 0 : border;
|
||||
outRect.left = left ? 0 : border;
|
||||
outRect.right = left && !singleInRow(pos, num) ? border : 0;
|
||||
outRect.bottom = isBottomRow(pos, num) ? 0 : border;
|
||||
}
|
||||
|
||||
public int getBorderSize() {
|
||||
return realBorderSize;
|
||||
}
|
||||
|
||||
public boolean isRtl() {
|
||||
return isRtl;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,15 +3,17 @@ 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 com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||
|
||||
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;
|
||||
@@ -23,24 +25,41 @@ class ImageViewHolder extends ViewHolder {
|
||||
private static final int ERROR_RES = R.drawable.ic_image_broken;
|
||||
|
||||
protected final ImageView imageView;
|
||||
protected Transformation<Bitmap> transformation = new CenterCrop();
|
||||
private final int imageSize, borderSize;
|
||||
|
||||
public ImageViewHolder(View v) {
|
||||
public ImageViewHolder(View v, int imageSize, int borderSize) {
|
||||
super(v);
|
||||
imageView = v.findViewById(R.id.imageView);
|
||||
this.imageSize = imageSize;
|
||||
this.borderSize = borderSize;
|
||||
}
|
||||
|
||||
void bind(AttachmentItem attachment) {
|
||||
void bind(AttachmentItem attachment, Radii r, boolean single,
|
||||
boolean needsStretch) {
|
||||
if (attachment.hasError()) {
|
||||
GlideApp.with(imageView)
|
||||
.clear(imageView);
|
||||
imageView.setImageResource(ERROR_RES);
|
||||
} else {
|
||||
loadImage(attachment);
|
||||
setImageViewDimensions(attachment, single, needsStretch);
|
||||
loadImage(attachment, r);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadImage(AttachmentItem a) {
|
||||
private void setImageViewDimensions(AttachmentItem a, boolean single,
|
||||
boolean needsStretch) {
|
||||
LayoutParams params = (LayoutParams) imageView.getLayoutParams();
|
||||
// actual image size will shrink half the border
|
||||
int stretchSize = (imageSize - borderSize / 2) * 2 + borderSize;
|
||||
int width = needsStretch ? stretchSize : 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)
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.conversation.glide.BriarImageTransformation;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.v4.view.ViewCompat.LAYOUT_DIRECTION_RTL;
|
||||
|
||||
@NotNullByDefault
|
||||
class SingleImageViewHolder extends ImageViewHolder {
|
||||
|
||||
private final int radiusBig, radiusSmall;
|
||||
private final boolean isRtl;
|
||||
|
||||
public SingleImageViewHolder(View v) {
|
||||
super(v);
|
||||
radiusBig = v.getContext().getResources()
|
||||
.getDimensionPixelSize(R.dimen.message_bubble_radius_big);
|
||||
radiusSmall = v.getContext().getResources()
|
||||
.getDimensionPixelSize(R.dimen.message_bubble_radius_small);
|
||||
|
||||
// find out if we are showing a RTL language, Use the configuration,
|
||||
// because getting the layout direction of views is not reliable
|
||||
Configuration config = v.getContext().getResources().getConfiguration();
|
||||
isRtl = SDK_INT >= 17 &&
|
||||
config.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
|
||||
}
|
||||
|
||||
void bind(AttachmentItem a, boolean isIncoming, boolean hasText) {
|
||||
if (!a.hasError()) beforeLoadingImage(a, isIncoming, hasText);
|
||||
super.bind(a);
|
||||
}
|
||||
|
||||
private void beforeLoadingImage(AttachmentItem a, boolean isIncoming,
|
||||
boolean hasText) {
|
||||
// apply image size constraints, so glides picks them up for scaling
|
||||
LayoutParams layoutParams =
|
||||
new LayoutParams(a.getThumbnailWidth(), a.getThumbnailHeight());
|
||||
imageView.setLayoutParams(layoutParams);
|
||||
|
||||
boolean leftCornerSmall =
|
||||
(isIncoming && !isRtl) || (!isIncoming && isRtl);
|
||||
boolean bottomRound = !hasText;
|
||||
transformation = new BriarImageTransformation(radiusSmall, radiusBig,
|
||||
leftCornerSmall, bottomRound);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,15 +16,18 @@
|
||||
android:elevation="@dimen/message_bubble_elevation">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/imageView"
|
||||
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:listitem="@layout/list_item_image"
|
||||
tools:src="@drawable/alerts_and_states_error"/>
|
||||
tools:listitem="@layout/list_item_image"/>
|
||||
|
||||
<com.vanniktech.emoji.EmojiTextView
|
||||
android:id="@+id/text"
|
||||
@@ -39,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
|
||||
@@ -54,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">
|
||||
|
||||
@@ -16,15 +16,18 @@
|
||||
android:elevation="@dimen/message_bubble_elevation">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/imageView"
|
||||
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:listitem="@layout/list_item_image"
|
||||
tools:src="@drawable/alerts_and_states_error"/>
|
||||
tools:listitem="@layout/list_item_image"/>
|
||||
|
||||
<com.vanniktech.emoji.EmojiTextView
|
||||
android:id="@+id/text"
|
||||
@@ -38,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
|
||||
|
||||
@@ -16,13 +16,17 @@
|
||||
android:elevation="@dimen/message_bubble_elevation">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/imageView"
|
||||
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:listitem="@layout/list_item_image"/>
|
||||
|
||||
@@ -41,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
|
||||
|
||||
@@ -22,13 +22,17 @@
|
||||
android:elevation="@dimen/message_bubble_elevation">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/imageView"
|
||||
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
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
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:src="@drawable/alerts_and_states_error"/>
|
||||
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_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>
|
||||
|
||||
Reference in New Issue
Block a user