1package eu.siacs.conversations.ui.widget;
2
3import android.content.SharedPreferences;
4import android.preference.PreferenceManager;
5import android.support.text.emoji.widget.EmojiAppCompatEditText;
6import android.support.v13.view.inputmethod.EditorInfoCompat;
7import android.support.v13.view.inputmethod.InputConnectionCompat;
8import android.support.v13.view.inputmethod.InputContentInfoCompat;
9
10import android.content.Context;
11import android.os.Build;
12import android.os.Bundle;
13import android.os.Handler;
14import android.text.Editable;
15import android.text.InputFilter;
16import android.text.InputType;
17import android.text.Spanned;
18import android.util.AttributeSet;
19import android.view.KeyEvent;
20import android.view.inputmethod.EditorInfo;
21import android.view.inputmethod.InputConnection;
22
23import eu.siacs.conversations.Config;
24import eu.siacs.conversations.R;
25
26public class EditMessage extends EmojiAppCompatEditText {
27
28 private static final InputFilter SPAN_FILTER = (source, start, end, dest, dstart, dend) -> source instanceof Spanned ? source.toString() : source;
29 protected Handler mTypingHandler = new Handler();
30 protected KeyboardListener keyboardListener;
31 private OnCommitContentListener mCommitContentListener = null;
32 private String[] mimeTypes = null;
33 private boolean isUserTyping = false;
34 protected Runnable mTypingTimeout = new Runnable() {
35 @Override
36 public void run() {
37 if (isUserTyping && keyboardListener != null) {
38 keyboardListener.onTypingStopped();
39 isUserTyping = false;
40 }
41 }
42 };
43 private boolean lastInputWasTab = false;
44
45 public EditMessage(Context context, AttributeSet attrs) {
46 super(context, attrs);
47 }
48
49 public EditMessage(Context context) {
50 super(context);
51 }
52
53 @Override
54 public boolean onKeyDown(int keyCode, KeyEvent e) {
55 if (keyCode == KeyEvent.KEYCODE_ENTER && !e.isShiftPressed()) {
56 lastInputWasTab = false;
57 if (keyboardListener != null && keyboardListener.onEnterPressed()) {
58 return true;
59 }
60 } else if (keyCode == KeyEvent.KEYCODE_TAB && !e.isAltPressed() && !e.isCtrlPressed()) {
61 if (keyboardListener != null && keyboardListener.onTabPressed(this.lastInputWasTab)) {
62 lastInputWasTab = true;
63 return true;
64 }
65 } else {
66 lastInputWasTab = false;
67 }
68 return super.onKeyDown(keyCode, e);
69 }
70
71 @Override
72 public int getAutofillType() {
73 return AUTOFILL_TYPE_NONE;
74 }
75
76 @Override
77 public void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
78 super.onTextChanged(text, start, lengthBefore, lengthAfter);
79 lastInputWasTab = false;
80 if (this.mTypingHandler != null && this.keyboardListener != null) {
81 this.mTypingHandler.removeCallbacks(mTypingTimeout);
82 this.mTypingHandler.postDelayed(mTypingTimeout, Config.TYPING_TIMEOUT * 1000);
83 final int length = text.length();
84 if (!isUserTyping && length > 0) {
85 this.isUserTyping = true;
86 this.keyboardListener.onTypingStarted();
87 } else if (length == 0) {
88 this.isUserTyping = false;
89 this.keyboardListener.onTextDeleted();
90 }
91 this.keyboardListener.onTextChanged();
92 }
93 }
94
95 public void setKeyboardListener(KeyboardListener listener) {
96 this.keyboardListener = listener;
97 if (listener != null) {
98 this.isUserTyping = false;
99 }
100 }
101
102 @Override
103 public boolean onTextContextMenuItem(int id) {
104 if (id == android.R.id.paste) {
105 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
106 return super.onTextContextMenuItem(android.R.id.pasteAsPlainText);
107 } else {
108 Editable editable = getEditableText();
109 InputFilter[] filters = editable.getFilters();
110 InputFilter[] tempFilters = new InputFilter[filters != null ? filters.length + 1 : 1];
111 if (filters != null) {
112 System.arraycopy(filters, 0, tempFilters, 1, filters.length);
113 }
114 tempFilters[0] = SPAN_FILTER;
115 editable.setFilters(tempFilters);
116 try {
117 return super.onTextContextMenuItem(id);
118 } finally {
119 editable.setFilters(filters);
120 }
121 }
122 } else {
123 return super.onTextContextMenuItem(id);
124 }
125 }
126
127 public void setRichContentListener(String[] mimeTypes, OnCommitContentListener listener) {
128 this.mimeTypes = mimeTypes;
129 this.mCommitContentListener = listener;
130 }
131
132 public void insertAsQuote(String text) {
133 text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)", "$1> ").replaceAll("\n$", "");
134 Editable editable = getEditableText();
135 int position = getSelectionEnd();
136 if (position == -1) position = editable.length();
137 if (position > 0 && editable.charAt(position - 1) != '\n') {
138 editable.insert(position++, "\n");
139 }
140 editable.insert(position, text);
141 position += text.length();
142 editable.insert(position++, "\n");
143 if (position < editable.length() && editable.charAt(position) != '\n') {
144 editable.insert(position, "\n");
145 }
146 setSelection(position);
147 }
148
149 @Override
150 public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
151 final InputConnection ic = super.onCreateInputConnection(editorInfo);
152
153 if (mimeTypes != null && mCommitContentListener != null) {
154 EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes);
155 return InputConnectionCompat.createWrapper(ic, editorInfo, (inputContentInfo, flags, opts) -> EditMessage.this.mCommitContentListener.onCommitContent(inputContentInfo, flags, opts, mimeTypes));
156 } else {
157 return ic;
158 }
159 }
160
161 public void refreshIme() {
162 SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(getContext());
163 final boolean usingEnterKey = p.getBoolean("display_enter_key", getResources().getBoolean(R.bool.display_enter_key));
164 final boolean enterIsSend = p.getBoolean("enter_is_send", getResources().getBoolean(R.bool.enter_is_send));
165
166 if (usingEnterKey && enterIsSend) {
167 setInputType(getInputType() & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE));
168 setInputType(getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
169 } else if (usingEnterKey) {
170 setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
171 setInputType(getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
172 } else {
173 setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
174 setInputType(getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE);
175 }
176 }
177
178 public interface OnCommitContentListener {
179 boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts, String[] mimeTypes);
180 }
181
182 public interface KeyboardListener {
183 boolean onEnterPressed();
184
185 void onTypingStarted();
186
187 void onTypingStopped();
188
189 void onTextDeleted();
190
191 void onTextChanged();
192
193 boolean onTabPressed(boolean repeated);
194 }
195}