Commit 1a4e5808 authored by Rafael Kellermann Streit's avatar Rafael Kellermann Streit Committed by GitHub

Merge branch 'develop' into fix/websocket-drops-keeping-bolts

parents bf51bde0 64f0a630
...@@ -301,8 +301,8 @@ public class RoomFragment extends AbstractChatRoomFragment implements ...@@ -301,8 +301,8 @@ public class RoomFragment extends AbstractChatRoomFragment implements
closeSideMenuIfNeeded(); closeSideMenuIfNeeded();
}); });
DrawerLayout drawerLayout = (DrawerLayout) rootView.findViewById(R.id.drawer_layout); DrawerLayout drawerLayout = rootView.findViewById(R.id.drawer_layout);
SlidingPaneLayout pane = (SlidingPaneLayout) getActivity().findViewById(R.id.sliding_pane); SlidingPaneLayout pane = getActivity().findViewById(R.id.sliding_pane);
if (drawerLayout != null && pane != null) { if (drawerLayout != null && pane != null) {
compositeDisposable.add(RxDrawerLayout.drawerOpen(drawerLayout, GravityCompat.END) compositeDisposable.add(RxDrawerLayout.drawerOpen(drawerLayout, GravityCompat.END)
.compose(bindToLifecycle()) .compose(bindToLifecycle())
......
...@@ -10,19 +10,7 @@ import android.support.v7.widget.RecyclerView; ...@@ -10,19 +10,7 @@ import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView; import android.support.v7.widget.SearchView;
import android.view.View; import android.view.View;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView; 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.BuildConfig;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache; import chat.rocket.android.RocketChatCache;
...@@ -40,19 +28,26 @@ import chat.rocket.android.layouthelper.chatroom.roomlist.FavoriteRoomListHeader ...@@ -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.RoomListAdapter;
import chat.rocket.android.layouthelper.chatroom.roomlist.RoomListHeader; import chat.rocket.android.layouthelper.chatroom.roomlist.RoomListHeader;
import chat.rocket.android.layouthelper.chatroom.roomlist.UnreadRoomListHeader; import chat.rocket.android.layouthelper.chatroom.roomlist.UnreadRoomListHeader;
import chat.rocket.android.renderer.UserRenderer;
import chat.rocket.core.SortDirection; import chat.rocket.core.SortDirection;
import chat.rocket.core.interactors.RoomInteractor; import chat.rocket.core.interactors.RoomInteractor;
import chat.rocket.core.interactors.SessionInteractor; import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.core.models.Room; import chat.rocket.core.models.Room;
import chat.rocket.core.models.SpotlightRoom; import chat.rocket.core.models.SpotlightRoom;
import chat.rocket.core.models.User; 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.RealmRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmServerInfoRepository; import chat.rocket.persistence.realm.repositories.RealmServerInfoRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository; import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmSpotlightRoomRepository; import chat.rocket.persistence.realm.repositories.RealmSpotlightRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository; 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 { public class SidebarMainFragment extends AbstractFragment implements SidebarMainContract.View {
...@@ -230,9 +225,9 @@ public class SidebarMainFragment extends AbstractFragment implements SidebarMain ...@@ -230,9 +225,9 @@ public class SidebarMainFragment extends AbstractFragment implements SidebarMain
private void onRenderCurrentUser(User user, RocketChatAbsoluteUrl absoluteUrl) { private void onRenderCurrentUser(User user, RocketChatAbsoluteUrl absoluteUrl) {
if (user != null && absoluteUrl != null) { if (user != null && absoluteUrl != null) {
new UserRenderer(getContext(), user) new UserRenderer(getContext(), user)
.avatarInto((RocketChatAvatar) rootView.findViewById(R.id.current_user_avatar), absoluteUrl) .avatarInto(rootView.findViewById(R.id.current_user_avatar), absoluteUrl, false)
.usernameInto((TextView) rootView.findViewById(R.id.current_user_name)) .usernameInto(rootView.findViewById(R.id.current_user_name))
.statusColorInto((ImageView) rootView.findViewById(R.id.current_user_status)); .statusColorInto(rootView.findViewById(R.id.current_user_status));
} }
} }
......
package chat.rocket.android.helper; 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.log.RCLog;
import chat.rocket.android.widget.AbsoluteUrl; import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.android.widget.RocketChatAvatar; import chat.rocket.android.widget.RocketChatAvatar;
import com.amulyakhare.textdrawable.TextDrawable;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
...@@ -19,11 +10,6 @@ import java.net.URLEncoder; ...@@ -19,11 +10,6 @@ import java.net.URLEncoder;
* Helper for rendering user avatar image. * Helper for rendering user avatar image.
*/ */
public class Avatar { 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 AbsoluteUrl absoluteUrl;
private final String username; private final String username;
...@@ -32,48 +18,11 @@ public class Avatar { ...@@ -32,48 +18,11 @@ public class Avatar {
this.username = username; 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() { private String getImageUrl() {
//from Rocket.Chat:packages/rocketchat-ui/lib/avatar.coffee //from Rocket.Chat:packages/rocketchat-ui/lib/avatar.coffee
//REMARK! this is often SVG image! (see: Rocket.Chat:server/startup/avatar.coffee) //REMARK! this is often SVG image! (see: Rocket.Chat:server/startup/avatar.coffee)
try { try {
final String avatarUrl = "/avatar/" + URLEncoder.encode(username, "UTF-8") + ".jpg"; final String avatarUrl = "/avatar/" + URLEncoder.encode(username, "UTF-8");
if (absoluteUrl == null) { if (absoluteUrl == null) {
return avatarUrl; return avatarUrl;
} }
...@@ -87,31 +36,11 @@ public class Avatar { ...@@ -87,31 +36,11 @@ public class Avatar {
/** /**
* render avatar into RocketChatAvatar. * render avatar into RocketChatAvatar.
*/ */
public void into(final RocketChatAvatar rocketChatAvatar) { public void into(final RocketChatAvatar rocketChatAvatar, boolean showFailureImage) {
final Context context = rocketChatAvatar.getContext(); if (showFailureImage) {
rocketChatAvatar.loadImage(getImageUrl(), getTextDrawable(context)); rocketChatAvatar.showFailureImage();
} } else {
rocketChatAvatar.loadImage(getImageUrl());
/**
* 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;
} }
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 ...@@ -25,13 +25,13 @@ public abstract class AbstractMessageViewHolder extends ModelViewHolder<PairedMe
*/ */
public AbstractMessageViewHolder(View itemView, AbsoluteUrl absoluteUrl) { public AbstractMessageViewHolder(View itemView, AbsoluteUrl absoluteUrl) {
super(itemView); super(itemView);
avatar = (RocketChatAvatar) itemView.findViewById(R.id.user_avatar); avatar = itemView.findViewById(R.id.user_avatar);
username = (TextView) itemView.findViewById(R.id.username); username = itemView.findViewById(R.id.username);
subUsername = (TextView) itemView.findViewById(R.id.sub_username); subUsername = itemView.findViewById(R.id.sub_username);
timestamp = (TextView) itemView.findViewById(R.id.timestamp); timestamp = itemView.findViewById(R.id.timestamp);
userAndTimeContainer = itemView.findViewById(R.id.user_and_timestamp_container); userAndTimeContainer = itemView.findViewById(R.id.user_and_timestamp_container);
newDayContainer = itemView.findViewById(R.id.newday_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; this.absoluteUrl = absoluteUrl;
} }
...@@ -39,10 +39,10 @@ public abstract class AbstractMessageViewHolder extends ModelViewHolder<PairedMe ...@@ -39,10 +39,10 @@ public abstract class AbstractMessageViewHolder extends ModelViewHolder<PairedMe
* bind the view model. * bind the view model.
*/ */
public final void bind(PairedMessage pairedMessage, boolean autoloadImages) { public final void bind(PairedMessage pairedMessage, boolean autoloadImages) {
if (pairedMessage.target != null) { if (pairedMessage.target.getSyncState() != SyncState.SYNCED) {
if (pairedMessage.target.getSyncState() != SyncState.SYNCED)
itemView.setAlpha(0.6f); itemView.setAlpha(0.6f);
else }
else {
itemView.setAlpha(1.0f); itemView.setAlpha(1.0f);
} }
......
...@@ -21,9 +21,9 @@ public class MessageNormalViewHolder extends AbstractMessageViewHolder { ...@@ -21,9 +21,9 @@ public class MessageNormalViewHolder extends AbstractMessageViewHolder {
*/ */
public MessageNormalViewHolder(View itemView, AbsoluteUrl absoluteUrl) { public MessageNormalViewHolder(View itemView, AbsoluteUrl absoluteUrl) {
super(itemView, absoluteUrl); super(itemView, absoluteUrl);
body = (RocketChatMessageLayout) itemView.findViewById(R.id.message_body); body = itemView.findViewById(R.id.message_body);
urls = (RocketChatMessageUrlsLayout) itemView.findViewById(R.id.message_urls); urls = itemView.findViewById(R.id.message_urls);
attachments = (RocketChatMessageAttachmentsLayout) itemView.findViewById(R.id.message_attachments); attachments = itemView.findViewById(R.id.message_attachments);
} }
@Override @Override
......
...@@ -18,7 +18,7 @@ public class MessageSystemViewHolder extends AbstractMessageViewHolder { ...@@ -18,7 +18,7 @@ public class MessageSystemViewHolder extends AbstractMessageViewHolder {
*/ */
public MessageSystemViewHolder(View itemView, AbsoluteUrl absoluteUrl) { public MessageSystemViewHolder(View itemView, AbsoluteUrl absoluteUrl) {
super(itemView, absoluteUrl); super(itemView, absoluteUrl);
body = (TextView) itemView.findViewById(R.id.message_body); body = itemView.findViewById(R.id.message_body);
} }
@Override @Override
......
...@@ -58,12 +58,12 @@ public class RoomUserAdapter extends RecyclerView.Adapter<RoomUserViewHolder> { ...@@ -58,12 +58,12 @@ public class RoomUserAdapter extends RecyclerView.Adapter<RoomUserViewHolder> {
.setUtcOffset(0) .setUtcOffset(0)
.build(); .build();
new UserRenderer(context, user) new UserRenderer(context, user)
.avatarInto(holder.avatar, absoluteUrl) .avatarInto(holder.avatar, absoluteUrl, false)
.usernameInto(holder.username); .usernameInto(holder.username);
} else { } else {
new UserRenderer(context, realmUser.asUser()) new UserRenderer(context, realmUser.asUser())
.statusColorInto(holder.status) .statusColorInto(holder.status)
.avatarInto(holder.avatar, absoluteUrl) .avatarInto(holder.avatar, absoluteUrl, false)
.usernameInto(holder.username); .usernameInto(holder.username);
} }
} }
......
...@@ -28,7 +28,7 @@ public class SuggestUserAdapter extends RealmAutoCompleteAdapter<RealmUser> { ...@@ -28,7 +28,7 @@ public class SuggestUserAdapter extends RealmAutoCompleteAdapter<RealmUser> {
protected void onBindItemView(View itemView, RealmUser user) { protected void onBindItemView(View itemView, RealmUser user) {
new UserRenderer(itemView.getContext(), user.asUser()) new UserRenderer(itemView.getContext(), user.asUser())
.statusColorInto((ImageView) itemView.findViewById(R.id.room_user_status)) .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 @Override
......
...@@ -4,7 +4,6 @@ import android.content.Context; ...@@ -4,7 +4,6 @@ import android.content.Context;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.helper.Avatar;
import chat.rocket.android.helper.DateTime; import chat.rocket.android.helper.DateTime;
import chat.rocket.android.helper.TextUtils; import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.widget.AbsoluteUrl; import chat.rocket.android.widget.AbsoluteUrl;
...@@ -15,7 +14,6 @@ import chat.rocket.android.widget.message.RocketChatMessageUrlsLayout; ...@@ -15,7 +14,6 @@ import chat.rocket.android.widget.message.RocketChatMessageUrlsLayout;
import chat.rocket.core.SyncState; import chat.rocket.core.SyncState;
import chat.rocket.core.models.Attachment; import chat.rocket.core.models.Attachment;
import chat.rocket.core.models.Message; import chat.rocket.core.models.Message;
import chat.rocket.core.models.User;
import chat.rocket.core.models.WebContent; import chat.rocket.core.models.WebContent;
import java.util.List; import java.util.List;
...@@ -41,16 +39,18 @@ public class MessageRenderer extends AbstractRenderer<Message> { ...@@ -41,16 +39,18 @@ public class MessageRenderer extends AbstractRenderer<Message> {
return this; return this;
} }
if (object.getSyncState() == SyncState.FAILED) switch (object.getSyncState()){
// rocketChatAvatar.loadImage(VectorDrawableCompat.create(context.getResources(), R.drawable.ic_error_outline_black_24dp, null)); case SyncState.FAILED:
userRenderer.errorAvatarInto(rocketChatAvatar); userRenderer.avatarInto(rocketChatAvatar, absoluteUrl, true);
else if (TextUtils.isEmpty(object.getAvatar())) break;
userRenderer.avatarInto(rocketChatAvatar, absoluteUrl); default:
else { if (TextUtils.isEmpty(object.getAvatar())) {
final User user = object.getUser(); userRenderer.avatarInto(rocketChatAvatar, absoluteUrl, false);
setAvatarInto(object.getAvatar(), absoluteUrl, user == null ? null : user.getUsername(), rocketChatAvatar); } else {
rocketChatAvatar.loadImage(object.getAvatar());
}
break;
} }
return this; return this;
} }
...@@ -91,7 +91,6 @@ public class MessageRenderer extends AbstractRenderer<Message> { ...@@ -91,7 +91,6 @@ public class MessageRenderer extends AbstractRenderer<Message> {
textView.setText(DateTime.fromEpocMs(object.getTimestamp(), DateTime.Format.TIME)); textView.setText(DateTime.fromEpocMs(object.getTimestamp(), DateTime.Format.TIME));
break; break;
} }
return this; return this;
} }
...@@ -104,7 +103,6 @@ public class MessageRenderer extends AbstractRenderer<Message> { ...@@ -104,7 +103,6 @@ public class MessageRenderer extends AbstractRenderer<Message> {
} }
rocketChatMessageLayout.setText(object.getMessage()); rocketChatMessageLayout.setText(object.getMessage());
return this; return this;
} }
...@@ -123,15 +121,13 @@ public class MessageRenderer extends AbstractRenderer<Message> { ...@@ -123,15 +121,13 @@ public class MessageRenderer extends AbstractRenderer<Message> {
urlsLayout.setVisibility(View.VISIBLE); urlsLayout.setVisibility(View.VISIBLE);
urlsLayout.setUrls(webContents, autoloadImages); urlsLayout.setUrls(webContents, autoloadImages);
} }
return this; return this;
} }
/** /**
* show urls in RocketChatMessageUrlsLayout. * show urls in RocketChatMessageUrlsLayout.
*/ */
public MessageRenderer attachmentsInto(RocketChatMessageAttachmentsLayout attachmentsLayout, public MessageRenderer attachmentsInto(RocketChatMessageAttachmentsLayout attachmentsLayout, AbsoluteUrl absoluteUrl) {
AbsoluteUrl absoluteUrl) {
if (!shouldHandle(attachmentsLayout)) { if (!shouldHandle(attachmentsLayout)) {
return this; return this;
} }
...@@ -144,14 +140,9 @@ public class MessageRenderer extends AbstractRenderer<Message> { ...@@ -144,14 +140,9 @@ public class MessageRenderer extends AbstractRenderer<Message> {
attachmentsLayout.setAbsoluteUrl(absoluteUrl); attachmentsLayout.setAbsoluteUrl(absoluteUrl);
attachmentsLayout.setAttachments(attachments, autoloadImages); attachmentsLayout.setAttachments(attachments, autoloadImages);
} }
return this; 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) { private void aliasAndUsernameInto(TextView aliasTextView, TextView usernameTextView) {
if (shouldHandle(aliasTextView)) { if (shouldHandle(aliasTextView)) {
aliasTextView.setText(object.getAlias()); aliasTextView.setText(object.getAlias());
......
...@@ -21,27 +21,14 @@ public class UserRenderer extends AbstractRenderer<User> { ...@@ -21,27 +21,14 @@ public class UserRenderer extends AbstractRenderer<User> {
/** /**
* show Avatar image * show Avatar image
*/ */
public UserRenderer avatarInto(RocketChatAvatar rocketChatAvatar, AbsoluteUrl absoluteUrl) { public UserRenderer avatarInto(RocketChatAvatar rocketChatAvatar, AbsoluteUrl absoluteUrl, boolean showFailureImage) {
if (!shouldHandle(rocketChatAvatar)) { if (!shouldHandle(rocketChatAvatar)) {
return this; return this;
} }
if (!TextUtils.isEmpty(object.getUsername())) { if (!TextUtils.isEmpty(object.getUsername())) {
new Avatar(absoluteUrl, object.getUsername()).into(rocketChatAvatar); new Avatar(absoluteUrl, object.getUsername())
} .into(rocketChatAvatar, showFailureImage);
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);
} }
return this; return this;
} }
......
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,8 @@
<string name="users_of_room_title">Members List</string> <string name="users_of_room_title">Members List</string>
<string name="fmt_room_user_count">Total: %,d users</string> <string name="fmt_room_user_count">Total: %,d users</string>
<string name="sending">Sending…</string> <string name="sending">Sending…</string>
<string name="not_synced">Not synced</string> <string name="not_synced">Not synced</string>
<string name="failed_to_sync">Failed to sync</string> <string name="failed_to_sync">Failed to sync</string>
<string name="resend">Resend</string> <string name="resend">Resend</string>
<string name="discard">Discard</string> <string name="discard">Discard</string>
......
...@@ -21,6 +21,7 @@ ext { ...@@ -21,6 +21,7 @@ ext {
targetSdkVersion = 26 targetSdkVersion = 26
buildToolsVersion = "26.0.0" buildToolsVersion = "26.0.0"
supportLibraryVersion = "25.4.0" supportLibraryVersion = "25.4.0"
kotlinVersion = "1.1.3"
} }
task clean(type: Delete) { task clean(type: Delete) {
......
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
buildscript { buildscript {
repositories { repositories {
jcenter() jcenter()
mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.android.tools.build:gradle:2.3.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.kotlinVersion"
} }
} }
...@@ -44,6 +47,8 @@ dependencies { ...@@ -44,6 +47,8 @@ dependencies {
compile "com.android.support:support-v13:$rootProject.ext.supportLibraryVersion" compile "com.android.support:support-v13:$rootProject.ext.supportLibraryVersion"
compile "com.android.support:design:$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 'org.nibor.autolink:autolink:0.6.0'
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
......
...@@ -2,15 +2,11 @@ package chat.rocket.android.widget; ...@@ -2,15 +2,11 @@ package chat.rocket.android.widget;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import com.facebook.drawee.backends.pipeline.Fresco; import chat.rocket.android.widget.helper.FrescoAvatarHelper;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.drawee.view.SimpleDraweeView;
public class RocketChatAvatar extends FrameLayout { public class RocketChatAvatar extends FrameLayout {
...@@ -39,36 +35,19 @@ public class RocketChatAvatar extends FrameLayout { ...@@ -39,36 +35,19 @@ public class RocketChatAvatar extends FrameLayout {
} }
private void initialize(Context context, AttributeSet attrs) { private void initialize(Context context, AttributeSet attrs) {
LayoutInflater.from(context) LayoutInflater
.from(context)
.inflate(R.layout.message_avatar, this, true); .inflate(R.layout.message_avatar, this, true);
draweeView = findViewById(R.id.drawee_avatar);
draweeView = (SimpleDraweeView) findViewById(R.id.drawee_avatar);
} }
public void loadImage(Drawable drawable) { public void loadImage(String imageUrl) {
// final GenericDraweeHierarchy hierarchy = draweeView.getHierarchy(); FrescoAvatarHelper
// hierarchy.setImage(drawable, 100, true); // Is there a better way? .loadImage(draweeView, imageUrl);
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 url, Drawable placeholder) { public void showFailureImage() {
final GenericDraweeHierarchy hierarchy = draweeView.getHierarchy(); FrescoAvatarHelper
hierarchy.setPlaceholderImage(placeholder); .showFailureImage(draweeView);
hierarchy.setFailureImage(placeholder);
final DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(Uri.parse(url))
.setAutoPlayAnimations(true)
.build();
draweeView.setController(controller);
} }
} }
\ No newline at end of file
package chat.rocket.android.widget; package chat.rocket.android.widget;
import android.content.Context; import android.content.Context;
import chat.rocket.android.widget.fresco.CustomImageFormatConfigurator;
import com.facebook.drawee.backends.pipeline.DraweeConfig; import com.facebook.drawee.backends.pipeline.DraweeConfig;
import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory; import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory;
import com.facebook.imagepipeline.core.ImagePipelineConfig; import com.facebook.imagepipeline.core.ImagePipelineConfig;
import chat.rocket.android.widget.fresco.ImageFormatConfigurator;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
public class RocketChatWidgets { public class RocketChatWidgets {
...@@ -16,14 +14,12 @@ public class RocketChatWidgets { ...@@ -16,14 +14,12 @@ public class RocketChatWidgets {
ImagePipelineConfig config = OkHttpImagePipelineConfigFactory ImagePipelineConfig config = OkHttpImagePipelineConfigFactory
.newBuilder(context, okHttpClient) .newBuilder(context, okHttpClient)
.setDownsampleEnabled(true) .setDownsampleEnabled(true)
.setImageDecoderConfig(ImageFormatConfigurator.createImageDecoderConfig()) .setImageDecoderConfig(CustomImageFormatConfigurator.createImageDecoderConfig())
.build(); .build();
DraweeConfig.Builder draweeConfigBuilder = DraweeConfig.newBuilder(); DraweeConfig.Builder draweeConfigBuilder = DraweeConfig.newBuilder();
ImageFormatConfigurator.addCustomDrawableFactories(draweeConfigBuilder); CustomImageFormatConfigurator.addCustomDrawableFactories(draweeConfigBuilder);
Fresco.initialize(context, config, draweeConfigBuilder.build()); 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; ...@@ -5,23 +5,20 @@ import com.facebook.drawee.backends.pipeline.DraweeConfig;
import com.facebook.imagepipeline.decoder.ImageDecoderConfig; 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 @Nullable
public static ImageDecoderConfig createImageDecoderConfig() { public static ImageDecoderConfig createImageDecoderConfig() {
ImageDecoderConfig.Builder config = ImageDecoderConfig.newBuilder(); ImageDecoderConfig.Builder config = ImageDecoderConfig.newBuilder();
config.addDecodingCapability(SvgDecoder.SVG_FORMAT, new SvgDecoder.SvgFormatChecker(), new SvgDecoder.Decoder());
config.addDecodingCapability(
SvgDecoderConfig.SVG_FORMAT,
new SvgDecoderConfig.SvgFormatChecker(),
new SvgDecoderConfig.SvgDecoder());
return config.build(); return config.build();
} }
public static void addCustomDrawableFactories(DraweeConfig.Builder draweeConfigBuilder) { 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; ...@@ -3,8 +3,8 @@ package chat.rocket.android.widget.fresco;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.PictureDrawable; 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.drawee.backends.pipeline.DrawableFactory;
import com.facebook.imageformat.ImageFormat; import com.facebook.imageformat.ImageFormat;
import com.facebook.imageformat.ImageFormatCheckerUtils; import com.facebook.imageformat.ImageFormatCheckerUtils;
...@@ -13,19 +13,18 @@ import com.facebook.imagepipeline.decoder.ImageDecoder; ...@@ -13,19 +13,18 @@ import com.facebook.imagepipeline.decoder.ImageDecoder;
import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.image.EncodedImage; import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imagepipeline.image.QualityInfo; import com.facebook.imagepipeline.image.QualityInfo;
import javax.annotation.Nullable;
import com.caverock.androidsvg.SVG;
import com.caverock.androidsvg.SVGParseException;
/** /**
* 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"); public static final ImageFormat SVG_FORMAT = new ImageFormat("SVG_FORMAT", "svg");
// We do not include the closing ">" since there can be additional information // 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 { public static class SvgFormatChecker implements ImageFormat.FormatChecker {
...@@ -92,7 +91,7 @@ public class SvgDecoderConfig { ...@@ -92,7 +91,7 @@ public class SvgDecoderConfig {
/** /**
* Decodes a SVG_FORMAT image * Decodes a SVG_FORMAT image
*/ */
public static class SvgDecoder implements ImageDecoder { public static class Decoder implements ImageDecoder {
@Override @Override
public CloseableImage decode( public CloseableImage decode(
......
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 { ...@@ -22,10 +22,8 @@ public class FrescoHelper {
public static void setupDrawee(SimpleDraweeView draweeView) { public static void setupDrawee(SimpleDraweeView draweeView) {
final GenericDraweeHierarchy hierarchy = draweeView.getHierarchy(); final GenericDraweeHierarchy hierarchy = draweeView.getHierarchy();
hierarchy.setPlaceholderImage( hierarchy.setPlaceholderImage(VectorDrawableCompat.create(draweeView.getResources(), R.drawable.image_dummy, null));
VectorDrawableCompat.create(draweeView.getResources(), R.drawable.image_dummy, null)); hierarchy.setFailureImage(VectorDrawableCompat.create(draweeView.getResources(), R.drawable.image_error, null));
hierarchy.setFailureImage(
VectorDrawableCompat.create(draweeView.getResources(), R.drawable.image_error, null));
hierarchy.setProgressBarImage(new ProgressBarDrawable()); hierarchy.setProgressBarImage(new ProgressBarDrawable());
} }
......
package chat.rocket.android.widget.message.autocomplete.user; 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.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; 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.AbsoluteUrl;
import chat.rocket.android.widget.R; import chat.rocket.android.widget.R;
import chat.rocket.android.widget.RocketChatAvatar; import chat.rocket.android.widget.RocketChatAvatar;
import chat.rocket.android.widget.message.autocomplete.AutocompleteViewHolder; import chat.rocket.android.widget.message.autocomplete.AutocompleteViewHolder;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
public class UserViewHolder extends AutocompleteViewHolder<UserItem> { 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 TextView titleTextView;
private final RocketChatAvatar avatar; private final RocketChatAvatar avatar;
private final ImageView status; private final ImageView status;
public UserViewHolder(View itemView, public UserViewHolder(View itemView, final AutocompleteViewHolder.OnClickListener<UserItem> onClickListener) {
final AutocompleteViewHolder.OnClickListener<UserItem> onClickListener) {
super(itemView); super(itemView);
titleTextView = (TextView) itemView.findViewById(R.id.title); titleTextView = itemView.findViewById(R.id.title);
avatar = (RocketChatAvatar) itemView.findViewById(R.id.avatar); avatar = itemView.findViewById(R.id.avatar);
status = (ImageView) itemView.findViewById(R.id.status); status = itemView.findViewById(R.id.status);
itemView.setOnClickListener(new View.OnClickListener() { itemView.setOnClickListener(new View.OnClickListener() {
@Override @Override
...@@ -59,10 +44,7 @@ public class UserViewHolder extends AutocompleteViewHolder<UserItem> { ...@@ -59,10 +44,7 @@ public class UserViewHolder extends AutocompleteViewHolder<UserItem> {
} }
if (avatar != null) { if (avatar != null) {
avatar.loadImage( avatar.loadImage(getImageUrl(suggestion, userItem.getAbsoluteUrl()));
getImageUrl(suggestion, userItem.getAbsoluteUrl()),
getTextDrawable(itemView.getContext(), suggestion)
);
} }
if (status != null) { if (status != null) {
...@@ -81,7 +63,7 @@ public class UserViewHolder extends AutocompleteViewHolder<UserItem> { ...@@ -81,7 +63,7 @@ public class UserViewHolder extends AutocompleteViewHolder<UserItem> {
//from Rocket.Chat:packages/rocketchat-ui/lib/avatar.coffee //from Rocket.Chat:packages/rocketchat-ui/lib/avatar.coffee
//REMARK! this is often SVG image! (see: Rocket.Chat:server/startup/avatar.coffee) //REMARK! this is often SVG image! (see: Rocket.Chat:server/startup/avatar.coffee)
try { 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 // TODO why absoluteUrl is nullable? By allowing that, the app tries to load non-existing images
if (absoluteUrl == null) { if (absoluteUrl == null) {
return avatarUrl; return avatarUrl;
...@@ -91,40 +73,4 @@ public class UserViewHolder extends AutocompleteViewHolder<UserItem> { ...@@ -91,40 +73,4 @@ public class UserViewHolder extends AutocompleteViewHolder<UserItem> {
return null; 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