5package io.flutter.plugin.editing;
9import android.text.SpannableStringBuilder;
11import android.view.inputmethod.BaseInputConnection;
12import androidx.annotation.NonNull;
13import androidx.annotation.Nullable;
15import io.flutter.embedding.engine.systemchannels.TextInputChannel;
16import java.util.ArrayList;
35 boolean textChanged,
boolean selectionChanged,
boolean composingRegionChanged);
38 private static final String TAG =
"ListenableEditingState";
40 private int mBatchEditNestDepth = 0;
43 private int mChangeNotificationDepth = 0;
44 private ArrayList<EditingStateWatcher> mListeners =
new ArrayList<>();
45 private ArrayList<EditingStateWatcher> mPendingListeners =
new ArrayList<>();
46 private ArrayList<TextEditingDelta> mBatchTextEditingDeltas =
new ArrayList<>();
48 private String mToStringCache;
50 private String mTextWhenBeginBatchEdit;
51 private int mSelectionStartWhenBeginBatchEdit;
52 private int mSelectionEndWhenBeginBatchEdit;
53 private int mComposingStartWhenBeginBatchEdit;
54 private int mComposingEndWhenBeginBatchEdit;
56 private BaseInputConnection mDummyConnection;
66 new BaseInputConnection(view,
true) {
68 public Editable getEditable() {
73 if (initialState !=
null) {
79 ArrayList<TextEditingDelta> currentBatchDeltas =
80 new ArrayList<TextEditingDelta>(mBatchTextEditingDeltas);
81 mBatchTextEditingDeltas.clear();
82 return currentBatchDeltas;
86 mBatchTextEditingDeltas.clear();
94 mBatchEditNestDepth++;
95 if (mChangeNotificationDepth > 0) {
96 Log.
e(
TAG,
"editing state should not be changed in a listener callback");
98 if (mBatchEditNestDepth == 1 && !mListeners.isEmpty()) {
99 mTextWhenBeginBatchEdit =
toString();
110 if (mBatchEditNestDepth == 0) {
111 Log.
e(
TAG,
"endBatchEdit called without a matching beginBatchEdit");
114 if (mBatchEditNestDepth == 1) {
116 notifyListener(listener,
true,
true,
true);
119 if (!mListeners.isEmpty()) {
120 Log.
v(
TAG,
"didFinishBatchEdit with " + String.valueOf(mListeners.size()) +
" listener(s)");
121 final boolean textChanged = !
toString().equals(mTextWhenBeginBatchEdit);
122 final boolean selectionChanged =
125 final boolean composingRegionChanged =
129 notifyListenersIfNeeded(textChanged, selectionChanged, composingRegionChanged);
133 mListeners.addAll(mPendingListeners);
134 mPendingListeners.clear();
135 mBatchEditNestDepth--;
142 if (composingStart < 0 || composingStart >= composingEnd) {
143 BaseInputConnection.removeComposingSpans(
this);
145 mDummyConnection.setComposingRegion(composingStart, composingEnd);
156 if (newState.hasSelection()) {
157 Selection.setSelection(
this, newState.selectionStart, newState.selectionEnd);
159 Selection.removeSelection(
this);
172 if (mChangeNotificationDepth > 0) {
173 Log.
e(
TAG,
"adding a listener " + listener.toString() +
" in a listener callback");
178 if (mBatchEditNestDepth > 0) {
179 Log.
w(
TAG,
"a listener was added to EditingState while a batch edit was in progress");
180 mPendingListeners.add(listener);
182 mListeners.add(listener);
187 if (mChangeNotificationDepth > 0) {
188 Log.
e(
TAG,
"removing a listener " + listener.toString() +
" in a listener callback");
190 mListeners.remove(listener);
191 if (mBatchEditNestDepth > 0) {
192 mPendingListeners.remove(listener);
198 int start,
int end, CharSequence tb,
int tbstart,
int tbend) {
200 if (mChangeNotificationDepth > 0) {
201 Log.
e(
TAG,
"editing state should not be changed in a listener callback");
204 final CharSequence oldText =
toString();
206 boolean textChanged =
end -
start != tbend - tbstart;
207 for (
int i = 0;
i <
end -
start && !textChanged;
i++) {
208 textChanged |= charAt(
start +
i) != tb.charAt(tbstart +
i);
211 mToStringCache =
null;
219 final SpannableStringBuilder editable = super.replace(
start,
end, tb, tbstart, tbend);
220 mBatchTextEditingDeltas.add(
231 if (mBatchEditNestDepth > 0) {
235 final boolean selectionChanged =
237 final boolean composingRegionChanged =
239 notifyListenersIfNeeded(textChanged, selectionChanged, composingRegionChanged);
243 private void notifyListener(
244 EditingStateWatcher listener,
246 boolean selectionChanged,
247 boolean composingChanged) {
248 mChangeNotificationDepth++;
249 listener.didChangeEditingState(textChanged, selectionChanged, composingChanged);
250 mChangeNotificationDepth--;
253 private void notifyListenersIfNeeded(
254 boolean textChanged,
boolean selectionChanged,
boolean composingChanged) {
255 if (textChanged || selectionChanged || composingChanged) {
256 for (
final EditingStateWatcher listener : mListeners) {
257 notifyListener(listener, textChanged, selectionChanged, composingChanged);
263 return Selection.getSelectionStart(
this);
267 return Selection.getSelectionEnd(
this);
271 return BaseInputConnection.getComposingSpanStart(
this);
275 return BaseInputConnection.getComposingSpanEnd(
this);
283 mBatchTextEditingDeltas.add(
294 return mToStringCache !=
null ? mToStringCache : (mToStringCache = super.toString());
static void v(@NonNull String tag, @NonNull String message)
static void e(@NonNull String tag, @NonNull String message)
static void w(@NonNull String tag, @NonNull String message)
final int getSelectionStart()
ArrayList< TextEditingDelta > extractBatchTextEditingDeltas()
void removeEditingStateListener(EditingStateWatcher listener)
void setSpan(Object what, int start, int end, int flags)
void setEditingState(TextInputChannel.TextEditState newState)
final int getComposingEnd()
SpannableStringBuilder replace(int start, int end, CharSequence tb, int tbstart, int tbend)
ListenableEditingState( @Nullable TextInputChannel.TextEditState initialState, @NonNull View view)
final int getSelectionEnd()
void addEditingStateListener(EditingStateWatcher listener)
final int getComposingStart()
void setComposingRange(int composingStart, int composingEnd)
FlutterSemanticsFlag flags
void didChangeEditingState(boolean textChanged, boolean selectionChanged, boolean composingRegionChanged)