Commit 64f0a630 authored by Rafael Kellermann Streit's avatar Rafael Kellermann Streit Committed by GitHub

Merge pull request #357 from filipedelimabrito/avatar-caching

Solve avatar caching and add SVG support
parents 91ca4280 387a845f
......@@ -301,8 +301,8 @@ public class RoomFragment extends AbstractChatRoomFragment implements
closeSideMenuIfNeeded();
});
DrawerLayout drawerLayout = (DrawerLayout) rootView.findViewById(R.id.drawer_layout);
SlidingPaneLayout pane = (SlidingPaneLayout) getActivity().findViewById(R.id.sliding_pane);
DrawerLayout drawerLayout = rootView.findViewById(R.id.drawer_layout);
SlidingPaneLayout pane = getActivity().findViewById(R.id.sliding_pane);
if (drawerLayout != null && pane != null) {
compositeDisposable.add(RxDrawerLayout.drawerOpen(drawerLayout, GravityCompat.END)
.compose(bindToLifecycle())
......
......@@ -10,19 +10,7 @@ import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
import com.jakewharton.rxbinding2.support.v7.widget.RxSearchView;
import com.jakewharton.rxbinding2.widget.RxCompoundButton;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import chat.rocket.android.BuildConfig;
import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
......@@ -40,19 +28,26 @@ import chat.rocket.android.layouthelper.chatroom.roomlist.FavoriteRoomListHeader
import chat.rocket.android.layouthelper.chatroom.roomlist.RoomListAdapter;
import chat.rocket.android.layouthelper.chatroom.roomlist.RoomListHeader;
import chat.rocket.android.layouthelper.chatroom.roomlist.UnreadRoomListHeader;
import chat.rocket.android.renderer.UserRenderer;
import chat.rocket.core.SortDirection;
import chat.rocket.core.interactors.RoomInteractor;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.SpotlightRoom;
import chat.rocket.core.models.User;
import chat.rocket.android.renderer.UserRenderer;
import chat.rocket.persistence.realm.repositories.RealmRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmServerInfoRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmSpotlightRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository;
import chat.rocket.android.widget.RocketChatAvatar;
import com.jakewharton.rxbinding2.support.v7.widget.RxSearchView;
import com.jakewharton.rxbinding2.widget.RxCompoundButton;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class SidebarMainFragment extends AbstractFragment implements SidebarMainContract.View {
......@@ -230,9 +225,9 @@ public class SidebarMainFragment extends AbstractFragment implements SidebarMain
private void onRenderCurrentUser(User user, RocketChatAbsoluteUrl absoluteUrl) {
if (user != null && absoluteUrl != null) {
new UserRenderer(getContext(), user)
.avatarInto((RocketChatAvatar) rootView.findViewById(R.id.current_user_avatar), absoluteUrl)
.usernameInto((TextView) rootView.findViewById(R.id.current_user_name))
.statusColorInto((ImageView) rootView.findViewById(R.id.current_user_status));
.avatarInto(rootView.findViewById(R.id.current_user_avatar), absoluteUrl, false)
.usernameInto(rootView.findViewById(R.id.current_user_name))
.statusColorInto(rootView.findViewById(R.id.current_user_status));
}
}
......
package chat.rocket.android.helper;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.graphics.drawable.VectorDrawableCompat;
import chat.rocket.android.R;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.android.widget.RocketChatAvatar;
import com.amulyakhare.textdrawable.TextDrawable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
......@@ -19,11 +10,6 @@ import java.net.URLEncoder;
* Helper for rendering user avatar image.
*/
public class Avatar {
private static final int[] COLORS = new int[]{
0xFFF44336, 0xFFE91E63, 0xFF9C27B0, 0xFF673AB7, 0xFF3F51B5, 0xFF2196F3,
0xFF03A9F4, 0xFF00BCD4, 0xFF009688, 0xFF4CAF50, 0xFF8BC34A, 0xFFCDDC39,
0xFFFFC107, 0xFFFF9800, 0xFFFF5722, 0xFF795548, 0xFF9E9E9E, 0xFF607D8B
};
private final AbsoluteUrl absoluteUrl;
private final String username;
......@@ -32,48 +18,11 @@ public class Avatar {
this.username = username;
}
private static int getColorForUser(String username) {
return COLORS[username.length() % COLORS.length];
}
private static String getInitialsForUser(String username) {
String name = username
.replaceAll("[^A-Za-z0-9]", ".")
.replaceAll("\\.+", ".")
.replaceAll("(^\\.)|(\\.$)", "");
String[] initials = name.split("\\.");
if (initials.length >= 2) {
return (firstChar(initials[0]) + firstChar(initials[initials.length - 1])).toUpperCase();
} else {
String name2 = name.replaceAll("[^A-Za-z0-9]", "");
return (name2.length() < 2) ? name2 : name2.substring(0, 2).toUpperCase();
}
}
private static String firstChar(String str) {
return TextUtils.isEmpty(str) ? "" : str.substring(0, 1);
}
private static Bitmap drawableToBitmap(Drawable drawable, int size) {
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
if (bitmapDrawable.getBitmap() != null) {
return bitmapDrawable.getBitmap();
}
}
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
private String getImageUrl() {
//from Rocket.Chat:packages/rocketchat-ui/lib/avatar.coffee
//REMARK! this is often SVG image! (see: Rocket.Chat:server/startup/avatar.coffee)
try {
final String avatarUrl = "/avatar/" + URLEncoder.encode(username, "UTF-8") + ".jpg";
final String avatarUrl = "/avatar/" + URLEncoder.encode(username, "UTF-8");
if (absoluteUrl == null) {
return avatarUrl;
}
......@@ -87,31 +36,11 @@ public class Avatar {
/**
* render avatar into RocketChatAvatar.
*/
public void into(final RocketChatAvatar rocketChatAvatar) {
final Context context = rocketChatAvatar.getContext();
rocketChatAvatar.loadImage(getImageUrl(), getTextDrawable(context));
}
/**
* render error avatar into RocketChatAvatar.
*/
public void errorInto(final RocketChatAvatar rocketChatAvatar) {
final Context context = rocketChatAvatar.getContext();
rocketChatAvatar.loadImage(VectorDrawableCompat.create(context.getResources(), R.drawable.ic_error_outline_black_24dp, null));
}
public Drawable getTextDrawable(Context context) {
if (username == null) {
return null;
public void into(final RocketChatAvatar rocketChatAvatar, boolean showFailureImage) {
if (showFailureImage) {
rocketChatAvatar.showFailureImage();
} else {
rocketChatAvatar.loadImage(getImageUrl());
}
int round = (int) (4 * context.getResources().getDisplayMetrics().density);
return TextDrawable.builder()
.beginConfig()
.useFont(Typeface.SANS_SERIF)
.endConfig()
.buildRoundRect(getInitialsForUser(username), getColorForUser(username), round);
}
}
}
\ No newline at end of file
......@@ -25,13 +25,13 @@ public abstract class AbstractMessageViewHolder extends ModelViewHolder<PairedMe
*/
public AbstractMessageViewHolder(View itemView, AbsoluteUrl absoluteUrl) {
super(itemView);
avatar = (RocketChatAvatar) itemView.findViewById(R.id.user_avatar);
username = (TextView) itemView.findViewById(R.id.username);
subUsername = (TextView) itemView.findViewById(R.id.sub_username);
timestamp = (TextView) itemView.findViewById(R.id.timestamp);
avatar = itemView.findViewById(R.id.user_avatar);
username = itemView.findViewById(R.id.username);
subUsername = itemView.findViewById(R.id.sub_username);
timestamp = itemView.findViewById(R.id.timestamp);
userAndTimeContainer = itemView.findViewById(R.id.user_and_timestamp_container);
newDayContainer = itemView.findViewById(R.id.newday_container);
newDayText = (TextView) itemView.findViewById(R.id.newday_text);
newDayText = itemView.findViewById(R.id.newday_text);
this.absoluteUrl = absoluteUrl;
}
......@@ -39,11 +39,11 @@ public abstract class AbstractMessageViewHolder extends ModelViewHolder<PairedMe
* bind the view model.
*/
public final void bind(PairedMessage pairedMessage, boolean autoloadImages) {
if (pairedMessage.target != null) {
if (pairedMessage.target.getSyncState() != SyncState.SYNCED)
itemView.setAlpha(0.6f);
else
itemView.setAlpha(1.0f);
if (pairedMessage.target.getSyncState() != SyncState.SYNCED) {
itemView.setAlpha(0.6f);
}
else {
itemView.setAlpha(1.0f);
}
bindMessage(pairedMessage, autoloadImages);
......
......@@ -21,9 +21,9 @@ public class MessageNormalViewHolder extends AbstractMessageViewHolder {
*/
public MessageNormalViewHolder(View itemView, AbsoluteUrl absoluteUrl) {
super(itemView, absoluteUrl);
body = (RocketChatMessageLayout) itemView.findViewById(R.id.message_body);
urls = (RocketChatMessageUrlsLayout) itemView.findViewById(R.id.message_urls);
attachments = (RocketChatMessageAttachmentsLayout) itemView.findViewById(R.id.message_attachments);
body = itemView.findViewById(R.id.message_body);
urls = itemView.findViewById(R.id.message_urls);
attachments = itemView.findViewById(R.id.message_attachments);
}
@Override
......
......@@ -18,7 +18,7 @@ public class MessageSystemViewHolder extends AbstractMessageViewHolder {
*/
public MessageSystemViewHolder(View itemView, AbsoluteUrl absoluteUrl) {
super(itemView, absoluteUrl);
body = (TextView) itemView.findViewById(R.id.message_body);
body = itemView.findViewById(R.id.message_body);
}
@Override
......
......@@ -58,12 +58,12 @@ public class RoomUserAdapter extends RecyclerView.Adapter<RoomUserViewHolder> {
.setUtcOffset(0)
.build();
new UserRenderer(context, user)
.avatarInto(holder.avatar, absoluteUrl)
.avatarInto(holder.avatar, absoluteUrl, false)
.usernameInto(holder.username);
} else {
new UserRenderer(context, realmUser.asUser())
.statusColorInto(holder.status)
.avatarInto(holder.avatar, absoluteUrl)
.avatarInto(holder.avatar, absoluteUrl, false)
.usernameInto(holder.username);
}
}
......
......@@ -28,7 +28,7 @@ public class SuggestUserAdapter extends RealmAutoCompleteAdapter<RealmUser> {
protected void onBindItemView(View itemView, RealmUser user) {
new UserRenderer(itemView.getContext(), user.asUser())
.statusColorInto((ImageView) itemView.findViewById(R.id.room_user_status))
.avatarInto((RocketChatAvatar) itemView.findViewById(R.id.room_user_avatar), absoluteUrl);
.avatarInto((RocketChatAvatar) itemView.findViewById(R.id.room_user_avatar), absoluteUrl, false);
}
@Override
......
......@@ -4,7 +4,6 @@ import android.content.Context;
import android.view.View;
import android.widget.TextView;
import chat.rocket.android.R;
import chat.rocket.android.helper.Avatar;
import chat.rocket.android.helper.DateTime;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.widget.AbsoluteUrl;
......@@ -15,7 +14,6 @@ import chat.rocket.android.widget.message.RocketChatMessageUrlsLayout;
import chat.rocket.core.SyncState;
import chat.rocket.core.models.Attachment;
import chat.rocket.core.models.Message;
import chat.rocket.core.models.User;
import chat.rocket.core.models.WebContent;
import java.util.List;
......@@ -41,16 +39,18 @@ public class MessageRenderer extends AbstractRenderer<Message> {
return this;
}
if (object.getSyncState() == SyncState.FAILED)
// rocketChatAvatar.loadImage(VectorDrawableCompat.create(context.getResources(), R.drawable.ic_error_outline_black_24dp, null));
userRenderer.errorAvatarInto(rocketChatAvatar);
else if (TextUtils.isEmpty(object.getAvatar()))
userRenderer.avatarInto(rocketChatAvatar, absoluteUrl);
else {
final User user = object.getUser();
setAvatarInto(object.getAvatar(), absoluteUrl, user == null ? null : user.getUsername(), rocketChatAvatar);
switch (object.getSyncState()){
case SyncState.FAILED:
userRenderer.avatarInto(rocketChatAvatar, absoluteUrl, true);
break;
default:
if (TextUtils.isEmpty(object.getAvatar())) {
userRenderer.avatarInto(rocketChatAvatar, absoluteUrl, false);
} else {
rocketChatAvatar.loadImage(object.getAvatar());
}
break;
}
return this;
}
......@@ -91,7 +91,6 @@ public class MessageRenderer extends AbstractRenderer<Message> {
textView.setText(DateTime.fromEpocMs(object.getTimestamp(), DateTime.Format.TIME));
break;
}
return this;
}
......@@ -104,7 +103,6 @@ public class MessageRenderer extends AbstractRenderer<Message> {
}
rocketChatMessageLayout.setText(object.getMessage());
return this;
}
......@@ -123,15 +121,13 @@ public class MessageRenderer extends AbstractRenderer<Message> {
urlsLayout.setVisibility(View.VISIBLE);
urlsLayout.setUrls(webContents, autoloadImages);
}
return this;
}
/**
* show urls in RocketChatMessageUrlsLayout.
*/
public MessageRenderer attachmentsInto(RocketChatMessageAttachmentsLayout attachmentsLayout,
AbsoluteUrl absoluteUrl) {
public MessageRenderer attachmentsInto(RocketChatMessageAttachmentsLayout attachmentsLayout, AbsoluteUrl absoluteUrl) {
if (!shouldHandle(attachmentsLayout)) {
return this;
}
......@@ -144,14 +140,9 @@ public class MessageRenderer extends AbstractRenderer<Message> {
attachmentsLayout.setAbsoluteUrl(absoluteUrl);
attachmentsLayout.setAttachments(attachments, autoloadImages);
}
return this;
}
private void setAvatarInto(String avatar, AbsoluteUrl absoluteUrl, String username, RocketChatAvatar imageView) {
imageView.loadImage(avatar, new Avatar(absoluteUrl, username).getTextDrawable(context));
}
private void aliasAndUsernameInto(TextView aliasTextView, TextView usernameTextView) {
if (shouldHandle(aliasTextView)) {
aliasTextView.setText(object.getAlias());
......@@ -167,4 +158,4 @@ public class MessageRenderer extends AbstractRenderer<Message> {
}
}
}
}
\ No newline at end of file
......@@ -21,27 +21,14 @@ public class UserRenderer extends AbstractRenderer<User> {
/**
* show Avatar image
*/
public UserRenderer avatarInto(RocketChatAvatar rocketChatAvatar, AbsoluteUrl absoluteUrl) {
public UserRenderer avatarInto(RocketChatAvatar rocketChatAvatar, AbsoluteUrl absoluteUrl, boolean showFailureImage) {
if (!shouldHandle(rocketChatAvatar)) {
return this;
}
if (!TextUtils.isEmpty(object.getUsername())) {
new Avatar(absoluteUrl, object.getUsername()).into(rocketChatAvatar);
}
return this;
}
/**
* show Avatar error image
*/
public UserRenderer errorAvatarInto(RocketChatAvatar rocketChatAvatar) {
if (!shouldHandle(rocketChatAvatar)) {
return this;
}
if (!TextUtils.isEmpty(object.getUsername())) {
new Avatar(null, object.getUsername()).errorInto(rocketChatAvatar);
new Avatar(absoluteUrl, object.getUsername())
.into(rocketChatAvatar, showFailureImage);
}
return this;
}
......
......@@ -16,8 +16,8 @@
<string name="users_of_room_title">Members List</string>
<string name="fmt_room_user_count">Total: %,d users</string>
<string name="sending">Sending…</string>
<string name="not_synced">Not synced</string>
<string name="failed_to_sync">Failed to sync</string>
<string name="not_synced">Not synced</string>
<string name="failed_to_sync">Failed to sync</string>
<string name="resend">Resend</string>
<string name="discard">Discard</string>
......
......@@ -21,6 +21,7 @@ ext {
targetSdkVersion = 26
buildToolsVersion = "26.0.0"
supportLibraryVersion = "25.4.0"
kotlinVersion = "1.1.3"
}
task clean(type: Delete) {
......
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.kotlinVersion"
}
}
......@@ -44,6 +47,8 @@ dependencies {
compile "com.android.support:support-v13:$rootProject.ext.supportLibraryVersion"
compile "com.android.support:design:$rootProject.ext.supportLibraryVersion"
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$rootProject.ext.kotlinVersion"
compile 'org.nibor.autolink:autolink:0.6.0'
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
......
......@@ -2,15 +2,11 @@ package chat.rocket.android.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.interfaces.DraweeController;
import chat.rocket.android.widget.helper.FrescoAvatarHelper;
import com.facebook.drawee.view.SimpleDraweeView;
public class RocketChatAvatar extends FrameLayout {
......@@ -39,36 +35,19 @@ public class RocketChatAvatar extends FrameLayout {
}
private void initialize(Context context, AttributeSet attrs) {
LayoutInflater.from(context)
LayoutInflater
.from(context)
.inflate(R.layout.message_avatar, this, true);
draweeView = (SimpleDraweeView) findViewById(R.id.drawee_avatar);
draweeView = findViewById(R.id.drawee_avatar);
}
public void loadImage(Drawable drawable) {
// final GenericDraweeHierarchy hierarchy = draweeView.getHierarchy();
// hierarchy.setImage(drawable, 100, true); // Is there a better way?
final GenericDraweeHierarchy hierarchy = draweeView.getHierarchy();
hierarchy.setPlaceholderImage(drawable);
hierarchy.setFailureImage(drawable);
final DraweeController controller = Fresco.newDraweeControllerBuilder()
.setAutoPlayAnimations(true)
.build();
draweeView.setController(controller);
public void loadImage(String imageUrl) {
FrescoAvatarHelper
.loadImage(draweeView, imageUrl);
}
public void loadImage(String url, Drawable placeholder) {
final GenericDraweeHierarchy hierarchy = draweeView.getHierarchy();
hierarchy.setPlaceholderImage(placeholder);
hierarchy.setFailureImage(placeholder);
final DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(Uri.parse(url))
.setAutoPlayAnimations(true)
.build();
draweeView.setController(controller);
public void showFailureImage() {
FrescoAvatarHelper
.showFailureImage(draweeView);
}
}
}
\ No newline at end of file
package chat.rocket.android.widget;
import android.content.Context;
import chat.rocket.android.widget.fresco.CustomImageFormatConfigurator;
import com.facebook.drawee.backends.pipeline.DraweeConfig;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory;
import com.facebook.imagepipeline.core.ImagePipelineConfig;
import chat.rocket.android.widget.fresco.ImageFormatConfigurator;
import okhttp3.OkHttpClient;
public class RocketChatWidgets {
......@@ -16,14 +14,12 @@ public class RocketChatWidgets {
ImagePipelineConfig config = OkHttpImagePipelineConfigFactory
.newBuilder(context, okHttpClient)
.setDownsampleEnabled(true)
.setImageDecoderConfig(ImageFormatConfigurator.createImageDecoderConfig())
.setImageDecoderConfig(CustomImageFormatConfigurator.createImageDecoderConfig())
.build();
DraweeConfig.Builder draweeConfigBuilder = DraweeConfig.newBuilder();
ImageFormatConfigurator.addCustomDrawableFactories(draweeConfigBuilder);
CustomImageFormatConfigurator.addCustomDrawableFactories(draweeConfigBuilder);
Fresco.initialize(context, config, draweeConfigBuilder.build());
Fresco.getImagePipeline().clearCaches();
}
}
}
\ No newline at end of file
package chat.rocket.android.widget.fresco;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.v4.graphics.ColorUtils;
import com.facebook.common.internal.ByteStreams;
import com.facebook.drawee.backends.pipeline.DrawableFactory;
import com.facebook.imageformat.ImageFormat;
import com.facebook.imageformat.ImageFormatCheckerUtils;
import com.facebook.imagepipeline.common.ImageDecodeOptions;
import com.facebook.imagepipeline.decoder.ImageDecoder;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imagepipeline.image.QualityInfo;
import java.io.IOException;
import javax.annotation.Nullable;
/**
* Simple decoder that can decode color images that have the following format:
*
* <color>#FF5722</color>
*
* See: https://github.com/facebook/fresco/blob/master/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/color/ColorImage.java
*/
class ColorImage {
/**
* XML color tag that our colors must start with.
*/
public static final String COLOR_TAG = "<color>";
/**
* Custom {@link ImageFormat} for color images.
*/
public static final ImageFormat IMAGE_FORMAT_COLOR =
new ImageFormat("IMAGE_FORMAT_COLOR", "color");
/**
* Create a new image format checker for {@link #IMAGE_FORMAT_COLOR}.
* @return the image format checker
*/
public static ImageFormat.FormatChecker createFormatChecker() {
return new ColorFormatChecker();
}
/**
* Create a new decoder that can decode {@link #IMAGE_FORMAT_COLOR} images.
* @return the decoder
*/
public static ImageDecoder createDecoder() {
return new ColorDecoder();
}
public static ColorDrawableFactory createDrawableFactory() {
return new ColorDrawableFactory();
}
/**
* Custom color format checker that verifies that the header of the file
* corresponds to our {@link #COLOR_TAG}.
*/
public static class ColorFormatChecker implements ImageFormat.FormatChecker {
public static final byte[] HEADER = ImageFormatCheckerUtils.asciiBytes(COLOR_TAG);
@Override
public int getHeaderSize() {
return HEADER.length;
}
@Nullable
@Override
public ImageFormat determineFormat(byte[] headerBytes, int headerSize) {
if (headerSize < getHeaderSize()) {
return null;
}
if (ImageFormatCheckerUtils.startsWithPattern(headerBytes, HEADER)) {
return IMAGE_FORMAT_COLOR;
}
return null;
}
}
/**
* Custom closeable color image that holds a single color int value.
*/
public static class CloseableColorImage extends CloseableImage {
@ColorInt
private final int mColor;
private boolean mClosed = false;
public CloseableColorImage(int color) {
mColor = color;
}
@ColorInt
public int getColor() {
return mColor;
}
@Override
public int getSizeInBytes() {
return 0;
}
@Override
public void close() {
mClosed = true;
}
@Override
public boolean isClosed() {
return mClosed;
}
@Override
public int getWidth() {
return 0;
}
@Override
public int getHeight() {
return 0;
}
}
/**
* Decodes a color XML tag: <color>#rrggbb</color>
*/
public static class ColorDecoder implements ImageDecoder {
@Override
public CloseableImage decode(
EncodedImage encodedImage,
int length,
QualityInfo qualityInfo,
ImageDecodeOptions options) {
try {
// Read the file as a string
String text = new String(ByteStreams.toByteArray(encodedImage.getInputStream()));
// Check if the string matches "<color>#"
if (!text.startsWith(COLOR_TAG + "#")) {
return null;
}
// Parse the int value between # and <
int startIndex = COLOR_TAG.length() + 1;
int endIndex = text.lastIndexOf('<');
int color = Integer.parseInt(text.substring(startIndex, endIndex), 16);
// Add the alpha component so that we actually see the color
color = ColorUtils.setAlphaComponent(color, 255);
// Return the CloseableImage
return new CloseableColorImage(color);
} catch (IOException e) {
e.printStackTrace();
}
// Return nothing if an error occurred
return null;
}
}
/**
* Color drawable factory that is able to render a {@link CloseableColorImage} by creating
* a new {@link ColorDrawable} for the given color.
*/
public static class ColorDrawableFactory implements DrawableFactory {
@Override
public boolean supportsImageType(CloseableImage image) {
// We can only handle CloseableColorImages
return image instanceof CloseableColorImage;
}
@Nullable
@Override
public Drawable createDrawable(CloseableImage image) {
// Just return a simple ColorDrawable with the given color value
return new ColorDrawable(((CloseableColorImage)image).getColor());
}
}
}
......@@ -5,23 +5,20 @@ import com.facebook.drawee.backends.pipeline.DraweeConfig;
import com.facebook.imagepipeline.decoder.ImageDecoderConfig;
/**
* Based on https://github.com/facebook/fresco/blob/master/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/CustomImageFormatConfigurator.java
* Helper class to add custom decoders and drawable factories.
* See: https://github.com/facebook/fresco/blob/master/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/CustomImageFormatConfigurator.java
*/
public class ImageFormatConfigurator {
public class CustomImageFormatConfigurator {
@Nullable
public static ImageDecoderConfig createImageDecoderConfig() {
ImageDecoderConfig.Builder config = ImageDecoderConfig.newBuilder();
config.addDecodingCapability(
SvgDecoderConfig.SVG_FORMAT,
new SvgDecoderConfig.SvgFormatChecker(),
new SvgDecoderConfig.SvgDecoder());
config.addDecodingCapability(SvgDecoder.SVG_FORMAT, new SvgDecoder.SvgFormatChecker(), new SvgDecoder.Decoder());
return config.build();
}
public static void addCustomDrawableFactories(DraweeConfig.Builder draweeConfigBuilder) {
draweeConfigBuilder.addCustomDrawableFactory(new SvgDecoderConfig.SvgDrawableFactory());
draweeConfigBuilder.addCustomDrawableFactory(ColorImage.createDrawableFactory());
draweeConfigBuilder.addCustomDrawableFactory(new SvgDecoder.SvgDrawableFactory());
}
}
}
\ No newline at end of file
......@@ -3,8 +3,8 @@ package chat.rocket.android.widget.fresco;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PictureDrawable;
import android.support.annotation.Nullable;
import com.caverock.androidsvg.SVG;
import com.caverock.androidsvg.SVGParseException;
import com.facebook.drawee.backends.pipeline.DrawableFactory;
import com.facebook.imageformat.ImageFormat;
import com.facebook.imageformat.ImageFormatCheckerUtils;
......@@ -13,19 +13,18 @@ import com.facebook.imagepipeline.decoder.ImageDecoder;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imagepipeline.image.QualityInfo;
import com.caverock.androidsvg.SVG;
import com.caverock.androidsvg.SVGParseException;
import javax.annotation.Nullable;
/**
* Based on https://github.com/facebook/fresco/blob/master/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/svg/SvgDecoderExample.java
* SVG example that defines all classes required to decode and render SVG images.
* See: https://github.com/facebook/fresco/blob/master/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/svg/SvgDecoderExample.java
*/
public class SvgDecoderConfig {
public class SvgDecoder {
public static final ImageFormat SVG_FORMAT = new ImageFormat("SVG_FORMAT", "svg");
// We do not include the closing ">" since there can be additional information
private static final String HEADER_TAG = "<svg";
private static final String HEADER_TAG = "<?xml";
public static class SvgFormatChecker implements ImageFormat.FormatChecker {
......@@ -92,7 +91,7 @@ public class SvgDecoderConfig {
/**
* Decodes a SVG_FORMAT image
*/
public static class SvgDecoder implements ImageDecoder {
public static class Decoder implements ImageDecoder {
@Override
public CloseableImage decode(
......@@ -142,4 +141,4 @@ public class SvgDecoderConfig {
setPicture(mSvg.renderToPicture(bounds.width(), bounds.height()));
}
}
}
}
\ No newline at end of file
package chat.rocket.android.widget.helper
import android.net.Uri
import android.support.graphics.drawable.VectorDrawableCompat
import chat.rocket.android.widget.R
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.drawable.ProgressBarDrawable
import com.facebook.drawee.view.SimpleDraweeView
class FrescoAvatarHelper {
companion object {
@JvmStatic fun loadImage(draweeView: SimpleDraweeView, imageUrl: String) {
val hierarchy = draweeView.hierarchy
hierarchy.setPlaceholderImage(VectorDrawableCompat.create(draweeView.resources, R.drawable.ic_avatar_placeholder, null))
hierarchy.setFailureImage(VectorDrawableCompat.create(draweeView.resources, R.drawable.ic_avatar_failure, null))
hierarchy.setProgressBarImage(ProgressBarDrawable())
val controller = Fresco.newDraweeControllerBuilder()
.setUri(Uri.parse(imageUrl))
.setOldController(draweeView.controller)
.build()
draweeView.controller = controller
}
@JvmStatic fun showFailureImage(draweeView: SimpleDraweeView) {
val hierarchy = draweeView.hierarchy
hierarchy.setPlaceholderImage(VectorDrawableCompat.create(draweeView.resources, R.drawable.ic_avatar_failure, null))
hierarchy.setFailureImage(VectorDrawableCompat.create(draweeView.resources, R.drawable.ic_avatar_failure, null))
val controller = Fresco.newDraweeControllerBuilder()
.setAutoPlayAnimations(true)
.build()
draweeView.controller = controller
}
}
}
\ No newline at end of file
......@@ -22,10 +22,8 @@ public class FrescoHelper {
public static void setupDrawee(SimpleDraweeView draweeView) {
final GenericDraweeHierarchy hierarchy = draweeView.getHierarchy();
hierarchy.setPlaceholderImage(
VectorDrawableCompat.create(draweeView.getResources(), R.drawable.image_dummy, null));
hierarchy.setFailureImage(
VectorDrawableCompat.create(draweeView.getResources(), R.drawable.image_error, null));
hierarchy.setPlaceholderImage(VectorDrawableCompat.create(draweeView.getResources(), R.drawable.image_dummy, null));
hierarchy.setFailureImage(VectorDrawableCompat.create(draweeView.getResources(), R.drawable.image_error, null));
hierarchy.setProgressBarImage(new ProgressBarDrawable());
}
......@@ -37,4 +35,4 @@ public class FrescoHelper {
.build();
draweeView.setController(controller);
}
}
}
\ No newline at end of file
package chat.rocket.android.widget.message.autocomplete.user;
import android.content.Context;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.amulyakhare.textdrawable.TextDrawable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.RocketChatAvatar;
import chat.rocket.android.widget.message.autocomplete.AutocompleteViewHolder;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
public class UserViewHolder extends AutocompleteViewHolder<UserItem> {
private static final int[] COLORS = new int[]{
0xFFF44336, 0xFFE91E63, 0xFF9C27B0, 0xFF673AB7, 0xFF3F51B5, 0xFF2196F3,
0xFF03A9F4, 0xFF00BCD4, 0xFF009688, 0xFF4CAF50, 0xFF8BC34A, 0xFFCDDC39,
0xFFFFC107, 0xFFFF9800, 0xFFFF5722, 0xFF795548, 0xFF9E9E9E, 0xFF607D8B
};
private final TextView titleTextView;
private final RocketChatAvatar avatar;
private final ImageView status;
public UserViewHolder(View itemView,
final AutocompleteViewHolder.OnClickListener<UserItem> onClickListener) {
public UserViewHolder(View itemView, final AutocompleteViewHolder.OnClickListener<UserItem> onClickListener) {
super(itemView);
titleTextView = (TextView) itemView.findViewById(R.id.title);
avatar = (RocketChatAvatar) itemView.findViewById(R.id.avatar);
status = (ImageView) itemView.findViewById(R.id.status);
titleTextView = itemView.findViewById(R.id.title);
avatar = itemView.findViewById(R.id.avatar);
status = itemView.findViewById(R.id.status);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
......@@ -59,10 +44,7 @@ public class UserViewHolder extends AutocompleteViewHolder<UserItem> {
}
if (avatar != null) {
avatar.loadImage(
getImageUrl(suggestion, userItem.getAbsoluteUrl()),
getTextDrawable(itemView.getContext(), suggestion)
);
avatar.loadImage(getImageUrl(suggestion, userItem.getAbsoluteUrl()));
}
if (status != null) {
......@@ -81,7 +63,7 @@ public class UserViewHolder extends AutocompleteViewHolder<UserItem> {
//from Rocket.Chat:packages/rocketchat-ui/lib/avatar.coffee
//REMARK! this is often SVG image! (see: Rocket.Chat:server/startup/avatar.coffee)
try {
final String avatarUrl = "/avatar/" + URLEncoder.encode(username, "UTF-8") + ".jpg";
final String avatarUrl = "/avatar/" + URLEncoder.encode(username, "UTF-8");
// TODO why absoluteUrl is nullable? By allowing that, the app tries to load non-existing images
if (absoluteUrl == null) {
return avatarUrl;
......@@ -91,40 +73,4 @@ public class UserViewHolder extends AutocompleteViewHolder<UserItem> {
return null;
}
}
private Drawable getTextDrawable(Context context, String username) {
if (username == null) {
return null;
}
int round = (int) (4 * context.getResources().getDisplayMetrics().density);
return TextDrawable.builder()
.beginConfig()
.useFont(Typeface.SANS_SERIF)
.endConfig()
.buildRoundRect(getInitialsForUser(username), getColorForUser(username), round);
}
private int getColorForUser(String username) {
return COLORS[username.length() % COLORS.length];
}
private String getInitialsForUser(String username) {
String name = username
.replaceAll("[^A-Za-z0-9]", ".")
.replaceAll("\\.+", ".")
.replaceAll("(^\\.)|(\\.$)", "");
String[] initials = name.split("\\.");
if (initials.length >= 2) {
return (firstChar(initials[0]) + firstChar(initials[initials.length - 1])).toUpperCase();
} else {
String name2 = name.replaceAll("[^A-Za-z0-9]", "");
return (name2.length() < 2) ? name2 : name2.substring(0, 2).toUpperCase();
}
}
private static String firstChar(String str) {
return TextUtils.isEmpty(str) ? "" : str.substring(0, 1);
}
}
}
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#C2D1DA"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M3,0L21,0A3,3 0,0 1,24 3L24,21A3,3 0,0 1,21 24L3,24A3,3 0,0 1,0 21L0,3A3,3 0,0 1,3 0z"
android:fillColor="#C2D1DA"/>
<path
android:pathData="M11.594,11.869C12.928,11.869 14.01,10.555 14.01,8.934C14.01,7.314 13.655,6 11.594,6C9.534,6 9.178,7.314 9.178,8.934C9.178,10.555 10.26,11.869 11.594,11.869Z"
android:fillColor="#5D8298"/>
<path
android:pathData="M16.152,16.231C16.107,13.408 15.739,12.603 12.917,12.094C12.917,12.094 12.52,12.6 11.594,12.6C10.669,12.6 10.271,12.094 10.271,12.094C7.481,12.598 7.089,13.39 7.038,16.139C7.034,16.364 7.032,16.375 7.031,16.349C7.031,16.398 7.032,16.488 7.032,16.646C7.032,16.646 7.703,18 11.594,18C15.485,18 16.157,16.646 16.157,16.646C16.157,16.545 16.157,16.474 16.157,16.426C16.156,16.443 16.155,16.411 16.152,16.231Z"
android:fillColor="#5D8298"/>
</vector>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment