Skip to content
This repository has been archived by the owner on Jun 7, 2020. It is now read-only.

Show avatar with username initials. #372

Merged
merged 32 commits into from
Aug 3, 2017
Merged

Show avatar with username initials. #372

merged 32 commits into from
Aug 3, 2017

Conversation

philipbrito
Copy link
Contributor

@RocketChat/android

Closes #369


return TextDrawable.builder()
.beginConfig()
.useFont(Typeface.SANS_SERIF)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it use the default one from the OS?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be rendered as expected on Application, since the Typeface was added by the Android platform since API level 1.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

0xFFF44336.toInt(), 0xFFE91E63.toInt(), 0xFF9C27B0.toInt(), 0xFF673AB7.toInt(), 0xFF3F51B5.toInt(),
0xFF2196F3.toInt(), 0xFF03A9F4.toInt(), 0xFF00BCD4.toInt(), 0xFF009688.toInt(), 0xFF4CAF50.toInt(),
0xFF8BC34A.toInt(), 0xFFCDDC39.toInt(), 0xFFFFC107.toInt(), 0xFFFF9800.toInt(), 0xFFFF5722.toInt(),
0xFF795548.toInt(), 0xFF9E9E9E.toInt(), 0xFF607D8B.toInt())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about creating unit tests for this class? You could test a few example names with a few different usernames. On iOS I tested a bunch of different cases: https://github.com/RocketChat/Rocket.Chat.iOS/blob/develop/Rocket.ChatTests/Views/AvatarViewSpec.swift.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course! I'll do unit tests! Thank you guy for putting those thoughts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}
}
});
return contentType;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This most likely will be null, since you are doing an Asynchronous request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To get the contentType we need to do a network call - blocking operation, so we need to do an async task, right? There are the following possibilities:

  1. Get the contentType using the HttpURLConnection with the AsyncTask.
  2. Get the contentType using the HttpURLConnection with RxJava.
  3. Get the contentType using the OkHttp which is currently used on RC app - OkHttp is a built-in response cache, a better cookie model, a better headers model and a better call model.

So, how we should handle this requirement?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not do this, if we really need to get the Content-Type, we will need to implement our own Image loading library, like Fresco or Picasso, to download, cache and display it...

Doing a lot of HEADs here will make a lot of useless requests on fling, we are not caching the result...

Ideally this should be handled on the Image library, on Fresco, or switch to some that already supports SVG.

* @see getUri
*/
fun getImageFormat(uri: String): String {
return OkHttpHelper.getContentType(uri)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have this information from the backend, each time we show an Avatar doing a HEAD (network operation) operation and a GET seems very expensive.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I pay attention w/ it, so to get the image format the requested method is HEAD and not GET. Take a look.

https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html:

The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response. The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request. This method can be used for obtaining metainformation about the entity implied by the request without transferring the entity-body itself. This method is often used for testing hypertext links for validity, accessibility, and recent modification.

private static OkHttpClient httpClientForUploadFile;
private static OkHttpClient httpClientForDownloadFile;
private static OkHttpClient httpClientForWS;
private static String contentType;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not work as expected...

First time you call getContentType() it will probably return null, since it is a async request.
Next time you call it, you will probably get the contentType from the previous request, which may be right or not.

On a list flinging, there will be dozens of requests overwriting contentType

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First time you call getContentType() it will probably return null, since it is a async request.

See the comment below.

On a list flinging, there will be dozens of requests overwriting contentType

I agree w/ you. There is a way to avoid it, but it involves involves a disabled support. See this comment.

* @param username The username.
* @return A string with username initials.
*/
fun getUsernameInitials(username: String): String {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function doesn't conform with the documentation, it handles only separation by "." and not by spaces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@luciofm What did you mean?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the javadoc (for kotlin 😄) says one thing and the function does another..

@@ -14,6 +14,7 @@
public abstract class AbstractMessageViewHolder extends ModelViewHolder<PairedMessage> {
protected final RocketChatAvatar avatar;
protected final ImageView userNotFoundAvatarImageView;
protected final ImageView imageViewSvgUserAvatar;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to use a separate ImageView? This should probably be handled on RocketChatAvatar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need, because RocketChatAvatar is a class which returns a SimpleDraweeView. There is a setImageDrawable() method on SimpleDraweeView but it is deprecated (trying to use it you'll get the messed avatars). Also, from Fresco:

Don’t set images directly on a DraweeView
Currently DraweeView is a subclass of Android’s ImageView. This has various methods to set an image (such as setImageBitmap, setImageDrawable)
If you set an image directly, you will completely lose your DraweeHierarchy, and will not get any results from the image pipeline.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion is to work with Fresco, and implement support for SVG directly on it...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, adding SVG support for Fresco

userNotFoundAvatarImageView.visibility = View.GONE
rocketChatAvatarWidget.visibility = View.VISIBLE
val userAvatarUri = RocketChatUserAvatar.getUri(hostname, username)
val userAvatarImageContentType = OkHttpHelper.getContentType(userAvatarUri)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As commented before, this will not work consistently.

@rafaelks
Copy link
Contributor

rafaelks commented Aug 2, 2017

@filipedelimabrito Why don't you implement it the way iOS does?

Here's the structure of the AvatarView, basically:

-> UIView (the avatar view itself, root)
     -> ViewInitials (view with the initials, manually created, always present)
     -> ImageView (imageView with avatar, after loaded and cached)

This way, while the image is loading the imageView is transparent and the ViewInitials is the one that's being present to the user. If ImageView is loaded/cached, it will show the ImageView instead of ViewInitials.

@philipbrito
Copy link
Contributor Author

That's I said before to you @rafaelks. We can set a placeholder image w/ username initials and if the image from backend is loaded successful then we show it. Note that the images w/ username initials are loaded from the backend, but it isn't build correctly - until now (see RocketChat/Rocket.Chat#7572). As the chatroom background is white we can't see the username initials which are white too (the background isn't load because the SVG is build in a wrong way).

We are supporting the SVG rendering on RC app (I added it on some PR times ago). We simply can disable the SVG support to set the avatar w/ username initials and Fresco will do the rest (loading images that are not in SVG format, and setting the placeholder w/ username initials when the images are in SVG format - or, when the loading from server fails).

See this comment also, can be useful.

@@ -26,7 +31,7 @@ public static void initialize(Context context, OkHttpClient okHttpClient) {
ImagePipelineConfig imagePipelineConfig = OkHttpImagePipelineConfigFactory
.newBuilder(context, okHttpClient)
.setRequestListeners(listeners)
.setImageDecoderConfig(CustomImageFormatConfigurator.createImageDecoderConfig())
// .setImageDecoderConfig(CustomImageFormatConfigurator.createImageDecoderConfig())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this commented code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we need to plan to support SVG image in the future I think that we could leave this commented code. If not, we can remove it (and the CustomImageFormatConfigurator and SvgDecoder class too).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have github... We can always look the code history to recover this...

@@ -16,6 +20,12 @@
private final RocketChatAvatar avatar;
private final ImageView status;

private static final int[] COLORS = new int[] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you repeating the colors here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for the same reason bellow.

:(

.buildRoundRect(getUsernameInitials(username), getUserAvatarBackgroundColor(username), round);
}

private String getUsernameInitials(String username) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method seems to be duplicated...?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes :(
I just put it as before (w/ some the correct modifications). This class belongs to another module, there is the possibility to add the :app dependency to this module to avoid that duplication, but I really don't know if this is the correct (in the terms of design, architecture...). So I just did like before.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think the widgets should live on the app module... unless they are meant to be reusable, them they should live on another repository...

Copy link
Contributor

@rafaelks rafaelks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just small change in your method docs.

}

/**
* Returns a string with the username initials. For example: username John Doe returns JD initials.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should change this docs to something that will work. In your method John Doe will not become JD.

@@ -0,0 +1,22 @@
import chat.rocket.android.widget.helper.UserAvatarHelper
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests should live in the same package (and directory structure) as the class you are testing...

xmlns:fresco="http://schemas.android.com/apk/res-auto"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Format it with 4 spaces indentation, please.

@@ -58,7 +58,7 @@ object UserAvatarHelper {
}

/**
* Returns a string with the username initials. For example: username John Doe returns JD initials.
* Returns a string with the username initials. For example: username John.Doe returns JD initials.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@rafaelks
Copy link
Contributor

rafaelks commented Aug 3, 2017

LGTM, what about you @luciofm?

@RocketChat RocketChat deleted a comment from rafaelks Aug 3, 2017
@@ -48,4 +63,28 @@ public static OkHttpClient getClientForWebSocket() {
}
return httpClientForWS;
}

public static String getContentType(String uri) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove this method that is not used anymore.

@luciofm luciofm merged commit 8ac51b5 into RocketChat:develop Aug 3, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants