Commit 44953579 authored by Yusuke Iwaki's avatar Yusuke Iwaki Committed by GitHub

Merge pull request #178 from RocketChat/feature/attachment-improvement

Better handling of attachments
parents d1bcc7c5 1921ce2c
......@@ -99,7 +99,11 @@ public class Avatar {
.into(imageView);
}
private Drawable getTextDrawable(Context context) {
public Drawable getTextDrawable(Context context) {
if (username == null) {
return null;
}
int round = (int) (4 * context.getResources().getDisplayMetrics().density);
return TextDrawable.builder()
......@@ -114,16 +118,16 @@ public class Avatar {
// Picasso can be triggered only on Main Thread.
if (Looper.myLooper() != Looper.getMainLooper()) {
new Handler(Looper.getMainLooper()).post(() -> {
getBitmap(context, size).continueWith(_task -> {
if (_task.isFaulted()) {
task.setError(_task.getError());
} else {
task.setResult(_task.getResult());
}
return null;
});
});
new Handler(Looper.getMainLooper()).post(() ->
getBitmap(context, size)
.continueWith(_task -> {
if (_task.isFaulted()) {
task.setError(_task.getError());
} else {
task.setResult(_task.getResult());
}
return null;
}));
return task.getTask();
}
......
......@@ -38,6 +38,8 @@ public class Message extends RealmObject {
private String msg;
private User u;
private boolean groupable;
private String alias;
private String avatar;
private String attachments; //JSONArray.
private String urls; //JSONArray.
......@@ -133,6 +135,22 @@ public class Message extends RealmObject {
this.urls = urls;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
@Override
public String toString() {
return "Message{" +
......@@ -144,6 +162,8 @@ public class Message extends RealmObject {
", msg='" + msg + '\'' +
", u=" + u +
", groupable=" + groupable +
", alias='" + alias + '\'' +
", avatar='" + avatar + '\'' +
", attachments='" + attachments + '\'' +
", urls='" + urls + '\'' +
'}';
......@@ -184,6 +204,12 @@ public class Message extends RealmObject {
if (u != null ? !u.equals(message.u) : message.u != null) {
return false;
}
if (alias != null ? !alias.equals(message.alias) : message.alias != null) {
return false;
}
if (avatar != null ? !avatar.equals(message.avatar) : message.avatar != null) {
return false;
}
if (attachments != null ? !attachments.equals(message.attachments)
: message.attachments != null) {
return false;
......@@ -202,6 +228,8 @@ public class Message extends RealmObject {
result = 31 * result + (msg != null ? msg.hashCode() : 0);
result = 31 * result + (u != null ? u.hashCode() : 0);
result = 31 * result + (groupable ? 1 : 0);
result = 31 * result + (alias != null ? alias.hashCode() : 0);
result = 31 * result + (avatar != null ? avatar.hashCode() : 0);
result = 31 * result + (attachments != null ? attachments.hashCode() : 0);
result = 31 * result + (urls != null ? urls.hashCode() : 0);
return result;
......
package chat.rocket.android.renderer;
import android.content.Context;
import android.graphics.Color;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
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.model.SyncState;
import chat.rocket.android.model.ddp.Message;
import chat.rocket.android.model.ddp.User;
import chat.rocket.android.widget.message.RocketChatMessageAttachmentsLayout;
import chat.rocket.android.widget.message.RocketChatMessageLayout;
import chat.rocket.android.widget.message.RocketChatMessageUrlsLayout;
......@@ -30,13 +38,14 @@ public class MessageRenderer extends AbstractRenderer<Message> {
* show Avatar image.
*/
public MessageRenderer avatarInto(ImageView imageView, String hostname) {
switch (object.getSyncState()) {
case SyncState.FAILED:
imageView.setImageResource(R.drawable.ic_error_outline_black_24dp);
break;
default:
userRenderer.avatarInto(imageView, hostname);
break;
if (object.getSyncState() == SyncState.FAILED) {
imageView.setImageResource(R.drawable.ic_error_outline_black_24dp);
} else if (TextUtils.isEmpty(object.getAvatar())) {
userRenderer.avatarInto(imageView, hostname);
} else {
final User user = object.getUser();
setAvatarInto(object.getAvatar(), hostname, user == null ? null : user.getUsername(),
imageView);
}
return this;
}
......@@ -45,7 +54,12 @@ public class MessageRenderer extends AbstractRenderer<Message> {
* show Username in textView.
*/
public MessageRenderer usernameInto(TextView textView) {
userRenderer.usernameInto(textView);
if (TextUtils.isEmpty(object.getAlias())) {
userRenderer.usernameInto(textView);
} else {
final User user = object.getUser();
setAliasInto(object.getAlias(), user == null ? null : user.getUsername(), textView);
}
return this;
}
......@@ -124,4 +138,29 @@ public class MessageRenderer extends AbstractRenderer<Message> {
return this;
}
private void setAvatarInto(String avatar, String hostname, String username, ImageView imageView) {
Picasso.with(context)
.load(avatar)
.placeholder(
new Avatar(hostname, username).getTextDrawable(context))
.into(imageView);
}
private void setAliasInto(String alias, String username, TextView textView) {
final SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
final ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(Color.BLACK);
spannableStringBuilder.append(alias);
if (username != null) {
spannableStringBuilder.append(" @");
spannableStringBuilder.append(username);
}
spannableStringBuilder
.setSpan(foregroundColorSpan, 0, alias.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(spannableStringBuilder);
}
}
package chat.rocket.android.renderer;
import android.content.Context;
import android.graphics.Color;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.widget.ImageView;
import android.widget.TextView;
......@@ -19,7 +23,7 @@ public class UserRenderer extends AbstractRenderer<User> {
}
/**
* show Avatar image.
* show Avatar image
*/
public UserRenderer avatarInto(ImageView imageView, String hostname) {
if (!shouldHandle(imageView)) {
......@@ -33,14 +37,22 @@ public class UserRenderer extends AbstractRenderer<User> {
}
/**
* show Username in textView.
* show Username in textView
*/
public UserRenderer usernameInto(TextView textView) {
if (!shouldHandle(textView)) {
return this;
}
textView.setText(object.getUsername());
final SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
final ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(Color.BLACK);
spannableStringBuilder.append(object.getUsername());
spannableStringBuilder.setSpan(foregroundColorSpan, 0, object.getUsername().length(),
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(spannableStringBuilder);
return this;
}
......
package chat.rocket.android.widget.message;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.emojione.Emojione;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.helper.InlineHightlighter;
import chat.rocket.android.widget.helper.Linkify;
import chat.rocket.android.widget.helper.MarkDown;
public class MessageAttachmentFieldLayout extends LinearLayout {
private TextView titleView;
private TextView valueView;
public MessageAttachmentFieldLayout(Context context) {
super(context);
initialize(context, null);
}
public MessageAttachmentFieldLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
public MessageAttachmentFieldLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context, attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MessageAttachmentFieldLayout(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize(context, attrs);
}
private void initialize(Context context, AttributeSet attrs) {
setOrientation(VERTICAL);
LayoutInflater.from(context)
.inflate(R.layout.message_inline_attachment_field, this, true);
titleView = (TextView) findViewById(R.id.field_title);
valueView = (TextView) findViewById(R.id.field_value);
}
public void setTitle(String title) {
titleView.setText(title);
}
public void setValue(String value) {
valueView.setText(Emojione.shortnameToUnicode(value, false));
MarkDown.apply(valueView);
Linkify.markup(valueView);
InlineHightlighter.highlight(valueView);
}
}
......@@ -3,13 +3,16 @@ package chat.rocket.android.widget.message;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.widget.TextViewCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
......@@ -21,7 +24,6 @@ import org.json.JSONObject;
import java.io.IOException;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.helper.ImageFormat;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
......@@ -81,10 +83,15 @@ public class RocketChatMessageAttachmentsLayout extends LinearLayout {
public Response intercept(Chain chain) throws IOException {
// uid/token is required to download attachment files.
// see: RocketChat:lib/fileUpload.coffee
Request newRequest = chain.request().newBuilder()
.header("Cookie", "rc_uid=" + userId + ";rc_token=" + token)
.build();
return chain.proceed(newRequest);
if (chain.request().url().host().equals(hostname)) {
Request newRequest = chain.request().newBuilder()
.header("Cookie", "rc_uid=" + userId + ";rc_token=" + token)
.build();
return chain.proceed(newRequest);
}
return chain.proceed(chain.request());
}
};
OkHttpClient okHttpClient = new OkHttpClient.Builder()
......@@ -114,56 +121,178 @@ public class RocketChatMessageAttachmentsLayout extends LinearLayout {
}
private void appendAttachmentView(JSONObject attachmentObj) throws JSONException {
if (attachmentObj.isNull("image_url")) {
if (attachmentObj == null) {
return;
}
String imageURL = attachmentObj.getString("image_url");
String imageType = attachmentObj.getString("image_type");
View attachmentView = inflater.inflate(R.layout.message_inline_attachment, this, false);
colorizeAttachmentBar(attachmentObj, attachmentView);
showAuthorAttachment(attachmentObj, attachmentView);
showTitleAttachment(attachmentObj, attachmentView);
showReferenceAttachment(attachmentObj, attachmentView);
showImageAttachment(attachmentObj, attachmentView);
// audio
// video
showFieldsAttachment(attachmentObj, attachmentView);
addView(attachmentView);
}
private void colorizeAttachmentBar(JSONObject attachmentObj, View attachmentView)
throws JSONException {
final View attachmentStrip = attachmentView.findViewById(R.id.attachment_strip);
if (TextUtils.isEmpty(imageURL)
|| !imageType.startsWith("image/")
|| !ImageFormat.SUPPORTED_LIST.contains(imageType)) {
final String colorString = attachmentObj.optString("color");
if (TextUtils.isEmpty(colorString)) {
attachmentStrip.setBackgroundResource(R.color.inline_attachment_quote_line);
return;
}
View attachmentView = inflater.inflate(R.layout.message_inline_attachment, this, false);
try {
attachmentStrip.setBackgroundColor(Color.parseColor(colorString));
} catch (Exception e) {
attachmentStrip.setBackgroundResource(R.color.inline_attachment_quote_line);
}
}
new Picasso.Builder(getContext())
.downloader(getDownloader())
.build()
.load(absolutize(imageURL))
.placeholder(VectorDrawableCompat.create(getResources(), R.drawable.image_dummy, null))
.error(VectorDrawableCompat.create(getResources(), R.drawable.image_error, null))
.into((ImageView) attachmentView.findViewById(R.id.image));
private void showAuthorAttachment(JSONObject attachmentObj, View attachmentView)
throws JSONException {
final View authorBox = attachmentView.findViewById(R.id.author_box);
if (attachmentObj.isNull("author_name") || attachmentObj.isNull("author_link")
|| attachmentObj.isNull("author_icon")) {
authorBox.setVisibility(GONE);
return;
}
authorBox.setVisibility(VISIBLE);
loadImage(attachmentObj.getString("author_icon"),
(ImageView) attachmentView.findViewById(R.id.author_icon));
final TextView authorName = (TextView) attachmentView.findViewById(R.id.author_name);
authorName.setText(attachmentObj.getString("author_name"));
final String link = absolutize(attachmentObj.getString("author_link"));
authorName.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(intent);
}
});
// timestamp and link - need to format time
}
private void showTitleAttachment(JSONObject attachmentObj, View attachmentView)
throws JSONException {
TextView titleView = (TextView) attachmentView.findViewById(R.id.title);
if (attachmentObj.isNull("title")) {
titleView.setVisibility(View.GONE);
return;
}
titleView.setVisibility(View.VISIBLE);
titleView.setText(attachmentObj.getString("title"));
if (attachmentObj.isNull("title_link")) {
titleView.setOnClickListener(null);
titleView.setClickable(false);
} else {
titleView.setVisibility(View.VISIBLE);
titleView.setText(attachmentObj.getString("title"));
if (attachmentObj.isNull("title_link")) {
titleView.setOnClickListener(null);
titleView.setClickable(false);
} else {
final String link = absolutize(attachmentObj.getString("title_link"));
titleView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(intent);
}
});
}
final String link = absolutize(attachmentObj.getString("title_link"));
titleView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(intent);
}
});
TextViewCompat.setTextAppearance(titleView,
R.style.TextAppearance_RocketChat_MessageAttachment_Title_Link);
}
}
addView(attachmentView);
private void showReferenceAttachment(JSONObject attachmentObj, View attachmentView)
throws JSONException {
final View refBox = attachmentView.findViewById(R.id.ref_box);
if (attachmentObj.isNull("thumb_url") && attachmentObj.isNull("text")) {
refBox.setVisibility(GONE);
return;
}
refBox.setVisibility(VISIBLE);
final ImageView thumbImage = (ImageView) refBox.findViewById(R.id.thumb);
final String thumbUrl = attachmentObj.optString("thumb_url");
if (TextUtils.isEmpty(thumbUrl)) {
thumbImage.setVisibility(GONE);
} else {
thumbImage.setVisibility(VISIBLE);
loadImage(thumbUrl, thumbImage);
}
final TextView refText = (TextView) refBox.findViewById(R.id.text);
final String refString = attachmentObj.optString("text");
if (TextUtils.isEmpty(refString)) {
refText.setVisibility(GONE);
} else {
refText.setVisibility(VISIBLE);
refText.setText(refString);
}
}
private void showImageAttachment(JSONObject attachmentObj, View attachmentView)
throws JSONException {
final ImageView attachedImage = (ImageView) attachmentView.findViewById(R.id.image);
if (attachmentObj.isNull("image_url")) {
attachedImage.setVisibility(GONE);
return;
}
attachedImage.setVisibility(VISIBLE);
loadImage(attachmentObj.getString("image_url"), attachedImage);
}
private void showFieldsAttachment(JSONObject attachmentObj, View attachmentView)
throws JSONException {
if (attachmentObj.isNull("fields")) {
return;
}
final ViewGroup attachmentContent =
(ViewGroup) attachmentView.findViewById(R.id.attachment_content);
final JSONArray fields = attachmentObj.getJSONArray("fields");
for (int i = 0, size = fields.length(); i < size; i++) {
final JSONObject fieldObject = fields.getJSONObject(i);
if (fieldObject.isNull("title") || fieldObject.isNull("value")) {
return;
}
MessageAttachmentFieldLayout fieldLayout = new MessageAttachmentFieldLayout(getContext());
fieldLayout.setTitle(fieldObject.getString("title"));
fieldLayout.setValue(fieldObject.getString("value"));
attachmentContent.addView(fieldLayout);
}
}
private String absolutize(String url) {
return url.startsWith("/") ? "https://" + hostname + url : url;
}
private void loadImage(String url, ImageView imageView) {
new Picasso.Builder(getContext())
.downloader(getDownloader())
.build()
.load(absolutize(url))
.placeholder(VectorDrawableCompat.create(getResources(), R.drawable.image_dummy, null))
.error(VectorDrawableCompat.create(getResources(), R.drawable.image_error, null))
.into(imageView);
}
}
......@@ -75,6 +75,10 @@ public class RocketChatMessageLayout extends LinearLayout {
}
private void appendTextView(String text) {
if (TextUtils.isEmpty(text)) {
return;
}
TextView textView = (TextView) inflater.inflate(R.layout.message_body, this, false);
textView.setText(Emojione.shortnameToUnicode(text, false));
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="4dp"
android:padding="4dp">
android:paddingTop="4dp"
android:paddingBottom="4dp">
<View
android:id="@+id/attachment_strip"
android:layout_width="3dp"
android:layout_height="match_parent"
android:layout_marginRight="5dp"
android:background="@color/inline_attachment_quote_line" />
<LinearLayout
android:id="@+id/attachment_content"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:id="@+id/author_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<ImageView
android:id="@+id/author_icon"
android:layout_width="16dp"
android:layout_height="16dp"
tools:src="@drawable/circle_black" />
<android.support.v4.widget.Space
android:layout_width="8dp"
android:layout_height="8dp" />
<TextView
android:id="@+id/author_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.RocketChat.MessageAttachment.Title.Link"
tools:text="Bradley Hilton" />
<android.support.v4.widget.Space
android:layout_width="8dp"
android:layout_height="8dp" />
<TextView
android:id="@+id/timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="14:53" />
</LinearLayout>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:layout_marginBottom="8dp"
android:textAppearance="@style/TextAppearance.RocketChat.MessageAttachment.Title"
android:background="?attr/selectableItemBackground" />
android:background="?attr/selectableItemBackground"
tools:text="Attachment Example" />
<LinearLayout
android:id="@+id/ref_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<ImageView
android:id="@+id/thumb"
android:layout_width="32dp"
android:layout_height="32dp"
tools:src="@drawable/circle_black" />
<android.support.v4.widget.Space
android:layout_width="8dp"
android:layout_height="8dp" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Bradley Hilton" />
</LinearLayout>
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxHeight="200dp"
android:layout_marginTop="4dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:adjustViewBounds="true"
android:scaleType="fitStart" />
<!-- audio -->
<!-- video -->
</LinearLayout>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/field_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.RocketChat.MessageAttachment.Field.Title"
tools:text="Test" />
<TextView
android:id="@+id/field_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Test" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="TextAppearance.RocketChat" parent="TextAppearance.AppCompat"/>
<style name="TextAppearance.RocketChat" parent="TextAppearance.AppCompat" />
<style name="TextAppearance.RocketChat.MessageBody" parent="TextAppearance.AppCompat.Body1">
......@@ -13,10 +13,13 @@
<item name="android:textStyle">bold</item>
</style>
<style name="TextAppearance.RocketChat.MessageAttachment" parent="TextAppearance.AppCompat.Body1"/>
<style name="TextAppearance.RocketChat.MessageAttachment" parent="TextAppearance.AppCompat.Body1" />
<style name="TextAppearance.RocketChat.MessageAttachment.Title" parent="TextAppearance.AppCompat.Title">
<item name="android:textSize">14sp</item>
</style>
<style name="TextAppearance.RocketChat.MessageAttachment.Title.Link">
<item name="android:textColor">?android:attr/textColorLink</item>
</style>
......@@ -24,11 +27,15 @@
<item name="android:textSize">12sp</item>
</style>
<style name="TextAppearance.RocketChat.MessageAttachment.Hostname"
parent="TextAppearance.AppCompat.Caption">
<style name="TextAppearance.RocketChat.MessageAttachment.Hostname" parent="TextAppearance.AppCompat.Caption">
<item name="android:textSize">8sp</item>
</style>
<style name="TextAppearance.RocketChat.MessageAttachment.Field.Title" parent="TextAppearance.AppCompat.Body1">
<item name="android:textStyle">bold</item>
<item name="android:textColor">@android:color/black</item>
</style>
<color name="highlight_text_color">#333</color>
<color name="highlight_text_background_color">#f8f8f8</color>
<color name="highlight_text_border_color">#ccc</color>
......
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