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 @Override
133 public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
134 final InputConnection ic = super.onCreateInputConnection(editorInfo);
135
136 if (mimeTypes != null && mCommitContentListener != null) {
137 EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes);
138 return InputConnectionCompat.createWrapper(ic, editorInfo, new InputConnectionCompat.OnCommitContentListener() {
139 @Override
140 public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts) {
141 return EditMessage.this.mCommitContentListener.onCommitContent(inputContentInfo, flags, opts, mimeTypes);
142 }
143 });
144 } else {
145 return ic;
146 }
147 }
148
149 public void refreshIme() {
150 SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(getContext());
151 final boolean usingEnterKey = p.getBoolean("display_enter_key", getResources().getBoolean(R.bool.display_enter_key));
152 final boolean enterIsSend = p.getBoolean("enter_is_send", getResources().getBoolean(R.bool.enter_is_send));
153
154 if (usingEnterKey && enterIsSend) {
155 setInputType(getInputType() & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE));
156 setInputType(getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
157 } else if (usingEnterKey) {
158 setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
159 setInputType(getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
160 } else {
161 setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
162 setInputType(getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE);
163 }
164 }
165
166 public interface OnCommitContentListener {
167 boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts, String[] mimeTypes);
168 }
169
170 public interface KeyboardListener {
171 boolean onEnterPressed();
172
173 void onTypingStarted();
174
175 void onTypingStopped();
176
177 void onTextDeleted();
178
179 void onTextChanged();
180
181 boolean onTabPressed(boolean repeated);
182 }
183}