Add quotation support

Mishiranu created

Change summary

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java        |  29 
src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java      | 178 
src/main/java/eu/siacs/conversations/ui/text/DividerSpan.java            |  29 
src/main/java/eu/siacs/conversations/ui/text/QuoteSpan.java              |  52 
src/main/java/eu/siacs/conversations/ui/widget/ListSelectionManager.java |  20 
src/main/res/drawable-hdpi/ic_action_reply.png                           |   0 
src/main/res/drawable-hdpi/ic_reply_white_24dp.png                       |   0 
src/main/res/drawable-mdpi/ic_action_reply.png                           |   0 
src/main/res/drawable-mdpi/ic_reply_white_24dp.png                       |   0 
src/main/res/drawable-xhdpi/ic_action_reply.png                          |   0 
src/main/res/drawable-xhdpi/ic_reply_white_24dp.png                      |   0 
src/main/res/drawable-xxhdpi/ic_action_reply.png                         |   0 
src/main/res/drawable-xxhdpi/ic_reply_white_24dp.png                     |   0 
src/main/res/drawable-xxxhdpi/ic_reply_white_24dp.png                    |   0 
src/main/res/values-ru/strings.xml                                       |   1 
src/main/res/values-v21/themes.xml                                       |   2 
src/main/res/values/attrs.xml                                            |   1 
src/main/res/values/colors.xml                                           |   1 
src/main/res/values/strings.xml                                          |   1 
src/main/res/values/themes.xml                                           |   2 
20 files changed, 305 insertions(+), 11 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -11,6 +11,7 @@ import android.content.Intent;
 import android.content.IntentSender.SendIntentException;
 import android.os.Bundle;
 import android.os.Handler;
+import android.text.Editable;
 import android.text.InputType;
 import android.util.Log;
 import android.util.Pair;
@@ -506,6 +507,34 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 						}
 					}
 				});
+		messageListAdapter.setOnQuoteListener(new MessageAdapter.OnQuoteListener() {
+
+			@Override
+			public void onQuote(String text) {
+				if (mEditMessage.isEnabled()) {
+					text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)", "$1> ").replaceAll("\n$", "");
+					Editable editable = mEditMessage.getEditableText();
+					int position = mEditMessage.getSelectionEnd();
+					if (position == -1) position = editable.length();
+					if (position > 0 && editable.charAt(position - 1) != '\n') {
+						editable.insert(position++, "\n");
+					}
+					editable.insert(position, text);
+					position += text.length();
+					editable.insert(position++, "\n");
+					if (position < editable.length() && editable.charAt(position) != '\n') {
+						editable.insert(position, "\n");
+					}
+					mEditMessage.setSelection(position);
+					mEditMessage.requestFocus();
+					InputMethodManager inputMethodManager = (InputMethodManager) getActivity()
+							.getSystemService(Context.INPUT_METHOD_SERVICE);
+					if (inputMethodManager != null) {
+						inputMethodManager.showSoftInput(mEditMessage, InputMethodManager.SHOW_IMPLICIT);
+					}
+				}
+			}
+		});
 		messagesView.setAdapter(messageListAdapter);
 
 		registerForContextMenu(messagesView);

src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java 🔗

@@ -5,6 +5,7 @@ import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Typeface;
 import android.graphics.drawable.BitmapDrawable;
@@ -23,6 +24,9 @@ import android.text.style.StyleSpan;
 import android.text.util.Linkify;
 import android.util.DisplayMetrics;
 import android.util.Patterns;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
@@ -38,8 +42,6 @@ import java.lang.ref.WeakReference;
 import java.net.URL;
 import java.util.List;
 import java.util.concurrent.RejectedExecutionException;
-import java.util.regex.MatchResult;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import eu.siacs.conversations.Config;
@@ -54,6 +56,8 @@ import eu.siacs.conversations.entities.Message.FileParams;
 import eu.siacs.conversations.entities.Transferable;
 import eu.siacs.conversations.persistance.FileBackend;
 import eu.siacs.conversations.ui.ConversationActivity;
+import eu.siacs.conversations.ui.text.DividerSpan;
+import eu.siacs.conversations.ui.text.QuoteSpan;
 import eu.siacs.conversations.ui.widget.ClickableMovementMethod;
 import eu.siacs.conversations.ui.widget.CopyTextView;
 import eu.siacs.conversations.ui.widget.ListSelectionManager;
@@ -82,6 +86,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 	private boolean mIndicateReceived = false;
 	private boolean mUseGreenBackground = false;
 
+	private OnQuoteListener onQuoteListener;
+
 	private final ListSelectionManager listSelectionManager = new ListSelectionManager();
 
 	public MessageAdapter(ConversationActivity activity, List<Message> messages) {
@@ -100,6 +106,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		this.mOnContactPictureLongClickedListener = listener;
 			}
 
+	public void setOnQuoteListener(OnQuoteListener listener) {
+		this.onQuoteListener = listener;
+	}
+
 	@Override
 	public int getViewTypeCount() {
 		return 3;
@@ -292,10 +302,78 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		viewHolder.messageBody.setIncludeFontPadding(false);
 		Spannable span = new SpannableString(body);
 		span.setSpan(new RelativeSizeSpan(4.0f), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-		span.setSpan(new ForegroundColorSpan(activity.getWarningTextColor()), 0, body.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+		span.setSpan(new ForegroundColorSpan(activity.getWarningTextColor()), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
 		viewHolder.messageBody.setText(span);
 	}
 
+	private int applyQuoteSpan(SpannableStringBuilder body, int start, int end, boolean darkBackground) {
+		if (start > 1 && !"\n\n".equals(body.subSequence(start - 2, start).toString())) {
+			body.insert(start++, "\n");
+			body.setSpan(new DividerSpan(false), start - 2, start, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+			end++;
+		}
+		if (end < body.length() - 1 && !"\n\n".equals(body.subSequence(end, end + 2).toString())) {
+			body.insert(end, "\n");
+			body.setSpan(new DividerSpan(false), end, end + 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+		}
+		int color = darkBackground ? this.getMessageTextColor(darkBackground, false)
+				: getContext().getResources().getColor(R.color.bubble);
+		DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+		body.setSpan(new QuoteSpan(color, metrics), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+		return 0;
+	}
+
+	/**
+	 * Applies QuoteSpan to group of lines which starts with > or » characters.
+	 * Appends likebreaks and applies DividerSpan to them to show a padding between quote and text.
+	 */
+	private boolean handleTextQuotes(SpannableStringBuilder body, boolean darkBackground) {
+		boolean startsWithQuote = false;
+		char previous = '\n';
+		int lineStart = -1;
+		int lineTextStart = -1;
+		int quoteStart = -1;
+		for (int i = 0; i <= body.length(); i++) {
+			char current = body.length() > i ? body.charAt(i) : '\n';
+			if (lineStart == -1) {
+				if (previous == '\n') {
+					if (current == '>' || current == '\u00bb') {
+						// Line start with quote
+						lineStart = i;
+						if (quoteStart == -1) quoteStart = i;
+						if (i == 0) startsWithQuote = true;
+					} else if (quoteStart >= 0) {
+						// Line start without quote, apply spans there
+						applyQuoteSpan(body, quoteStart, i - 1, darkBackground);
+						quoteStart = -1;
+					}
+				}
+			} else {
+				// Remove extra spaces between > and first character in the line
+				// > character will be removed too
+				if (current != ' ' && lineTextStart == -1) {
+					lineTextStart = i;
+				}
+				if (current == '\n') {
+					body.delete(lineStart, lineTextStart);
+					i -= lineTextStart - lineStart;
+					if (i == lineStart) {
+						// Avoid empty lines because span over empty line can be hidden
+						body.insert(i++, " ");
+					}
+					lineStart = -1;
+					lineTextStart = -1;
+				}
+			}
+			previous = current;
+		}
+		if (quoteStart >= 0) {
+			// Apply spans to finishing open quote
+			applyQuoteSpan(body, quoteStart, body.length(), darkBackground);
+		}
+		return startsWithQuote;
+	}
+
 	private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground, int type) {
 		if (viewHolder.download_button != null) {
 			viewHolder.download_button.setVisibility(View.GONE);
@@ -318,8 +396,9 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 			for (Message.MergeSeparator mergeSeparator : mergeSeparators) {
 				int start = body.getSpanStart(mergeSeparator);
 				int end = body.getSpanEnd(mergeSeparator);
-				body.setSpan(new RelativeSizeSpan(0.3f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+				body.setSpan(new DividerSpan(true), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
 			}
+			boolean startsWithQuote = handleTextQuotes(body, darkBackground);
 			if (message.getType() != Message.TYPE_PRIVATE) {
 				if (hasMeCommand) {
 					body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(),
@@ -340,7 +419,13 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 				}
 				body.insert(0, privateMarker);
 				int privateMarkerIndex = privateMarker.length();
-				body.insert(privateMarkerIndex, " ");
+				if (startsWithQuote) {
+					body.insert(privateMarkerIndex, "\n\n");
+					body.setSpan(new DividerSpan(false), privateMarkerIndex, privateMarkerIndex + 2,
+							Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+				} else {
+					body.insert(privateMarkerIndex, " ");
+				}
 				body.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground, false)),
 						0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
 				body.setSpan(new StyleSpan(Typeface.BOLD),
@@ -364,7 +449,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		}
 		viewHolder.messageBody.setTextColor(this.getMessageTextColor(darkBackground, true));
 		viewHolder.messageBody.setLinkTextColor(this.getMessageTextColor(darkBackground, true));
-		viewHolder.messageBody.setHighlightColor(activity.getResources().getColor(darkBackground ? (type == SENT || !mUseGreenBackground ? R.color.black26 : R.color.grey800) : R.color.grey500));
+		viewHolder.messageBody.setHighlightColor(activity.getResources().getColor(darkBackground
+				? (type == SENT || !mUseGreenBackground ? R.color.black26 : R.color.grey800) : R.color.grey500));
 		viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
 	}
 
@@ -527,7 +613,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 					break;
 			}
 			if (viewHolder.messageBody != null) {
-				listSelectionManager.onCreate(viewHolder.messageBody);
+				listSelectionManager.onCreate(viewHolder.messageBody,
+						new MessageBodyActionModeCallback(viewHolder.messageBody));
 				viewHolder.messageBody.setCopyHandler(this);
 			}
 			view.setTag(viewHolder);
@@ -687,9 +774,84 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		listSelectionManager.onAfterNotifyDataSetChanged();
 	}
 
+	private String transformText(CharSequence text, int start, int end, boolean forCopy) {
+		SpannableStringBuilder builder = new SpannableStringBuilder(text);
+		Object copySpan = new Object();
+		builder.setSpan(copySpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+		DividerSpan[] dividerSpans = builder.getSpans(0, builder.length(), DividerSpan.class);
+		for (DividerSpan dividerSpan : dividerSpans) {
+			builder.replace(builder.getSpanStart(dividerSpan), builder.getSpanEnd(dividerSpan),
+					dividerSpan.isLarge() ? "\n\n" : "\n");
+		}
+		start = builder.getSpanStart(copySpan);
+		end = builder.getSpanEnd(copySpan);
+		if (start == -1 || end == -1) return "";
+		builder = new SpannableStringBuilder(builder, start, end);
+		if (forCopy) {
+			QuoteSpan[] quoteSpans = builder.getSpans(0, builder.length(), QuoteSpan.class);
+			for (QuoteSpan quoteSpan : quoteSpans) {
+				builder.insert(builder.getSpanStart(quoteSpan), "> ");
+			}
+		}
+		return builder.toString();
+	}
+
 	@Override
 	public String transformTextForCopy(CharSequence text, int start, int end) {
-		return text.toString().substring(start, end);
+		if (text instanceof Spanned) {
+			return transformText(text, start, end, true);
+		} else {
+			return text.toString().substring(start, end);
+		}
+	}
+
+	public interface OnQuoteListener {
+		public void onQuote(String text);
+	}
+
+	private class MessageBodyActionModeCallback implements ActionMode.Callback {
+
+		private final TextView textView;
+
+		public MessageBodyActionModeCallback(TextView textView) {
+			this.textView = textView;
+		}
+
+		@Override
+		public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+			if (onQuoteListener != null) {
+				int quoteResId = activity.getThemeResource(R.attr.icon_quote, R.drawable.ic_action_reply);
+				// 3rd item is placed after "copy" item
+				menu.add(0, android.R.id.button1, 3, R.string.quote).setIcon(quoteResId)
+						.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+			}
+			return false;
+		}
+
+		@Override
+		public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+			return false;
+		}
+
+		@Override
+		public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+			if (item.getItemId() == android.R.id.button1) {
+				int start = textView.getSelectionStart();
+				int end = textView.getSelectionEnd();
+				if (end > start) {
+					String text = transformText(textView.getText(), start, end, false);
+					if (onQuoteListener != null) {
+						onQuoteListener.onQuote(text);
+					}
+					mode.finish();
+				}
+				return true;
+			}
+			return false;
+		}
+
+		@Override
+		public void onDestroyActionMode(ActionMode mode) {}
 	}
 
 	public void openDownloadable(Message message) {

src/main/java/eu/siacs/conversations/ui/text/DividerSpan.java 🔗

@@ -0,0 +1,29 @@
+package eu.siacs.conversations.ui.text;
+
+import android.text.TextPaint;
+import android.text.style.MetricAffectingSpan;
+
+public class DividerSpan extends MetricAffectingSpan {
+
+	private static final float PROPORTION = 0.3f;
+
+	private final boolean large;
+
+	public DividerSpan(boolean large) {
+		this.large = large;
+	}
+
+	public boolean isLarge() {
+		return large;
+	}
+
+	@Override
+	public void updateDrawState(TextPaint tp) {
+		tp.setTextSize(tp.getTextSize() * PROPORTION);
+	}
+
+	@Override
+	public void updateMeasureState(TextPaint p) {
+		p.setTextSize(p.getTextSize() * PROPORTION);
+	}
+}

src/main/java/eu/siacs/conversations/ui/text/QuoteSpan.java 🔗

@@ -0,0 +1,52 @@
+package eu.siacs.conversations.ui.text;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.text.Layout;
+import android.text.TextPaint;
+import android.text.style.CharacterStyle;
+import android.text.style.LeadingMarginSpan;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+public class QuoteSpan extends CharacterStyle implements LeadingMarginSpan {
+
+	private final int color;
+
+	private final int width;
+	private final int paddingLeft;
+	private final int paddingRight;
+
+	private static final float WIDTH_SP = 2f;
+	private static final float PADDING_LEFT_SP = 1.5f;
+	private static final float PADDING_RIGHT_SP = 8f;
+
+	public QuoteSpan(int color, DisplayMetrics metrics) {
+		this.color = color;
+		this.width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, WIDTH_SP, metrics);
+		this.paddingLeft = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, PADDING_LEFT_SP, metrics);
+		this.paddingRight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, PADDING_RIGHT_SP, metrics);
+	}
+
+	@Override
+	public void updateDrawState(TextPaint tp) {
+		tp.setColor(this.color);
+	}
+
+	@Override
+	public int getLeadingMargin(boolean first) {
+		return paddingLeft + width + paddingRight;
+	}
+
+	@Override
+	public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom,
+			CharSequence text, int start, int end, boolean first, Layout layout) {
+		Paint.Style style = p.getStyle();
+		int color = p.getColor();
+		p.setStyle(Paint.Style.FILL);
+		p.setColor(this.color);
+		c.drawRect(x + dir * paddingLeft, top, x + dir * (paddingLeft + width), bottom, p);
+		p.setStyle(style);
+		p.setColor(color);
+	}
+}

src/main/java/eu/siacs/conversations/ui/widget/ListSelectionManager.java 🔗

@@ -69,8 +69,8 @@ public class ListSelectionManager {
 	private int futureSelectionStart;
 	private int futureSelectionEnd;
 
-	public void onCreate(TextView textView) {
-		final CustomCallback callback = new CustomCallback(textView);
+	public void onCreate(TextView textView, ActionMode.Callback additionalCallback) {
+		final CustomCallback callback = new CustomCallback(textView, additionalCallback);
 		textView.setCustomSelectionActionModeCallback(callback);
 	}
 
@@ -112,10 +112,12 @@ public class ListSelectionManager {
 	private class CustomCallback implements ActionMode.Callback {
 
 		private final TextView textView;
+		private final ActionMode.Callback additionalCallback;
 		public Object identifier;
 
-		public CustomCallback(TextView textView) {
+		public CustomCallback(TextView textView, ActionMode.Callback additionalCallback) {
 			this.textView = textView;
+			this.additionalCallback = additionalCallback;
 		}
 
 		@Override
@@ -123,21 +125,33 @@ public class ListSelectionManager {
 			selectionActionMode = mode;
 			selectionIdentifier = identifier;
 			selectionTextView = textView;
+			if (additionalCallback != null) {
+				additionalCallback.onCreateActionMode(mode, menu);
+			}
 			return true;
 		}
 
 		@Override
 		public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+			if (additionalCallback != null) {
+				additionalCallback.onPrepareActionMode(mode, menu);
+			}
 			return true;
 		}
 
 		@Override
 		public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+			if (additionalCallback != null && additionalCallback.onActionItemClicked(mode, item)) {
+				return true;
+			}
 			return false;
 		}
 
 		@Override
 		public void onDestroyActionMode(ActionMode mode) {
+			if (additionalCallback != null) {
+				additionalCallback.onDestroyActionMode(mode);
+			}
 			if (selectionActionMode == mode) {
 				selectionActionMode = null;
 				selectionIdentifier = null;

src/main/res/values-ru/strings.xml 🔗

@@ -332,6 +332,7 @@
   <string name="message_options">Опции сообщения</string>
   <string name="copy_text">Копировать текст</string>
   <string name="select_text">Выбрать текст</string>
+  <string name="quote">Цитировать</string>
   <string name="copy_original_url">Копировать адрес ссылки</string>
   <string name="send_again">Отправить ещё раз</string>
   <string name="file_url">URL файла</string>

src/main/res/values-v21/themes.xml 🔗

@@ -51,6 +51,7 @@
         <item name="attr/icon_done">@drawable/ic_done_black_24dp</item>
         <item name="attr/icon_group">@drawable/ic_group_white_24dp</item>
         <item name="attr/icon_new">@drawable/ic_add_white_24dp</item>
+        <item name="attr/icon_quote">@drawable/ic_reply_white_24dp</item>
         <item name="attr/icon_refresh">@drawable/ic_refresh_black_24dp</item>
         <item name="attr/icon_new_attachment">@drawable/ic_attach_file_white_24dp</item>
         <item name="attr/icon_not_secure">@drawable/ic_lock_open_white_24dp</item>
@@ -117,6 +118,7 @@
         <item name="attr/icon_done">@drawable/ic_done_black_24dp</item>
         <item name="attr/icon_group">@drawable/ic_group_white_24dp</item>
         <item name="attr/icon_new">@drawable/ic_add_white_24dp</item>
+        <item name="attr/icon_quote">@drawable/ic_reply_white_24dp</item>
         <item name="attr/icon_refresh">@drawable/ic_refresh_white_24dp</item>
         <item name="attr/icon_new_attachment">@drawable/ic_attach_file_white_24dp</item>
         <item name="attr/icon_not_secure">@drawable/ic_lock_open_white_24dp</item>

src/main/res/values/attrs.xml 🔗

@@ -40,6 +40,7 @@
     <attr name="icon_new" format="reference"/>
     <attr name="icon_new_attachment" format="reference"/>
     <attr name="icon_not_secure" format="reference"/>
+    <attr name="icon_quote" format="reference"/>
     <attr name="icon_refresh" format="reference"/>
     <attr name="icon_remove" format="reference"/>
     <attr name="icon_search" format="reference"/>

src/main/res/values/colors.xml 🔗

@@ -21,4 +21,5 @@
 	<color name="red800">#ffc62828</color>
 	<color name="orange500">#ffff9800</color>
 	<color name="green500">#ff259b24</color>
+	<color name="bubble">#ff4b9b4a</color>
 </resources>

src/main/res/values/strings.xml 🔗

@@ -364,6 +364,7 @@
 	<string name="message_options">Message options</string>
 	<string name="copy_text">Copy text</string>
 	<string name="select_text">Select text</string>
+	<string name="quote">Quote</string>
 	<string name="copy_original_url">Copy original URL</string>
 	<string name="send_again">Send again</string>
 	<string name="file_url">File URL</string>

src/main/res/values/themes.xml 🔗

@@ -50,6 +50,7 @@
         <item name="attr/icon_new">@drawable/ic_action_new</item>
         <item name="attr/icon_new_attachment">@drawable/ic_action_new_attachment</item>
         <item name="attr/icon_not_secure">@drawable/ic_action_not_secure</item>
+        <item name="attr/icon_quote">@drawable/ic_action_reply</item>
         <item name="attr/icon_refresh">@drawable/ic_action_refresh</item>
         <item name="attr/icon_remove">@drawable/ic_action_remove</item>
         <item name="attr/icon_search">@drawable/ic_action_search</item>
@@ -113,6 +114,7 @@
         <item name="attr/icon_new">@drawable/ic_action_new</item>
         <item name="attr/icon_new_attachment">@drawable/ic_action_new_attachment</item>
         <item name="attr/icon_not_secure">@drawable/ic_action_not_secure</item>
+        <item name="attr/icon_quote">@drawable/ic_action_reply</item>
         <item name="attr/icon_refresh">@drawable/ic_action_refresh_white</item>
         <item name="attr/icon_remove">@drawable/ic_action_remove_white</item>
         <item name="attr/icon_search">@drawable/ic_action_search</item>