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