| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package com.taobao.weex.ui.component; |
| |
| import android.app.Activity; |
| import android.content.Context; |
| import android.support.annotation.NonNull; |
| import android.text.Editable; |
| import android.text.InputFilter; |
| import android.text.InputType; |
| import android.text.TextPaint; |
| import android.text.TextUtils; |
| import android.text.TextWatcher; |
| import android.text.method.PasswordTransformationMethod; |
| import android.util.Log; |
| import android.util.TypedValue; |
| import android.view.Gravity; |
| import android.view.KeyEvent; |
| import android.view.View; |
| import android.view.inputmethod.EditorInfo; |
| import android.view.inputmethod.InputMethodManager; |
| import android.widget.EditText; |
| import android.widget.TextView; |
| |
| import com.alibaba.fastjson.JSONObject; |
| import com.taobao.weex.WXSDKInstance; |
| import com.taobao.weex.WXSDKManager; |
| import com.taobao.weex.annotation.JSMethod; |
| import com.taobao.weex.bridge.WXBridgeManager; |
| import com.taobao.weex.common.Constants; |
| import com.taobao.weex.common.WXThread; |
| import com.taobao.weex.dom.CSSConstants; |
| import com.taobao.weex.dom.WXStyle; |
| import com.taobao.weex.layout.ContentBoxMeasurement; |
| import com.taobao.weex.layout.MeasureMode; |
| import com.taobao.weex.layout.MeasureSize; |
| import com.taobao.weex.ui.action.BasicComponentData; |
| import com.taobao.weex.ui.component.helper.SoftKeyboardDetector; |
| import com.taobao.weex.ui.component.helper.WXTimeInputHelper; |
| import com.taobao.weex.ui.view.WXEditText; |
| import com.taobao.weex.utils.TypefaceUtil; |
| import com.taobao.weex.utils.WXLogUtils; |
| import com.taobao.weex.utils.WXResourceUtils; |
| import com.taobao.weex.utils.WXUtils; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.regex.Pattern; |
| import java.util.regex.PatternSyntaxException; |
| |
| import static com.taobao.weex.dom.WXStyle.UNSET; |
| |
| /** |
| * Created by sospartan on 7/11/16. |
| */ |
| public abstract class AbstractEditComponent extends WXComponent<WXEditText> { |
| |
| private final InputMethodManager mInputMethodManager; |
| private String mBeforeText = ""; |
| private boolean mAutoFocus; |
| private String mType = "text"; |
| private String mMax = null; |
| private String mMin = null; |
| private String mLastValue = ""; |
| private int mEditorAction = EditorInfo.IME_ACTION_DONE; |
| private String mReturnKeyType = null; |
| private List<TextView.OnEditorActionListener> mEditorActionListeners; |
| private boolean mListeningKeyboard = false; |
| private SoftKeyboardDetector.Unregister mUnregister; |
| private boolean mIgnoreNextOnInputEvent = false; |
| private boolean mKeepSelectionIndex = false; |
| private TextFormatter mFormatter = null; |
| private List<TextWatcher> mTextChangedListeners; |
| private TextWatcher mTextChangedEventDispatcher; |
| private int mFormatRepeatCount = 0; |
| private static final int MAX_TEXT_FORMAT_REPEAT = 3; |
| |
| private TextPaint mPaint = new TextPaint(); |
| private int mLineHeight = UNSET; |
| |
| public AbstractEditComponent(WXSDKInstance instance, WXVContainer parent, boolean isLazy, BasicComponentData basicComponentData) { |
| super(instance, parent, isLazy, basicComponentData); |
| mInputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); |
| setContentBoxMeasurement(new ContentBoxMeasurement() { |
| /** uiThread = false **/ |
| @Override |
| public void measureInternal(float width, float height, int widthMeasureMode, int heightMeasureMode) { |
| if (CSSConstants.isUndefined(width) || widthMeasureMode == MeasureMode.UNSPECIFIED) { |
| width = 0; |
| } |
| mMeasureWidth = width; |
| mMeasureHeight = getMeasureHeight(); |
| } |
| |
| /** uiThread = false **/ |
| @Override |
| public void layoutBefore() { |
| updateStyleAndAttrs(); |
| } |
| |
| /** uiThread = false **/ |
| @Override |
| public void layoutAfter(float computedWidth, float computedHeight) { |
| |
| } |
| }); |
| } |
| |
| protected final float getMeasuredLineHeight() { |
| return mLineHeight != UNSET && mLineHeight > 0 ? mLineHeight : mPaint.getFontMetrics(null); |
| } |
| |
| protected float getMeasureHeight() { |
| return getMeasuredLineHeight(); |
| } |
| |
| protected void updateStyleAndAttrs() { |
| if (getStyles().size() > 0) { |
| int fontSize = UNSET, fontStyle = UNSET, fontWeight = UNSET; |
| String fontFamily = null; |
| if (getStyles().containsKey(Constants.Name.FONT_SIZE)) { |
| fontSize = WXStyle.getFontSize(getStyles(),getViewPortWidth()); |
| } |
| |
| if (getStyles().containsKey(Constants.Name.FONT_FAMILY)) { |
| fontFamily = WXStyle.getFontFamily(getStyles()); |
| } |
| |
| if (getStyles().containsKey(Constants.Name.FONT_STYLE)) { |
| fontStyle = WXStyle.getFontStyle(getStyles()); |
| } |
| |
| if (getStyles().containsKey(Constants.Name.FONT_WEIGHT)) { |
| fontWeight = WXStyle.getFontWeight(getStyles()); |
| } |
| |
| int lineHeight = WXStyle.getLineHeight(getStyles(),getViewPortWidth()); |
| if (lineHeight != UNSET) |
| mLineHeight = lineHeight; |
| |
| if (fontSize != UNSET) |
| mPaint.setTextSize(fontSize); |
| |
| if (fontFamily != null) { |
| TypefaceUtil.applyFontStyle(mPaint, fontStyle, fontWeight, fontFamily); |
| } |
| } |
| } |
| |
| @Override |
| protected WXEditText initComponentHostView(@NonNull Context context) { |
| final WXEditText inputView = new WXEditText(context); |
| appleStyleAfterCreated(inputView); |
| return inputView; |
| } |
| |
| @Override |
| protected void onHostViewInitialized(WXEditText host) { |
| super.onHostViewInitialized(host); |
| addFocusChangeListener(new OnFocusChangeListener() { |
| @Override |
| public void onFocusChange(boolean hasFocus) { |
| if (!hasFocus) { |
| decideSoftKeyboard(); |
| } |
| setPseudoClassStatus(Constants.PSEUDO.FOCUS,hasFocus); |
| } |
| }); |
| |
| addKeyboardListener(host); |
| } |
| |
| @Override |
| protected boolean isConsumeTouch() { |
| //EditText always consume touch event except disabled. |
| return !isDisabled(); |
| } |
| |
| private OnClickListener mOnClickListener = new OnClickListener() { |
| @Override |
| public void onHostViewClick() { |
| switch (mType) { |
| case Constants.Value.DATE: |
| hideSoftKeyboard(); |
| if (getParent() != null) { |
| getParent().interceptFocus(); |
| } |
| WXTimeInputHelper.pickDate(mMax, mMin, AbstractEditComponent.this); |
| break; |
| case Constants.Value.TIME: |
| hideSoftKeyboard(); |
| if (getParent() != null) { |
| getParent().interceptFocus(); |
| } |
| WXTimeInputHelper.pickTime(AbstractEditComponent.this); |
| break; |
| } |
| } |
| }; |
| |
| private void applyOnClickListener() { |
| addClickListener(mOnClickListener); |
| } |
| |
| |
| protected int getVerticalGravity(){ |
| return Gravity.CENTER_VERTICAL; |
| } |
| |
| /** |
| * Process view after created. |
| * |
| * @param editText |
| */ |
| protected void appleStyleAfterCreated(final WXEditText editText) { |
| String alignStr = (String) getStyles().get(Constants.Name.TEXT_ALIGN); |
| int textAlign = getTextAlign(alignStr); |
| if (textAlign <= 0) { |
| textAlign = Gravity.START; |
| } |
| editText.setGravity(textAlign | getVerticalGravity()); |
| final int colorInt = WXResourceUtils.getColor("#999999"); |
| if (colorInt != Integer.MIN_VALUE) { |
| editText.setHintTextColor(colorInt); |
| } |
| |
| mTextChangedEventDispatcher = new TextWatcher() { |
| @Override |
| public void beforeTextChanged(CharSequence s, int start, int count, int after) { |
| if (mTextChangedListeners != null) { |
| for (TextWatcher watcher : mTextChangedListeners) { |
| watcher.beforeTextChanged(s, start, count, after); |
| } |
| } |
| } |
| |
| @Override |
| public void onTextChanged(CharSequence s, int start, int before, int count) { |
| if (mFormatter != null) { |
| String raw = mFormatter.recover(s.toString()); |
| String result = mFormatter.format(raw); |
| // prevent infinite loop caused by bad format and recover regexp |
| if (!result.equals(s.toString()) && mFormatRepeatCount < MAX_TEXT_FORMAT_REPEAT) { |
| mFormatRepeatCount = mFormatRepeatCount + 1; |
| int index = editText.getSelectionStart(); |
| int cursorIndex = mFormatter.format(mFormatter.recover(s.subSequence(0, index).toString())).length(); |
| editText.setText(result); |
| editText.setSelection(cursorIndex); |
| return; |
| } |
| |
| mFormatRepeatCount = 0; |
| } |
| |
| if (mTextChangedListeners != null) { |
| for (TextWatcher watcher : mTextChangedListeners) { |
| watcher.onTextChanged(s, start, before, count); |
| } |
| } |
| } |
| |
| @Override |
| public void afterTextChanged(Editable s) { |
| if (mTextChangedListeners != null) { |
| for (TextWatcher watcher : mTextChangedListeners) { |
| watcher.afterTextChanged(s); |
| } |
| } |
| } |
| }; |
| editText.addTextChangedListener(mTextChangedEventDispatcher); |
| |
| editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, WXStyle.getFontSize(getStyles(), getInstance().getInstanceViewPortWidth())); |
| } |
| |
| |
| @Override |
| public void addEvent(final String type) { |
| super.addEvent(type); |
| if (getHostView() == null || TextUtils.isEmpty(type)) { |
| return; |
| } |
| final TextView text = getHostView(); |
| |
| if (type.equals(Constants.Event.CHANGE)) { |
| addFocusChangeListener(new OnFocusChangeListener() { |
| @Override |
| public void onFocusChange(boolean hasFocus) { |
| if (hasFocus) { |
| mLastValue = text.getText().toString(); |
| } else { |
| CharSequence newValue = text.getText(); |
| newValue = newValue == null ? "" : newValue; |
| if (!newValue.toString().equals(mLastValue)) { |
| fireEvent(Constants.Event.CHANGE, newValue.toString()); |
| mLastValue = text.getText().toString(); |
| } |
| } |
| } |
| }); |
| |
| addEditorActionListener(new TextView.OnEditorActionListener() { |
| @Override |
| public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { |
| if (actionId == mEditorAction) { |
| CharSequence newValue = text.getText(); |
| newValue = newValue == null ? "" : newValue; |
| if (!newValue.toString().equals(mLastValue)) { |
| fireEvent(Constants.Event.CHANGE, newValue.toString()); |
| mLastValue = text.getText().toString(); |
| } |
| if (getParent() != null) { |
| getParent().interceptFocus(); |
| } |
| hideSoftKeyboard(); |
| return true; |
| } |
| return false; |
| } |
| }); |
| } else if (type.equals(Constants.Event.INPUT)) { |
| addTextChangedListener(new TextWatcher() { |
| @Override |
| public void beforeTextChanged(CharSequence s, int start, int count, int after) { |
| |
| } |
| |
| @Override |
| public void onTextChanged(CharSequence s, int start, int before, int count) { |
| if (mIgnoreNextOnInputEvent) { |
| mIgnoreNextOnInputEvent = false; |
| return; |
| } |
| |
| if (mBeforeText.equals(s.toString())) { |
| return; |
| } |
| |
| mBeforeText = s.toString(); |
| |
| fireEvent(Constants.Event.INPUT, s.toString()); |
| } |
| |
| @Override |
| public void afterTextChanged(Editable s) { |
| |
| } |
| }); |
| } |
| |
| if (Constants.Event.RETURN.equals(type)) { |
| addEditorActionListener(new TextView.OnEditorActionListener() { |
| @Override |
| public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { |
| if (actionId == mEditorAction) { |
| Map<String, Object> ret = new HashMap<>(2); |
| ret.put("returnKeyType", mReturnKeyType); |
| ret.put("value", v.getText().toString()); |
| fireEvent(Constants.Event.RETURN, ret); |
| return true; |
| } |
| return false; |
| } |
| }); |
| } |
| |
| if (Constants.Event.KEYBOARD.equals(type)) { |
| mListeningKeyboard = true; |
| } |
| } |
| |
| private void fireEvent(String event, String value) { |
| if (event != null) { |
| Map<String, Object> params = new HashMap<>(2); |
| params.put("value", value); |
| params.put("timeStamp", System.currentTimeMillis()); |
| |
| Map<String, Object> domChanges = new HashMap<>(); |
| Map<String, Object> attrsChanges = new HashMap<>(); |
| attrsChanges.put("value", value); |
| domChanges.put("attrs", attrsChanges); |
| |
| WXSDKManager.getInstance().fireEvent(getInstanceId(), getRef(), event, params, domChanges); |
| } |
| } |
| |
| public void performOnChange(String value) { |
| if (getEvents() != null) { |
| String event = getEvents().contains(Constants.Event.CHANGE) ? Constants.Event.CHANGE : null; |
| fireEvent(event, value); |
| } |
| } |
| |
| @Override |
| protected boolean setProperty(String key, Object param) { |
| switch (key) { |
| case Constants.Name.PLACEHOLDER: |
| String placeholder = WXUtils.getString(param, null); |
| if (placeholder != null) |
| setPlaceholder(placeholder); |
| return true; |
| case Constants.Name.PLACEHOLDER_COLOR: |
| String placeholder_color = WXUtils.getString(param, null); |
| if (placeholder_color != null) |
| setPlaceholderColor(placeholder_color); |
| return true; |
| case Constants.Name.TYPE: |
| String input_type = WXUtils.getString(param, null); |
| if (input_type != null) |
| setType(input_type); |
| return true; |
| case Constants.Name.AUTOFOCUS: |
| Boolean result = WXUtils.getBoolean(param, null); |
| if (result != null) |
| setAutofocus(result); |
| return true; |
| case Constants.Name.COLOR: |
| String color = WXUtils.getString(param, null); |
| if (color != null) |
| setColor(color); |
| return true; |
| case Constants.Name.FONT_SIZE: |
| String fontsize = WXUtils.getString(param, null); |
| if (fontsize != null) |
| setFontSize(fontsize); |
| return true; |
| case Constants.Name.TEXT_ALIGN: |
| String text_align = WXUtils.getString(param, null); |
| if (text_align != null) |
| setTextAlign(text_align); |
| return true; |
| case Constants.Name.SINGLELINE: |
| Boolean singLineResult = WXUtils.getBoolean(param, null); |
| if (singLineResult != null) |
| setSingleLine(singLineResult); |
| return true; |
| case Constants.Name.LINES: |
| Integer lines = WXUtils.getInteger(param, null); |
| if (lines != null) |
| setLines(lines); |
| return true; |
| case Constants.Name.MAX_LENGTH: |
| Integer maxlength = WXUtils.getInteger(param, null); |
| if (maxlength != null) |
| setMaxLength(maxlength); |
| return true; |
| case Constants.Name.MAXLENGTH: |
| Integer maxLength = WXUtils.getInteger(param, null); |
| if (maxLength != null) |
| setMaxLength(maxLength); |
| return true; |
| case Constants.Name.MAX: |
| setMax(String.valueOf(param)); |
| return true; |
| case Constants.Name.MIN: |
| setMin(String.valueOf(param)); |
| return true; |
| case Constants.Name.RETURN_KEY_TYPE: |
| setReturnKeyType(String.valueOf(param)); |
| return true; |
| case Constants.Name.KEEP_SELECTION_INDEX: |
| boolean keepIndex = WXUtils.getBoolean(param, false); |
| mKeepSelectionIndex = keepIndex; |
| return true; |
| case Constants.Name.ALLOW_COPY_PASTE: |
| boolean allowCopyPaste = WXUtils.getBoolean(param, true); |
| if (getHostView() != null) { |
| getHostView().setAllowCopyPaste(allowCopyPaste); |
| } |
| return true; |
| } |
| return super.setProperty(key, param); |
| } |
| |
| @WXComponentProp(name = Constants.Name.RETURN_KEY_TYPE) |
| public void setReturnKeyType(String type) { |
| if (getHostView() == null) { |
| return; |
| } |
| mReturnKeyType = type; |
| switch (type) { |
| case ReturnTypes.DEFAULT: |
| mEditorAction = EditorInfo.IME_ACTION_UNSPECIFIED; |
| break; |
| case ReturnTypes.GO: |
| mEditorAction = EditorInfo.IME_ACTION_GO; |
| break; |
| case ReturnTypes.NEXT: |
| mEditorAction = EditorInfo.IME_ACTION_NEXT; |
| break; |
| case ReturnTypes.SEARCH: |
| mEditorAction = EditorInfo.IME_ACTION_SEARCH; |
| break; |
| case ReturnTypes.SEND: |
| mEditorAction = EditorInfo.IME_ACTION_SEND; |
| break; |
| case ReturnTypes.DONE: |
| mEditorAction = EditorInfo.IME_ACTION_DONE; |
| break; |
| default: |
| break; |
| } |
| |
| //remove focus and hide keyboard first, the ImeOptions will take effect when show keyboard next time |
| blur(); |
| getHostView().setImeOptions(mEditorAction); |
| } |
| |
| @WXComponentProp(name = Constants.Name.PLACEHOLDER) |
| public void setPlaceholder(String placeholder) { |
| if (placeholder == null || getHostView() == null) { |
| return; |
| } |
| ((WXEditText) getHostView()).setHint(placeholder); |
| } |
| |
| @WXComponentProp(name = Constants.Name.PLACEHOLDER_COLOR) |
| public void setPlaceholderColor(String color) { |
| if (getHostView() != null && !TextUtils.isEmpty(color)) { |
| int colorInt = WXResourceUtils.getColor(color); |
| if (colorInt != Integer.MIN_VALUE) { |
| ((WXEditText) getHostView()).setHintTextColor(colorInt); |
| } |
| } |
| } |
| |
| @WXComponentProp(name = Constants.Name.TYPE) |
| public void setType(String type) { |
| Log.e("weex", "setType=" + type); |
| if (type == null || getHostView() == null) { |
| return; |
| } |
| mType = type; |
| ((EditText) getHostView()).setRawInputType(getInputType(mType)); |
| switch (mType) { |
| case Constants.Value.DATE: |
| case Constants.Value.TIME: |
| applyOnClickListener(); |
| break; |
| } |
| } |
| |
| @WXComponentProp(name = Constants.Name.AUTOFOCUS) |
| public void setAutofocus(boolean autofocus) { |
| if (getHostView() == null) { |
| return; |
| } |
| mAutoFocus = autofocus; |
| EditText inputView = getHostView(); |
| if (mAutoFocus) { |
| inputView.setFocusable(true); |
| inputView.requestFocus(); |
| inputView.setFocusableInTouchMode(true); |
| showSoftKeyboard(); |
| } else { |
| hideSoftKeyboard(); |
| } |
| } |
| |
| @WXComponentProp(name = Constants.Name.VALUE) |
| public void setValue(String value) { |
| WXEditText view; |
| if ((view = getHostView()) == null) { |
| return; |
| } |
| |
| mIgnoreNextOnInputEvent = true; |
| int oldIndex = view.getSelectionStart(); |
| view.setText(value); |
| int index = mKeepSelectionIndex ? oldIndex : value.length(); |
| view.setSelection(value == null ? 0 : index); |
| } |
| |
| @WXComponentProp(name = Constants.Name.COLOR) |
| public void setColor(String color) { |
| if (getHostView() != null && !TextUtils.isEmpty(color)) { |
| int colorInt = WXResourceUtils.getColor(color); |
| if (colorInt != Integer.MIN_VALUE) { |
| getHostView().setTextColor(colorInt); |
| } |
| } |
| } |
| |
| @WXComponentProp(name = Constants.Name.FONT_SIZE) |
| public void setFontSize(String fontSize) { |
| if (getHostView() != null && fontSize != null ) { |
| Map<String, Object> map = new HashMap<>(1); |
| map.put(Constants.Name.FONT_SIZE, fontSize); |
| getHostView().setTextSize(TypedValue.COMPLEX_UNIT_PX, WXStyle.getFontSize(map, getInstance().getInstanceViewPortWidth())); |
| } |
| } |
| |
| @WXComponentProp(name = Constants.Name.TEXT_ALIGN) |
| public void setTextAlign(String textAlign) { |
| int align = getTextAlign(textAlign); |
| if (align > 0) { |
| getHostView().setGravity(align | getVerticalGravity()); |
| } |
| } |
| |
| @WXComponentProp(name = Constants.Name.SINGLELINE) |
| public void setSingleLine(boolean singleLine) { |
| if (getHostView() == null) { |
| return; |
| } |
| getHostView().setSingleLine(singleLine); |
| } |
| |
| @WXComponentProp(name = Constants.Name.LINES) |
| public void setLines(int lines) { |
| if (getHostView() == null) { |
| return; |
| } |
| getHostView().setLines(lines); |
| } |
| |
| /** |
| * Compatible with both 'max-length' and 'maxlength' |
| * @param maxLength |
| */ |
| @WXComponentProp(name = Constants.Name.MAX_LENGTH) |
| public void setMaxLength(int maxLength) { |
| if (getHostView() == null) { |
| return; |
| } |
| getHostView().setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)}); |
| } |
| |
| /** |
| * Compatible with both 'max-length' and 'maxlength' |
| * @param maxLength |
| */ |
| @WXComponentProp(name = Constants.Name.MAXLENGTH) |
| @Deprecated |
| public void setMaxlength(int maxLength) { |
| setMaxLength(maxLength); |
| } |
| |
| private int getInputType(String type) { |
| int inputType; |
| switch (type) { |
| case Constants.Value.TEXT: |
| inputType = InputType.TYPE_CLASS_TEXT; |
| break; |
| case Constants.Value.DATE: |
| inputType = InputType.TYPE_NULL; |
| getHostView().setFocusable(false); |
| break; |
| case Constants.Value.DATETIME: |
| inputType = InputType.TYPE_CLASS_DATETIME; |
| break; |
| case Constants.Value.EMAIL: |
| inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS; |
| break; |
| case Constants.Value.PASSWORD: |
| inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD; |
| getHostView().setTransformationMethod(PasswordTransformationMethod.getInstance()); |
| break; |
| case Constants.Value.TEL: |
| inputType = InputType.TYPE_CLASS_PHONE; |
| break; |
| case Constants.Value.TIME: |
| inputType = InputType.TYPE_NULL; |
| getHostView().setFocusable(false); |
| break; |
| case Constants.Value.URL: |
| inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI; |
| break; |
| case Constants.Value.NUMBER: |
| inputType = InputType.TYPE_CLASS_NUMBER; |
| break; |
| default: |
| inputType = InputType.TYPE_CLASS_TEXT; |
| } |
| return inputType; |
| } |
| |
| @WXComponentProp(name = Constants.Name.MAX) |
| public void setMax(String max) { |
| mMax = max; |
| } |
| |
| @WXComponentProp(name = Constants.Name.MIN) |
| public void setMin(String min) { |
| mMin = min; |
| } |
| |
| private boolean showSoftKeyboard() { |
| if (getHostView() == null) { |
| return false; |
| } else { |
| getHostView().postDelayed(WXThread.secure(new Runnable() { |
| @Override |
| public void run() { |
| mInputMethodManager.showSoftInput(getHostView(), InputMethodManager.SHOW_IMPLICIT); |
| } |
| }), 100); |
| } |
| return true; |
| } |
| |
| private void hideSoftKeyboard() { |
| if (getHostView() != null) { |
| getHostView().postDelayed(WXThread.secure(new Runnable() { |
| @Override |
| public void run() { |
| mInputMethodManager.hideSoftInputFromWindow(getHostView().getWindowToken(), 0); |
| } |
| }), 16); |
| } |
| } |
| |
| private int getTextAlign(String textAlign) { |
| int align = Gravity.START; |
| if (TextUtils.isEmpty(textAlign)) { |
| return align; |
| } |
| if (textAlign.equals(Constants.Value.LEFT)) { |
| align = Gravity.START; |
| } else if (textAlign.equals(Constants.Value.CENTER)) { |
| align = Gravity.CENTER; |
| } else if (textAlign.equals(Constants.Value.RIGHT)) { |
| align = Gravity.END; |
| } |
| return align; |
| } |
| |
| @JSMethod |
| public void blur() { |
| WXEditText host = getHostView(); |
| if (host != null && host.hasFocus()) { |
| if (getParent() != null) { |
| getParent().interceptFocus(); |
| } |
| host.clearFocus(); |
| hideSoftKeyboard(); |
| } |
| } |
| |
| @JSMethod |
| public void focus() { |
| WXEditText host = getHostView(); |
| if (host != null && !host.hasFocus()) { |
| if (getParent() != null) { |
| getParent().ignoreFocus(); |
| } |
| host.requestFocus(); |
| host.setFocusable(true); |
| host.setFocusableInTouchMode(true); |
| showSoftKeyboard(); |
| } |
| } |
| |
| @Override |
| protected Object convertEmptyProperty(String propName, Object originalValue) { |
| switch (propName) { |
| case Constants.Name.FONT_SIZE: |
| return WXText.sDEFAULT_SIZE; |
| case Constants.Name.COLOR: |
| return "black"; |
| } |
| return super.convertEmptyProperty(propName, originalValue); |
| } |
| |
| private void decideSoftKeyboard() { |
| View hostView; |
| if ((hostView = getHostView()) != null) { |
| final Context context = getContext(); |
| if (context != null && context instanceof Activity) { |
| hostView.postDelayed(WXThread.secure(new Runnable() { |
| @Override |
| public void run() { |
| View currentFocus = ((Activity) context).getCurrentFocus(); |
| if (!(currentFocus instanceof EditText)) { |
| mInputMethodManager.hideSoftInputFromWindow(getHostView().getWindowToken(), 0); |
| } |
| } |
| }), 16); |
| } |
| } |
| } |
| |
| @JSMethod |
| public void setSelectionRange(int selectionStart, int selectionEnd) { |
| EditText hostView; |
| if ((hostView = getHostView()) != null) { |
| int length = getHostView().length(); |
| if (selectionStart > length || selectionEnd > length) { |
| return; |
| } |
| focus(); |
| hostView.setSelection(selectionStart, selectionEnd); |
| } |
| } |
| |
| @JSMethod |
| public void getSelectionRange(String callbackId) { |
| EditText hostView; |
| Map<String, Object> result = new HashMap<>(2); |
| if ((hostView = getHostView()) != null) { |
| int start = hostView.getSelectionStart(); |
| int end = hostView.getSelectionEnd(); |
| |
| if (!hostView.hasFocus()) { |
| //The default behavior, same as iOS and web |
| start = 0; |
| end = 0; |
| } |
| |
| result.put(Constants.Name.SELECTION_START, start); |
| result.put(Constants.Name.SELECTION_END, end); |
| } |
| WXBridgeManager.getInstance().callback(getInstanceId(), callbackId, result, false); |
| } |
| |
| @JSMethod |
| public void setTextFormatter(JSONObject params) { |
| try { |
| String formatRule = params.getString("formatRule"); |
| String formatReplace = params.getString("formatReplace"); |
| String recoverRule = params.getString("recoverRule"); |
| String recoverReplace = params.getString("recoverReplace"); |
| |
| PatternWrapper format = parseToPattern(formatRule, formatReplace); |
| PatternWrapper recover = parseToPattern(recoverRule, recoverReplace); |
| |
| if (format != null && recover != null) { |
| mFormatter = new TextFormatter(format, recover); |
| } |
| } catch (Throwable t) { |
| t.printStackTrace(); |
| } |
| } |
| |
| protected final void addEditorActionListener(TextView.OnEditorActionListener listener) { |
| TextView view; |
| if (listener != null && (view = getHostView()) != null) { |
| if (mEditorActionListeners == null) { |
| mEditorActionListeners = new ArrayList<>(); |
| view.setOnEditorActionListener(new TextView.OnEditorActionListener() { |
| private boolean handled = true; |
| |
| @Override |
| public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { |
| for (TextView.OnEditorActionListener l : mEditorActionListeners) { |
| if (l != null) { |
| handled = handled & l.onEditorAction(v, actionId, event); |
| } |
| } |
| return handled; |
| } |
| }); |
| } |
| mEditorActionListeners.add(listener); |
| } |
| } |
| |
| public final void addTextChangedListener(TextWatcher watcher) { |
| if (mTextChangedListeners == null) { |
| mTextChangedListeners = new ArrayList<>(); |
| } |
| mTextChangedListeners.add(watcher); |
| } |
| |
| private void addKeyboardListener(final WXEditText host) { |
| if (host == null) { |
| return; |
| } |
| Context context = host.getContext(); |
| if (context != null && context instanceof Activity) { |
| SoftKeyboardDetector.registerKeyboardEventListener((Activity) context, new SoftKeyboardDetector.OnKeyboardEventListener() { |
| @Override |
| public void onKeyboardEvent(boolean isShown) { |
| if (mListeningKeyboard) { |
| Map<String, Object> event = new HashMap<>(1); |
| event.put("isShow", isShown); |
| fireEvent(Constants.Event.KEYBOARD, event); |
| } |
| if (!isShown) { |
| blur(); |
| } |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void destroy() { |
| super.destroy(); |
| if (mUnregister != null) { |
| try { |
| mUnregister.execute(); |
| mUnregister = null; |
| } catch (Throwable throwable) { |
| WXLogUtils.w("Unregister throw ", throwable); |
| } |
| } |
| } |
| |
| private PatternWrapper parseToPattern(String jsPattern, String replace) { |
| if (jsPattern == null || replace == null) { |
| return null; |
| } |
| |
| String checker = "/[\\S]+/[i]?[m]?[g]?"; |
| if (!Pattern.compile(checker).matcher(jsPattern).matches()) { |
| WXLogUtils.w("WXInput", "Illegal js pattern syntax: " + jsPattern); |
| return null; |
| } |
| |
| int flags = 0; |
| boolean global = false; |
| String flagsStr = jsPattern.substring(jsPattern.lastIndexOf("/") + 1); |
| String regExp = jsPattern.substring(jsPattern.indexOf("/") + 1, jsPattern.lastIndexOf("/")); |
| |
| if (flagsStr.contains("i")) { |
| flags |= Pattern.CASE_INSENSITIVE; |
| } |
| |
| if (flagsStr.contains("m")) { |
| flags |= Pattern.DOTALL; |
| } |
| |
| if (flagsStr.contains("g")) { |
| global = true; |
| } |
| |
| Pattern pattern = null; |
| try { |
| pattern = Pattern.compile(regExp, flags); |
| } catch (PatternSyntaxException e) { |
| WXLogUtils.w("WXInput", "Pattern syntax error: " + regExp); |
| } |
| if (pattern == null) { |
| return null; |
| } |
| |
| PatternWrapper wrapper = new PatternWrapper(); |
| wrapper.global = global; |
| wrapper.matcher = pattern; |
| wrapper.replace = replace; |
| return wrapper; |
| } |
| |
| private interface ReturnTypes { |
| String DEFAULT = "default"; |
| String GO = "go"; |
| String NEXT = "next"; |
| String SEARCH = "search"; |
| String SEND = "send"; |
| String DONE = "done"; |
| } |
| |
| private static class PatternWrapper { |
| private boolean global = false; |
| private Pattern matcher; |
| private String replace; |
| } |
| |
| private static class TextFormatter { |
| private PatternWrapper format; |
| private PatternWrapper recover; |
| |
| private TextFormatter(PatternWrapper format, PatternWrapper recover) { |
| this.format = format; |
| this.recover = recover; |
| } |
| |
| private String format(String src) { |
| try { |
| if (format != null) { |
| if (format.global) { |
| return format.matcher.matcher(src).replaceAll(format.replace); |
| } else { |
| return format.matcher.matcher(src).replaceFirst(format.replace); |
| } |
| } |
| } catch (Throwable t) { |
| //maybe IndexOutOfBoundsException caused by illegal replace |
| WXLogUtils.w("WXInput", "[format] " + t.getMessage()); |
| } |
| return src; |
| } |
| |
| private String recover(String formatted) { |
| try { |
| if (recover != null) { |
| if (recover.global) { |
| return recover.matcher.matcher(formatted).replaceAll(recover.replace); |
| } else { |
| return recover.matcher.matcher(formatted).replaceFirst(recover.replace); |
| } |
| } |
| } catch (Throwable t) { |
| //same cause as format |
| WXLogUtils.w("WXInput", "[formatted] " + t.getMessage()); |
| } |
| return formatted; |
| } |
| } |
| } |