Merge branch 'feature-quotation' of https://github.com/Mishiranu/Conversations into Mishiranu-feature-quotation
|
@ -11,6 +11,7 @@ import android.content.Intent;
|
||||||
import android.content.IntentSender.SendIntentException;
|
import android.content.IntentSender.SendIntentException;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.text.Editable;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
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);
|
messagesView.setAdapter(messageListAdapter);
|
||||||
|
|
||||||
registerForContextMenu(messagesView);
|
registerForContextMenu(messagesView);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
@ -23,6 +24,9 @@ import android.text.style.StyleSpan;
|
||||||
import android.text.util.Linkify;
|
import android.text.util.Linkify;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Patterns;
|
import android.util.Patterns;
|
||||||
|
import android.view.ActionMode;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.View.OnLongClickListener;
|
import android.view.View.OnLongClickListener;
|
||||||
|
@ -38,8 +42,6 @@ import java.lang.ref.WeakReference;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.regex.MatchResult;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
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.entities.Transferable;
|
||||||
import eu.siacs.conversations.persistance.FileBackend;
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
import eu.siacs.conversations.ui.ConversationActivity;
|
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.ClickableMovementMethod;
|
||||||
import eu.siacs.conversations.ui.widget.CopyTextView;
|
import eu.siacs.conversations.ui.widget.CopyTextView;
|
||||||
import eu.siacs.conversations.ui.widget.ListSelectionManager;
|
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 mIndicateReceived = false;
|
||||||
private boolean mUseGreenBackground = false;
|
private boolean mUseGreenBackground = false;
|
||||||
|
|
||||||
|
private OnQuoteListener onQuoteListener;
|
||||||
|
|
||||||
private final ListSelectionManager listSelectionManager = new ListSelectionManager();
|
private final ListSelectionManager listSelectionManager = new ListSelectionManager();
|
||||||
|
|
||||||
public MessageAdapter(ConversationActivity activity, List<Message> messages) {
|
public MessageAdapter(ConversationActivity activity, List<Message> messages) {
|
||||||
|
@ -100,6 +106,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
||||||
this.mOnContactPictureLongClickedListener = listener;
|
this.mOnContactPictureLongClickedListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOnQuoteListener(OnQuoteListener listener) {
|
||||||
|
this.onQuoteListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getViewTypeCount() {
|
public int getViewTypeCount() {
|
||||||
return 3;
|
return 3;
|
||||||
|
@ -292,10 +302,78 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
||||||
viewHolder.messageBody.setIncludeFontPadding(false);
|
viewHolder.messageBody.setIncludeFontPadding(false);
|
||||||
Spannable span = new SpannableString(body);
|
Spannable span = new SpannableString(body);
|
||||||
span.setSpan(new RelativeSizeSpan(4.0f), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
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);
|
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) {
|
private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground, int type) {
|
||||||
if (viewHolder.download_button != null) {
|
if (viewHolder.download_button != null) {
|
||||||
viewHolder.download_button.setVisibility(View.GONE);
|
viewHolder.download_button.setVisibility(View.GONE);
|
||||||
|
@ -318,8 +396,9 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
||||||
for (Message.MergeSeparator mergeSeparator : mergeSeparators) {
|
for (Message.MergeSeparator mergeSeparator : mergeSeparators) {
|
||||||
int start = body.getSpanStart(mergeSeparator);
|
int start = body.getSpanStart(mergeSeparator);
|
||||||
int end = body.getSpanEnd(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 (message.getType() != Message.TYPE_PRIVATE) {
|
||||||
if (hasMeCommand) {
|
if (hasMeCommand) {
|
||||||
body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(),
|
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);
|
body.insert(0, privateMarker);
|
||||||
int privateMarkerIndex = privateMarker.length();
|
int privateMarkerIndex = privateMarker.length();
|
||||||
|
if (startsWithQuote) {
|
||||||
|
body.insert(privateMarkerIndex, "\n\n");
|
||||||
|
body.setSpan(new DividerSpan(false), privateMarkerIndex, privateMarkerIndex + 2,
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
} else {
|
||||||
body.insert(privateMarkerIndex, " ");
|
body.insert(privateMarkerIndex, " ");
|
||||||
|
}
|
||||||
body.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground, false)),
|
body.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground, false)),
|
||||||
0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
body.setSpan(new StyleSpan(Typeface.BOLD),
|
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.setTextColor(this.getMessageTextColor(darkBackground, true));
|
||||||
viewHolder.messageBody.setLinkTextColor(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);
|
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,7 +614,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (viewHolder.messageBody != null) {
|
if (viewHolder.messageBody != null) {
|
||||||
listSelectionManager.onCreate(viewHolder.messageBody);
|
listSelectionManager.onCreate(viewHolder.messageBody,
|
||||||
|
new MessageBodyActionModeCallback(viewHolder.messageBody));
|
||||||
viewHolder.messageBody.setCopyHandler(this);
|
viewHolder.messageBody.setCopyHandler(this);
|
||||||
}
|
}
|
||||||
view.setTag(viewHolder);
|
view.setTag(viewHolder);
|
||||||
|
@ -692,10 +779,85 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
|
||||||
listSelectionManager.onAfterNotifyDataSetChanged();
|
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
|
@Override
|
||||||
public String transformTextForCopy(CharSequence text, int start, int end) {
|
public String transformTextForCopy(CharSequence text, int start, int end) {
|
||||||
|
if (text instanceof Spanned) {
|
||||||
|
return transformText(text, start, end, true);
|
||||||
|
} else {
|
||||||
return text.toString().substring(start, end);
|
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) {
|
public void openDownloadable(Message message) {
|
||||||
DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
|
DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,8 +69,8 @@ public class ListSelectionManager {
|
||||||
private int futureSelectionStart;
|
private int futureSelectionStart;
|
||||||
private int futureSelectionEnd;
|
private int futureSelectionEnd;
|
||||||
|
|
||||||
public void onCreate(TextView textView) {
|
public void onCreate(TextView textView, ActionMode.Callback additionalCallback) {
|
||||||
final CustomCallback callback = new CustomCallback(textView);
|
final CustomCallback callback = new CustomCallback(textView, additionalCallback);
|
||||||
textView.setCustomSelectionActionModeCallback(callback);
|
textView.setCustomSelectionActionModeCallback(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,10 +112,12 @@ public class ListSelectionManager {
|
||||||
private class CustomCallback implements ActionMode.Callback {
|
private class CustomCallback implements ActionMode.Callback {
|
||||||
|
|
||||||
private final TextView textView;
|
private final TextView textView;
|
||||||
|
private final ActionMode.Callback additionalCallback;
|
||||||
public Object identifier;
|
public Object identifier;
|
||||||
|
|
||||||
public CustomCallback(TextView textView) {
|
public CustomCallback(TextView textView, ActionMode.Callback additionalCallback) {
|
||||||
this.textView = textView;
|
this.textView = textView;
|
||||||
|
this.additionalCallback = additionalCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -123,21 +125,33 @@ public class ListSelectionManager {
|
||||||
selectionActionMode = mode;
|
selectionActionMode = mode;
|
||||||
selectionIdentifier = identifier;
|
selectionIdentifier = identifier;
|
||||||
selectionTextView = textView;
|
selectionTextView = textView;
|
||||||
|
if (additionalCallback != null) {
|
||||||
|
additionalCallback.onCreateActionMode(mode, menu);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||||
|
if (additionalCallback != null) {
|
||||||
|
additionalCallback.onPrepareActionMode(mode, menu);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||||
|
if (additionalCallback != null && additionalCallback.onActionItemClicked(mode, item)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyActionMode(ActionMode mode) {
|
public void onDestroyActionMode(ActionMode mode) {
|
||||||
|
if (additionalCallback != null) {
|
||||||
|
additionalCallback.onDestroyActionMode(mode);
|
||||||
|
}
|
||||||
if (selectionActionMode == mode) {
|
if (selectionActionMode == mode) {
|
||||||
selectionActionMode = null;
|
selectionActionMode = null;
|
||||||
selectionIdentifier = null;
|
selectionIdentifier = null;
|
||||||
|
|
After Width: | Height: | Size: 462 B |
After Width: | Height: | Size: 253 B |
After Width: | Height: | Size: 343 B |
After Width: | Height: | Size: 186 B |
After Width: | Height: | Size: 561 B |
After Width: | Height: | Size: 306 B |
After Width: | Height: | Size: 775 B |
After Width: | Height: | Size: 436 B |
After Width: | Height: | Size: 579 B |
|
@ -330,6 +330,7 @@
|
||||||
<string name="message_options">Опции сообщения</string>
|
<string name="message_options">Опции сообщения</string>
|
||||||
<string name="copy_text">Копировать текст</string>
|
<string name="copy_text">Копировать текст</string>
|
||||||
<string name="select_text">Выбрать текст</string>
|
<string name="select_text">Выбрать текст</string>
|
||||||
|
<string name="quote">Цитировать</string>
|
||||||
<string name="copy_original_url">Копировать адрес ссылки</string>
|
<string name="copy_original_url">Копировать адрес ссылки</string>
|
||||||
<string name="send_again">Отправить ещё раз</string>
|
<string name="send_again">Отправить ещё раз</string>
|
||||||
<string name="file_url">URL файла</string>
|
<string name="file_url">URL файла</string>
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
<item name="attr/icon_done">@drawable/ic_done_black_24dp</item>
|
<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_group">@drawable/ic_group_white_24dp</item>
|
||||||
<item name="attr/icon_new">@drawable/ic_add_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_refresh">@drawable/ic_refresh_black_24dp</item>
|
||||||
<item name="attr/icon_new_attachment">@drawable/ic_attach_file_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>
|
<item name="attr/icon_not_secure">@drawable/ic_lock_open_white_24dp</item>
|
||||||
|
@ -118,6 +119,7 @@
|
||||||
<item name="attr/icon_done">@drawable/ic_done_black_24dp</item>
|
<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_group">@drawable/ic_group_white_24dp</item>
|
||||||
<item name="attr/icon_new">@drawable/ic_add_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_refresh">@drawable/ic_refresh_white_24dp</item>
|
||||||
<item name="attr/icon_new_attachment">@drawable/ic_attach_file_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>
|
<item name="attr/icon_not_secure">@drawable/ic_lock_open_white_24dp</item>
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
<attr name="icon_new" format="reference"/>
|
<attr name="icon_new" format="reference"/>
|
||||||
<attr name="icon_new_attachment" format="reference"/>
|
<attr name="icon_new_attachment" format="reference"/>
|
||||||
<attr name="icon_not_secure" format="reference"/>
|
<attr name="icon_not_secure" format="reference"/>
|
||||||
|
<attr name="icon_quote" format="reference"/>
|
||||||
<attr name="icon_refresh" format="reference"/>
|
<attr name="icon_refresh" format="reference"/>
|
||||||
<attr name="icon_remove" format="reference"/>
|
<attr name="icon_remove" format="reference"/>
|
||||||
<attr name="icon_search" format="reference"/>
|
<attr name="icon_search" format="reference"/>
|
||||||
|
|
|
@ -21,4 +21,5 @@
|
||||||
<color name="red800">#ffc62828</color>
|
<color name="red800">#ffc62828</color>
|
||||||
<color name="orange500">#ffff9800</color>
|
<color name="orange500">#ffff9800</color>
|
||||||
<color name="green500">#ff259b24</color>
|
<color name="green500">#ff259b24</color>
|
||||||
|
<color name="bubble">#ff4b9b4a</color>
|
||||||
</resources>
|
</resources>
|
|
@ -364,6 +364,7 @@
|
||||||
<string name="message_options">Message options</string>
|
<string name="message_options">Message options</string>
|
||||||
<string name="copy_text">Copy text</string>
|
<string name="copy_text">Copy text</string>
|
||||||
<string name="select_text">Select 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="copy_original_url">Copy original URL</string>
|
||||||
<string name="send_again">Send again</string>
|
<string name="send_again">Send again</string>
|
||||||
<string name="file_url">File URL</string>
|
<string name="file_url">File URL</string>
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
<item name="attr/icon_new">@drawable/ic_action_new</item>
|
<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_new_attachment">@drawable/ic_action_new_attachment</item>
|
||||||
<item name="attr/icon_not_secure">@drawable/ic_action_not_secure</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_refresh">@drawable/ic_action_refresh</item>
|
||||||
<item name="attr/icon_remove">@drawable/ic_action_remove</item>
|
<item name="attr/icon_remove">@drawable/ic_action_remove</item>
|
||||||
<item name="attr/icon_search">@drawable/ic_action_search</item>
|
<item name="attr/icon_search">@drawable/ic_action_search</item>
|
||||||
|
@ -114,6 +115,7 @@
|
||||||
<item name="attr/icon_new">@drawable/ic_action_new</item>
|
<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_new_attachment">@drawable/ic_action_new_attachment</item>
|
||||||
<item name="attr/icon_not_secure">@drawable/ic_action_not_secure</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_refresh">@drawable/ic_action_refresh_white</item>
|
||||||
<item name="attr/icon_remove">@drawable/ic_action_remove_white</item>
|
<item name="attr/icon_remove">@drawable/ic_action_remove_white</item>
|
||||||
<item name="attr/icon_search">@drawable/ic_action_search</item>
|
<item name="attr/icon_search">@drawable/ic_action_search</item>
|
||||||
|
|